Files
CS2-SimpleAdmin/CS2-SimpleAdmin-docs/docs/developer/api/events.md
Dawid Bepierszcz b0d8696756 Add CS2-SimpleAdmin documentation site
Introduces a new documentation site for CS2-SimpleAdmin using Docusaurus, including developer API references, tutorials, user guides, and module documentation. Removes the CleanModule example and updates FunCommands and ExampleModule. Also updates main plugin and API files to support new documentation and module structure.
2025-10-20 01:27:01 +02:00

15 KiB

sidebar_position
sidebar_position
5

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
}