Plugin Architecture

Deep dive into CS2-SimpleAdmin's architecture and design patterns.

Overview

CS2-SimpleAdmin follows a layered architecture with clear separation of concerns and well-defined responsibilities for each component.

---

Architecture Layers

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CounterStrikeSharp Integration Layer   โ”‚ โ† CS2_SimpleAdmin.cs
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚         Manager Layer                   โ”‚ โ† /Managers/
โ”‚  โ€ข PermissionManager                    โ”‚
โ”‚  โ€ข BanManager                           โ”‚
โ”‚  โ€ข MuteManager                          โ”‚
โ”‚  โ€ข WarnManager                          โ”‚
โ”‚  โ€ข CacheManager                         โ”‚
โ”‚  โ€ข PlayerManager                        โ”‚
โ”‚  โ€ข ServerManager                        โ”‚
โ”‚  โ€ข DiscordManager                       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚         Database Layer                  โ”‚ โ† /Database/
โ”‚  โ€ข IDatabaseProvider (Interface)        โ”‚
โ”‚  โ€ข MySqlDatabaseProvider                โ”‚
โ”‚  โ€ข SqliteDatabaseProvider               โ”‚
โ”‚  โ€ข Migration System                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚         Menu System                     โ”‚ โ† /Menus/
โ”‚  โ€ข MenuManager (Singleton)              โ”‚
โ”‚  โ€ข MenuBuilder (Factory)                โ”‚
โ”‚  โ€ข Specific Menu Classes                โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚         Command System                  โ”‚ โ† /Commands/
โ”‚  โ€ข RegisterCommands                     โ”‚
โ”‚  โ€ข Command Handlers (basebans, etc.)    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚         Public API                      โ”‚ โ† /Api/
โ”‚  โ€ข ICS2_SimpleAdminApi (Interface)      โ”‚
โ”‚  โ€ข CS2_SimpleAdminApi (Implementation)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

---

Core Components

1. CounterStrikeSharp Integration Layer

File: CS2_SimpleAdmin.cs

Responsibilities:

Key Methods:

public override void Load(bool hotReload)
public override void Unload(bool hotReload)
private HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo info)
private HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)

---

2. Manager Layer

Each manager encapsulates specific domain logic:

PermissionManager

File: /Managers/PermissionManager.cs

Responsibilities:

Key Patterns:

BanManager

File: /Managers/BanManager.cs

Responsibilities:

Key Operations:

Task BanPlayer(...)
Task AddBanBySteamId(...)
Task RemoveBan(...)

MuteManager

File: /Managers/MuteManager.cs

Responsibilities:

WarnManager

File: /Managers/WarnManager.cs

Responsibilities:

CacheManager

File: /Managers/CacheManager.cs

Purpose: Performance optimization layer

Features:

Data Structures:

Dictionary<ulong, BanInfo> _banCacheBySteamId
Dictionary<string, List<BanInfo>> _banCacheByIp
Dictionary<ulong, List<string>> _playerIpHistory

PlayerManager

File: /Managers/PlayerManager.cs

Responsibilities:

Key Pattern:

private readonly SemaphoreSlim _semaphore = new(5, 5);

public async Task LoadPlayerData(CCSPlayerController player)
{
    await _semaphore.WaitAsync();
    try
    {
        // Load player data
    }
    finally
    {
        _semaphore.Release();
    }
}

ServerManager

File: /Managers/ServerManager.cs

Responsibilities:

DiscordManager

File: /Managers/DiscordManager.cs

Responsibilities:

---

3. Database Layer

Files: /Database/

Provider Pattern for database abstraction:

public interface IDatabaseProvider
{
    Task ExecuteAsync(string query, object? parameters = null);
    Task<T> QueryFirstOrDefaultAsync<T>(string query, object? parameters = null);
    Task<List<T>> QueryAsync<T>(string query, object? parameters = null);

    // Query generation methods
    string GetBanQuery(bool multiServer);
    string GetMuteQuery(bool multiServer);
    // ... more query methods
}

Implementations:

Benefits:

Migration System:

File: Database/Migration.cs

---

4. Menu System

