Plugin Lifecycle
This guide explains the complete lifecycle of a Deadworks plugin, from loading to unloading.
Lifecycle Flow
Server Start
│
├── OnPrecacheResources() ← Precache particles, models, heroes
│
├── OnLoad(isReload: false) ← Plugin first loaded
│
├── OnStartupServer() ← Server map loaded, set convars here
│
│ ┌─────────────────────────────────────────┐
│ │ SERVER RUNNING │
│ │ │
│ │ OnClientPutInServer() ← Client joins │
│ │ OnClientFullConnect() ← Client ready │
│ │ OnGameFrame() ← Every tick │
│ │ OnEntityCreated() ← Entity born │
│ │ OnEntitySpawned() ← Entity ready │
│ │ OnEntityDeleted() ← Entity dying │
│ │ OnTakeDamage() ← Damage event │
│ │ OnModifyCurrency() ← Currency event │
│ │ OnChatMessage() ← Chat message │
│ │ OnClientConCommand() ← Console cmd │
│ │ OnClientDisconnect() ← Client leaves │
│ └─────────────────────────────────────────┘
│
├── OnUnload() ← Plugin unloaded
│
└── (Hot-reload) → OnLoad(isReload: true)
Startup Phase
OnPrecacheResources
Called during map load. Must precache all resources (particles, models, heroes) here.
public override void OnPrecacheResources()
{
Precache.AddResource("particles/upgrades/mystical_piano_hit.vpcf");
Precache.AddAllHeroes();
}
See Precaching.
OnLoad
Called when the plugin is loaded. The isReload parameter is true during hot-reload.
public override void OnLoad(bool isReload)
{
Console.WriteLine($"[{Name}] Loaded! (reload={isReload})");
if (!isReload)
{
// First-time initialization only
}
}
OnStartupServer
Called when the server starts a new map. Ideal for setting game convars:
public override void OnStartupServer()
{
ConVar.Find("citadel_trooper_spawn_enabled")?.SetInt(0);
ConVar.Find("citadel_allow_duplicate_heroes")?.SetInt(1);
}
Runtime Phase
During runtime, your plugin responds to events through hooks and registered commands.
Event Processing Order
- Entity events — creation, spawn, deletion, touch
- Player events — connect, disconnect, commands
- Gameplay events — damage, currency, chat
- Frame events —
OnGameFrameevery tick
Hot-Reloading
When a plugin is hot-reloaded:
OnUnload()is called on the old instanceOnLoad(isReload: true)is called on the new instance- All registered commands and hooks are re-registered
Important: Clean up timers and hooks in OnUnload() to avoid duplicates after reload.
Shutdown Phase
OnUnload
Called when the plugin is unloaded (server shutdown, hot-reload, or manual unload).
public override void OnUnload()
{
Console.WriteLine($"[{Name}] Unloaded!");
// Timers are automatically cleaned up per-plugin
// EntityData stores are automatically cleaned up
}
What's cleaned up automatically:
- Per-plugin timers
EntityData<T>entries (on entity deletion)
What you should clean up manually:
- Dynamic game event listeners (via
IHandle.Cancel()) - Any external resources or connections
Client Lifecycle
Player connects
│
├── OnClientPutInServer() ← Initial connection
│
├── OnClientFullConnect() ← Fully in-game, can interact
│
│ (player is active in-game)
│
└── OnClientDisconnect() ← Player leaves
Example: Player Tracking
private readonly HashSet<int> _activePlayers = new();
public override HookResult OnClientFullConnect(ClientFullConnectEvent ev)
{
_activePlayers.Add(ev.Slot);
Console.WriteLine($"Player connected: slot {ev.Slot}");
return HookResult.Handled;
}
public override HookResult OnClientDisconnect(ClientDisconnectedEvent ev)
{
_activePlayers.Remove(ev.Slot);
Console.WriteLine($"Player disconnected: slot {ev.Slot}");
return HookResult.Handled;
}
See Also
- Plugin Base — All lifecycle hooks
- Precaching — Resource precaching
- Console Commands — ConVar setup in OnStartupServer