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:
- Plugin lifecycle management (Load/Unload)
- Event registration (
player_connect,player_disconnect, etc.) - Befehl routing
- Low-level game operations using
MemoryFunctionVoid - Timer management
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:
- Load admin flags and groups from database
- Maintain in-memory
AdminCachewith lazy-loading - Time-based cache expiry
- Immunity level management
Key Patterns:
- Caching for performance
- Lazy loading of admin data
- Periodic refresh
BanManager
Datei: /Managers/BanManager.cs
Responsibilities:
- Issue bans (SteamID, IP, or hybrid)
- Remove bans (unban)
- Handle ban expiration cleanup
- Multi-server ban synchronization
Key Operations:
Task BanPlayer(...)
Task AddBanBySteamId(...)
Task RemoveBan(...)
MuteManager
Datei: /Managers/MuteManager.cs
Responsibilities:
- Three mute types: GAG (text), MUTE (voice), SILENCE (both)
- Duration-based mutes
- Expiration tracking
WarnManager
Datei: /Managers/WarnManager.cs
Responsibilities:
- Progressive warning system
- Auto-escalation to bans based on
WarnThresholdconfig - Warning history tracking
CacheManager
Datei: /Managers/CacheManager.cs
Purpose: Performance optimization layer
Funktionen:
- In-memory ban cache with O(1) lookups by SteamID and IP
- Spieler IP history tracking for multi-account detection
- Reduces database queries on player join
Data Structures:
Dictionary<ulong, BanInfo> _banCacheBySteamId
Dictionary<string, List<BanInfo>> _banCacheByIp
Dictionary<ulong, List<string>> _playerIpHistory
PlayerManager
Datei: /Managers/PlayerManager.cs
Responsibilities:
- Load player data on connect
- Check bans against cache
- Update IP history
- Semaphore limiting (max 5 concurrent loads)
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:
- Load/register server metadata (IP, port, hostname, RCON)
- Multi-server mode support
- Server ID management
DiscordManager
Datei: /Managers/DiscordManager.cs
Responsibilities:
- Send webhook notifications for admin actions
- Configurable webhooks per penalty type
- Embed formatting with placeholders
---
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:
MySqlDatabaseProvider- MySQL-specific SQL syntaxSqliteDatabaseProvider- SQLite-specific SQL syntax
Benefits:
- Single codebase supports both MySQL and SQLite
- Easy to add new database providers
- Query methods accept
multiServerboolean for scoping
Migration System:
Datei: Datenbank/Migration.cs
- Datei-based migrations in
/Datenbank/Migrations/{mysql,sqlite}/ - Numbered files:
001_CreateTables.sql,002_AddColumn.sql - Tracking table:
sa_migrations - Auto-applies on plugin load
- Safe for multi-server environments
---
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:
AdminMenu- Main admin menu with categoriesManagePlayersMenu- Spieler management menusManageServerMenu- Server settingsDurationMenu- Duration selectionReasonMenu- Reason selection
Benefits:
- Centralized menu management
- Berechtigung-aware rendering
- Automatic back button handling
- Reusable menu components
---
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:
basebans.cs- Ban, unban, warn commandsbasecomms.cs- Gag, mute, silence commandsbasecommands.cs- Admin management, server commandsbasechat.cs- Chat commands (asay, csay, etc.)playercommands.cs- Spieler manipulation (slay, hp, etc.)funcommands.cs- Fun commands (god, noclip, etc.)basevotes.cs- Voting system
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:
- Separation of parsing and execution
- Reusable core methods
- Consistent event triggering
- Easy to test
---
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:
- MenuManager - Single menu registry
- Cache management - Single source of truth
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:
- MenΓΌ creation
- Datenbank provider creation
Strategy Pattern
public interface IDatabaseProvider
{
Task<List<BanInfo>> GetBans(bool multiServer);
}
public class MySqlDatabaseProvider : IDatabaseProvider { /* ... */ }
public class SqliteDatabaseProvider : IDatabaseProvider { /* ... */ }
Used for:
- Datenbank abstraction
- Query generation per DB type
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:
- Event system
- Modul communication
---
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:
- Reduces database load
- O(1) lookups
- TTL-based expiry
Cleanup
// On player disconnect
PlayersInfo.TryRemove(player.SteamID, out _);
// Periodic cache cleanup
AddTimer(3600f, CleanupExpiredCache, TimerFlags.REPEAT);
---
Konfiguration System
Multi-Level Konfiguration
- Main Config:
CS2-SimpleAdmin.json - Befehle Config:
Befehle.json - Modul Configs: Per-module JSON files
Hot Neu laden Support
public void OnConfigParsed(Config config)
{
Config = config;
// Reconfigure without restart
}
---
Performance Optimizations
- Caching - Minimize database queries
- Lazy Loading - Load admin data on-demand
- Semaphore - Limit concurrent operations
- Connection Pooling - Reuse DB connections
- Indexed Queries - Fast database lookups
- 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
- Managers can be tested independently
- Mock
IDatabaseProviderfor testing - Test command handlers with mock players
Integration Tests
- Test on actual CS2 server
- Multi-server scenarios
- Datenbank migration testing
---
Related Dokumentation
- API-Γbersicht - Public API details
- Modulentwicklung - Create modules
- GitHub Quellcode - Browse code