Events API
Complete reference for CS2-SimpleAdmin event system.
Event System Overview
CS2-SimpleAdmin exposes events that allow modules to react to plugin actions and state changes.
All events use C# event delegates and should be subscribed to in OnAllPluginsLoaded and unsubscribed in Unload.
Plugin Lifecycle Events
OnSimpleAdminReady
Fired when CS2-SimpleAdmin is fully initialized and ready.
event Action? OnSimpleAdminReady
When Fired:
- After plugin load
- After database initialization
- After migrations complete
- Menu system ready
Example:
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null) return;
// Subscribe to ready event
_api.OnSimpleAdminReady += OnSimpleAdminReady;
// Also call directly for hot reload case
OnSimpleAdminReady();
}
private void OnSimpleAdminReady()
{
Logger.LogInformation("SimpleAdmin is ready!");
// Register menus (requires SimpleAdmin to be ready)
RegisterMenus();
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
_api.OnSimpleAdminReady -= OnSimpleAdminReady;
}
Best Practice: Always call your handler directly after subscribing to handle hot reload:
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // ← Also call directly
Penalty Events
OnPlayerPenaltied
Fired when an online player receives a penalty.
event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied
Parameters:
PlayerInfo player- Player who received penaltyPlayerInfo? admin- Admin who issued penalty (null if console)PenaltyType penaltyType- Type of penaltystring reason- Penalty reasonint duration- Duration in minutes (0 = permanent)int? penaltyId- Database penalty ID (null if not stored)int? serverId- Server ID (null in single-server mode)
Example:
_api.OnPlayerPenaltied += OnPlayerPenaltied;
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
var adminName = admin?.PlayerName ?? "Console";
Logger.LogInformation($"{adminName} penaltied {player.PlayerName}: {type} ({duration}m) - {reason}");
// React to specific penalty types
switch (type)
{
case PenaltyType.Ban:
// Log ban to external system
LogBanToWebhook(player, admin, reason, duration);
break;
case PenaltyType.Warn:
// Check warning count
if (player.Warnings >= 3)
{
Logger.LogWarning($"{player.PlayerName} has {player.Warnings} warnings!");
}
break;
}
}
OnPlayerPenaltiedAdded
Fired when a penalty is added to an offline player by SteamID.
event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded
Parameters:
SteamID steamId- Target player's SteamIDPlayerInfo? admin- Admin who issued penaltyPenaltyType penaltyType- Type of penaltystring reason- Penalty reasonint duration- Duration in minutesint? penaltyId- Database penalty IDint? serverId- Server ID
Example:
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
private void OnPlayerPenaltiedAdded(
SteamID steamId,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
var adminName = admin?.PlayerName ?? "Console";
Logger.LogInformation($"Offline penalty: {adminName} -> SteamID {steamId}: {type} ({duration}m)");
// Log to external database or webhook
if (type == PenaltyType.Ban)
{
LogOfflineBan(steamId, admin, reason, duration);
}
}
Admin Activity Events
OnAdminShowActivity
Fired when an admin action is displayed to players.
event Action<string, string?, bool, object>? OnAdminShowActivity
Parameters:
string messageKey- Translation key for the messagestring? callerName- Admin name (null if console)bool dontPublish- If true, don't broadcast to other systemsobject messageArgs- Arguments for message formatting
Example:
_api.OnAdminShowActivity += OnAdminShowActivity;
private void OnAdminShowActivity(
string messageKey,
string? callerName,
bool dontPublish,
object messageArgs)
{
if (dontPublish) return;
Logger.LogInformation($"Admin activity: {messageKey} by {callerName ?? "Console"}");
// Log to Discord, database, etc.
LogAdminAction(messageKey, callerName, messageArgs);
}
OnAdminToggleSilent
Fired when an admin toggles silent mode.
event Action<int, bool>? OnAdminToggleSilent
Parameters:
int slot- Player slot of adminbool status- New silent status (true = silent, false = normal)
Example:
_api.OnAdminToggleSilent += OnAdminToggleSilent;
private void OnAdminToggleSilent(int slot, bool status)
{
var player = Utilities.GetPlayerFromSlot(slot);
if (player == null) return;
var statusText = status ? "enabled" : "disabled";
Logger.LogInformation($"{player.PlayerName} {statusText} silent mode");
// Update UI or external systems
UpdateAdminStatus(player, status);
}
Complete Examples
Ban Logging System
public class BanLogger
{
private ICS2_SimpleAdminApi? _api;
public void Initialize(ICS2_SimpleAdminApi api)
{
_api = api;
// Subscribe to both ban events
_api.OnPlayerPenaltied += OnPlayerPenaltied;
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
}
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
if (type != PenaltyType.Ban) return;
// Log to file
File.AppendAllText("bans.log",
$"[{DateTime.Now}] {player.PlayerName} ({player.SteamId}) " +
$"banned by {admin?.PlayerName ?? "Console"} " +
$"for {duration} minutes: {reason}\n");
// Send to Discord webhook
SendDiscordNotification(
$"🔨 **Ban Issued**\n" +
$"Player: {player.PlayerName}\n" +
$"Admin: {admin?.PlayerName ?? "Console"}\n" +
$"Duration: {FormatDuration(duration)}\n" +
$"Reason: {reason}"
);
}
private void OnPlayerPenaltiedAdded(
SteamID steamId,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
if (type != PenaltyType.Ban) return;
File.AppendAllText("bans.log",
$"[{DateTime.Now}] Offline ban: SteamID {steamId} " +
$"by {admin?.PlayerName ?? "Console"} " +
$"for {duration} minutes: {reason}\n");
}
private string FormatDuration(int minutes)
{
if (minutes == 0) return "Permanent";
if (minutes < 60) return $"{minutes} minutes";
if (minutes < 1440) return $"{minutes / 60} hours";
return $"{minutes / 1440} days";
}
}
Warning Escalation System
public class WarningEscalation
{
private ICS2_SimpleAdminApi? _api;
public void Initialize(ICS2_SimpleAdminApi api)
{
_api = api;
_api.OnPlayerPenaltied += OnPlayerPenaltied;
}
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
// Only handle warnings
if (type != PenaltyType.Warn) return;
Logger.LogInformation($"{player.PlayerName} now has {player.Warnings} warnings");
// Auto-escalate based on warning count
if (player.Warnings >= 3)
{
// 3 warnings = 1 hour ban
_api.IssuePenalty(
GetPlayerController(player.SteamId),
null,
PenaltyType.Ban,
"Automatic: 3 warnings",
60
);
}
else if (player.Warnings >= 5)
{
// 5 warnings = 1 day ban
_api.IssuePenalty(
GetPlayerController(player.SteamId),
null,
PenaltyType.Ban,
"Automatic: 5 warnings",
1440
);
}
}
}
Admin Activity Monitor
public class AdminMonitor
{
private readonly Dictionary<string, int> _adminActions = new();
public void Initialize(ICS2_SimpleAdminApi api)
{
api.OnAdminShowActivity += OnAdminShowActivity;
}
private void OnAdminShowActivity(
string messageKey,
string? callerName,
bool dontPublish,
object messageArgs)
{
if (callerName == null) return; // Ignore console actions
// Track admin actions
if (!_adminActions.ContainsKey(callerName))
{
_adminActions[callerName] = 0;
}
_adminActions[callerName]++;
// Log every 10th action
if (_adminActions[callerName] % 10 == 0)
{
Logger.LogInformation(
$"{callerName} has performed {_adminActions[callerName]} admin actions"
);
}
// Alert if admin is very active
if (_adminActions[callerName] > 100)
{
Logger.LogWarning($"{callerName} has performed many actions ({_adminActions[callerName]})");
}
}
}
Best Practices
1. Always Unsubscribe
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null) return;
// Subscribe
_api.OnPlayerPenaltied += OnPlayerPenaltied;
_api.OnAdminShowActivity += OnAdminShowActivity;
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
// ALWAYS unsubscribe
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
_api.OnAdminShowActivity -= OnAdminShowActivity;
}
2. Handle Null Admins
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin, // ← Can be null!
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
var adminName = admin?.PlayerName ?? "Console";
// Use adminName safely
}
3. Use Events for Integration
// ✅ Good - React to penalties
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
if (type == PenaltyType.Ban)
{
NotifyExternalSystem(player, reason);
}
};
// ❌ Bad - Wrapping penalty methods
// Don't wrap IssuePenalty, use events instead
4. Check Event Parameters
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
// Check nullable parameters
if (penaltyId.HasValue)
{
Logger.LogInformation($"Penalty ID: {penaltyId.Value}");
}
if (serverId.HasValue)
{
Logger.LogInformation($"Server ID: {serverId.Value}");
}
}
5. OnSimpleAdminReady Pattern
// ✅ Good - Handles both normal load and hot reload
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus();
// ❌ Bad - Only works on normal load
_api.OnSimpleAdminReady += RegisterMenus;
Common Patterns
Event-Based Statistics
public class ServerStatistics
{
private int _totalBans;
private int _totalMutes;
private int _totalWarnings;
public void Initialize(ICS2_SimpleAdminApi api)
{
api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
switch (type)
{
case PenaltyType.Ban:
_totalBans++;
break;
case PenaltyType.Mute:
case PenaltyType.Gag:
case PenaltyType.Silence:
_totalMutes++;
break;
case PenaltyType.Warn:
_totalWarnings++;
break;
}
};
}
public void PrintStatistics()
{
Logger.LogInformation($"Server Statistics:");
Logger.LogInformation($"Total Bans: {_totalBans}");
Logger.LogInformation($"Total Mutes: {_totalMutes}");
Logger.LogInformation($"Total Warnings: {_totalWarnings}");
}
}
Conditional Event Handling
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
// Only handle bans
if (type != PenaltyType.Ban) return;
// Only handle permanent bans
if (duration != 0) return;
// Only handle admin-issued bans
if (admin == null) return;
// Process permanent admin bans
NotifyImportantBan(player, admin, reason);
};
Performance Considerations
Async Operations in Events
// ⚠️ Be careful with async in event handlers
_api.OnPlayerPenaltied += async (player, admin, type, reason, duration, id, sid) =>
{
// Don't block the game thread
await Task.Run(() =>
{
// Long-running operation
LogToExternalDatabase(player, type, reason);
});
};
Efficient Event Handlers
// ✅ Good - Quick processing
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
// Quick logging
Logger.LogInformation($"Ban: {player.PlayerName}");
};
// ❌ Bad - Heavy processing
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
// Don't do expensive operations synchronously
SendEmailNotification(player); // ← This blocks the game thread!
};
Troubleshooting
Event Not Firing
Check:
- Did you subscribe to the event?
- Is
_apinot null? - Are you testing the right scenario?
- Check server console for errors
Memory Leaks
Always unsubscribe:
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Unsubscribe ALL events
_api.OnSimpleAdminReady -= OnReady;
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
// ... etc
}
Related APIs
- Penalties API - Issue penalties that trigger events
- Commands API - Commands that may trigger admin activity
- Utilities API - Helper functions for event handlers