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 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().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; } }