67 lines
3.7 KiB
C#
67 lines
3.7 KiB
C#
using CounterStrikeSharp.API.Core;
|
|
|
|
namespace Outnumbered.Engine;
|
|
|
|
// The weapon-inventory read surface (WeaponServices: MyWeapons / ActiveWeapon) plus the slot-select client commands.
|
|
// Centralizes the null-safe "walk / read the player's weapons" idiom shared by Abilities + Shop, so a
|
|
// CSSharp rename of WeaponServices/MyWeapons/ActiveWeapon — or a change to slot-select semantics — is one edit here.
|
|
// (The bare GiveNamedItem/RemoveItemByDesignerName loadout calls stay at their call sites: single-call, nothing to share.)
|
|
internal static class Inventory
|
|
{
|
|
// Slot-select client commands (engine INPUT, not schema). slot3 (the melee slot) is the neutral "rest" anchor; the zeus
|
|
// is deployed by name because the engine auto-deploys a granted grenade over the knife but not over a real weapon.
|
|
public const string SlotPrimary = "slot1";
|
|
public const string SlotSecondary = "slot2";
|
|
public const string SlotMelee = "slot3";
|
|
public const string UseZeus = "use weapon_taser";
|
|
|
|
// The player's currently-deployed weapon, or null (null-safe through pawn -> WeaponServices -> handle, IsValid-checked).
|
|
public static CBasePlayerWeapon? ActiveWeapon(CCSPlayerPawn? pawn)
|
|
{
|
|
var w = pawn?.WeaponServices?.ActiveWeapon.Value;
|
|
return w is { IsValid: true } ? w : null;
|
|
}
|
|
|
|
public static CBasePlayerWeapon? ActiveWeapon(CCSPlayerController p) => ActiveWeapon(p.PlayerPawn.Value);
|
|
|
|
// The active weapon's designer name, or "" when none — the common "what is this player holding?" probe.
|
|
public static string ActiveWeaponName(CCSPlayerPawn? pawn) => ActiveWeapon(pawn)?.DesignerName ?? "";
|
|
|
|
// TRUE if the designer name is a knife (any skin — weapon_knife, weapon_knife_t, weapon_bayonet, …) or the zeus. Lets the
|
|
// thorns abuse-guard tell "real melee in hand" from a gun (mirrors Shop's knife-neutral test, plus the zeus).
|
|
public static bool IsMeleeOrZeus(string designerName) =>
|
|
designerName == EngineNames.ShopMelee || designerName.Contains("knife") || designerName.Contains("bayonet");
|
|
|
|
// Every valid weapon the player holds (null-safe walk of WeaponServices.MyWeapons). Empty if no pawn / no services.
|
|
// Lazy (iterator) — fine for the cold classify-all walk; the hot exact-match check uses Holds (no enumerator alloc).
|
|
public static IEnumerable<CBasePlayerWeapon> Weapons(CCSPlayerController p)
|
|
{
|
|
var weapons = p.PlayerPawn.Value?.WeaponServices?.MyWeapons;
|
|
if (weapons is null) yield break;
|
|
foreach (var h in weapons)
|
|
{
|
|
var w = h.Value;
|
|
if (w is { IsValid: true }) yield return w;
|
|
}
|
|
}
|
|
|
|
// The weapon's full magazine size from its VData, or -1 if unavailable (the No-Reload / infinite-clip top-up reads it).
|
|
public static int MaxClip(CBasePlayerWeapon w) => w.As<CCSWeaponBase>().VData?.MaxClip1 ?? -1;
|
|
|
|
// Whether the pawn's weapon inventory is readable at all (pawn + WeaponServices + MyWeapons present) — distinguishes
|
|
// "no inventory" from "an empty inventory" for callers that branch on it (e.g. PreferredSlotCmd's no-services fallback).
|
|
public static bool HasWeaponServices(CCSPlayerController p) => p.PlayerPawn.Value?.WeaponServices?.MyWeapons is not null;
|
|
|
|
// True if the player holds a weapon with this designer name. Direct walk (no iterator alloc) for the per-reconcile path.
|
|
public static bool Holds(CCSPlayerController p, string designerName)
|
|
{
|
|
var weapons = p.PlayerPawn.Value?.WeaponServices?.MyWeapons;
|
|
if (weapons is null) return false;
|
|
foreach (var h in weapons)
|
|
{
|
|
var w = h.Value;
|
|
if (w is { IsValid: true } && w.DesignerName == designerName) return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|