using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; namespace Outnumbered.Engine; // The one place pawn HP/armor are written: every write is paired with the required SetStateChanged so the schema-field // names and the change-notification contract live together. Callers resolve the pawn; these never read config or pd. internal static class PawnWriter { // MaxHealth MUST be set before Health, else Health clamps to 100. Sets both the pawn and the controller mirror. public static void SetMaxHealth(CCSPlayerController p, CCSPlayerPawn pawn, int max) { pawn.MaxHealth = max; p.MaxHealth = max; Utilities.SetStateChanged(pawn, EngineNames.CBaseEntity, EngineNames.MaxHealth); } public static void SetHealth(CCSPlayerPawn pawn, int hp) { pawn.Health = hp; Utilities.SetStateChanged(pawn, EngineNames.CBaseEntity, EngineNames.Health); } public static void SetArmor(CCSPlayerPawn pawn, int armor) { pawn.ArmorValue = armor; Utilities.SetStateChanged(pawn, EngineNames.CCSPlayerPawn, EngineNames.ArmorValue); } // The pawn movement-state write + its change-notification (the shop freeze/unfreeze). Caller owns any velocity reset. public static void SetMoveType(CCSPlayerPawn pawn, MoveType_t moveType) { pawn.MoveType = moveType; Utilities.SetStateChanged(pawn, EngineNames.CBaseEntity, EngineNames.MoveType); } // A capped ADD never REDUCES: if the pawn is already at/over the cap (e.g. an !og_reload lowered Max-HP below the // live value) we no-op rather than snapping it down — so regen/lifesteal preserve an over-cap surplus. public static void AddHealthCapped(CCSPlayerPawn pawn, int amount, int cap) { if (amount <= 0 || pawn.Health <= 0) return; int next = Math.Min(cap, pawn.Health + amount); if (next <= pawn.Health) return; SetHealth(pawn, next); } public static void AddArmorCapped(CCSPlayerPawn pawn, int amount, int cap) { if (amount <= 0 || pawn.Health <= 0) return; int next = Math.Min(cap, pawn.ArmorValue + amount); if (next <= pawn.ArmorValue) return; SetArmor(pawn, next); } }