Files: /Menus/

MenuManager (Singleton Pattern):

public class MenuManager
{
    public static MenuManager Instance { get; private set; }

    private readonly Dictionary<string, MenuCategory> _categories = new();
    private readonly Dictionary<string, Dictionary<string, MenuInfo>> _menus = new();

    public void RegisterCategory(string id, string name, string permission);
    public void RegisterMenu(string categoryId, string menuId, string name, ...);
    public MenuBuilder CreateCategoryMenuPublic(MenuCategory category, CCSPlayerController player);
}

MenuBuilder (Factory Pattern):

public class MenuBuilder
{
    public MenuBuilder(string title);
    public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, ...);
    public MenuBuilder AddSubMenu(string name, Func<CCSPlayerController, MenuBuilder> factory, ...);
    public MenuBuilder WithBackAction(Action<CCSPlayerController> backAction);
    public void OpenMenu(CCSPlayerController player);
}

Specific Menu Classes:

Benefits:

---

5. Command System

Files: /Commands/

Central Registration:

File: RegisterCommands.cs

public static class RegisterCommands
{
    public static Dictionary<string, List<CommandDefinition>> _commandDefinitions = new();

    public static void RegisterCommands(CS2_SimpleAdmin plugin)
    {
        // Load Commands.json
        // Map commands to handler methods
        // Register with CounterStrikeSharp
    }
}

Command Handlers:

Organized by category:

Two-Tier Pattern:

// Entry command - parses arguments
[CommandHelper(2, "<#userid> <duration> [reason]")]
[RequiresPermissions("@css/ban")]
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
    var targets = GetTarget(command);
    int duration = ParseDuration(command.GetArg(2));
    string reason = ParseReason(command);

    foreach (var target in targets)
    {
        Ban(caller, target, duration, reason);  // Core method
    }
}

// Core method - database writes, events
private void Ban(CCSPlayerController? admin, CCSPlayerController target, int duration, string reason)
{
    // Write to database
    BanManager.BanPlayer(target, admin, duration, reason);

    // Update cache
    CacheManager.AddBan(target);

    // Trigger events
    ApiInstance.OnPlayerPenaltiedEvent(target, admin, PenaltyType.Ban, reason, duration);

    // Kick player
    Server.ExecuteCommand($"kick {target.UserId}");

    // Send Discord notification
    DiscordManager.SendBanNotification(target, admin, duration, reason);

    // Broadcast action
    ShowAdminActivity("ban_message", admin?.PlayerName, target.PlayerName, duration, reason);
}

Benefits:

---

6. Public API

Files: /Api/

Interface: ICS2_SimpleAdminApi.cs (in CS2-SimpleAdminApi project)

Implementation: CS2_SimpleAdminApi.cs

Capability System:

// In API interface
public static readonly PluginCapability<ICS2_SimpleAdminApi> PluginCapability = new("simpleadmin:api");

// In module
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");

public override void OnAllPluginsLoaded(bool hotReload)
{
    _api = _pluginCapability.Get();
}

Event Publishing:

// API exposes events
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, ...>? OnPlayerPenaltied;

// Core plugin triggers events
ApiInstance.OnPlayerPenaltiedEvent(player, admin, type, reason, duration, id);

// Modules subscribe
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
    // React to penalty
};

---

Data Flow Patterns

Player Join Flow

1. player_connect event
   โ†“
2. PlayerManager.LoadPlayerData()
   โ†“
3. Semaphore.WaitAsync() โ† Max 5 concurrent
   โ†“
4. CacheManager.CheckBan(steamId, ip)
   โ†“
5a. BANNED โ†’ Kick player immediately
5b. CLEAN โ†’ Continue
   โ†“
6. Load active penalties from DB
   โ†“
7. Store in PlayersInfo dictionary
   โ†“
8. Update player IP history

Ban Command Flow

1. OnBanCommand() โ† Parse arguments
   โ†“
2. Ban() โ† Core method
   โ†“
3. BanManager.BanPlayer() โ† Write to DB
   โ†“
4. CacheManager.AddBan() โ† Update cache
   โ†“
