Events-API

Complete reference for CS2-SimpleAdmin event system.

Event-System Übersicht

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:

Beispiel:

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

---

Strafe Events

OnPlayerPenaltied

Fired when an online player receives a penalty.

event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied

Parameters:

  1. PlayerInfo player - Spieler who received penalty
  2. PlayerInfo? admin - Admin who issued penalty (null if console)
  3. PenaltyType penaltyType - Type of penalty
  4. string reason - Strafe reason
  5. int duration - Duration in minutes (0 = permanent)
  6. int? penaltyId - Datenbank penalty ID (null if not stored)
  7. int? serverId - Server ID (null in single-server mode)

Beispiel:

_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 - Strafe reason
  5. int duration - Duration in minutes
  6. int? penaltyId - Datenbank penalty ID
  7. int? serverId - Server ID

Beispiel:

_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

Beispiel:

_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 - Spieler slot of admin
  2. bool status - New silent status (true = silent, false = normal)

Beispiel:

_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);
}

---

Vollständige Beispiele

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]})");
        }
    }
}

---

Bewährte Vorgehensweisen

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;

---

Häufige Muster

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!
};

---

Fehlerbehebung

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 Serverkonsole 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
}

---