Skip to main content

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:

  1. PlayerInfo player - Player who received penalty
  2. PlayerInfo? admin - Admin who issued penalty (null if console)
  3. PenaltyType penaltyType - Type of penalty
  4. string reason - Penalty reason
  5. int duration - Duration in minutes (0 = permanent)
  6. int? penaltyId - Database penalty ID (null if not stored)
  7. 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:

  1. SteamID steamId - Target player's SteamID
  2. PlayerInfo? admin - Admin who issued penalty
  3. PenaltyType penaltyType - Type of penalty
  4. string reason - Penalty reason
  5. int duration - Duration in minutes
  6. int? penaltyId - Database penalty ID
  7. int? 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:

  1. string messageKey - Translation key for the message
  2. string? callerName - Admin name (null if console)
  3. bool dontPublish - If true, don't broadcast to other systems
  4. object 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:

  1. int slot - Player slot of admin
  2. bool 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:

  1. Did you subscribe to the event?
  2. Is _api not null?
  3. Are you testing the right scenario?
  4. 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
}