5. ApiInstance.OnPlayerPenaltiedEvent() โ† Trigger event
   โ†“
6. Server.ExecuteCommand("kick") โ† Kick player
   โ†“
7. DiscordManager.SendNotification() โ† Discord webhook
   โ†“
8. ShowAdminActivity() โ† Broadcast action

Admin Permission Check Flow

1. Plugin Load
   โ†“
2. PermissionManager.LoadAdmins()
   โ†“
3. Build AdminCache โ† SteamID โ†’ Flags/Immunity
   โ†“
4. Command Execution
   โ†“
5. RequiresPermissions attribute check
   โ†“
6. AdminManager.PlayerHasPermissions() โ† Check cache
   โ†“
7a. HAS PERMISSION โ†’ Execute
7b. NO PERMISSION โ†’ Deny

---

Design Patterns Used

Singleton Pattern

public class MenuManager
{
    public static MenuManager Instance { get; private set; }

    public static void Initialize(CS2_SimpleAdmin plugin)
    {
        Instance = new MenuManager(plugin);
    }
}

Used for:

Factory Pattern

public class MenuBuilder
{
    public static MenuBuilder Create(string title) => new MenuBuilder(title);

    public MenuBuilder AddOption(...) { /* ... */ return this; }
    public MenuBuilder AddSubMenu(...) { /* ... */ return this; }
}

Used for:

Strategy Pattern

public interface IDatabaseProvider
{
    Task<List<BanInfo>> GetBans(bool multiServer);
}

public class MySqlDatabaseProvider : IDatabaseProvider { /* ... */ }
public class SqliteDatabaseProvider : IDatabaseProvider { /* ... */ }

Used for:

Observer Pattern

// Publisher
public event Action<PlayerInfo, ...>? OnPlayerPenaltied;

// Trigger
OnPlayerPenaltied?.Invoke(player, admin, type, reason, duration, id, sid);

// Subscribers
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
    // React
};

Used for:

---

Concurrency & Thread Safety

Async/Await Patterns

All database operations use async/await:

public async Task BanPlayer(...)
{
    await _database.ExecuteAsync(query, parameters);
}

Semaphore for Rate Limiting

private readonly SemaphoreSlim _semaphore = new(5, 5);

public async Task LoadPlayerData(CCSPlayerController player)
{
    await _semaphore.WaitAsync();
    try
    {
        // Load data
    }
    finally
    {
        _semaphore.Release();
    }
}

Thread-Safe Collections

private readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = new();

---

Memory Management

In-Memory Caches

AdminCache:

Dictionary<ulong, (List<string> Flags, int Immunity, DateTime Expiry)> AdminCache

BanCache:

Dictionary<ulong, BanInfo> _banCacheBySteamId
Dictionary<string, List<BanInfo>> _banCacheByIp

Benefits:

Cleanup

// On player disconnect
PlayersInfo.TryRemove(player.SteamID, out _);

// Periodic cache cleanup
AddTimer(3600f, CleanupExpiredCache, TimerFlags.REPEAT);

---

Configuration System

Multi-Level Configuration

  1. Main Config: CS2-SimpleAdmin.json
  2. Commands Config: Commands.json
  3. Module Configs: Per-module JSON files

Hot Reload Support

public void OnConfigParsed(Config config)
{
    Config = config;
    // Reconfigure without restart
}

---

Performance Optimizations

  1. Caching - Minimize database queries
  2. Lazy Loading - Load admin data on-demand
  3. Semaphore - Limit concurrent operations
  4. Connection Pooling - Reuse DB connections
  5. Indexed Queries - Fast database lookups
  6. Memory Cleanup - Remove disconnected player data

---

Future Extensibility

Plugin Capabilities

New modules can extend functionality:

// New capability
var customCapability = new PluginCapability<ICustomFeature>("custom:feature");
Capabilities.RegisterPluginCapability(customCapability, () => _customFeature);

// Other plugins can use it
var feature = _customCapability.Get();

Event-Driven Architecture

New events can be added without breaking changes:

public event Action<NewEventArgs>? OnNewEvent;

---

Testing Considerations

Unit Testing

Integration Testing

---