Plugin-Architektur

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

Übersicht

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

---

Architektur 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                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         MenΓΌ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

Datei: 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

Datei: /Managers/PermissionManager.cs

Responsibilities:

Key Patterns:

BanManager

Datei: /Managers/BanManager.cs

Responsibilities:

Key Operations:

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

MuteManager

Datei: /Managers/MuteManager.cs

Responsibilities:

WarnManager

Datei: /Managers/WarnManager.cs

Responsibilities:

CacheManager

Datei: /Managers/CacheManager.cs

Purpose: Performance optimization layer

Funktionen:

Data Structures:

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

PlayerManager

Datei: /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

Datei: /Managers/ServerManager.cs

Responsibilities:

DiscordManager

Datei: /Managers/DiscordManager.cs

Responsibilities:

---

3. Datenbank Layer

Dateien: /Datenbank/

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:

Datei: Datenbank/Migration.cs

---

4. MenΓΌsystem

Dateien: /MenΓΌs/

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 MenΓΌ Classes:

Benefits:

---

5. Befehlssystem

Dateien: /Befehle/

Central Registration:

Datei: 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
    }
}

Befehl 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

Dateien: /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

Spieler 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 Befehl 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 Berechtigung 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);

---

Konfiguration System

Multi-Level Konfiguration

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

Hot Neu laden 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 Architektur

New events can be added without breaking changes:

public event Action<NewEventArgs>? OnNewEvent;

---

Tests Considerations

Unit Tests

Integration Tests

---