initial commit
All checks were successful
CI / build (push) Successful in 32s
CI / release (push) Successful in 32s
CI / lint (push) Successful in 30s

This commit is contained in:
Kamal Tufekcic 2026-07-05 13:28:35 +03:00
commit d701598350
67 changed files with 9351 additions and 0 deletions

View file

@ -0,0 +1,67 @@
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;
}
}