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:
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
AdminCachewith 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:
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
WarnThresholdconfig - 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:
Dictionary<ulong, BanInfo> _banCacheBySteamId
Dictionary<string, List<BanInfo>> _banCacheByIp
Dictionary<ulong, List<string>> _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:
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:
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:
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):
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:
AdminMenu- Main admin menu with categoriesManagePlayersMenu- Player management menusManageServerMenu- Server settingsDurationMenu- Duration selectionReasonMenu- 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
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:
basebans.cs- Ban, unban, warn commandsbasecomms.cs- Gag, mute, silence commandsbasecommands.cs- Admin management, server commandsbasechat.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:
// 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
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:
- 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:
- Menu creation
- Database provider creation
Strategy Pattern
public interface IDatabaseProvider
{
Task<List<BanInfo>> GetBans(bool multiServer);
}
public class MySqlDatabaseProvider : IDatabaseProvider { /* ... */ }
public class SqliteDatabaseProvider : IDatabaseProvider { /* ... */ }
Used for:
- Database 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
- Module 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);
---
Configuration System
Multi-Level Configuration
- Main Config:
CS2-SimpleAdmin.json - Commands Config:
Commands.json - Module Configs: Per-module JSON files
Hot Reload 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 Architecture
New events can be added without breaking changes:
public event Action<NewEventArgs>? OnNewEvent;
---
Testing Considerations
Unit Testing
- Managers can be tested independently
- Mock
IDatabaseProviderfor testing - Test command handlers with mock players
Integration Testing
- Test on actual CS2 server
- Multi-server scenarios
- Database migration testing
---
Related Documentation
- API Overview - Public API details
- Module Development - Create modules
- GitHub Source - Browse code