using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using Microsoft.Extensions.Logging; using Outnumbered.Config; namespace Outnumbered; // Entry point. The plugin is a sealed partial class split by concern across files // (Persistence.cs, Commands.cs, ...) — cookbook §0. public sealed partial class OutnumberedPlugin : BasePlugin, IPluginConfig { public override string ModuleName => "Outnumbered"; public override string ModuleVersion => "1.0.0-modes"; public override string ModuleAuthor => "snake"; public override string ModuleDescription => "Players-vs-bots RPG / perk mod."; public OutnumberedConfig Config { get; set; } = new(); public void OnConfigParsed(OutnumberedConfig config) => Config = config; // MUST assign (cookbook §0) // True between Load and Unload. Untracked one-shot AddTimer/NextFrame callbacks (SetupMap, the draft auto-pop, the // changelevel) check this so a hot-reload/unload landing inside their delay window no-ops instead of firing against a // torn-down instance. The REPEAT timers are killed explicitly in their Shutdown_*; this guards the deferred one-shots. private bool _live; public override void Load(bool hotReload) { Initialize_Database(hotReload); Initialize_Driver(); Initialize_Handicap(); // structural guard: HandicapOverride mirrors HandicapConfig (fail-loud at load) Initialize_Stats(); Initialize_Abilities(); Initialize_Effects(); // survival burn-DoT tick (no-op outside survival) Initialize_Hud(); Initialize_Shop(); Initialize_Ranks(); Initialize_Feel(); Initialize_Api(); // the local UDS status/balance/top API — last: it reads the driver + effective handicap RegisterListener(OnTick_All); // ONE per-tick roster walk fanning out to the subsystems RegisterSkillCommands(); // !s1..!sN direct skill-buy RegisterAbilityCommands(); // !ability1..!abilityN bindable casts _live = true; Logger.LogInformation("Outnumbered loaded (mode driver: {Mode}) — persistence + stats + progression + handicap + abilities + HUD + shop.", _driver.Id); } // ONE per-tick roster walk shared by all per-tick subsystems: Utilities.GetPlayers() allocates a List, so materializing // it once and fanning out (each subsystem keeps its own Enabled gate + throttle counter) avoids 3 extra full-roster // scans + List allocs per tick. private void OnTick_All() { var players = Utilities.GetPlayers(); OnTick_Abilities(players); OnTick_Hud(players); OnTick_Shop(players); OnTick_Feel(players); } public override void Unload(bool hotReload) { _live = false; // make any in-flight one-shot timer/NextFrame callback a no-op RemoveListener(OnTick_All); Shutdown_Api(); // reverse init order: stop serving (and unlink the socket) before the subsystems it reads tear down Shutdown_Feel(); Shutdown_Ranks(); Shutdown_Shop(); Shutdown_Hud(); Shutdown_Abilities(); Shutdown_Effects(); Shutdown_Stats(); Shutdown_Driver(); Shutdown_Database(); } }