--- sidebar_position: 8 --- # 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:** - Plugin lifecycle management (Load/Unload) - Event registration (`player_connect`, `player_disconnect`, etc.) - Command routing - Low-level game operations using `MemoryFunctionVoid` - Timer management **Key Methods:** ```csharp 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:** - Load admin flags and groups from database - Maintain in-memory `AdminCache` with lazy-loading - Time-based cache expiry - Immunity level management **Key Patterns:** - Caching for performance - Lazy loading of admin data - Periodic refresh #### BanManager **File:** `/Managers/BanManager.cs` **Responsibilities:** - Issue bans (SteamID, IP, or hybrid) - Remove bans (unban) - Handle ban expiration cleanup - Multi-server ban synchronization **Key Operations:** ```csharp Task BanPlayer(...) Task AddBanBySteamId(...) Task RemoveBan(...) ``` #### MuteManager **File:** `/Managers/MuteManager.cs` **Responsibilities:** - Three mute types: GAG (text), MUTE (voice), SILENCE (both) - Duration-based mutes - Expiration tracking #### WarnManager **File:** `/Managers/WarnManager.cs` **Responsibilities:** - Progressive warning system - Auto-escalation to bans based on `WarnThreshold` config - Warning history tracking #### CacheManager **File:** `/Managers/CacheManager.cs` **Purpose:** Performance optimization layer **Features:** - In-memory ban cache with O(1) lookups by SteamID and IP - Player IP history tracking for multi-account detection - Reduces database queries on player join **Data Structures:** ```csharp Dictionary _banCacheBySteamId Dictionary> _banCacheByIp Dictionary> _playerIpHistory ``` #### PlayerManager **File:** `/Managers/PlayerManager.cs` **Responsibilities:** - Load player data on connect - Check bans against cache - Update IP history - Semaphore limiting (max 5 concurrent loads) **Key Pattern:** ```csharp 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:** - Load/register server metadata (IP, port, hostname, RCON) - Multi-server mode support - Server ID management #### DiscordManager **File:** `/Managers/DiscordManager.cs` **Responsibilities:** - Send webhook notifications for admin actions - Configurable webhooks per penalty type - Embed formatting with placeholders --- ### 3. Database Layer **Files:** `/Database/` **Provider Pattern** for database abstraction: ```csharp public interface IDatabaseProvider { Task ExecuteAsync(string query, object? parameters = null); Task QueryFirstOrDefaultAsync(string query, object? parameters = null); Task> QueryAsync(string query, object? parameters = null); // Query generation methods string GetBanQuery(bool multiServer); string GetMuteQuery(bool multiServer); // ... more query methods } ``` **Implementations:** - `MySqlDatabaseProvider` - MySQL-specific SQL syntax - `SqliteDatabaseProvider` - SQLite-specific SQL syntax **Benefits:** - Single codebase supports both MySQL and SQLite - Easy to add new database providers - Query methods accept `multiServer` boolean for scoping **Migration System:** **File:** `Database/Migration.cs` - File-based migrations in `/Database/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. Menu System **Files:** `/Menus/` **MenuManager (Singleton Pattern):** ```csharp public class MenuManager { public static MenuManager Instance { get; private set; } private readonly Dictionary _categories = new(); private readonly Dictionary> _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):** ```csharp public class MenuBuilder { public MenuBuilder(string title); public MenuBuilder AddOption(string name, Action action, ...); public MenuBuilder AddSubMenu(string name, Func factory, ...); public MenuBuilder WithBackAction(Action backAction); public void OpenMenu(CCSPlayerController player); } ``` **Specific Menu Classes:** - `AdminMenu` - Main admin menu with categories - `ManagePlayersMenu` - Player management menus - `ManageServerMenu` - Server settings - `DurationMenu` - Duration selection - `ReasonMenu` - Reason selection **Benefits:** - Centralized menu management - Permission-aware rendering - Automatic back button handling - Reusable menu components --- ### 5. Command System **Files:** `/Commands/` **Central Registration:** **File:** `RegisterCommands.cs` ```csharp public static class RegisterCommands { public static Dictionary> _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: - `basebans.cs` - Ban, unban, warn commands - `basecomms.cs` - Gag, mute, silence commands - `basecommands.cs` - Admin management, server commands - `basechat.cs` - Chat commands (asay, csay, etc.) - `playercommands.cs` - Player manipulation (slay, hp, etc.) - `funcommands.cs` - Fun commands (god, noclip, etc.) - `basevotes.cs` - Voting system **Two-Tier Pattern:** ```csharp // Entry command - parses arguments [CommandHelper(2, "<#userid> [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 **Files:** `/Api/` **Interface:** `ICS2_SimpleAdminApi.cs` (in CS2-SimpleAdminApi project) **Implementation:** `CS2_SimpleAdminApi.cs` **Capability System:** ```csharp // In API interface public static readonly PluginCapability PluginCapability = new("simpleadmin:api"); // In module private readonly PluginCapability _pluginCapability = new("simpleadmin:api"); public override void OnAllPluginsLoaded(bool hotReload) { _api = _pluginCapability.Get(); } ``` **Event Publishing:** ```csharp // API exposes events public event Action? 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 ```csharp 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 ```csharp public class MenuBuilder { public static MenuBuilder Create(string title) => new MenuBuilder(title); public MenuBuilder AddOption(...) { /* ... */ return this; } public MenuBuilder AddSubMenu(...) { /* ... */ return this; } } ``` **Used for:** - Menu creation - Database provider creation ### Strategy Pattern ```csharp public interface IDatabaseProvider { Task> GetBans(bool multiServer); } public class MySqlDatabaseProvider : IDatabaseProvider { /* ... */ } public class SqliteDatabaseProvider : IDatabaseProvider { /* ... */ } ``` **Used for:** - Database abstraction - Query generation per DB type ### Observer Pattern ```csharp // Publisher public event Action? 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 - Module communication --- ## Concurrency & Thread Safety ### Async/Await Patterns All database operations use `async`/`await`: ```csharp public async Task BanPlayer(...) { await _database.ExecuteAsync(query, parameters); } ``` ### Semaphore for Rate Limiting ```csharp 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 ```csharp private readonly ConcurrentDictionary PlayersInfo = new(); ``` --- ## Memory Management ### In-Memory Caches **AdminCache:** ```csharp Dictionary Flags, int Immunity, DateTime Expiry)> AdminCache ``` **BanCache:** ```csharp Dictionary _banCacheBySteamId Dictionary> _banCacheByIp ``` **Benefits:** - Reduces database load - O(1) lookups - TTL-based expiry ### Cleanup ```csharp // 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 ```csharp 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: ```csharp // New capability var customCapability = new PluginCapability("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: ```csharp public event Action? OnNewEvent; ``` --- ## Testing Considerations ### Unit Testing - Managers can be tested independently - Mock `IDatabaseProvider` for testing - Test command handlers with mock players ### Integration Testing - Test on actual CS2 server - Multi-server scenarios - Database migration testing --- ## Related Documentation - **[API Overview](api/overview)** - Public API details - **[Module Development](module/getting-started)** - Create modules - **[GitHub Source](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Browse code