Introduces a new documentation site for CS2-SimpleAdmin using Docusaurus, including developer API references, tutorials, user guides, and module documentation. Removes the CleanModule example and updates FunCommands and ExampleModule. Also updates main plugin and API files to support new documentation and module structure.
16 KiB
sidebar_position
| 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:
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