Add CS2-SimpleAdmin documentation site

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.
This commit is contained in:
Dawid Bepierszcz
2025-10-20 01:27:01 +02:00
parent 21a5de6b3d
commit b0d8696756
74 changed files with 32732 additions and 279 deletions

20
CS2-SimpleAdmin-docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
## Installation
```bash
yarn
```
## Local Development
```bash
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
## Build
```bash
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
## Deployment
Using SSH:
```bash
USE_SSH=true yarn deploy
```
Not using SSH:
```bash
GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@@ -0,0 +1,465 @@
---
sidebar_position: 2
---
# Commands API
Complete reference for command registration and management.
## Command Registration
### RegisterCommand
Register a new command that integrates with CS2-SimpleAdmin.
```csharp
void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback)
```
**Parameters:**
- `name` - Command name (e.g., "css_mycommand")
- `description` - Command description (optional)
- `callback` - Method to call when command is executed
**Example:**
```csharp
_api.RegisterCommand("css_mycommand", "My custom command", OnMyCommand);
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
// Command logic here
}
```
**Throws:**
- `ArgumentException` - If command name is null or empty
- `ArgumentNullException` - If callback is null
---
### UnRegisterCommand
Unregister a previously registered command.
```csharp
void UnRegisterCommand(string commandName)
```
**Parameters:**
- `commandName` - Name of command to unregister
**Example:**
```csharp
_api.UnRegisterCommand("css_mycommand");
```
**Best Practice:**
Always unregister commands in your plugin's `Unload()` method:
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
_api.UnRegisterCommand("css_mycommand");
}
```
---
## Target Parsing
### GetTarget
Parse player targets from command arguments.
```csharp
TargetResult? GetTarget(CommandInfo command)
```
**Parameters:**
- `command` - Command info containing arguments
**Returns:**
- `TargetResult` - Contains matched players
- `null` - If no targets found or error
**Example:**
```csharp
[CommandHelper(1, "<#userid or name>")]
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var player in targets.Players)
{
// Do something with player
}
}
```
**Supported Target Syntax:**
- `@all` - All players
- `@ct` - All Counter-Terrorists
- `@t` - All Terrorists
- `@spec` - All spectators
- `@alive` - All alive players
- `@dead` - All dead players
- `@bot` - All bots
- `@human` - All human players
- `@me` - Command caller
- `#123` - Player by user ID
- `PlayerName` - Player by name (partial match)
---
## Command Logging
### LogCommand (with CommandInfo)
Log a command execution with full command info.
```csharp
void LogCommand(CCSPlayerController? caller, CommandInfo command)
```
**Parameters:**
- `caller` - Player who executed command (null for console)
- `command` - Command info object
**Example:**
```csharp
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
// Execute command logic
// Log the command
_api!.LogCommand(caller, command);
}
```
---
### LogCommand (with string)
Log a command execution with custom command string.
```csharp
void LogCommand(CCSPlayerController? caller, string command)
```
**Parameters:**
- `caller` - Player who executed command (null for console)
- `command` - Command string to log
**Example:**
```csharp
private void DoAction(CCSPlayerController? caller, CCSPlayerController target)
{
// Perform action
// Log with custom string
_api!.LogCommand(caller, $"css_mycommand {target.PlayerName}");
}
```
---
## Complete Example
### Basic Command
```csharp
private void RegisterCommands()
{
_api!.RegisterCommand("css_hello", "Say hello to a player", OnHelloCommand);
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnHelloCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var player in targets.Players.Where(p => p.IsValid))
{
player.PrintToChat($"Hello {player.PlayerName}!");
}
_api.LogCommand(caller, command);
}
public override void Unload(bool hotReload)
{
_api?.UnRegisterCommand("css_hello");
}
```
---
### Command with Permission Check
```csharp
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/ban")]
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
// Get targets
var targets = _api!.GetTarget(command);
if (targets == null) return;
// Filter for players caller can target
var validPlayers = targets.Players
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
.ToList();
foreach (var player in validPlayers)
{
// Issue ban
_api.IssuePenalty(player, caller, PenaltyType.Ban, "Banned via command", 1440);
}
_api.LogCommand(caller, command);
}
```
---
### Command with Arguments
```csharp
[CommandHelper(2, "<#userid or name> <value>")]
[RequiresPermissions("@css/slay")]
private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
{
// Parse HP value
if (!int.TryParse(command.GetArg(2), out int hp))
{
caller?.PrintToChat("Invalid HP value!");
return;
}
// Get targets
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var player in targets.Players.Where(p => p.IsValid && p.PawnIsAlive))
{
player.PlayerPawn?.Value?.SetHealth(hp);
}
_api.LogCommand(caller, $"css_sethp {hp}");
}
```
---
## Best Practices
### 1. Always Validate Targets
```csharp
var targets = _api!.GetTarget(command);
if (targets == null) return; // No targets found
// Filter for valid players
var validPlayers = targets.Players
.Where(p => p.IsValid && !p.IsBot)
.ToList();
if (validPlayers.Count == 0)
{
caller?.PrintToChat("No valid targets found!");
return;
}
```
### 2. Check Immunity
```csharp
foreach (var player in targets.Players)
{
// Check if caller can target this player (immunity check)
if (!caller!.CanTarget(player))
{
caller.PrintToChat($"You cannot target {player.PlayerName}!");
continue;
}
// Safe to target player
DoAction(player);
}
```
### 3. Always Log Commands
```csharp
// Log every admin command execution
_api.LogCommand(caller, command);
```
### 4. Use CommandHelper Attribute
```csharp
// Specify minimum args and usage
[CommandHelper(minArgs: 1, usage: "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnCommand(CCSPlayerController? caller, CommandInfo command)
{
// CounterStrikeSharp validates args automatically
}
```
### 5. Cleanup on Unload
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Unregister ALL commands
_api.UnRegisterCommand("css_command1");
_api.UnRegisterCommand("css_command2");
}
```
---
## Common Patterns
### Multiple Aliases
```csharp
// Register same command with multiple aliases
var aliases = new[] { "css_mycommand", "css_mycmd", "css_mc" };
foreach (var alias in aliases)
{
_api.RegisterCommand(alias, "My command", OnMyCommand);
}
// Unregister all
public override void Unload(bool hotReload)
{
foreach (var alias in aliases)
{
_api?.UnRegisterCommand(alias);
}
}
```
### Command from Config
```csharp
// In Config.cs
public List<string> MyCommands { get; set; } = ["css_mycommand"];
// In Plugin
private void RegisterCommands()
{
foreach (var cmd in Config.MyCommands)
{
_api!.RegisterCommand(cmd, "Description", OnMyCommand);
}
}
// Allows users to add aliases or disable by clearing list
```
### Target Filtering
```csharp
// Get only alive players
var alivePlayers = targets.Players
.Where(p => p.IsValid && p.PawnIsAlive)
.ToList();
// Get only enemy team
var enemies = targets.Players
.Where(p => p.IsValid && p.Team != caller!.Team)
.ToList();
// Get targetable players
var targetable = targets.Players
.Where(p => p.IsValid && caller!.CanTarget(p))
.ToList();
```
---
## Error Handling
### Command Registration Errors
```csharp
try
{
_api.RegisterCommand("css_mycommand", "Description", OnMyCommand);
}
catch (ArgumentException ex)
{
Logger.LogError($"Failed to register command: {ex.Message}");
}
```
### Target Parsing Errors
```csharp
var targets = _api!.GetTarget(command);
if (targets == null)
{
// Target parsing failed
// Error message already sent to caller by SimpleAdmin
return;
}
if (targets.Players.Count == 0)
{
caller?.PrintToChat("No players matched your target!");
return;
}
```
---
## Performance Tips
### Cache Command Lists
```csharp
// Don't create new list every time
private readonly List<string> _commandAliases = new() { "css_cmd1", "css_cmd2" };
private void RegisterCommands()
{
foreach (var cmd in _commandAliases)
{
_api!.RegisterCommand(cmd, "Description", OnCommand);
}
}
```
### Efficient Target Filtering
```csharp
// ✅ Good - single LINQ query
var players = targets.Players
.Where(p => p.IsValid && !p.IsBot && p.PawnIsAlive && caller!.CanTarget(p))
.ToList();
// ❌ Bad - multiple iterations
var players = targets.Players.Where(p => p.IsValid).ToList();
players = players.Where(p => !p.IsBot).ToList();
players = players.Where(p => p.PawnIsAlive).ToList();
```
---
## Related APIs
- **[Menus API](menus)** - Create interactive menus
- **[Penalties API](penalties)** - Issue penalties from commands
- **[Utilities API](utilities)** - Helper functions for commands

View File

@@ -0,0 +1,642 @@
---
sidebar_position: 5
---
# Events API
Complete reference for CS2-SimpleAdmin event system.
## Event System Overview
CS2-SimpleAdmin exposes events that allow modules to react to plugin actions and state changes.
**All events use C# event delegates and should be subscribed to in `OnAllPluginsLoaded` and unsubscribed in `Unload`.**
---
## Plugin Lifecycle Events
### OnSimpleAdminReady
Fired when CS2-SimpleAdmin is fully initialized and ready.
```csharp
event Action? OnSimpleAdminReady
```
**When Fired:**
- After plugin load
- After database initialization
- After migrations complete
- Menu system ready
**Example:**
```csharp
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null) return;
// Subscribe to ready event
_api.OnSimpleAdminReady += OnSimpleAdminReady;
// Also call directly for hot reload case
OnSimpleAdminReady();
}
private void OnSimpleAdminReady()
{
Logger.LogInformation("SimpleAdmin is ready!");
// Register menus (requires SimpleAdmin to be ready)
RegisterMenus();
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
_api.OnSimpleAdminReady -= OnSimpleAdminReady;
}
```
**Best Practice:**
Always call your handler directly after subscribing to handle hot reload:
```csharp
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // ← Also call directly
```
---
## Penalty Events
### OnPlayerPenaltied
Fired when an **online player** receives a penalty.
```csharp
event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied
```
**Parameters:**
1. `PlayerInfo player` - Player who received penalty
2. `PlayerInfo? admin` - Admin who issued penalty (null if console)
3. `PenaltyType penaltyType` - Type of penalty
4. `string reason` - Penalty reason
5. `int duration` - Duration in minutes (0 = permanent)
6. `int? penaltyId` - Database penalty ID (null if not stored)
7. `int? serverId` - Server ID (null in single-server mode)
**Example:**
```csharp
_api.OnPlayerPenaltied += OnPlayerPenaltied;
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
var adminName = admin?.PlayerName ?? "Console";
Logger.LogInformation($"{adminName} penaltied {player.PlayerName}: {type} ({duration}m) - {reason}");
// React to specific penalty types
switch (type)
{
case PenaltyType.Ban:
// Log ban to external system
LogBanToWebhook(player, admin, reason, duration);
break;
case PenaltyType.Warn:
// Check warning count
if (player.Warnings >= 3)
{
Logger.LogWarning($"{player.PlayerName} has {player.Warnings} warnings!");
}
break;
}
}
```
---
### OnPlayerPenaltiedAdded
Fired when a penalty is added to an **offline player** by SteamID.
```csharp
event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded
```
**Parameters:**
1. `SteamID steamId` - Target player's SteamID
2. `PlayerInfo? admin` - Admin who issued penalty
3. `PenaltyType penaltyType` - Type of penalty
4. `string reason` - Penalty reason
5. `int duration` - Duration in minutes
6. `int? penaltyId` - Database penalty ID
7. `int? serverId` - Server ID
**Example:**
```csharp
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
private void OnPlayerPenaltiedAdded(
SteamID steamId,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
var adminName = admin?.PlayerName ?? "Console";
Logger.LogInformation($"Offline penalty: {adminName} -> SteamID {steamId}: {type} ({duration}m)");
// Log to external database or webhook
if (type == PenaltyType.Ban)
{
LogOfflineBan(steamId, admin, reason, duration);
}
}
```
---
## Admin Activity Events
### OnAdminShowActivity
Fired when an admin action is displayed to players.
```csharp
event Action<string, string?, bool, object>? OnAdminShowActivity
```
**Parameters:**
1. `string messageKey` - Translation key for the message
2. `string? callerName` - Admin name (null if console)
3. `bool dontPublish` - If true, don't broadcast to other systems
4. `object messageArgs` - Arguments for message formatting
**Example:**
```csharp
_api.OnAdminShowActivity += OnAdminShowActivity;
private void OnAdminShowActivity(
string messageKey,
string? callerName,
bool dontPublish,
object messageArgs)
{
if (dontPublish) return;
Logger.LogInformation($"Admin activity: {messageKey} by {callerName ?? "Console"}");
// Log to Discord, database, etc.
LogAdminAction(messageKey, callerName, messageArgs);
}
```
---
### OnAdminToggleSilent
Fired when an admin toggles silent mode.
```csharp
event Action<int, bool>? OnAdminToggleSilent
```
**Parameters:**
1. `int slot` - Player slot of admin
2. `bool status` - New silent status (true = silent, false = normal)
**Example:**
```csharp
_api.OnAdminToggleSilent += OnAdminToggleSilent;
private void OnAdminToggleSilent(int slot, bool status)
{
var player = Utilities.GetPlayerFromSlot(slot);
if (player == null) return;
var statusText = status ? "enabled" : "disabled";
Logger.LogInformation($"{player.PlayerName} {statusText} silent mode");
// Update UI or external systems
UpdateAdminStatus(player, status);
}
```
---
## Complete Examples
### Ban Logging System
```csharp
public class BanLogger
{
private ICS2_SimpleAdminApi? _api;
public void Initialize(ICS2_SimpleAdminApi api)
{
_api = api;
// Subscribe to both ban events
_api.OnPlayerPenaltied += OnPlayerPenaltied;
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
}
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
if (type != PenaltyType.Ban) return;
// Log to file
File.AppendAllText("bans.log",
$"[{DateTime.Now}] {player.PlayerName} ({player.SteamId}) " +
$"banned by {admin?.PlayerName ?? "Console"} " +
$"for {duration} minutes: {reason}\n");
// Send to Discord webhook
SendDiscordNotification(
$"🔨 **Ban Issued**\n" +
$"Player: {player.PlayerName}\n" +
$"Admin: {admin?.PlayerName ?? "Console"}\n" +
$"Duration: {FormatDuration(duration)}\n" +
$"Reason: {reason}"
);
}
private void OnPlayerPenaltiedAdded(
SteamID steamId,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
if (type != PenaltyType.Ban) return;
File.AppendAllText("bans.log",
$"[{DateTime.Now}] Offline ban: SteamID {steamId} " +
$"by {admin?.PlayerName ?? "Console"} " +
$"for {duration} minutes: {reason}\n");
}
private string FormatDuration(int minutes)
{
if (minutes == 0) return "Permanent";
if (minutes < 60) return $"{minutes} minutes";
if (minutes < 1440) return $"{minutes / 60} hours";
return $"{minutes / 1440} days";
}
}
```
---
### Warning Escalation System
```csharp
public class WarningEscalation
{
private ICS2_SimpleAdminApi? _api;
public void Initialize(ICS2_SimpleAdminApi api)
{
_api = api;
_api.OnPlayerPenaltied += OnPlayerPenaltied;
}
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
// Only handle warnings
if (type != PenaltyType.Warn) return;
Logger.LogInformation($"{player.PlayerName} now has {player.Warnings} warnings");
// Auto-escalate based on warning count
if (player.Warnings >= 3)
{
// 3 warnings = 1 hour ban
_api.IssuePenalty(
GetPlayerController(player.SteamId),
null,
PenaltyType.Ban,
"Automatic: 3 warnings",
60
);
}
else if (player.Warnings >= 5)
{
// 5 warnings = 1 day ban
_api.IssuePenalty(
GetPlayerController(player.SteamId),
null,
PenaltyType.Ban,
"Automatic: 5 warnings",
1440
);
}
}
}
```
---
### Admin Activity Monitor
```csharp
public class AdminMonitor
{
private readonly Dictionary<string, int> _adminActions = new();
public void Initialize(ICS2_SimpleAdminApi api)
{
api.OnAdminShowActivity += OnAdminShowActivity;
}
private void OnAdminShowActivity(
string messageKey,
string? callerName,
bool dontPublish,
object messageArgs)
{
if (callerName == null) return; // Ignore console actions
// Track admin actions
if (!_adminActions.ContainsKey(callerName))
{
_adminActions[callerName] = 0;
}
_adminActions[callerName]++;
// Log every 10th action
if (_adminActions[callerName] % 10 == 0)
{
Logger.LogInformation(
$"{callerName} has performed {_adminActions[callerName]} admin actions"
);
}
// Alert if admin is very active
if (_adminActions[callerName] > 100)
{
Logger.LogWarning($"{callerName} has performed many actions ({_adminActions[callerName]})");
}
}
}
```
---
## Best Practices
### 1. Always Unsubscribe
```csharp
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null) return;
// Subscribe
_api.OnPlayerPenaltied += OnPlayerPenaltied;
_api.OnAdminShowActivity += OnAdminShowActivity;
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
// ALWAYS unsubscribe
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
_api.OnAdminShowActivity -= OnAdminShowActivity;
}
```
### 2. Handle Null Admins
```csharp
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin, // ← Can be null!
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
var adminName = admin?.PlayerName ?? "Console";
// Use adminName safely
}
```
### 3. Use Events for Integration
```csharp
// ✅ Good - React to penalties
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
if (type == PenaltyType.Ban)
{
NotifyExternalSystem(player, reason);
}
};
// ❌ Bad - Wrapping penalty methods
// Don't wrap IssuePenalty, use events instead
```
### 4. Check Event Parameters
```csharp
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
// Check nullable parameters
if (penaltyId.HasValue)
{
Logger.LogInformation($"Penalty ID: {penaltyId.Value}");
}
if (serverId.HasValue)
{
Logger.LogInformation($"Server ID: {serverId.Value}");
}
}
```
### 5. OnSimpleAdminReady Pattern
```csharp
// ✅ Good - Handles both normal load and hot reload
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus();
// ❌ Bad - Only works on normal load
_api.OnSimpleAdminReady += RegisterMenus;
```
---
## Common Patterns
### Event-Based Statistics
```csharp
public class ServerStatistics
{
private int _totalBans;
private int _totalMutes;
private int _totalWarnings;
public void Initialize(ICS2_SimpleAdminApi api)
{
api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
switch (type)
{
case PenaltyType.Ban:
_totalBans++;
break;
case PenaltyType.Mute:
case PenaltyType.Gag:
case PenaltyType.Silence:
_totalMutes++;
break;
case PenaltyType.Warn:
_totalWarnings++;
break;
}
};
}
public void PrintStatistics()
{
Logger.LogInformation($"Server Statistics:");
Logger.LogInformation($"Total Bans: {_totalBans}");
Logger.LogInformation($"Total Mutes: {_totalMutes}");
Logger.LogInformation($"Total Warnings: {_totalWarnings}");
}
}
```
### Conditional Event Handling
```csharp
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
// Only handle bans
if (type != PenaltyType.Ban) return;
// Only handle permanent bans
if (duration != 0) return;
// Only handle admin-issued bans
if (admin == null) return;
// Process permanent admin bans
NotifyImportantBan(player, admin, reason);
};
```
---
## Performance Considerations
### Async Operations in Events
```csharp
// ⚠️ Be careful with async in event handlers
_api.OnPlayerPenaltied += async (player, admin, type, reason, duration, id, sid) =>
{
// Don't block the game thread
await Task.Run(() =>
{
// Long-running operation
LogToExternalDatabase(player, type, reason);
});
};
```
### Efficient Event Handlers
```csharp
// ✅ Good - Quick processing
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
// Quick logging
Logger.LogInformation($"Ban: {player.PlayerName}");
};
// ❌ Bad - Heavy processing
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
{
// Don't do expensive operations synchronously
SendEmailNotification(player); // ← This blocks the game thread!
};
```
---
## Troubleshooting
### Event Not Firing
**Check:**
1. Did you subscribe to the event?
2. Is `_api` not null?
3. Are you testing the right scenario?
4. Check server console for errors
### Memory Leaks
**Always unsubscribe:**
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Unsubscribe ALL events
_api.OnSimpleAdminReady -= OnReady;
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
// ... etc
}
```
---
## Related APIs
- **[Penalties API](penalties)** - Issue penalties that trigger events
- **[Commands API](commands)** - Commands that may trigger admin activity
- **[Utilities API](utilities)** - Helper functions for event handlers

View File

@@ -0,0 +1,706 @@
---
sidebar_position: 3
---
# Menus API
Complete reference for creating interactive admin menus.
## Menu Categories
### RegisterMenuCategory
Register a new menu category in the admin menu.
```csharp
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")
```
**Parameters:**
- `categoryId` - Unique identifier for the category
- `categoryName` - Display name shown in menu
- `permission` - Required permission to see category (default: "@css/generic")
**Example:**
```csharp
_api.RegisterMenuCategory("mycategory", "My Custom Category", "@css/generic");
```
**Best Practice:**
Register categories in the `OnSimpleAdminReady` event handler:
```csharp
_api.OnSimpleAdminReady += () =>
{
_api.RegisterMenuCategory("mycategory", Localizer?["category_name"] ?? "My Category");
};
```
---
## Menu Registration
### RegisterMenu (Basic)
Register a menu within a category.
```csharp
void RegisterMenu(
string categoryId,
string menuId,
string menuName,
Func<CCSPlayerController, object> menuFactory,
string? permission = null,
string? commandName = null
)
```
**Parameters:**
- `categoryId` - Category to add menu to
- `menuId` - Unique menu identifier
- `menuName` - Display name in menu
- `menuFactory` - Function that creates the menu
- `permission` - Required permission (optional)
- `commandName` - Command for permission override (optional, e.g., "css_god")
**Example:**
```csharp
_api.RegisterMenu(
"mycategory",
"mymenu",
"My Menu",
CreateMyMenu,
"@css/generic"
);
private object CreateMyMenu(CCSPlayerController player)
{
var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", player);
// Add options...
return menu;
}
```
---
### RegisterMenu (with MenuContext) ⭐ RECOMMENDED
Register a menu with automatic context passing - **eliminates duplication!**
```csharp
void RegisterMenu(
string categoryId,
string menuId,
string menuName,
Func<CCSPlayerController, MenuContext, object> menuFactory,
string? permission = null,
string? commandName = null
)
```
**Parameters:**
- `categoryId` - Category to add menu to
- `menuId` - Unique menu identifier
- `menuName` - Display name in menu
- `menuFactory` - Function that receives player AND context
- `permission` - Required permission (optional)
- `commandName` - Command for permission override (optional)
**Example:**
```csharp
// ✅ NEW WAY - No duplication!
_api.RegisterMenu(
"fun",
"god",
"God Mode",
CreateGodMenu,
"@css/cheats",
"css_god"
);
private object CreateGodMenu(CCSPlayerController admin, MenuContext context)
{
// context contains: CategoryId, MenuId, MenuTitle, Permission, CommandName
return _api!.CreateMenuWithPlayers(
context, // ← Automatically uses "God Mode" and "fun"
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) => ToggleGod(admin, target)
);
}
// ❌ OLD WAY - Had to repeat "God Mode" and "fun"
private object CreateGodMenuOld(CCSPlayerController admin)
{
return _api!.CreateMenuWithPlayers(
"God Mode", // ← Repeated from RegisterMenu
"fun", // ← Repeated from RegisterMenu
admin,
filter,
action
);
}
```
**MenuContext Properties:**
```csharp
public class MenuContext
{
public string CategoryId { get; } // e.g., "fun"
public string MenuId { get; } // e.g., "god"
public string MenuTitle { get; } // e.g., "God Mode"
public string? Permission { get; } // e.g., "@css/cheats"
public string? CommandName { get; } // e.g., "css_god"
}
```
---
### UnregisterMenu
Remove a menu from a category.
```csharp
void UnregisterMenu(string categoryId, string menuId)
```
**Example:**
```csharp
public override void Unload(bool hotReload)
{
_api?.UnregisterMenu("mycategory", "mymenu");
}
```
---
## Menu Creation
### CreateMenuWithBack
Create a menu with automatic back button.
```csharp
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)
```
**Parameters:**
- `title` - Menu title
- `categoryId` - Category for back button navigation
- `player` - Player viewing the menu
**Example:**
```csharp
var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", admin);
_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
_api.AddMenuOption(menu, "Option 2", _ => DoAction2());
return menu;
```
---
### CreateMenuWithBack (with Context) ⭐ RECOMMENDED
Create a menu using context - **no duplication!**
```csharp
object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
```
**Example:**
```csharp
private object CreateMenu(CCSPlayerController admin, MenuContext context)
{
// ✅ Uses context.MenuTitle and context.CategoryId automatically
var menu = _api!.CreateMenuWithBack(context, admin);
_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
return menu;
}
```
---
### CreateMenuWithPlayers
Create a menu showing a list of players.
```csharp
object CreateMenuWithPlayers(
string title,
string categoryId,
CCSPlayerController admin,
Func<CCSPlayerController, bool> filter,
Action<CCSPlayerController, CCSPlayerController> onSelect
)
```
**Parameters:**
- `title` - Menu title
- `categoryId` - Category for back button
- `admin` - Admin viewing menu
- `filter` - Function to filter which players to show
- `onSelect` - Action when player is selected (admin, selectedPlayer)
**Example:**
```csharp
return _api!.CreateMenuWithPlayers(
"Select Player",
"mycategory",
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) =>
{
// Do something with selected player
DoAction(admin, target);
}
);
```
---
### CreateMenuWithPlayers (with Context) ⭐ RECOMMENDED
```csharp
object CreateMenuWithPlayers(
MenuContext context,
CCSPlayerController admin,
Func<CCSPlayerController, bool> filter,
Action<CCSPlayerController, CCSPlayerController> onSelect
)
```
**Example:**
```csharp
private object CreatePlayerMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context, // ← Automatically uses correct title and category!
admin,
player => player.PawnIsAlive && admin.CanTarget(player),
(admin, target) => PerformAction(admin, target)
);
}
```
---
## Menu Options
### AddMenuOption
Add a clickable option to a menu.
```csharp
void AddMenuOption(
object menu,
string name,
Action<CCSPlayerController> action,
bool disabled = false,
string? permission = null
)
```
**Parameters:**
- `menu` - Menu to add option to
- `name` - Option display text
- `action` - Function called when selected
- `disabled` - Whether option is disabled (default: false)
- `permission` - Required permission to see option (optional)
**Example:**
```csharp
var menu = _api!.CreateMenuWithBack("Actions", "mycategory", admin);
_api.AddMenuOption(menu, "Heal Player", _ =>
{
target.SetHp(100);
});
_api.AddMenuOption(menu, "Admin Only Option", _ =>
{
// Admin action
}, false, "@css/root");
return menu;
```
---
### AddSubMenu
Add a submenu option that opens another menu.
```csharp
void AddSubMenu(
object menu,
string name,
Func<CCSPlayerController, object> subMenuFactory,
bool disabled = false,
string? permission = null
)
```
**Parameters:**
- `menu` - Parent menu
- `name` - Submenu option display text
- `subMenuFactory` - Function that creates the submenu
- `disabled` - Whether option is disabled (default: false)
- `permission` - Required permission (optional)
**Example:**
```csharp
var menu = _api!.CreateMenuWithBack("Main Menu", "mycategory", admin);
_api.AddSubMenu(menu, "Player Actions", admin =>
{
return CreatePlayerActionsMenu(admin);
});
_api.AddSubMenu(menu, "Server Settings", admin =>
{
return CreateServerSettingsMenu(admin);
}, false, "@css/root");
return menu;
```
---
## Opening Menus
### OpenMenu
Display a menu to a player.
```csharp
void OpenMenu(object menu, CCSPlayerController player)
```
**Example:**
```csharp
var menu = CreateMyMenu(player);
_api!.OpenMenu(menu, player);
```
**Note:** Usually menus open automatically when selected, but this can be used for direct opening.
---
## Complete Examples
### Simple Player Selection Menu
```csharp
private void RegisterMenus()
{
_api!.RegisterMenuCategory("actions", "Player Actions", "@css/generic");
_api.RegisterMenu(
"actions",
"slay",
"Slay Player",
CreateSlayMenu,
"@css/slay"
);
}
private object CreateSlayMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context,
admin,
player => player.PawnIsAlive && admin.CanTarget(player),
(admin, target) =>
{
target.PlayerPawn?.Value?.CommitSuicide(false, true);
admin.PrintToChat($"Slayed {target.PlayerName}");
}
);
}
```
---
### Nested Menu with Value Selection
```csharp
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
var players = _api.GetValidPlayers()
.Where(p => p.PawnIsAlive && admin.CanTarget(p));
foreach (var player in players)
{
_api.AddSubMenu(menu, player.PlayerName, admin =>
{
return CreateHpValueMenu(admin, player);
});
}
return menu;
}
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "mycategory", admin);
var hpValues = new[] { 1, 10, 50, 100, 200, 500 };
foreach (var hp in hpValues)
{
_api.AddMenuOption(menu, $"{hp} HP", _ =>
{
if (target.IsValid && target.PawnIsAlive)
{
target.PlayerPawn?.Value?.SetHealth(hp);
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
}
});
}
return menu;
}
```
---
### Menu with Permissions
```csharp
private object CreateAdminMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
// Everyone with menu access sees this
_api.AddMenuOption(menu, "Basic Action", _ => DoBasicAction());
// Only root admins see this
_api.AddMenuOption(menu, "Dangerous Action", _ =>
{
DoDangerousAction();
}, false, "@css/root");
// Submenu with permission
_api.AddSubMenu(menu, "Advanced Options", admin =>
{
return CreateAdvancedMenu(admin);
}, false, "@css/root");
return menu;
}
```
---
### Dynamic Menu with Current State
```csharp
private object CreateToggleMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
var players = _api.GetValidPlayers()
.Where(p => admin.CanTarget(p));
foreach (var player in players)
{
// Show current state in option name
bool hasGod = GodPlayers.Contains(player.Slot);
string status = hasGod ? "✓ ON" : "✗ OFF";
_api.AddMenuOption(menu, $"{player.PlayerName} ({status})", _ =>
{
if (hasGod)
GodPlayers.Remove(player.Slot);
else
GodPlayers.Add(player.Slot);
// Recreate menu to show updated state
var newMenu = CreateToggleMenu(admin, context);
_api.OpenMenu(newMenu, admin);
});
}
return menu;
}
```
---
## Best Practices
### 1. Use MenuContext
```csharp
// ✅ Good - Uses context
_api.RegisterMenu("cat", "id", "Title", CreateMenu, "@css/generic");
private object CreateMenu(CCSPlayerController admin, MenuContext context)
{
return _api.CreateMenuWithPlayers(context, admin, filter, action);
}
// ❌ Bad - Duplicates title and category
_api.RegisterMenu("cat", "id", "Title", CreateMenuOld, "@css/generic");
private object CreateMenuOld(CCSPlayerController admin)
{
return _api.CreateMenuWithPlayers("Title", "cat", admin, filter, action);
}
```
### 2. Register in OnSimpleAdminReady
```csharp
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Also call directly for hot reload
private void RegisterMenus()
{
if (_menusRegistered) return;
_api!.RegisterMenuCategory("category", "Category Name");
_api.RegisterMenu("category", "menu", "Menu Name", CreateMenu);
_menusRegistered = true;
}
```
### 3. Always Unregister
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
_api.UnregisterMenu("category", "menu");
_api.OnSimpleAdminReady -= RegisterMenus;
}
```
### 4. Validate Player State
```csharp
private object CreateMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context,
admin,
player => player.IsValid && // Player exists
!player.IsBot && // Not a bot
player.PawnIsAlive && // Alive
admin.CanTarget(player), // Can be targeted
(admin, target) =>
{
// Extra validation before action
if (!target.IsValid || !target.PawnIsAlive)
return;
DoAction(admin, target);
}
);
}
```
### 5. Use Translations for Menu Names
```csharp
_api.RegisterMenuCategory(
"mycategory",
Localizer?["category_name"] ?? "Default Name",
"@css/generic"
);
_api.RegisterMenu(
"mycategory",
"mymenu",
Localizer?["menu_name"] ?? "Default Menu",
CreateMenu
);
```
---
## Permission Override
The `commandName` parameter allows server admins to override menu permissions via CounterStrikeSharp's admin system.
**Example:**
```csharp
_api.RegisterMenu(
"fun",
"god",
"God Mode",
CreateGodMenu,
"@css/cheats", // Default permission
"css_god" // Command name for override
);
```
**Admin config can override:**
```json
{
"css_god": ["@css/vip"]
}
```
Now VIPs will see the God Mode menu instead of requiring @css/cheats!
---
## Common Patterns
### Player List with Actions
```csharp
private object CreatePlayerListMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
foreach (var player in _api.GetValidPlayers())
{
if (!admin.CanTarget(player)) continue;
_api.AddSubMenu(menu, player.PlayerName, admin =>
{
var actionMenu = _api.CreateMenuWithBack($"Actions: {player.PlayerName}", context.CategoryId, admin);
_api.AddMenuOption(actionMenu, "Slay", _ => player.CommitSuicide());
_api.AddMenuOption(actionMenu, "Kick", _ => KickPlayer(player));
_api.AddMenuOption(actionMenu, "Ban", _ => BanPlayer(admin, player));
return actionMenu;
});
}
return menu;
}
```
### Category-Based Organization
```csharp
private void RegisterAllMenus()
{
// Player management category
_api!.RegisterMenuCategory("players", "Player Management", "@css/generic");
_api.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
_api.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
// Server management category
_api.RegisterMenuCategory("server", "Server Management", "@css/generic");
_api.RegisterMenu("server", "map", "Change Map", CreateMapMenu, "@css/changemap");
_api.RegisterMenu("server", "settings", "Settings", CreateSettingsMenu, "@css/root");
}
```
---
## Related APIs
- **[Commands API](commands)** - Command integration
- **[Penalties API](penalties)** - Issue penalties from menus
- **[Utilities API](utilities)** - Helper functions for menus

View File

@@ -0,0 +1,621 @@
---
sidebar_position: 1
---
# API Overview
Complete reference for the CS2-SimpleAdmin API (ICS2_SimpleAdminApi).
## Introduction
The CS2-SimpleAdmin API is exposed via the `ICS2_SimpleAdminApi` interface, accessible through CounterStrikeSharp's capability system.
---
## Getting the API
### Using Capability System
```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CS2_SimpleAdminApi;
public class YourPlugin : BasePlugin
{
private ICS2_SimpleAdminApi? _api;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
new("simpleadmin:api");
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("CS2-SimpleAdmin API not found!");
Unload(false);
return;
}
// API is ready to use
RegisterFeatures();
}
}
```
### Static Capability Reference
```csharp
// Alternative approach
var capability = ICS2_SimpleAdminApi.PluginCapability;
var api = capability?.Get();
```
---
## API Categories
The API is organized into logical categories:
| Category | Description | Learn More |
|----------|-------------|------------|
| **Commands** | Register/unregister commands, parse targets | [](commands) |
| **Menus** | Create admin menus with player selection | [](menus) |
| **Penalties** | Issue bans, mutes, gags, warnings | [](penalties) |
| **Events** | Subscribe to plugin events | [](events) |
| **Utilities** | Helper functions, player info, activity messages | [](utilities) |
---
## Quick Reference
### Command Management
```csharp
// Register command
_api.RegisterCommand(name, description, callback);
// Unregister command
_api.UnRegisterCommand(name);
// Parse player targets
var targets = _api.GetTarget(command);
// Log command
_api.LogCommand(caller, command);
```
**[Full Documentation →](commands)**
---
### Menu System
```csharp
// Register category
_api.RegisterMenuCategory(categoryId, categoryName, permission);
// Register menu
_api.RegisterMenu(categoryId, menuId, menuName, menuFactory, permission, commandName);
// Create menu with players
_api.CreateMenuWithPlayers(context, admin, filter, onSelect);
// Create menu with back button
_api.CreateMenuWithBack(context, admin);
// Add menu option
_api.AddMenuOption(menu, name, action, disabled, permission);
// Add submenu
_api.AddSubMenu(menu, name, subMenuFactory, disabled, permission);
// Open menu
_api.OpenMenu(menu, player);
// Unregister menu
_api.UnregisterMenu(categoryId, menuId);
```
**[Full Documentation →](menus)**
---
### Penalty Management
```csharp
// Issue penalty to online player
_api.IssuePenalty(player, admin, penaltyType, reason, duration);
// Issue penalty by SteamID
_api.IssuePenalty(steamId, admin, penaltyType, reason, duration);
// Get player info
var playerInfo = _api.GetPlayerInfo(player);
// Get mute status
var muteStatus = _api.GetPlayerMuteStatus(player);
```
**Penalty Types:**
- `PenaltyType.Ban` - Ban player
- `PenaltyType.Kick` - Kick player
- `PenaltyType.Gag` - Block text chat
- `PenaltyType.Mute` - Block voice chat
- `PenaltyType.Silence` - Block both
- `PenaltyType.Warn` - Issue warning
**[Full Documentation →](penalties)**
---
### Event System
```csharp
// Plugin ready event
_api.OnSimpleAdminReady += OnReady;
// Player penaltied
_api.OnPlayerPenaltied += OnPlayerPenaltied;
// Offline penalty added
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
// Admin activity
_api.OnAdminShowActivity += OnAdminActivity;
// Admin silent toggle
_api.OnAdminToggleSilent += OnAdminToggleSilent;
```
**[Full Documentation →](events)**
---
### Utility Functions
```csharp
// Get player info with penalties
var info = _api.GetPlayerInfo(player);
// Get database connection string
var connectionString = _api.GetConnectionString();
// Get server address
var serverAddress = _api.GetServerAddress();
// Get server ID
var serverId = _api.GetServerId();
// Get valid players
var players = _api.GetValidPlayers();
// Check if admin is silent
bool isSilent = _api.IsAdminSilent(player);
// Get all silent admins
var silentAdmins = _api.ListSilentAdminsSlots();
// Show admin activity
_api.ShowAdminActivity(messageKey, callerName, dontPublish, args);
// Show admin activity with custom translation
_api.ShowAdminActivityTranslated(translatedMessage, callerName, dontPublish);
// Show admin activity with module localizer (recommended)
_api.ShowAdminActivityLocalized(moduleLocalizer, messageKey, callerName, dontPublish, args);
```
**[Full Documentation →](utilities)**
---
## Common Patterns
### Basic Module Structure
```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CS2_SimpleAdminApi;
namespace MyModule;
public class MyModule : BasePlugin
{
private ICS2_SimpleAdminApi? _api;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
new("simpleadmin:api");
public override string ModuleName => "My Module";
public override string ModuleVersion => "1.0.0";
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null) return;
// Register features
RegisterCommands();
// Wait for SimpleAdmin ready
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Fallback for hot reload
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Cleanup
_api.UnRegisterCommand("css_mycommand");
_api.UnregisterMenu("category", "menu");
_api.OnSimpleAdminReady -= RegisterMenus;
}
}
```
---
### Command with Target Selection
```csharp
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
// Parse targets
var targets = _api!.GetTarget(command);
if (targets == null) return;
// Filter valid players
var players = targets.Players
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
.ToList();
// Process each player
foreach (var player in players)
{
DoSomethingToPlayer(caller, player);
}
// Log command
_api.LogCommand(caller, command);
}
```
---
### Menu with Player Selection
```csharp
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
{
// Context contains categoryId, menuId, menuName, permission, commandName
return _api!.CreateMenuWithPlayers(
context, // Automatic title and category
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) =>
{
// Action when player selected
PerformAction(admin, target);
}
);
}
```
---
### Nested Menu
```csharp
private object CreatePlayerMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
var players = _api.GetValidPlayers()
.Where(p => admin.CanTarget(p));
foreach (var player in players)
{
_api.AddSubMenu(menu, player.PlayerName, admin =>
{
return CreateActionMenu(admin, player);
});
}
return menu;
}
private object CreateActionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api!.CreateMenuWithBack($"Actions for {target.PlayerName}", "category", admin);
_api.AddMenuOption(menu, "Action 1", _ => DoAction1(admin, target));
_api.AddMenuOption(menu, "Action 2", _ => DoAction2(admin, target));
return menu;
}
```
---
### Issue Penalty
```csharp
private void BanPlayer(CCSPlayerController? admin, CCSPlayerController target, int duration, string reason)
{
// Issue ban
_api!.IssuePenalty(
target,
admin,
PenaltyType.Ban,
reason,
duration // minutes, 0 = permanent
);
// Show activity
if (admin == null || !_api.IsAdminSilent(admin))
{
_api.ShowAdminActivityLocalized(
Localizer,
"ban_message",
admin?.PlayerName,
false,
target.PlayerName,
duration
);
}
}
```
---
### Event Subscription
```csharp
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null) return;
// Subscribe to events
_api.OnPlayerPenaltied += OnPlayerPenaltied;
}
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
Logger.LogInformation($"{player.PlayerName} received {type}: {reason} ({duration} min)");
// React to penalty
if (type == PenaltyType.Ban)
{
// Handle ban
}
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
}
```
---
## Best Practices
### 1. Always Check for Null
```csharp
if (_api == null)
{
Logger.LogError("API not available!");
return;
}
```
### 2. Use OnSimpleAdminReady Event
```csharp
_api.OnSimpleAdminReady += () =>
{
// Register menus only when SimpleAdmin is ready
RegisterMenus();
};
// Also call directly for hot reload case
RegisterMenus();
```
### 3. Clean Up on Unload
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Unregister all commands
_api.UnRegisterCommand("css_mycommand");
// Unregister all menus
_api.UnregisterMenu("category", "menu");
// Unsubscribe all events
_api.OnSimpleAdminReady -= OnReady;
}
```
### 4. Validate Player State
```csharp
if (!player.IsValid || !player.PawnIsAlive)
{
return;
}
if (!caller.CanTarget(player))
{
return; // Immunity check
}
```
### 5. Use Per-Player Translations
```csharp
// Each player sees message in their configured language
_api.ShowAdminActivityLocalized(
Localizer, // Your module's localizer
"translation_key",
caller?.PlayerName,
false,
args
);
```
### 6. Log All Admin Actions
```csharp
_api.LogCommand(caller, command);
// or
_api.LogCommand(caller, $"css_mycommand {player.PlayerName}");
```
---
## API Versioning
The API uses semantic versioning:
- **Major** - Breaking changes
- **Minor** - New features, backwards compatible
- **Patch** - Bug fixes
**Current Version:** Check [GitHub Releases](https://github.com/daffyyyy/CS2-SimpleAdmin/releases)
---
## Thread Safety
The API is designed for single-threaded use within the CounterStrikeSharp game thread.
**Do NOT:**
- Call API methods from background threads
- Use async/await with API calls without proper synchronization
**Do:**
- Call API methods from event handlers
- Call API methods from commands
- Call API methods from timers
---
## Error Handling
The API uses exceptions for critical errors:
```csharp
try
{
_api.RegisterCommand("css_cmd", "Desc", callback);
}
catch (ArgumentException ex)
{
Logger.LogError($"Failed to register command: {ex.Message}");
}
```
**Common exceptions:**
- `ArgumentException` - Invalid arguments
- `InvalidOperationException` - Invalid state
- `KeyNotFoundException` - Player not found
---
## Performance Considerations
### Efficient Player Filtering
```csharp
// ✅ Good - single LINQ query
var players = _api.GetValidPlayers()
.Where(p => p.IsValid && admin.CanTarget(p))
.ToList();
// ❌ Bad - multiple iterations
var players = _api.GetValidPlayers();
players = players.Where(p => p.IsValid).ToList();
players = players.Where(p => admin.CanTarget(p)).ToList();
```
### Cache Expensive Operations
```csharp
// Cache menu creation if used multiple times
private object? _cachedMenu;
private object GetMenu(CCSPlayerController player)
{
if (_cachedMenu == null)
{
_cachedMenu = CreateMenu(player);
}
return _cachedMenu;
}
```
---
## Debugging
### Enable Detailed Logging
```csharp
Logger.LogInformation("Debug: API loaded");
Logger.LogWarning("Warning: Player not found");
Logger.LogError("Error: Failed to execute command");
```
### Check API Availability
```csharp
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("❌ CS2-SimpleAdmin API not found!");
Logger.LogError("Make sure CS2-SimpleAdmin is installed and loaded.");
return;
}
Logger.LogInformation("✅ CS2-SimpleAdmin API loaded successfully");
}
```
---
## Next Steps
- **[Commands API](commands)** - Command registration and targeting
- **[Menus API](menus)** - Menu system details
- **[Penalties API](penalties)** - Penalty management
- **[Events API](events)** - Event subscription
- **[Utilities API](utilities)** - Helper functions
---
## Resources
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
- **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Reference implementation
- **[Module Development Guide](../module/getting-started)** - Create modules

View File

@@ -0,0 +1,610 @@
---
sidebar_position: 4
---
# Penalties API
Complete reference for issuing and managing player penalties.
## Penalty Types
```csharp
public enum PenaltyType
{
Ban, // Ban player from server
Kick, // Kick player from server
Gag, // Block text chat
Mute, // Block voice chat
Silence, // Block both text and voice
Warn // Issue warning
}
```
---
## Issue Penalties
### IssuePenalty (Online Player)
Issue a penalty to a currently connected player.
```csharp
void IssuePenalty(
CCSPlayerController player,
CCSPlayerController? admin,
PenaltyType penaltyType,
string reason,
int duration = -1
)
```
**Parameters:**
- `player` - Target player controller
- `admin` - Admin issuing penalty (null for console)
- `penaltyType` - Type of penalty
- `reason` - Reason for penalty
- `duration` - Duration in minutes (0 = permanent, -1 = default)
**Example:**
```csharp
// Ban player for 1 day
_api!.IssuePenalty(
player,
admin,
PenaltyType.Ban,
"Cheating",
1440 // 24 hours in minutes
);
// Permanent ban
_api.IssuePenalty(
player,
admin,
PenaltyType.Ban,
"Severe rule violation",
0
);
// Kick player
_api.IssuePenalty(
player,
admin,
PenaltyType.Kick,
"AFK"
);
// Gag for 30 minutes
_api.IssuePenalty(
player,
admin,
PenaltyType.Gag,
"Chat spam",
30
);
```
---
### IssuePenalty (Offline Player)
Issue a penalty to a player by SteamID (even if offline).
```csharp
void IssuePenalty(
SteamID steamid,
CCSPlayerController? admin,
PenaltyType penaltyType,
string reason,
int duration = -1
)
```
**Parameters:**
- `steamid` - Target player's SteamID
- `admin` - Admin issuing penalty (null for console)
- `penaltyType` - Type of penalty
- `reason` - Reason for penalty
- `duration` - Duration in minutes (0 = permanent, -1 = default)
**Example:**
```csharp
// Ban offline player
var steamId = new SteamID(76561198012345678);
_api!.IssuePenalty(
steamId,
admin,
PenaltyType.Ban,
"Ban evasion",
10080 // 7 days
);
// Mute offline player
_api.IssuePenalty(
steamId,
admin,
PenaltyType.Mute,
"Voice abuse",
1440
);
```
**Supported SteamID Formats:**
```csharp
// SteamID64
new SteamID(76561198012345678)
// Also works with SteamID string parsing
SteamID.FromString("STEAM_1:0:12345678")
SteamID.FromString("[U:1:12345678]")
```
---
## Get Player Information
### GetPlayerInfo
Get detailed player information including penalty counts.
```csharp
PlayerInfo GetPlayerInfo(CCSPlayerController player)
```
**Returns:** `PlayerInfo` object containing:
- `PlayerName` - Player's name
- `SteamId` - Steam ID (ulong)
- `IpAddress` - Player's IP address
- `Warnings` - Warning count
- `Bans` - Ban count
- `Mutes` - Mute count
- `Gags` - Gag count
**Example:**
```csharp
var playerInfo = _api!.GetPlayerInfo(player);
Console.WriteLine($"Player: {playerInfo.PlayerName}");
Console.WriteLine($"SteamID: {playerInfo.SteamId}");
Console.WriteLine($"Warnings: {playerInfo.Warnings}");
Console.WriteLine($"Total Bans: {playerInfo.Bans}");
// Check if player has penalties
if (playerInfo.Warnings >= 3)
{
_api.IssuePenalty(player, null, PenaltyType.Ban, "Too many warnings", 1440);
}
```
**Throws:**
- `KeyNotFoundException` - If player doesn't have a valid UserId
---
### GetPlayerMuteStatus
Get current mute/gag/silence status for a player.
```csharp
Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(
CCSPlayerController player
)
```
**Returns:** Dictionary mapping penalty types to lists of active penalties
**Example:**
```csharp
var muteStatus = _api!.GetPlayerMuteStatus(player);
// Check if player is gagged
if (muteStatus.ContainsKey(PenaltyType.Gag))
{
var gagPenalties = muteStatus[PenaltyType.Gag];
foreach (var (endTime, duration, passed) in gagPenalties)
{
if (!passed)
{
var remaining = endTime - DateTime.UtcNow;
Console.WriteLine($"Gagged for {remaining.TotalMinutes:F0} more minutes");
}
}
}
// Check if player is muted
if (muteStatus.ContainsKey(PenaltyType.Mute))
{
Console.WriteLine("Player is currently muted");
}
// Check if player is silenced
if (muteStatus.ContainsKey(PenaltyType.Silence))
{
Console.WriteLine("Player is silenced (gag + mute)");
}
```
---
## Server Information
### GetConnectionString
Get the database connection string.
```csharp
string GetConnectionString()
```
**Example:**
```csharp
var connectionString = _api!.GetConnectionString();
// Use for custom database operations
```
---
### GetServerAddress
Get the server's IP address and port.
```csharp
string GetServerAddress()
```
**Example:**
```csharp
var serverAddress = _api!.GetServerAddress();
Console.WriteLine($"Server: {serverAddress}");
// Example output: "192.168.1.100:27015"
```
---
### GetServerId
Get the server's unique ID in the database.
```csharp
int? GetServerId()
```
**Returns:**
- `int` - Server ID if multi-server mode enabled
- `null` - If single-server mode
**Example:**
```csharp
var serverId = _api!.GetServerId();
if (serverId.HasValue)
{
Console.WriteLine($"Server ID: {serverId.Value}");
}
else
{
Console.WriteLine("Single-server mode");
}
```
---
## Complete Examples
### Ban with Validation
```csharp
private void BanPlayer(CCSPlayerController? admin, CCSPlayerController target, int duration, string reason)
{
// Validate player
if (!target.IsValid)
{
admin?.PrintToChat("Invalid player!");
return;
}
// Check immunity
if (admin != null && !admin.CanTarget(target))
{
admin.PrintToChat($"You cannot ban {target.PlayerName} (higher immunity)!");
return;
}
// Get player info to check history
var playerInfo = _api!.GetPlayerInfo(target);
Logger.LogInformation(
$"{admin?.PlayerName ?? "Console"} banning {playerInfo.PlayerName} " +
$"(SteamID: {playerInfo.SteamId}, Previous bans: {playerInfo.Bans})"
);
// Issue ban
_api.IssuePenalty(target, admin, PenaltyType.Ban, reason, duration);
// Show activity
if (admin == null || !_api.IsAdminSilent(admin))
{
var durationText = duration == 0 ? "permanently" : $"for {duration} minutes";
Server.PrintToChatAll($"{admin?.PlayerName ?? "Console"} banned {target.PlayerName} {durationText}: {reason}");
}
}
```
---
### Progressive Punishment System
```csharp
private void HandlePlayerOffense(CCSPlayerController? admin, CCSPlayerController target, string reason)
{
var playerInfo = _api!.GetPlayerInfo(target);
// Progressive punishment based on warning count
if (playerInfo.Warnings == 0)
{
// First offense - warning
_api.IssuePenalty(target, admin, PenaltyType.Warn, reason);
target.PrintToChat("This is your first warning!");
}
else if (playerInfo.Warnings == 1)
{
// Second offense - gag for 30 minutes
_api.IssuePenalty(target, admin, PenaltyType.Gag, $"Second offense: {reason}", 30);
target.PrintToChat("Second warning! You are gagged for 30 minutes.");
}
else if (playerInfo.Warnings == 2)
{
// Third offense - 1 day ban
_api.IssuePenalty(target, admin, PenaltyType.Ban, $"Third offense: {reason}", 1440);
target.PrintToChat("Third offense! You are banned for 1 day.");
}
else
{
// More than 3 warnings - permanent ban
_api.IssuePenalty(target, admin, PenaltyType.Ban, $"Multiple offenses: {reason}", 0);
}
}
```
---
### Check Active Penalties Before Action
```csharp
private void AllowPlayerToChat(CCSPlayerController player)
{
var muteStatus = _api!.GetPlayerMuteStatus(player);
// Check if player is gagged
if (muteStatus.ContainsKey(PenaltyType.Gag))
{
player.PrintToChat("You are currently gagged and cannot use chat!");
return;
}
// Check if player is silenced (includes gag)
if (muteStatus.ContainsKey(PenaltyType.Silence))
{
player.PrintToChat("You are silenced and cannot communicate!");
return;
}
// Player can chat
ProcessChatMessage(player);
}
```
---
### Offline Player Ban
```csharp
private void BanOfflinePlayer(CCSPlayerController? admin, string steamIdString, int duration, string reason)
{
// Parse SteamID
if (!ulong.TryParse(steamIdString, out ulong steamId64))
{
admin?.PrintToChat("Invalid SteamID format!");
return;
}
var steamId = new SteamID(steamId64);
// Issue offline ban
_api!.IssuePenalty(steamId, admin, PenaltyType.Ban, reason, duration);
Logger.LogInformation(
$"{admin?.PlayerName ?? "Console"} banned offline player " +
$"(SteamID: {steamId64}) for {duration} minutes: {reason}"
);
admin?.PrintToChat($"Offline ban issued to SteamID {steamId64}");
}
```
---
### Multi-Account Detection
```csharp
[GameEventHandler]
public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo info)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return HookResult.Continue;
var playerInfo = _api!.GetPlayerInfo(player);
// Check if player has multiple accounts
if (playerInfo.Bans > 0)
{
// Notify admins
var admins = Utilities.GetPlayers()
.Where(p => AdminManager.PlayerHasPermissions(p, "@css/ban"));
foreach (var admin in admins)
{
admin.PrintToChat(
$"⚠ {player.PlayerName} has {playerInfo.Bans} previous ban(s)!"
);
}
}
return HookResult.Continue;
}
```
---
## Best Practices
### 1. Always Validate Players
```csharp
if (!target.IsValid || !target.PawnIsAlive)
{
return;
}
// Check immunity
if (admin != null && !admin.CanTarget(target))
{
admin.PrintToChat("Cannot target this player!");
return;
}
```
### 2. Provide Clear Reasons
```csharp
// ✅ Good - Specific reason
_api.IssuePenalty(player, admin, PenaltyType.Ban, "Aimbot detected in Round 12", 10080);
// ❌ Bad - Vague reason
_api.IssuePenalty(player, admin, PenaltyType.Ban, "cheating", 10080);
```
### 3. Log Penalty Actions
```csharp
_api.IssuePenalty(player, admin, PenaltyType.Ban, reason, duration);
Logger.LogInformation(
$"Penalty issued: {admin?.PlayerName ?? "Console"} -> {player.PlayerName} " +
$"| Type: {PenaltyType.Ban} | Duration: {duration}m | Reason: {reason}"
);
```
### 4. Handle Kick Separately
```csharp
// Kick doesn't need duration
_api.IssuePenalty(player, admin, PenaltyType.Kick, reason);
// NOT:
_api.IssuePenalty(player, admin, PenaltyType.Kick, reason, 0);
```
### 5. Check Active Penalties
```csharp
// Before issuing new penalty, check existing ones
var muteStatus = _api.GetPlayerMuteStatus(player);
if (muteStatus.ContainsKey(PenaltyType.Gag))
{
admin?.PrintToChat($"{player.PlayerName} is already gagged!");
return;
}
```
---
## Common Patterns
### Duration Helpers
```csharp
public static class PenaltyDurations
{
public const int OneHour = 60;
public const int OneDay = 1440;
public const int OneWeek = 10080;
public const int TwoWeeks = 20160;
public const int OneMonth = 43200;
public const int Permanent = 0;
}
// Usage
_api.IssuePenalty(player, admin, PenaltyType.Ban, reason, PenaltyDurations.OneWeek);
```
### Penalty History Display
```csharp
private void ShowPlayerHistory(CCSPlayerController admin, CCSPlayerController target)
{
var info = _api!.GetPlayerInfo(target);
admin.PrintToChat($"=== {info.PlayerName} History ===");
admin.PrintToChat($"Warnings: {info.Warnings}");
admin.PrintToChat($"Bans: {info.Bans}");
admin.PrintToChat($"Mutes: {info.Mutes}");
admin.PrintToChat($"Gags: {info.Gags}");
var muteStatus = _api.GetPlayerMuteStatus(target);
if (muteStatus.ContainsKey(PenaltyType.Gag))
admin.PrintToChat("Currently: GAGGED");
if (muteStatus.ContainsKey(PenaltyType.Mute))
admin.PrintToChat("Currently: MUTED");
if (muteStatus.ContainsKey(PenaltyType.Silence))
admin.PrintToChat("Currently: SILENCED");
}
```
---
## Error Handling
### Handle Invalid Players
```csharp
try
{
var playerInfo = _api!.GetPlayerInfo(player);
// Use playerInfo...
}
catch (KeyNotFoundException)
{
Logger.LogError($"Player info not found for {player?.PlayerName}");
return;
}
```
### Validate SteamID
```csharp
private bool TryParseSteamId(string input, out SteamID steamId)
{
steamId = default;
if (ulong.TryParse(input, out ulong steamId64))
{
steamId = new SteamID(steamId64);
return true;
}
return false;
}
```
---
## Related APIs
- **[Commands API](commands)** - Issue penalties from commands
- **[Menus API](menus)** - Issue penalties from menus
- **[Events API](events)** - React to penalty events
- **[Utilities API](utilities)** - Helper functions

View File

@@ -0,0 +1,585 @@
---
sidebar_position: 6
---
# Utilities API
Helper functions and utility methods for module development.
## Player Management
### GetValidPlayers
Get a list of all valid, connected players.
```csharp
List<CCSPlayerController> GetValidPlayers()
```
**Returns:** List of valid player controllers
**Example:**
```csharp
var players = _api!.GetValidPlayers();
foreach (var player in players)
{
Console.WriteLine($"Player: {player.PlayerName}");
}
// Filter for specific criteria
var alivePlayers = _api.GetValidPlayers()
.Where(p => p.PawnIsAlive)
.ToList();
var ctPlayers = _api.GetValidPlayers()
.Where(p => p.Team == CsTeam.CounterTerrorist)
.ToList();
```
**Note:** This method filters out invalid and bot players automatically.
---
## Admin Status
### IsAdminSilent
Check if an admin is in silent mode.
```csharp
bool IsAdminSilent(CCSPlayerController player)
```
**Parameters:**
- `player` - Player to check
**Returns:** `true` if player is in silent mode, `false` otherwise
**Example:**
```csharp
private void PerformAdminAction(CCSPlayerController admin, CCSPlayerController target)
{
// Do the action
DoAction(target);
// Only show activity if not silent
if (!_api!.IsAdminSilent(admin))
{
Server.PrintToChatAll($"{admin.PlayerName} performed action on {target.PlayerName}");
}
}
```
---
### ListSilentAdminsSlots
Get a list of player slots for all admins currently in silent mode.
```csharp
HashSet<int> ListSilentAdminsSlots()
```
**Returns:** HashSet of player slots
**Example:**
```csharp
var silentAdmins = _api!.ListSilentAdminsSlots();
Console.WriteLine($"Silent admins: {silentAdmins.Count}");
foreach (var slot in silentAdmins)
{
var player = Utilities.GetPlayerFromSlot(slot);
if (player != null)
{
Console.WriteLine($"- {player.PlayerName} (slot {slot})");
}
}
```
---
## Activity Messages
### ShowAdminActivity
Show an admin activity message to all players.
```csharp
void ShowAdminActivity(
string messageKey,
string? callerName = null,
bool dontPublish = false,
params object[] messageArgs
)
```
**Parameters:**
- `messageKey` - Translation key from SimpleAdmin's lang files
- `callerName` - Admin name (null for console)
- `dontPublish` - If true, don't trigger OnAdminShowActivity event
- `messageArgs` - Arguments for message formatting
**Example:**
```csharp
// Using SimpleAdmin's built-in translations
_api!.ShowAdminActivity(
"sa_admin_player_kick_message", // Translation key
admin?.PlayerName,
false,
player.PlayerName,
reason
);
```
**Limitations:**
- Only works with SimpleAdmin's own translation keys
- For module-specific messages, use `ShowAdminActivityLocalized`
---
### ShowAdminActivityTranslated
Show a pre-translated admin activity message.
```csharp
void ShowAdminActivityTranslated(
string translatedMessage,
string? callerName = null,
bool dontPublish = false
)
```
**Parameters:**
- `translatedMessage` - Already translated message
- `callerName` - Admin name
- `dontPublish` - If true, don't trigger event
**Example:**
```csharp
// Use when you've already translated the message
var message = Localizer?["my_action_message", player.PlayerName] ?? $"Action on {player.PlayerName}";
_api!.ShowAdminActivityTranslated(
message,
admin?.PlayerName,
false
);
```
**Use Case:**
- When you need custom message formatting
- When translation is already done
---
### ShowAdminActivityLocalized ⭐ RECOMMENDED
Show admin activity with per-player language support using module's localizer.
```csharp
void ShowAdminActivityLocalized(
object moduleLocalizer,
string messageKey,
string? callerName = null,
bool dontPublish = false,
params object[] messageArgs
)
```
**Parameters:**
- `moduleLocalizer` - Your module's `IStringLocalizer` instance
- `messageKey` - Translation key from your module's lang files
- `callerName` - Admin name
- `dontPublish` - If true, don't trigger event
- `messageArgs` - Message arguments
**Example:**
```csharp
// Each player sees message in their configured language!
if (Localizer != null)
{
_api!.ShowAdminActivityLocalized(
Localizer, // Your module's localizer
"fun_admin_god_message", // From your lang/en.json
admin?.PlayerName,
false,
player.PlayerName
);
}
```
**lang/en.json:**
```json
{
"fun_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!"
}
```
**Why This is Best:**
- ✅ Each player sees message in their own language
- ✅ Uses your module's translations
- ✅ Supports color codes
- ✅ Per-player localization
---
## Complete Examples
### Action with Activity Message
```csharp
private void ToggleGodMode(CCSPlayerController? admin, CCSPlayerController target)
{
// Perform action
if (GodPlayers.Contains(target.Slot))
{
GodPlayers.Remove(target.Slot);
}
else
{
GodPlayers.Add(target.Slot);
}
// Show activity (respecting silent mode)
if (admin == null || !_api!.IsAdminSilent(admin))
{
if (Localizer != null)
{
_api!.ShowAdminActivityLocalized(
Localizer,
"fun_admin_god_message",
admin?.PlayerName,
false,
target.PlayerName
);
}
}
// Log action
_api!.LogCommand(admin, $"css_god {target.PlayerName}");
}
```
---
### Broadcast to Non-Silent Admins
```csharp
private void NotifyAdmins(string message)
{
var silentAdmins = _api!.ListSilentAdminsSlots();
var players = _api.GetValidPlayers();
foreach (var player in players)
{
// Check if player is admin
if (!AdminManager.PlayerHasPermissions(player, "@css/generic"))
continue;
// Skip if admin is in silent mode
if (silentAdmins.Contains(player.Slot))
continue;
player.PrintToChat(message);
}
}
```
---
### Filter Players by Criteria
```csharp
private List<CCSPlayerController> GetTargetablePlayers(CCSPlayerController admin)
{
return _api!.GetValidPlayers()
.Where(p =>
p.IsValid &&
!p.IsBot &&
p.PawnIsAlive &&
admin.CanTarget(p))
.ToList();
}
private List<CCSPlayerController> GetAliveEnemies(CCSPlayerController player)
{
return _api!.GetValidPlayers()
.Where(p =>
p.Team != player.Team &&
p.PawnIsAlive)
.ToList();
}
private List<CCSPlayerController> GetAdmins()
{
return _api!.GetValidPlayers()
.Where(p => AdminManager.PlayerHasPermissions(p, "@css/generic"))
.ToList();
}
```
---
## Best Practices
### 1. Use ShowAdminActivityLocalized
```csharp
// ✅ Good - Per-player language
_api.ShowAdminActivityLocalized(
Localizer,
"my_message_key",
admin?.PlayerName,
false,
args
);
// ❌ Bad - Single language for all
Server.PrintToChatAll($"{admin?.PlayerName} did something");
```
### 2. Respect Silent Mode
```csharp
// ✅ Good - Check silent mode
if (admin == null || !_api.IsAdminSilent(admin))
{
ShowActivity();
}
// ❌ Bad - Always show activity
ShowActivity(); // Ignores silent mode!
```
### 3. Validate Players from GetValidPlayers
```csharp
var players = _api.GetValidPlayers();
foreach (var player in players)
{
// Still good to check, especially for async operations
if (!player.IsValid) continue;
DoSomething(player);
}
```
### 4. Cache Silent Admin List if Checking Multiple Times
```csharp
// ✅ Good - Cache for multiple checks
var silentAdmins = _api.ListSilentAdminsSlots();
foreach (var admin in admins)
{
if (silentAdmins.Contains(admin.Slot)) continue;
NotifyAdmin(admin);
}
// ❌ Bad - Query for each admin
foreach (var admin in admins)
{
if (_api.IsAdminSilent(admin)) continue; // ← Repeated calls
NotifyAdmin(admin);
}
```
---
## Common Patterns
### Silent Mode Wrapper
```csharp
private void ShowActivityIfNotSilent(
CCSPlayerController? admin,
string messageKey,
params object[] args)
{
if (admin != null && _api!.IsAdminSilent(admin))
return;
if (Localizer != null)
{
_api!.ShowAdminActivityLocalized(
Localizer,
messageKey,
admin?.PlayerName,
false,
args
);
}
}
// Usage
ShowActivityIfNotSilent(admin, "my_action", player.PlayerName);
```
---
### Get Online Admins
```csharp
private List<CCSPlayerController> GetOnlineAdmins(string permission = "@css/generic")
{
return _api!.GetValidPlayers()
.Where(p => AdminManager.PlayerHasPermissions(p, permission))
.ToList();
}
// Usage
var admins = GetOnlineAdmins("@css/root");
foreach (var admin in admins)
{
admin.PrintToChat("Important admin message");
}
```
---
### Notify All Players Except Silent Admins
```csharp
private void BroadcastMessage(string message, bool excludeSilentAdmins = true)
{
var silentAdmins = excludeSilentAdmins
? _api!.ListSilentAdminsSlots()
: new HashSet<int>();
foreach (var player in _api.GetValidPlayers())
{
if (silentAdmins.Contains(player.Slot))
continue;
player.PrintToChat(message);
}
}
```
---
## Activity Message Formatting
### Color Codes in Messages
All activity messages support color codes:
```json
{
"my_message": "{lightred}Admin{default} banned {lightred}{0}{default} for {yellow}{1}{default}"
}
```
**Available Colors:**
- `{default}` - Default color
- `{white}` - White
- `{darkred}` - Dark red
- `{green}` - Green
- `{lightyellow}` - Light yellow
- `{lightblue}` - Light blue
- `{olive}` - Olive
- `{lime}` - Lime
- `{red}` - Red
- `{purple}` - Purple
- `{grey}` - Grey
- `{yellow}` - Yellow
- `{gold}` - Gold
- `{silver}` - Silver
- `{blue}` - Blue
- `{darkblue}` - Dark blue
- `{bluegrey}` - Blue grey
- `{magenta}` - Magenta
- `{lightred}` - Light red
- `{orange}` - Orange
---
### Message Arguments
```csharp
// lang/en.json
{
"ban_message": "{lightred}{0}{default} banned {lightred}{1}{default} for {yellow}{2}{default} minutes: {red}{3}"
}
// Code
_api.ShowAdminActivityLocalized(
Localizer,
"ban_message",
admin?.PlayerName,
false,
admin?.PlayerName, // {0}
target.PlayerName, // {1}
duration, // {2}
reason // {3}
);
```
---
## Performance Tips
### Minimize GetValidPlayers Calls
```csharp
// ✅ Good - Call once, filter multiple times
var allPlayers = _api.GetValidPlayers();
var alivePlayers = allPlayers.Where(p => p.PawnIsAlive).ToList();
var deadPlayers = allPlayers.Where(p => !p.PawnIsAlive).ToList();
// ❌ Bad - Multiple calls
var alivePlayers = _api.GetValidPlayers().Where(p => p.PawnIsAlive).ToList();
var deadPlayers = _api.GetValidPlayers().Where(p => !p.PawnIsAlive).ToList();
```
---
### Efficient Filtering
```csharp
// ✅ Good - Single LINQ query
var targets = _api.GetValidPlayers()
.Where(p => p.Team == CsTeam.Terrorist &&
p.PawnIsAlive &&
admin.CanTarget(p))
.ToList();
// ❌ Bad - Multiple iterations
var players = _api.GetValidPlayers();
players = players.Where(p => p.Team == CsTeam.Terrorist).ToList();
players = players.Where(p => p.PawnIsAlive).ToList();
players = players.Where(p => admin.CanTarget(p)).ToList();
```
---
## Troubleshooting
### Activity Messages Not Showing
**Check:**
1. Is `Localizer` not null?
2. Does translation key exist in lang files?
3. Is message correctly formatted?
4. Check `dontPublish` parameter
### Silent Mode Not Working
**Check:**
1. Is player actually in silent mode? (`css_hide` command)
2. Are you checking before showing activity?
3. Check slot vs player controller mismatch
---
## Related APIs
- **[Commands API](commands)** - Log commands
- **[Menus API](menus)** - Get players for menus
- **[Events API](events)** - Admin activity events
- **[Penalties API](penalties)** - Get player info

View File

@@ -0,0 +1,695 @@
---
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<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:**
```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<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 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<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):**
```csharp
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 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<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 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> <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:**
```csharp
// 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:**
```csharp
// 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
```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<List<BanInfo>> 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<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`:
```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<ulong, PlayerInfo> PlayersInfo = new();
```
---
## Memory Management
### In-Memory Caches
**AdminCache:**
```csharp
Dictionary<ulong, (List<string> Flags, int Immunity, DateTime Expiry)> AdminCache
```
**BanCache:**
```csharp
Dictionary<ulong, BanInfo> _banCacheBySteamId
Dictionary<string, List<BanInfo>> _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<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:
```csharp
public event Action<NewEventArgs>? 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

View File

@@ -0,0 +1,379 @@
---
sidebar_position: 1
---
# Developer Introduction
Welcome to the CS2-SimpleAdmin developer documentation!
## Overview
This section contains technical documentation for developers who want to:
- Create modules using the CS2-SimpleAdmin API
- Contribute to the core plugin
- Integrate with CS2-SimpleAdmin from other plugins
- Understand the plugin architecture
---
## API Documentation
The CS2-SimpleAdmin API provides a rich set of features for module developers:
### Core Features
- **[Commands](api/commands)** - Register and manage commands
- **[Menus](api/menus)** - Create admin menus with player selection
- **[Penalties](api/penalties)** - Issue bans, mutes, gags, warnings
- **[Events](api/events)** - Subscribe to plugin events
- **[Utilities](api/utilities)** - Helper functions and player management
---
## Quick Links
### For Module Developers
- **[Module Development Guide](module/getting-started)** - Start creating modules
- **[Best Practices](module/best-practices)** - Write better code
- **[Examples](module/examples)** - Code examples and patterns
### For Core Contributors
- **[Architecture](architecture)** - Plugin structure and design
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
---
## Getting Started
### Prerequisites
- C# knowledge (intermediate level)
- .NET 8.0 SDK
- CounterStrikeSharp understanding
- CS2 dedicated server for testing
### Development Environment
**Recommended:**
- Visual Studio 2022 (Community or higher)
- VS Code with C# extension
- Git for version control
---
## CS2-SimpleAdminApi Interface
The main API interface provides all functionality:
```csharp
using CounterStrikeSharp.API.Core.Capabilities;
using CS2_SimpleAdminApi;
// Get the API
private ICS2_SimpleAdminApi? _api;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
new("simpleadmin:api");
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("CS2-SimpleAdmin API not found!");
return;
}
// Use the API
_api.RegisterCommand("css_mycommand", "Description", OnMyCommand);
}
```
---
## API Categories
### Command Management
Register custom commands that integrate with CS2-SimpleAdmin:
```csharp
_api.RegisterCommand("css_mycommand", "Description", callback);
_api.UnRegisterCommand("css_mycommand");
_api.GetTarget(command); // Parse player targets
```
**[Learn more →](api/commands)**
---
### Menu System
Create interactive menus with automatic back button handling:
```csharp
// Register category
_api.RegisterMenuCategory("mycategory", "My Category", "@css/generic");
// Register menu
_api.RegisterMenu("mycategory", "mymenu", "My Menu", CreateMenu, "@css/generic");
// Create menu with players
_api.CreateMenuWithPlayers(context, admin, filter, onSelect);
```
**[Learn more →](api/menus)**
---
### Penalty System
Issue and manage player penalties:
```csharp
// Ban player
_api.IssuePenalty(player, admin, PenaltyType.Ban, "Reason", 1440);
// Offline ban
_api.IssuePenalty(steamId, admin, PenaltyType.Ban, "Reason", 0);
// Check penalties
var status = _api.GetPlayerMuteStatus(player);
```
**[Learn more →](api/penalties)**
---
### Event System
React to plugin events:
```csharp
_api.OnSimpleAdminReady += () => { /* Plugin ready */ };
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, serverId) =>
{
// Player received penalty
};
```
**[Learn more →](api/events)**
---
### Utility Functions
Helper functions for common tasks:
```csharp
// Get player info
var playerInfo = _api.GetPlayerInfo(player);
// Get valid players
var players = _api.GetValidPlayers();
// Check admin status
if (_api.IsAdminSilent(admin)) { /* ... */ }
// Show admin activity
_api.ShowAdminActivity("message_key", callerName, false, args);
```
**[Learn more →](api/utilities)**
---
## Code Examples
### Simple Command
```csharp
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var target in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
{
// Do something with target
}
_api.LogCommand(caller, command);
}
```
### Simple Menu
```csharp
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context,
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) => DoAction(admin, target)
);
}
```
---
## Best Practices
### Error Handling
```csharp
if (_api == null)
{
Logger.LogError("API not available!");
return;
}
if (!player.IsValid || !player.PawnIsAlive)
{
return;
}
```
### Resource Cleanup
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Unregister commands
_api.UnRegisterCommand("css_mycommand");
// Unregister menus
_api.UnregisterMenu("mycategory", "mymenu");
// Unsubscribe events
_api.OnSimpleAdminReady -= OnReady;
}
```
### Translations
```csharp
// Use per-player language support
_api.ShowAdminActivityLocalized(
Localizer,
"translation_key",
caller?.PlayerName,
false,
args
);
```
---
## Reference Implementation
The **Fun Commands Module** serves as a complete reference implementation demonstrating all API features:
- Command registration from config
- Menu creation with context
- Per-player translations
- Proper cleanup
- Code organization
**[View Source Code](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)**
---
## Architecture Overview
CS2-SimpleAdmin follows a layered architecture:
**Layers:**
1. **CounterStrikeSharp Integration** - Game event handling
2. **Manager Layer** - Business logic (Bans, Mutes, Permissions)
3. **Database Layer** - MySQL/SQLite with migrations
4. **Menu System** - MenuManager with factory pattern
5. **Command System** - Dynamic registration
6. **Public API** - ICS2_SimpleAdminApi interface
**[Learn more →](architecture)**
---
## Contributing
### Ways to Contribute
1. **Report Bugs** - [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
2. **Suggest Features** - [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
3. **Submit Pull Requests** - Code contributions
4. **Create Modules** - Extend functionality
5. **Improve Documentation** - Help others learn
### Development Workflow
1. Fork the repository
2. Create feature branch
3. Make changes
4. Test thoroughly
5. Submit pull request
---
## Resources
### Documentation
- **[API Reference](api/overview)** - Complete API documentation
- **[Module Development](module/getting-started)** - Create modules
- **[Architecture](architecture)** - Plugin design
### External Resources
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS framework
- **[CS2 Docs](https://developer.valvesoftware.com/wiki/Counter-Strike_2)** - Game documentation
### Community
- **[GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
- **[Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)** - Bug reports
- **[Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)** - Questions and ideas
---
## Support
### Getting Help
1. **Check Documentation** - Most questions answered here
2. **Search Issues** - Someone may have had same problem
3. **Ask in Discussions** - Community help
4. **Create Issue** - For bugs or feature requests
### Reporting Bugs
Include:
- CS2-SimpleAdmin version
- CounterStrikeSharp version
- Error messages
- Steps to reproduce
- Expected vs actual behavior
---
## Next Steps
### For New Developers
1. **[Read API Overview](api/overview)** - Understand available features
2. **[Study Examples](module/examples)** - Learn from code
3. **[Create First Module](module/getting-started)** - Get hands-on
### For Advanced Developers
1. **[Read Architecture](architecture)** - Deep dive into structure
2. **[Review Source Code](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Understand implementation
3. **[Contribute](https://github.com/daffyyyy/CS2-SimpleAdmin/pulls)** - Help improve the plugin

View File

@@ -0,0 +1,540 @@
---
sidebar_position: 2
---
# Best Practices
Guidelines for writing high-quality CS2-SimpleAdmin modules.
## Code Organization
### Use Partial Classes
Split your code into logical files:
```
MyModule/
├── MyModule.cs # Main class, initialization
├── Commands.cs # Command handlers
├── Menus.cs # Menu creation
├── Actions.cs # Core logic
└── Config.cs # Configuration
```
```csharp
// MyModule.cs
public partial class MyModule : BasePlugin, IPluginConfig<Config>
{
// Initialization
}
// Commands.cs
public partial class MyModule
{
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
// Command logic
}
}
// Menus.cs
public partial class MyModule
{
private object CreateMyMenu(CCSPlayerController player, MenuContext context)
{
// Menu logic
}
}
```
**Benefits:**
- ✅ Easy to navigate
- ✅ Logical separation
- ✅ Better maintainability
---
## Configuration
### Use Command Lists
Allow users to customize aliases:
```csharp
public class Config : IBasePluginConfig
{
// ✅ Good - List allows multiple aliases
public List<string> MyCommands { get; set; } = ["css_mycommand"];
// ❌ Bad - Single string
public string MyCommand { get; set; } = "css_mycommand";
}
```
**Usage:**
```csharp
foreach (var cmd in Config.MyCommands)
{
_api!.RegisterCommand(cmd, "Description", OnMyCommand);
}
```
### Provide Sensible Defaults
```csharp
public class Config : IBasePluginConfig
{
public int Version { get; set; } = 1;
// Good defaults
public bool EnableFeature { get; set; } = true;
public int MaxValue { get; set; } = 100;
public List<string> Commands { get; set; } = ["css_default"];
}
```
---
## API Usage
### Always Check for Null
```csharp
// ✅ Good
if (_api == null)
{
Logger.LogError("API not available!");
return;
}
_api.RegisterCommand(...);
// ❌ Bad
_api!.RegisterCommand(...); // Can crash if null
```
### Use OnSimpleAdminReady Pattern
```csharp
// ✅ Good - Handles both normal load and hot reload
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Also call directly
// ❌ Bad - Only works on normal load
_api.OnSimpleAdminReady += RegisterMenus;
```
### Always Clean Up
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Unregister ALL commands
foreach (var cmd in Config.MyCommands)
{
_api.UnRegisterCommand(cmd);
}
// Unregister ALL menus
_api.UnregisterMenu("category", "menu");
// Unsubscribe ALL events
_api.OnSimpleAdminReady -= RegisterMenus;
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
}
```
---
## Player Validation
### Validate Before Acting
```csharp
// ✅ Good - Multiple checks
if (!player.IsValid)
{
Logger.LogWarning("Player is invalid!");
return;
}
if (!player.PawnIsAlive)
{
caller?.PrintToChat("Target must be alive!");
return;
}
if (admin != null && !admin.CanTarget(player))
{
admin.PrintToChat("Cannot target this player!");
return;
}
// Safe to proceed
DoAction(player);
```
### Check State Changes
```csharp
// ✅ Good - Validate in callback
_api.AddMenuOption(menu, "Action", _ =>
{
// Validate again - player state may have changed
if (!target.IsValid || !target.PawnIsAlive)
return;
DoAction(target);
});
// ❌ Bad - No validation in callback
_api.AddMenuOption(menu, "Action", _ =>
{
DoAction(target); // Might crash!
});
```
---
## Translations
### Use MenuContext for Menus
```csharp
// ✅ Good - No duplication
_api.RegisterMenu("cat", "id", "Title", CreateMenu, "@css/generic");
private object CreateMenu(CCSPlayerController admin, MenuContext context)
{
return _api.CreateMenuWithPlayers(context, admin, filter, action);
}
// ❌ Bad - Duplicates title and category
_api.RegisterMenu("cat", "id", "Title", CreateMenuOld, "@css/generic");
private object CreateMenuOld(CCSPlayerController admin)
{
return _api.CreateMenuWithPlayers("Title", "cat", admin, filter, action);
}
```
### Use Per-Player Translations
```csharp
// ✅ Good - Each player sees their language
if (Localizer != null)
{
_api.ShowAdminActivityLocalized(
Localizer,
"translation_key",
admin?.PlayerName,
false,
args
);
}
// ❌ Bad - Single language for all
Server.PrintToChatAll($"{admin?.PlayerName} did something");
```
### Provide English Fallbacks
```csharp
// ✅ Good - Fallback if translation missing
_api.RegisterMenuCategory(
"mycat",
Localizer?["category_name"] ?? "Default Category Name",
"@css/generic"
);
// ❌ Bad - No fallback
_api.RegisterMenuCategory(
"mycat",
Localizer["category_name"], // Crashes if no translation!
"@css/generic"
);
```
---
## Performance
### Cache Expensive Operations
```csharp
// ✅ Good - Cache on first access
private static Dictionary<int, string>? _itemCache;
private static Dictionary<int, string> GetItemCache()
{
if (_itemCache != null) return _itemCache;
// Build cache once
_itemCache = new Dictionary<int, string>();
// ... populate
return _itemCache;
}
// ❌ Bad - Rebuild every time
private Dictionary<int, string> GetItems()
{
var items = new Dictionary<int, string>();
// ... expensive operation
return items;
}
```
### Efficient LINQ Queries
```csharp
// ✅ Good - Single query
var players = _api.GetValidPlayers()
.Where(p => p.IsValid && !p.IsBot && p.PawnIsAlive)
.ToList();
// ❌ Bad - Multiple iterations
var players = _api.GetValidPlayers();
players = players.Where(p => p.IsValid).ToList();
players = players.Where(p => !p.IsBot).ToList();
players = players.Where(p => p.PawnIsAlive).ToList();
```
---
## Error Handling
### Log Errors
```csharp
// ✅ Good - Detailed logging
try
{
DoAction();
}
catch (Exception ex)
{
Logger.LogError($"Failed to perform action: {ex.Message}");
Logger.LogError($"Stack trace: {ex.StackTrace}");
}
// ❌ Bad - Silent failure
try
{
DoAction();
}
catch
{
// Ignore
}
```
### Graceful Degradation
```csharp
// ✅ Good - Continue with reduced functionality
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("SimpleAdmin API not found - limited functionality!");
// Module still loads, just without SimpleAdmin integration
return;
}
// ❌ Bad - Crash the entire module
_api = _pluginCapability.Get() ?? throw new Exception("No API!");
```
---
## Security
### Validate Admin Permissions
```csharp
// ✅ Good - Check permissions
[RequiresPermissions("@css/ban")]
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
// Already validated by attribute
}
// ❌ Bad - No permission check
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
// Anyone can use this!
}
```
### Check Immunity
```csharp
// ✅ Good - Check immunity
if (admin != null && !admin.CanTarget(target))
{
admin.PrintToChat($"Cannot target {target.PlayerName}!");
return;
}
// ❌ Bad - Ignore immunity
DoAction(target); // Can target higher immunity!
```
### Sanitize Input
```csharp
// ✅ Good - Validate and sanitize
private void OnSetValueCommand(CCSPlayerController? caller, CommandInfo command)
{
if (!int.TryParse(command.GetArg(1), out int value))
{
caller?.PrintToChat("Invalid number!");
return;
}
if (value < 0 || value > 1000)
{
caller?.PrintToChat("Value must be between 0 and 1000!");
return;
}
SetValue(value);
}
// ❌ Bad - No validation
private void OnSetValueCommand(CCSPlayerController? caller, CommandInfo command)
{
var value = int.Parse(command.GetArg(1)); // Can crash!
SetValue(value); // No range check!
}
```
---
## Documentation
### Comment Complex Logic
```csharp
// ✅ Good - Explain why, not what
// We need to check immunity twice because player state can change
// between menu creation and action execution
if (!admin.CanTarget(player))
{
return;
}
// ❌ Bad - States the obvious
// Check if admin can target player
if (!admin.CanTarget(player))
{
return;
}
```
### XML Documentation
```csharp
/// <summary>
/// Toggles god mode for the specified player.
/// </summary>
/// <param name="admin">Admin performing the action (null for console)</param>
/// <param name="target">Player to toggle god mode for</param>
/// <returns>True if god mode is now enabled, false otherwise</returns>
public bool ToggleGodMode(CCSPlayerController? admin, CCSPlayerController target)
{
// Implementation
}
```
---
## Testing
### Test Edge Cases
```csharp
// Test with:
// - Invalid players
// - Disconnected players
// - Players who changed teams
// - Null admins (console)
// - Silent admins
// - Players with higher immunity
```
### Test Hot Reload
```bash
# Server console
css_plugins reload YourModule
```
Make sure everything works after reload!
---
## Common Mistakes
### ❌ Forgetting to Unsubscribe
```csharp
public override void Unload(bool hotReload)
{
// Missing unsubscribe = memory leak!
// _api.OnSimpleAdminReady -= RegisterMenus; ← FORGOT THIS
}
```
### ❌ Not Checking API Availability
```csharp
// Crashes if SimpleAdmin not loaded!
_api.RegisterCommand(...); // ← No null check
```
### ❌ Hardcoding Strings
```csharp
// Bad - not translatable
player.PrintToChat("You have been banned!");
// Good - uses translations
var message = Localizer?["ban_message"] ?? "You have been banned!";
player.PrintToChat(message);
```
### ❌ Blocking Game Thread
```csharp
// Bad - blocks game thread
Thread.Sleep(5000);
// Good - use CounterStrikeSharp timers
AddTimer(5.0f, () => DoAction());
```
---
## Reference Implementation
Study the **Fun Commands Module** for best practices:
**[View Source](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)**
Shows:
- ✅ Proper code organization
- ✅ Configuration best practices
- ✅ Menu creation with context
- ✅ Per-player translations
- ✅ Proper cleanup
- ✅ Error handling
---
## Next Steps
- **[Examples](examples)** - More code examples
- **[API Reference](../api/overview)** - Full API documentation
- **[GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Browse source code

View File

@@ -0,0 +1,552 @@
---
sidebar_position: 3
---
# Code Examples
Practical examples for common module development scenarios.
## Complete Mini Module
A fully working minimal module:
```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CS2_SimpleAdminApi;
namespace HelloModule;
public class HelloModule : BasePlugin, IPluginConfig<Config>
{
public override string ModuleName => "Hello Module";
public override string ModuleVersion => "1.0.0";
private ICS2_SimpleAdminApi? _api;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
public Config Config { get; set; } = new();
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("CS2-SimpleAdmin API not found!");
return;
}
// Register command
foreach (var cmd in Config.HelloCommands)
{
_api.RegisterCommand(cmd, "Say hello to a player", OnHelloCommand);
}
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnHelloCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
{
player.PrintToChat($"Hello {player.PlayerName}!");
caller?.PrintToChat($"Said hello to {player.PlayerName}");
}
_api.LogCommand(caller, command);
}
public void OnConfigParsed(Config config) => Config = config;
public override void Unload(bool hotReload)
{
if (_api == null) return;
foreach (var cmd in Config.HelloCommands)
{
_api.UnRegisterCommand(cmd);
}
}
}
public class Config : IBasePluginConfig
{
public int Version { get; set; } = 1;
public List<string> HelloCommands { get; set; } = ["css_hello"];
}
```
---
## Command Examples
### Simple Target Command
```csharp
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/slay")]
private void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
{
if (player.PawnIsAlive)
{
player.PlayerPawn?.Value?.CommitSuicide(false, true);
caller?.PrintToChat($"Slayed {player.PlayerName}");
}
}
_api.LogCommand(caller, command);
}
```
### Command with Value Parameter
```csharp
[CommandHelper(2, "<#userid or name> <value>")]
[RequiresPermissions("@css/slay")]
private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
{
// Parse HP value
if (!int.TryParse(command.GetArg(2), out int hp) || hp < 1 || hp > 999)
{
caller?.PrintToChat("Invalid HP! Use 1-999");
return;
}
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var player in targets.Players.Where(p => p.IsValid && p.PawnIsAlive && caller!.CanTarget(p)))
{
player.PlayerPawn?.Value?.SetHealth(hp);
caller?.PrintToChat($"Set {player.PlayerName} HP to {hp}");
}
_api.LogCommand(caller, $"css_sethp {hp}");
}
```
### Command with Penalty
```csharp
[CommandHelper(1, "<#userid or name> [duration] [reason]")]
[RequiresPermissions("@css/ban")]
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
// Parse duration (default: 60 minutes)
int duration = 60;
if (command.ArgCount > 2)
{
int.TryParse(command.GetArg(2), out duration);
}
// Get reason (default: "Banned")
string reason = command.ArgCount > 3
? string.Join(" ", command.ArgString.Split(' ').Skip(2))
: "Banned";
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
{
_api.IssuePenalty(player, caller, PenaltyType.Ban, reason, duration);
caller?.PrintToChat($"Banned {player.PlayerName} for {duration} minutes");
}
_api.LogCommand(caller, command);
}
```
---
## Menu Examples
### Simple Player Selection Menu
```csharp
private void RegisterMenus()
{
_api!.RegisterMenuCategory("actions", "Player Actions", "@css/generic");
_api.RegisterMenu(
"actions",
"kick",
"Kick Player",
CreateKickMenu,
"@css/kick"
);
}
private object CreateKickMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context,
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) =>
{
Server.ExecuteCommand($"css_kick #{target.UserId} Kicked via menu");
admin.PrintToChat($"Kicked {target.PlayerName}");
}
);
}
```
### Nested Menu (Player → Action)
```csharp
private object CreatePlayerActionsMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
foreach (var player in _api.GetValidPlayers().Where(p => admin.CanTarget(p)))
{
_api.AddSubMenu(menu, player.PlayerName, admin =>
{
return CreateActionSelectMenu(admin, player);
});
}
return menu;
}
private object CreateActionSelectMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api!.CreateMenuWithBack($"Actions: {target.PlayerName}", "actions", admin);
_api.AddMenuOption(menu, "Slay", _ =>
{
if (target.IsValid && target.PawnIsAlive)
{
target.PlayerPawn?.Value?.CommitSuicide(false, true);
admin.PrintToChat($"Slayed {target.PlayerName}");
}
});
_api.AddMenuOption(menu, "Kick", _ =>
{
if (target.IsValid)
{
Server.ExecuteCommand($"css_kick #{target.UserId}");
}
});
_api.AddMenuOption(menu, "Ban", _ =>
{
if (target.IsValid)
{
_api.IssuePenalty(target, admin, PenaltyType.Ban, "Banned via menu", 1440);
}
});
return menu;
}
```
### Menu with Value Selection
```csharp
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
var players = _api.GetValidPlayers()
.Where(p => p.PawnIsAlive && admin.CanTarget(p));
foreach (var player in players)
{
_api.AddSubMenu(menu, player.PlayerName, admin =>
{
return CreateHpValueMenu(admin, player);
});
}
return menu;
}
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "actions", admin);
var hpValues = new[] { 1, 10, 50, 100, 200, 500 };
foreach (var hp in hpValues)
{
_api.AddMenuOption(menu, $"{hp} HP", _ =>
{
if (target.IsValid && target.PawnIsAlive)
{
target.PlayerPawn?.Value?.SetHealth(hp);
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
}
});
}
return menu;
}
```
---
## Event Examples
### React to Bans
```csharp
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null) return;
_api.OnPlayerPenaltied += OnPlayerPenaltied;
}
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
if (type != PenaltyType.Ban) return;
var adminName = admin?.PlayerName ?? "Console";
Logger.LogInformation($"Ban: {adminName} -> {player.PlayerName} ({duration}m): {reason}");
// Log to file
File.AppendAllText("bans.log",
$"[{DateTime.Now}] {player.PlayerName} banned by {adminName} for {duration}m: {reason}\n");
}
```
### Warning Escalation
```csharp
private void OnPlayerPenaltied(
PlayerInfo player,
PlayerInfo? admin,
PenaltyType type,
string reason,
int duration,
int? penaltyId,
int? serverId)
{
if (type != PenaltyType.Warn) return;
Logger.LogInformation($"{player.PlayerName} has {player.Warnings} warnings");
// Auto-ban at 3 warnings
if (player.Warnings >= 3)
{
var controller = Utilities.GetPlayers()
.FirstOrDefault(p => p.SteamID == player.SteamId);
if (controller != null)
{
_api!.IssuePenalty(
controller,
null,
PenaltyType.Ban,
"Automatic: 3 warnings",
1440 // 1 day
);
}
}
}
```
---
## Translation Examples
### Module with Translations
**lang/en.json:**
```json
{
"category_name": "My Module",
"menu_name": "My Action",
"action_message": "{lightred}{0}{default} performed action on {lightred}{1}{default}!",
"error_invalid_player": "{red}Error:{default} Invalid player!",
"success": "{green}Success!{default} Action completed."
}
```
**Code:**
```csharp
private void PerformAction(CCSPlayerController? admin, CCSPlayerController target)
{
// Perform action
DoSomething(target);
// Show activity with translation
if (admin == null || !_api!.IsAdminSilent(admin))
{
if (Localizer != null)
{
_api!.ShowAdminActivityLocalized(
Localizer,
"action_message",
admin?.PlayerName,
false,
admin?.PlayerName ?? "Console",
target.PlayerName
);
}
}
// Send success message
admin?.PrintToChat(Localizer?["success"] ?? "Success!");
}
```
---
## Utility Examples
### Get Players by Team
```csharp
private List<CCSPlayerController> GetTeamPlayers(CsTeam team)
{
return _api!.GetValidPlayers()
.Where(p => p.Team == team)
.ToList();
}
// Usage
var ctPlayers = GetTeamPlayers(CsTeam.CounterTerrorist);
var tPlayers = GetTeamPlayers(CsTeam.Terrorist);
```
### Get Alive Players
```csharp
private List<CCSPlayerController> GetAlivePlayers()
{
return _api!.GetValidPlayers()
.Where(p => p.PawnIsAlive)
.ToList();
}
```
### Notify Admins
```csharp
private void NotifyAdmins(string message, string permission = "@css/generic")
{
var admins = _api!.GetValidPlayers()
.Where(p => AdminManager.PlayerHasPermissions(p, permission));
foreach (var admin in admins)
{
admin.PrintToChat(message);
}
}
// Usage
NotifyAdmins("⚠ Important admin message", "@css/root");
```
---
## Timer Examples
### Delayed Action
```csharp
private void DelayedAction(CCSPlayerController player, float delay)
{
AddTimer(delay, () =>
{
if (player.IsValid && player.PawnIsAlive)
{
DoAction(player);
}
});
}
```
### Repeating Timer
```csharp
private void StartRepeatingAction()
{
AddTimer(1.0f, () =>
{
foreach (var player in _api!.GetValidPlayers())
{
if (player.PawnIsAlive)
{
UpdatePlayer(player);
}
}
}, TimerFlags.REPEAT);
}
```
---
## Configuration Examples
### Multiple Feature Toggles
```csharp
public class Config : IBasePluginConfig
{
public int Version { get; set; } = 1;
[JsonPropertyName("EnableFeature1")]
public bool EnableFeature1 { get; set; } = true;
[JsonPropertyName("EnableFeature2")]
public bool EnableFeature2 { get; set; } = false;
[JsonPropertyName("Feature1Commands")]
public List<string> Feature1Commands { get; set; } = ["css_feature1"];
[JsonPropertyName("Feature2Commands")]
public List<string> Feature2Commands { get; set; } = ["css_feature2"];
[JsonPropertyName("MaxValue")]
public int MaxValue { get; set; } = 100;
}
// Usage
private void RegisterCommands()
{
if (Config.EnableFeature1)
{
foreach (var cmd in Config.Feature1Commands)
{
_api!.RegisterCommand(cmd, "Feature 1", OnFeature1Command);
}
}
if (Config.EnableFeature2)
{
foreach (var cmd in Config.Feature2Commands)
{
_api!.RegisterCommand(cmd, "Feature 2", OnFeature2Command);
}
}
}
```
---
## Next Steps
- **[Best Practices](best-practices)** - Write better code
- **[Getting Started](getting-started)** - Create your first module
- **[API Reference](../api/overview)** - Full API documentation
- **[Fun Commands Source](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Complete reference implementation

View File

@@ -0,0 +1,282 @@
---
sidebar_position: 1
---
# Getting Started with Module Development
Step-by-step guide to creating your first CS2-SimpleAdmin module.
## Prerequisites
Before you begin:
- C# knowledge (intermediate level)
- .NET 8.0 SDK installed
- Visual Studio 2022 or VS Code
- Basic understanding of CounterStrikeSharp
- CS2 dedicated server for testing
---
## Step 1: Create Project
### Using .NET CLI
```bash
dotnet new classlib -n MyModule -f net8.0
cd MyModule
```
### Using Visual Studio
1. File → New → Project
2. Select "Class Library (.NET 8.0)"
3. Name: `MyModule`
4. Click Create
---
## Step 2: Add References
Edit `MyModule.csproj`:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="CounterStrikeSharp.API">
<HintPath>path/to/CounterStrikeSharp.API.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="CS2-SimpleAdminApi">
<HintPath>path/to/CS2-SimpleAdminApi.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>
```
---
## Step 3: Create Main Plugin Class
Create `MyModule.cs`:
```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CS2_SimpleAdminApi;
namespace MyModule;
public class MyModule : BasePlugin, IPluginConfig<Config>
{
public override string ModuleName => "My Module";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "Your Name";
public override string ModuleDescription => "My awesome module";
private ICS2_SimpleAdminApi? _api;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
new("simpleadmin:api");
public Config Config { get; set; } = new();
public override void OnAllPluginsLoaded(bool hotReload)
{
// Get SimpleAdmin API
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("CS2-SimpleAdmin API not found!");
return;
}
Logger.LogInformation("MyModule loaded successfully!");
// Register features
RegisterCommands();
// Register menus when ready
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Also call for hot reload
}
private void RegisterCommands()
{
if (_api == null) return;
foreach (var cmd in Config.MyCommands)
{
_api.RegisterCommand(cmd, "My command description", OnMyCommand);
}
}
private void RegisterMenus()
{
if (_api == null) return;
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
_api.RegisterMenu("mymodule", "mymenu", "My Menu", CreateMyMenu, "@css/generic");
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
{
player.PrintToChat($"Hello from MyModule!");
}
_api.LogCommand(caller, command);
}
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
{
return _api!.CreateMenuWithPlayers(
context,
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) =>
{
target.PrintToChat("You were selected!");
}
);
}
public void OnConfigParsed(Config config)
{
Config = config;
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Unregister commands
foreach (var cmd in Config.MyCommands)
{
_api.UnRegisterCommand(cmd);
}
// Unregister menus
_api.UnregisterMenu("mymodule", "mymenu");
// Unsubscribe events
_api.OnSimpleAdminReady -= RegisterMenus;
}
}
```
---
## Step 4: Create Configuration
Create `Config.cs`:
```csharp
using CounterStrikeSharp.API.Core;
using System.Text.Json.Serialization;
public class Config : IBasePluginConfig
{
[JsonPropertyName("Version")]
public int Version { get; set; } = 1;
[JsonPropertyName("MyCommands")]
public List<string> MyCommands { get; set; } = ["css_mycommand"];
[JsonPropertyName("EnableFeature")]
public bool EnableFeature { get; set; } = true;
}
```
---
## Step 5: Build and Deploy
### Build
```bash
dotnet build -c Release
```
### Deploy
Copy files to server:
```
game/csgo/addons/counterstrikesharp/plugins/MyModule/
└── MyModule.dll
```
### Restart Server
```bash
# Server console
css_plugins reload
```
---
## Step 6: Test
1. Join your server
2. Open admin menu: `css_admin`
3. Look for "My Module" category
4. Test command: `css_mycommand @me`
---
## Next Steps
- **[Best Practices](best-practices)** - Write better code
- **[Examples](examples)** - More code examples
- **[API Reference](../api/overview)** - Full API documentation
- **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Reference implementation
---
## Common Issues
### API Not Found
**Error:** `CS2-SimpleAdmin API not found!`
**Solution:**
- Ensure CS2-SimpleAdmin is installed
- Check that CS2-SimpleAdminApi.dll is in shared folder
- Verify CS2-SimpleAdmin loads before your module
### Commands Not Working
**Check:**
- Command registered in `RegisterCommands()`
- Permission is correct
- Player has required permission
### Menu Not Showing
**Check:**
- `OnSimpleAdminReady` event subscribed
- Menu registered in category
- Permission is correct
- SimpleAdmin loaded successfully
---
## Resources
- **[Module Development Guide](../../modules/development)** - Detailed guide
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS framework

View File

@@ -0,0 +1,47 @@
---
sidebar_position: 1
---
# Tutorial Intro
Let's discover **Docusaurus in less than 5 minutes**.
## Getting Started
Get started by **creating a new site**.
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
### What you'll need
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above:
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
## Generate a new site
Generate a new Docusaurus site using the **classic template**.
The classic template will automatically be added to your project after you run the command:
```bash
npm init docusaurus@latest my-website classic
```
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
The command also installs all necessary dependencies you need to run Docusaurus.
## Start your site
Run the development server:
```bash
cd my-website
npm run start
```
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.

View File

@@ -0,0 +1,799 @@
---
sidebar_position: 3
---
# Module Development
Learn how to create your own CS2-SimpleAdmin modules.
## Introduction
Creating modules for CS2-SimpleAdmin allows you to extend the plugin's functionality while keeping your code separate and maintainable.
:::tip Reference Implementation
The **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** serves as a complete reference implementation. Study its code to learn best practices!
:::
---
## Prerequisites
### Knowledge Required
- C# programming (intermediate level)
- .NET 8.0
- CounterStrikeSharp basics
- Understanding of CS2-SimpleAdmin structure
### Tools Needed
- Visual Studio 2022 or VS Code
- .NET 8.0 SDK
- CS2 server for testing
---
## Quick Start
### 1. Create Project
```bash
dotnet new classlib -n YourModuleName -f net8.0
cd YourModuleName
```
### 2. Add References
Edit your `.csproj` file:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<!-- CounterStrikeSharp -->
<Reference Include="CounterStrikeSharp.API">
<HintPath>path/to/CounterStrikeSharp.API.dll</HintPath>
<Private>false</Private>
</Reference>
<!-- CS2-SimpleAdmin API -->
<Reference Include="CS2-SimpleAdminApi">
<HintPath>path/to/CS2-SimpleAdminApi.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>
```
### 3. Create Main Plugin Class
```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CS2_SimpleAdminApi;
namespace YourModuleName;
public class YourModule : BasePlugin, IPluginConfig<Config>
{
public override string ModuleName => "Your Module Name";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "Your Name";
public override string ModuleDescription => "Description";
private ICS2_SimpleAdminApi? _api;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
public Config Config { get; set; } = new();
public override void OnAllPluginsLoaded(bool hotReload)
{
// Get SimpleAdmin API
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("CS2-SimpleAdmin API not found!");
return;
}
// Register your commands and menus
RegisterCommands();
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Fallback for hot reload
}
public void OnConfigParsed(Config config)
{
Config = config;
}
private void RegisterCommands()
{
// Register commands here
}
private void RegisterMenus()
{
// Register menus here
}
}
```
---
## Module Structure
### Recommended File Organization
```
YourModuleName/
├── YourModule.cs # Main plugin class
├── Config.cs # Configuration
├── Commands.cs # Command handlers (partial class)
├── Menus.cs # Menu creation (partial class)
├── Actions.cs # Core logic (partial class)
├── lang/ # Translations
│ ├── en.json
│ ├── pl.json
│ └── ...
└── YourModuleName.csproj
```
### Using Partial Classes
Split your code for better organization:
```csharp
// YourModule.cs
public partial class YourModule : BasePlugin, IPluginConfig<Config>
{
// Plugin initialization
}
// Commands.cs
public partial class YourModule
{
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
// Command logic
}
}
// Menus.cs
public partial class YourModule
{
private object CreateMyMenu(CCSPlayerController player, MenuContext context)
{
// Menu creation
}
}
```
---
## Configuration
### Create Config Class
```csharp
using CounterStrikeSharp.API.Core;
using System.Text.Json.Serialization;
public class Config : IBasePluginConfig
{
[JsonPropertyName("Version")]
public int Version { get; set; } = 1;
[JsonPropertyName("MyCommands")]
public List<string> MyCommands { get; set; } = ["css_mycommand"];
[JsonPropertyName("EnableFeature")]
public bool EnableFeature { get; set; } = true;
[JsonPropertyName("MaxValue")]
public int MaxValue { get; set; } = 100;
}
```
### Config Best Practices
1. **Use command lists** - Allow users to add aliases or disable features
2. **Provide defaults** - Sensible default values
3. **Version your config** - Track config changes
4. **Document settings** - Clear property names
---
## Registering Commands
### Basic Command Registration
```csharp
private void RegisterCommands()
{
if (_api == null) return;
foreach (var cmd in Config.MyCommands)
{
_api.RegisterCommand(cmd, "Command description", OnMyCommand);
}
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
// Get target players
var targets = _api!.GetTarget(command);
if (targets == null) return;
// Filter for valid players
var players = targets.Players
.Where(p => p.IsValid && !p.IsBot)
.ToList();
// Process each player
foreach (var player in players)
{
if (caller!.CanTarget(player))
{
DoSomething(caller, player);
}
}
// Log the command
_api.LogCommand(caller, command);
}
```
### Command Cleanup
Always unregister commands when unloading:
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
foreach (var cmd in Config.MyCommands)
{
_api.UnRegisterCommand(cmd);
}
}
```
---
## Creating Menus
### Register Menu Category
```csharp
private void RegisterMenus()
{
if (_api == null || _menusRegistered) return;
// Register category
_api.RegisterMenuCategory(
"mycategory",
Localizer?["category_name"] ?? "My Category",
"@css/generic"
);
// Register menu
_api.RegisterMenu(
"mycategory",
"mymenu",
Localizer?["menu_name"] ?? "My Menu",
CreateMyMenu,
"@css/generic",
"css_mycommand" // For permission override
);
_menusRegistered = true;
}
```
### Menu with Player Selection (NEW API)
```csharp
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
{
// Context contains: CategoryId, MenuId, MenuTitle, Permission, CommandName
// No need to repeat "mycategory" and "My Menu" here!
return _api!.CreateMenuWithPlayers(
context, // ← Automatically uses menu title and category
admin,
player => player.IsValid && admin.CanTarget(player),
(admin, target) => DoSomethingToPlayer(admin, target)
);
}
```
### Menu with Custom Options
```csharp
private object CreateValueSelectionMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
var values = new[] { 10, 25, 50, 100, 200 };
foreach (var value in values)
{
_api.AddMenuOption(menu, $"{value} points", player =>
{
GivePoints(player, value);
});
}
return menu;
}
```
### Nested Menus
```csharp
private object CreatePlayerSelectionMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api!.CreateMenuWithBack(context, admin);
var players = _api.GetValidPlayers()
.Where(p => admin.CanTarget(p));
foreach (var player in players)
{
_api.AddSubMenu(menu, player.PlayerName, admin =>
{
return CreateValueMenu(admin, player);
});
}
return menu;
}
private object CreateValueMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api!.CreateMenuWithBack($"Select value for {target.PlayerName}", "mycategory", admin);
// Add options...
return menu;
}
```
---
## Translations
### Create Translation Files
Create `lang/en.json`:
```json
{
"command_success": "{green}Success! {default}Action performed on {lightred}{0}",
"command_failed": "{red}Failed! {default}Could not perform action",
"menu_title": "My Custom Menu"
}
```
### Use Translations in Code
```csharp
// In commands
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
{
// Using module's own localizer for per-player language
if (Localizer != null)
{
_api!.ShowAdminActivityLocalized(
Localizer,
"command_success",
caller?.PlayerName,
false,
target.PlayerName
);
}
}
```
### Multiple Language Support
Create files for each language:
- `lang/en.json` - English
- `lang/pl.json` - Polish
- `lang/ru.json` - Russian
- `lang/de.json` - German
- etc.
---
## Working with API
### Issue Penalties
```csharp
// Ban online player
_api!.IssuePenalty(
player,
admin,
PenaltyType.Ban,
"Cheating",
1440 // 1 day in minutes
);
// Ban offline player by SteamID
_api!.IssuePenalty(
new SteamID(76561198012345678),
admin,
PenaltyType.Ban,
"Ban evasion",
0 // Permanent
);
// Other penalty types
_api!.IssuePenalty(player, admin, PenaltyType.Gag, "Chat spam", 30);
_api!.IssuePenalty(player, admin, PenaltyType.Mute, "Mic spam", 60);
_api!.IssuePenalty(player, admin, PenaltyType.Silence, "Total abuse", 120);
_api!.IssuePenalty(player, admin, PenaltyType.Warn, "Rule break");
```
### Get Player Information
```csharp
// Get player info with penalty data
var playerInfo = _api!.GetPlayerInfo(player);
Console.WriteLine($"Player: {playerInfo.PlayerName}");
Console.WriteLine($"SteamID: {playerInfo.SteamId}");
Console.WriteLine($"Warnings: {playerInfo.Warnings}");
// Get player mute status
var muteStatus = _api!.GetPlayerMuteStatus(player);
if (muteStatus.ContainsKey(PenaltyType.Gag))
{
Console.WriteLine("Player is gagged");
}
```
### Check Admin Status
```csharp
// Check if admin is in silent mode
if (_api!.IsAdminSilent(admin))
{
// Don't broadcast this action
}
// Get all silent admins
var silentAdmins = _api!.ListSilentAdminsSlots();
```
---
## Events
### Subscribe to Events
```csharp
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
// Subscribe to events
_api.OnSimpleAdminReady += OnSimpleAdminReady;
_api.OnPlayerPenaltied += OnPlayerPenaltied;
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
_api.OnAdminShowActivity += OnAdminShowActivity;
}
private void OnSimpleAdminReady()
{
Logger.LogInformation("SimpleAdmin is ready!");
RegisterMenus();
}
private void OnPlayerPenaltied(PlayerInfo player, PlayerInfo? admin,
PenaltyType type, string reason, int duration, int? penaltyId, int? serverId)
{
Logger.LogInformation($"{player.PlayerName} received {type} for {reason}");
}
private void OnPlayerPenaltiedAdded(SteamID steamId, PlayerInfo? admin,
PenaltyType type, string reason, int duration, int? penaltyId, int? serverId)
{
Logger.LogInformation($"Offline ban added to {steamId}");
}
private void OnAdminShowActivity(string messageKey, string? callerName,
bool dontPublish, object messageArgs)
{
// React to admin activity
}
```
### Unsubscribe on Unload
```csharp
public override void Unload(bool hotReload)
{
if (_api == null) return;
_api.OnSimpleAdminReady -= OnSimpleAdminReady;
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
// ... unsubscribe all events
}
```
---
## Best Practices
### 1. Always Check for Null
```csharp
if (_api == null)
{
Logger.LogError("API not available!");
return;
}
```
### 2. Validate Player State
```csharp
if (!player.IsValid || !player.PawnIsAlive)
{
return;
}
```
### 3. Check Target Permissions
```csharp
if (!caller.CanTarget(target))
{
// caller can't target this player (immunity)
return;
}
```
### 4. Log Commands
```csharp
_api.LogCommand(caller, command);
// or
_api.LogCommand(caller, $"css_mycommand {player.PlayerName}");
```
### 5. Use Per-Player Translations
```csharp
// Each player sees message in their language!
_api.ShowAdminActivityLocalized(
Localizer,
"translation_key",
callerName,
false,
args
);
```
### 6. Clean Up Resources
```csharp
public override void Unload(bool hotReload)
{
// Unregister commands
// Unregister menus
// Unsubscribe events
// Dispose resources
}
```
---
## Common Patterns
### Player Targeting Helper
```csharp
private List<CCSPlayerController> GetTargets(CommandInfo command, CCSPlayerController? caller)
{
var targets = _api!.GetTarget(command);
if (targets == null) return new List<CCSPlayerController>();
return targets.Players
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
.ToList();
}
```
### Menu Context Pattern (NEW!)
```csharp
// ✅ NEW: Use context to avoid duplication
private object CreateMenu(CCSPlayerController player, MenuContext context)
{
// context.MenuTitle, context.CategoryId already set!
return _api!.CreateMenuWithPlayers(context, player, filter, action);
}
// ❌ OLD: Had to repeat title and category
private object CreateMenu(CCSPlayerController player)
{
return _api!.CreateMenuWithPlayers("My Menu", "mycategory", player, filter, action);
}
```
### Action with Activity Message
```csharp
private void DoAction(CCSPlayerController? caller, CCSPlayerController target)
{
// Perform action
// ...
// Show activity
if (caller == null || !_api!.IsAdminSilent(caller))
{
_api!.ShowAdminActivityLocalized(
Localizer,
"action_message",
caller?.PlayerName,
false,
target.PlayerName
);
}
// Log action
_api!.LogCommand(caller, $"css_action {target.PlayerName}");
}
```
---
## Testing Your Module
### 1. Build
```bash
dotnet build -c Release
```
### 2. Copy to Server
```
game/csgo/addons/counterstrikesharp/plugins/YourModuleName/
```
### 3. Test
- Start server
- Check console for load messages
- Test commands
- Test menus
- Check translations
### 4. Debug
Enable detailed logging:
```csharp
Logger.LogInformation("Debug: ...");
Logger.LogWarning("Warning: ...");
Logger.LogError("Error: ...");
```
---
## Example: Complete Mini-Module
Here's a complete working example:
```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CS2_SimpleAdminApi;
namespace ExampleModule;
public class ExampleModule : BasePlugin, IPluginConfig<Config>
{
public override string ModuleName => "Example Module";
public override string ModuleVersion => "1.0.0";
private ICS2_SimpleAdminApi? _api;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
public Config Config { get; set; } = new();
public override void OnAllPluginsLoaded(bool hotReload)
{
_api = _pluginCapability.Get();
if (_api == null)
{
Logger.LogError("CS2-SimpleAdmin API not found!");
return;
}
// Register command
if (Config.ExampleCommands.Count > 0)
{
foreach (var cmd in Config.ExampleCommands)
{
_api.RegisterCommand(cmd, "Example command", OnExampleCommand);
}
}
}
public void OnConfigParsed(Config config)
{
Config = config;
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/generic")]
private void OnExampleCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _api!.GetTarget(command);
if (targets == null) return;
foreach (var target in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
{
// Do something to target
caller?.PrintToChat($"Performed action on {target.PlayerName}");
}
_api.LogCommand(caller, command);
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
foreach (var cmd in Config.ExampleCommands)
{
_api.UnRegisterCommand(cmd);
}
}
}
public class Config : IBasePluginConfig
{
public int Version { get; set; } = 1;
public List<string> ExampleCommands { get; set; } = ["css_example"];
}
```
---
## Next Steps
- **[Study Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Complete reference
- **[Read API Documentation](../developer/api/overview)** - Full API reference
- **[Check Examples](../developer/module/examples)** - More code examples
---
## Resources
- **[CS2-SimpleAdmin GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS documentation
- **[Module Development Guide](../developer/module/getting-started)** - Detailed guide
---
## Need Help?
- **Issues:** [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
- **Discussions:** [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
- **Examples:** Study official modules for reference

View File

@@ -0,0 +1,691 @@
---
sidebar_position: 2
---
# Fun Commands Module
Add entertaining and powerful player manipulation commands to your server.
## Overview
The Fun Commands module extends CS2-SimpleAdmin with commands for god mode, noclip, freeze, respawn, weapon management, and player attribute modification.
**Module Name:** `CS2-SimpleAdmin_FunCommands`
---
## Features
- ⭐ God Mode - Make players invincible
- 👻 No Clip - Allow players to fly through walls
- 🧊 Freeze/Unfreeze - Freeze players in place
- 🔄 Respawn - Bring dead players back
- 🔫 Give Weapons - Provide any weapon to players
- 🗑️ Strip Weapons - Remove all weapons
- ❤️ Set HP - Modify player health
- ⚡ Set Speed - Change movement speed
- 🌙 Set Gravity - Modify gravity
- 💰 Set Money - Adjust player money
- 📏 Resize Player - Change player model size
---
## Installation
### Prerequisites
- CS2-SimpleAdmin installed and working
- CS2-SimpleAdminApi.dll in shared folder
### Install Steps
1. **Download** the module from releases
2. **Extract** to your server:
```
game/csgo/addons/counterstrikesharp/plugins/CS2-SimpleAdmin_FunCommands/
```
3. **Restart** your server or reload plugins:
```
css_plugins reload
```
4. **Verify** the module loaded:
- Check server console for load message
- Try `css_admin` and look for "Fun Commands" menu
---
## Commands
### God Mode
Toggle god mode (invincibility) for a player.
```bash
css_god <#userid or name>
css_godmode <#userid or name>
```
**Permission:** `@css/cheats`
**Examples:**
```bash
css_god #123
css_god PlayerName
css_god @all # Toggle god mode for everyone
```
**Effects:**
- Player takes no damage
- Toggles on/off with each use
---
### No Clip
Enable noclip mode (fly through walls).
```bash
css_noclip <#userid or name>
```
**Permission:** `@css/cheats`
**Examples:**
```bash
css_noclip #123
css_noclip PlayerName
```
**Effects:**
- Player can fly
- Can pass through walls
- Gravity disabled
- Toggles on/off with each use
---
### Freeze
Freeze a player in place.
```bash
css_freeze <#userid or name> [duration]
```
**Permission:** `@css/slay`
**Parameters:**
- `duration` - Freeze duration in seconds (optional, default: permanent until unfreeze)
**Examples:**
```bash
css_freeze #123 # Freeze permanently
css_freeze PlayerName 30 # Freeze for 30 seconds
css_freeze @t 10 # Freeze all terrorists for 10 seconds
```
**Effects:**
- Player cannot move
- Player cannot shoot
- Auto-unfreezes after duration (if specified)
---
### Unfreeze
Unfreeze a frozen player.
```bash
css_unfreeze <#userid or name>
```
**Permission:** `@css/slay`
**Examples:**
```bash
css_unfreeze #123
css_unfreeze PlayerName
css_unfreeze @all # Unfreeze everyone
```
---
### Respawn
Respawn a dead player at last death position.
```bash
css_respawn <#userid or name>
```
**Permission:** `@css/cheats`
**Examples:**
```bash
css_respawn #123
css_respawn PlayerName
css_respawn @dead # Respawn all dead players
```
**Effects:**
- Player spawns at death point
- Gets default weapons
- Joins their team
---
### Give Weapon
Give a weapon to a player.
```bash
css_give <#userid or name> <weapon>
```
**Permission:** `@css/cheats`
**Weapon names:**
**Rifles:**
- `weapon_ak47` or `ak47`
- `weapon_m4a1` or `m4a1`
- `weapon_m4a1_silencer` or `m4a1_silencer`
- `weapon_awp` or `awp`
- `weapon_aug` or `aug`
- `weapon_sg556` or `sg556`
- `weapon_ssg08` or `ssg08` (Scout)
- `weapon_g3sg1` or `g3sg1`
- `weapon_scar20` or `scar20`
**SMGs:**
- `weapon_mp5sd` or `mp5sd`
- `weapon_mp7` or `mp7`
- `weapon_mp9` or `mp9`
- `weapon_mac10` or `mac10`
- `weapon_p90` or `p90`
- `weapon_ump45` or `ump45`
- `weapon_bizon` or `bizon`
**Heavy:**
- `weapon_nova` or `nova`
- `weapon_xm1014` or `xm1014`
- `weapon_mag7` or `mag7`
- `weapon_sawedoff` or `sawedoff`
- `weapon_m249` or `m249`
- `weapon_negev` or `negev`
**Pistols:**
- `weapon_deagle` or `deagle`
- `weapon_elite` or `elite` (Dual Berettas)
- `weapon_fiveseven` or `fiveseven`
- `weapon_glock` or `glock`
- `weapon_hkp2000` or `hkp2000`
- `weapon_p250` or `p250`
- `weapon_usp_silencer` or `usp_silencer`
- `weapon_tec9` or `tec9`
- `weapon_cz75a` or `cz75a`
- `weapon_revolver` or `revolver`
**Grenades:**
- `weapon_flashbang` or `flashbang`
- `weapon_hegrenade` or `hegrenade`
- `weapon_smokegrenade` or `smokegrenade`
- `weapon_molotov` or `molotov`
- `weapon_incgrenade` or `incgrenade`
- `weapon_decoy` or `decoy`
**Equipment:**
- `weapon_knife` or `knife`
- `weapon_taser` or `taser`
- `item_defuser` or `defuser`
- `item_kevlar` or `kevlar`
- `item_assaultsuit` or `assaultsuit`
**Examples:**
```bash
css_give #123 awp
css_give PlayerName ak47
css_give @ct m4a1
css_give @all deagle
```
---
### Strip Weapons
Remove all weapons from a player.
```bash
css_strip <#userid or name>
```
**Permission:** `@css/slay`
**Examples:**
```bash
css_strip #123
css_strip PlayerName
css_strip @t # Disarm all terrorists
```
---
### Set HP
Set a player's health.
```bash
css_hp <#userid or name> <health>
```
**Permission:** `@css/slay`
**Parameters:**
- `health` - Health amount (1-999+)
**Examples:**
```bash
css_hp #123 100 # Full health
css_hp PlayerName 200 # 200 HP
css_hp @all 1 # 1 HP everyone
```
**Common values:**
- `1` - 1 HP (one-shot mode)
- `100` - Normal health
- `200` - Double health
- `500` - Tank mode
---
### Set Speed
Modify a player's movement speed.
```bash
css_speed <#userid or name> <speed>
```
**Permission:** `@css/slay`
**Parameters:**
- `speed` - Speed multiplier (0.1 - 10.0)
- `1.0` = Normal speed
- `2.0` = Double speed
- `0.5` = Half speed
**Examples:**
```bash
css_speed #123 1.5 # 50% faster
css_speed PlayerName 0.5 # Slow motion
css_speed @all 2.0 # Everyone fast
css_speed #123 1.0 # Reset to normal
```
**Common values:**
- `0.5` - Slow motion mode
- `1.0` - Normal (reset)
- `1.5` - Fast mode
- `2.0` - Super fast
- `3.0` - Extremely fast
---
### Set Gravity
Modify a player's gravity.
```bash
css_gravity <#userid or name> <gravity>
```
**Permission:** `@css/slay`
**Parameters:**
- `gravity` - Gravity multiplier (0.1 - 10.0)
- `1.0` = Normal gravity
- `0.5` = Moon jump
- `2.0` = Heavy
**Examples:**
```bash
css_gravity #123 0.5 # Moon jump
css_gravity PlayerName 0.1 # Super jump
css_gravity @all 2.0 # Heavy gravity
css_gravity #123 1.0 # Reset to normal
```
**Common values:**
- `0.1` - Super high jumps
- `0.5` - Moon gravity
- `1.0` - Normal (reset)
- `2.0` - Heavy/fast falling
---
### Set Money
Set a player's money amount.
```bash
css_money <#userid or name> <amount>
```
**Permission:** `@css/slay`
**Parameters:**
- `amount` - Money amount (0-65535)
**Examples:**
```bash
css_money #123 16000 # Max money
css_money PlayerName 0 # Remove all money
css_money @ct 10000 # Give all CTs $10,000
```
---
### Resize Player
Change a player's model size.
```bash
css_resize <#userid or name> <scale>
```
**Permission:** `@css/slay`
**Parameters:**
- `scale` - Size scale (0.1 - 10.0)
- `1.0` = Normal size
- `0.5` = Half size
- `2.0` = Double size
**Examples:**
```bash
css_resize #123 0.5 # Tiny player
css_resize PlayerName 2.0 # Giant player
css_resize #123 1.0 # Reset to normal
```
**Common values:**
- `0.5` - Tiny mode
- `1.0` - Normal (reset)
- `1.5` - Big
- `2.0` - Giant
---
## Configuration
Configuration file location:
```
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.json
```
### Default Configuration
```json
{
"Version": 1,
"GodCommands": ["css_god", "css_godmode"],
"NoclipCommands": ["css_noclip"],
"FreezeCommands": ["css_freeze"],
"UnfreezeCommands": ["css_unfreeze"],
"RespawnCommands": ["css_respawn"],
"GiveCommands": ["css_give"],
"StripCommands": ["css_strip"],
"HpCommands": ["css_hp"],
"SpeedCommands": ["css_speed"],
"GravityCommands": ["css_gravity"],
"MoneyCommands": ["css_money"],
"ResizeCommands": ["css_resize"]
}
```
### Customizing Commands
**Add aliases:**
```json
"GodCommands": ["css_god", "css_godmode", "css_immortal"]
```
**Disable feature:**
```json
"GodCommands": []
```
**Rename command:**
```json
"NoclipCommands": ["css_fly"]
```
---
## Admin Menu Integration
The module automatically adds a "Fun Commands" category to the admin menu with these options:
- God Mode
- No Clip
- Freeze
- Respawn
- Give Weapon
- Strip Weapons
- Set HP
- Set Speed
- Set Gravity
- Set Money
- Resize Player
**Access menu:**
```bash
css_admin # Navigate to "Fun Commands"
```
---
## Permission System
### Permission Override
Admins can override command permissions using CounterStrikeSharp's admin system.
**Example:**
If you want VIPs to use god mode:
1. **In admin config**, add permission override for `css_god`:
```json
{
"css_god": ["@css/vip"]
}
```
2. **VIPs will now see God Mode** in the menu
---
## Permissions Required
| Command | Default Permission | Description |
|---------|------------------|-------------|
| `css_god` | `@css/cheats` | God mode |
| `css_noclip` | `@css/cheats` | No clip |
| `css_freeze` | `@css/slay` | Freeze players |
| `css_unfreeze` | `@css/slay` | Unfreeze players |
| `css_respawn` | `@css/cheats` | Respawn players |
| `css_give` | `@css/cheats` | Give weapons |
| `css_strip` | `@css/slay` | Strip weapons |
| `css_hp` | `@css/slay` | Set health |
| `css_speed` | `@css/slay` | Set speed |
| `css_gravity` | `@css/slay` | Set gravity |
| `css_money` | `@css/slay` | Set money |
| `css_resize` | `@css/slay` | Resize player |
---
## Use Cases
### Fun Rounds
```bash
# Low gravity, high speed round
css_gravity @all 0.3
css_speed @all 1.5
# One-shot mode
css_hp @all 1
css_give @all deagle
# Tiny players
css_resize @all 0.5
```
### Admin Events
```bash
# Hide and seek (seekers)
css_speed @ct 1.5
css_hp @ct 200
# Hide and seek (hiders)
css_resize @t 0.5
css_speed @t 0.8
```
### Testing & Debug
```bash
# Test map navigation
css_noclip @me
css_god @me
# Test weapon balance
css_give @me awp
css_hp @me 100
```
---
## Best Practices
### Competitive Balance
1. **Don't use during serious matches** - Breaks game balance
2. **Announce fun rounds** - Let players know it's for fun
3. **Reset after use** - Return to normal settings
4. **Save for appropriate times** - End of night, special events
### Reset Commands
Always reset modifications after fun rounds:
```bash
css_speed @all 1.0
css_gravity @all 1.0
css_resize @all 1.0
```
### Permission Management
1. **Limit @css/cheats** - Only trusted admins
2. **@css/slay is safer** - For HP/speed/gravity
3. **Monitor usage** - Check logs for abuse
---
## Troubleshooting
### Speed/Gravity not persisting
**Solution:**
- These are maintained by a repeating timer
- If they reset, reapply them
- Check server console for timer errors
### God mode not working
**Check:**
- Is player alive?
- Check console for errors
- Try toggling off and on
### Can't give weapons
**Check:**
- Correct weapon name
- Player is alive
- Player has inventory space
### Noclip doesn't work
**Check:**
- Player must be alive
- sv_cheats doesn't need to be enabled
- Check console for errors
---
## Module Development
This module serves as a **reference implementation** for creating CS2-SimpleAdmin modules.
**Key concepts demonstrated:**
- Command registration from configuration
- Menu creation with SimpleAdmin API
- Per-player translation support
- Proper cleanup on module unload
- Code organization using partial classes
**[View source code](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** for implementation details.
---
## Translations
The module includes translations for 13 languages:
- English (en)
- Polish (pl)
- Russian (ru)
- Portuguese (pt)
- And 9 more...
Translation files location:
```
plugins/CS2-SimpleAdmin_FunCommands/lang/
```
---
## Related Documentation
- **[Player Commands](../user/commands/playercommands)** - Core player commands
- **[Module Development](development)** - Create your own modules
- **[API Reference](../developer/api/overview)** - CS2-SimpleAdmin API
---
## Version History
**v1.0.0** - Initial release
- God mode
- Noclip
- Freeze/Unfreeze
- Respawn
- Give/Strip weapons
- HP/Speed/Gravity/Money
- Resize player
- Admin menu integration
- 13 language support
---
## Support
**Issues:** [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
**Questions:** [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)

View File

@@ -0,0 +1,260 @@
---
sidebar_position: 1
---
# Modules Introduction
Extend CS2-SimpleAdmin functionality with powerful modules.
## What are Modules?
Modules are extensions that add new features to CS2-SimpleAdmin. They use the CS2-SimpleAdmin API to integrate seamlessly with the core plugin.
## Official Modules
### Fun Commands Module
Adds entertainment and player manipulation commands like god mode, noclip, freeze, and more.
**[Learn more →](funcommands)**
---
## Benefits of Modules
### 🔌 Easy Integration
- Built on CS2-SimpleAdmin API
- Automatic menu registration
- Command system integration
### 🎨 Feature Separation
- Keep core plugin lightweight
- Add only features you need
- Easy to enable/disable
### 🔧 Customizable
- Configure each module independently
- Disable unwanted commands
- Customize permissions
### 📦 Simple Installation
- Drop module files in folder
- Restart server
- Module auto-loads
---
## Installing Modules
### Standard Installation
1. **Download the module** from releases or build from source
2. **Extract to plugins folder:**
```
game/csgo/addons/counterstrikesharp/plugins/ModuleName/
```
3. **Restart server** or reload plugins:
```
css_plugins reload
```
4. **Configure** (if needed):
```
addons/counterstrikesharp/configs/plugins/ModuleName/
```
---
## Module Structure
Typical module structure:
```
plugins/
└── CS2-SimpleAdmin_ModuleName/
├── CS2-SimpleAdmin_ModuleName.dll
├── CS2-SimpleAdmin_ModuleName.json (config)
└── lang/ (translations)
├── en.json
├── pl.json
└── ...
```
---
## Module Configuration
Each module has its own configuration file:
```
addons/counterstrikesharp/configs/plugins/ModuleName/ModuleName.json
```
### Common Configuration Pattern
```json
{
"Version": 1,
"CommandName": ["css_command", "css_alias"],
"OtherSettings": {
"EnableFeature": true
}
}
```
**Key Features:**
- Command lists allow multiple aliases
- Empty command list = feature disabled
- Module-specific settings
---
## Available Modules
### Core Modules
| Module | Description | Status |
|--------|-------------|--------|
| **[Fun Commands](funcommands)** | God mode, noclip, freeze, speed, gravity | ✅ Official |
### Community Modules
Check the [GitHub repository](https://github.com/daffyyyy/CS2-SimpleAdmin) for community-contributed modules.
---
## Developing Modules
Want to create your own module?
**[See Module Development Guide →](development)**
**[See Developer Documentation →](../developer/intro)**
---
## Module vs Core Plugin
### When to use Core Plugin:
- Essential admin functions
- Punishment system
- Permission management
- Database operations
### When to use Modules:
- Optional features
- Server-specific functionality
- Experimental features
- Custom integrations
---
## Module Dependencies
### Required for All Modules:
- CS2-SimpleAdmin (core plugin)
- CS2-SimpleAdminApi.dll
### Module-Specific:
Check each module's documentation for specific requirements.
---
## Troubleshooting Modules
### Module doesn't load
**Check:**
1. Is CS2-SimpleAdmin loaded?
2. Is CS2-SimpleAdminApi.dll in shared folder?
3. Check server console for errors
4. Verify module files are complete
### Module commands not working
**Check:**
1. Is command enabled in module config?
2. Do you have required permissions?
3. Check Commands.json for conflicts
4. Verify module loaded successfully
### Module conflicts
**Check:**
- Multiple modules providing same command
- Check server console for warnings
- Disable conflicting module
---
## Best Practices
### Module Management
1. **Use only needed modules** - Don't overload server
2. **Keep modules updated** - Check for updates regularly
3. **Test before production** - Test modules on dev server first
4. **Review permissions** - Understand what each module can do
### Performance
1. **Monitor resource usage** - Some modules may impact performance
2. **Configure wisely** - Disable unused features
3. **Check logs** - Monitor for errors
---
## Module Updates
### Updating Modules
1. **Backup current version**
2. **Download new version**
3. **Replace files** in plugins folder
4. **Check configuration** - New config options may exist
5. **Restart server**
### Breaking Changes
Some updates may have breaking changes:
- Check module changelog
- Review new configuration options
- Test thoroughly
---
## Community Contributions
### Sharing Modules
Created a module? Share it with the community!
1. **Publish on GitHub**
2. **Document thoroughly**
3. **Provide examples**
4. **Include README**
### Using Community Modules
1. **Review code** - Ensure it's safe
2. **Check compatibility** - Verify CS2-SimpleAdmin version
3. **Test thoroughly** - Don't trust blindly
4. **Report issues** - Help improve modules
---
## Next Steps
- **[Explore Fun Commands Module](funcommands)** - Add entertainment features
- **[Learn Module Development](development)** - Create your own modules
- **[Read API Documentation](../developer/intro)** - Understand the API
---
## Need Help?
- **Issues** - [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
- **Discussions** - [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
- **Examples** - Check official modules for reference

View File

@@ -0,0 +1,8 @@
{
"label": "Tutorial - Basics",
"position": 2,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Docusaurus concepts."
}
}

View File

@@ -0,0 +1,23 @@
---
sidebar_position: 6
---
# Congratulations!
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
Docusaurus has **much more to offer**!
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
## What's next?
- Read the [official documentation](https://docusaurus.io/)
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
- Add a [search bar](https://docusaurus.io/docs/search)
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)

View File

@@ -0,0 +1,34 @@
---
sidebar_position: 3
---
# Create a Blog Post
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
## Create your first Post
Create a file at `blog/2021-02-28-greetings.md`:
```md title="blog/2021-02-28-greetings.md"
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much as you like.
```
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).

View File

@@ -0,0 +1,57 @@
---
sidebar_position: 2
---
# Create a Document
Documents are **groups of pages** connected through:
- a **sidebar**
- **previous/next navigation**
- **versioning**
## Create your first Doc
Create a Markdown file at `docs/hello.md`:
```md title="docs/hello.md"
# Hello
This is my **first Docusaurus document**!
```
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
## Configure the Sidebar
Docusaurus automatically **creates a sidebar** from the `docs` folder.
Add metadata to customize the sidebar label and position:
```md title="docs/hello.md" {1-4}
---
sidebar_label: 'Hi!'
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
```
It is also possible to create your sidebar explicitly in `sidebars.js`:
```js title="sidebars.js"
export default {
tutorialSidebar: [
'intro',
// highlight-next-line
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
};
```

View File

@@ -0,0 +1,43 @@
---
sidebar_position: 1
---
# Create a Page
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
- `src/pages/index.js``localhost:3000/`
- `src/pages/foo.md``localhost:3000/foo`
- `src/pages/foo/bar.js``localhost:3000/foo/bar`
## Create your first React Page
Create a file at `src/pages/my-react-page.js`:
```jsx title="src/pages/my-react-page.js"
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
```
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
## Create your first Markdown Page
Create a file at `src/pages/my-markdown-page.md`:
```mdx title="src/pages/my-markdown-page.md"
# My Markdown page
This is a Markdown page
```
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).

View File

@@ -0,0 +1,31 @@
---
sidebar_position: 5
---
# Deploy your site
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
It builds your site as simple **static HTML, JavaScript and CSS files**.
## Build your site
Build your site **for production**:
```bash
npm run build
```
The static files are generated in the `build` folder.
## Deploy your site
Test your production build locally:
```bash
npm run serve
```
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).

View File

@@ -0,0 +1,152 @@
---
sidebar_position: 4
---
# Markdown Features
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
## Front Matter
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
```text title="my-doc.md"
// highlight-start
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
// highlight-end
## Markdown heading
Markdown text with [links](./hello.md)
```
## Links
Regular Markdown links are supported, using url paths or relative file paths.
```md
Let's see how to [Create a page](/create-a-page).
```
```md
Let's see how to [Create a page](./create-a-page.md).
```
**Result:** Let's see how to [Create a page](./create-a-page.md).
## Images
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
```md
![Docusaurus logo](/img/docusaurus.png)
```
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
```md
![Docusaurus logo](./img/docusaurus.png)
```
## Code Blocks
Markdown code blocks are supported with Syntax highlighting.
````md
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
````
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
## Admonitions
Docusaurus has a special syntax to create admonitions and callouts:
```md
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
```
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
## MDX and React Components
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
```jsx
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
```
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`);
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !

View File

@@ -0,0 +1,7 @@
{
"label": "Tutorial - Extras",
"position": 3,
"link": {
"type": "generated-index"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,55 @@
---
sidebar_position: 1
---
# Manage Docs Versions
Docusaurus can manage multiple versions of your docs.
## Create a docs version
Release a version 1.0 of your project:
```bash
npm run docusaurus docs:version 1.0
```
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
Your docs now have 2 versions:
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
## Add a Version Dropdown
To navigate seamlessly across versions, add a version dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
export default {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'docsVersionDropdown',
},
// highlight-end
],
},
},
};
```
The docs version dropdown appears in your navbar:
![Docs Version Dropdown](./img/docsVersionDropdown.png)
## Update an existing version
It is possible to edit versioned docs in their respective folder:
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`

View File

@@ -0,0 +1,88 @@
---
sidebar_position: 2
---
# Translate your site
Let's translate `docs/intro.md` to French.
## Configure i18n
Modify `docusaurus.config.js` to add support for the `fr` locale:
```js title="docusaurus.config.js"
export default {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
};
```
## Translate a doc
Copy the `docs/intro.md` file to the `i18n/fr` folder:
```bash
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
```
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
## Start your localized site
Start your site on the French locale:
```bash
npm run start -- --locale fr
```
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
:::caution
In development, you can only use one locale at a time.
:::
## Add a Locale Dropdown
To navigate seamlessly across languages, add a locale dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
export default {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'localeDropdown',
},
// highlight-end
],
},
},
};
```
The locale dropdown now appears in your navbar:
![Locale Dropdown](./img/localeDropdown.png)
## Build your localized site
Build your site for a specific locale:
```bash
npm run build -- --locale fr
```
Or build your site to include all the locales at once:
```bash
npm run build
```

View File

@@ -0,0 +1,262 @@
---
sidebar_position: 1
---
# Ban Commands
Commands for managing player bans.
## Ban Player
Ban a player currently on the server.
```bash
css_ban <#userid or name> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/ban`
**Examples:**
```bash
css_ban @all 60 "Timeout for everyone"
css_ban #123 1440 "Hacking - 1 day ban"
css_ban PlayerName 0 "Permanent ban for cheating"
css_ban @ct 30 "CT team timeout"
```
**Notes:**
- Time in minutes (0 = permanent)
- Supports player targeting (@all, @ct, @t, #userid, name)
- Reason is optional but recommended
---
## Add Ban (Offline Player)
Ban a player by SteamID even if they're not online.
```bash
css_addban <steamid> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/ban`
**Examples:**
```bash
css_addban STEAM_1:0:12345678 1440 "Ban evasion"
css_addban 76561198012345678 10080 "Hacking - 7 day ban"
css_addban STEAM_1:1:87654321 0 "Permanent ban"
```
**Supported SteamID formats:**
- SteamID64: `76561198012345678`
- SteamID: `STEAM_1:0:12345678`
- SteamID3: `[U:1:12345678]`
---
## Ban IP Address
Ban an IP address.
```bash
css_banip <ip> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/ban`
**Examples:**
```bash
css_banip 192.168.1.100 1440 "Ban evasion attempt"
css_banip 10.0.0.5 0 "Persistent troublemaker"
```
**Notes:**
- Useful for preventing ban evasion
- Can be combined with SteamID bans
- Check config for `BanType` setting (SteamID, IP, or Both)
---
## Unban Player
Remove a ban from a player.
```bash
css_unban <steamid or name or ip> [reason]
```
**Permission:** `@css/unban`
**Examples:**
```bash
css_unban 76561198012345678 "Appeal accepted"
css_unban STEAM_1:0:12345678 "Ban lifted"
css_unban 192.168.1.100 "Wrong person banned"
css_unban PlayerName "Mistake"
```
**Notes:**
- Works with SteamID, IP, or player name
- Unban reason is logged
- Can unban offline players
---
## Warn Player
Issue a warning to a player.
```bash
css_warn <#userid or name> [reason]
```
**Permission:** `@css/kick`
**Examples:**
```bash
css_warn #123 "Mic spam"
css_warn PlayerName "Language"
css_warn @all "Final warning"
```
**Notes:**
- Warnings can accumulate
- Auto-escalation to bans based on `WarnThreshold` config
- Example: 3 warnings = 1 hour ban, 4 warnings = 2 hour ban
**Warning Threshold Configuration:**
```json
"WarnThreshold": {
"3": "css_addban STEAMID64 60 \"3 warnings\"",
"4": "css_ban #USERID 120 \"4 warnings\""
}
```
---
## Unwarn Player
Remove a warning from a player.
```bash
css_unwarn <steamid or name>
```
**Permission:** `@css/kick`
**Examples:**
```bash
css_unwarn 76561198012345678
css_unwarn PlayerName
```
**Notes:**
- Removes the most recent warning
- Helps manage warning thresholds
- Can be used for offline players
---
## Permission Requirements
| Command | Required Permission | Description |
|---------|-------------------|-------------|
| `css_ban` | `@css/ban` | Ban online players |
| `css_addban` | `@css/ban` | Ban offline players by SteamID |
| `css_banip` | `@css/ban` | Ban IP addresses |
| `css_unban` | `@css/unban` | Remove bans |
| `css_warn` | `@css/kick` | Issue warnings |
| `css_unwarn` | `@css/kick` | Remove warnings |
## Ban Types
Configure ban behavior in `CS2-SimpleAdmin.json`:
```json
"BanType": 1
```
**Options:**
- `1` - SteamID only (default)
- `2` - IP only
- `3` - Both SteamID and IP
## Time Durations
Common time values:
| Duration | Minutes | Description |
|----------|---------|-------------|
| 1 minute | 1 | Very short timeout |
| 5 minutes | 5 | Short timeout |
| 15 minutes | 15 | Medium timeout |
| 1 hour | 60 | Standard timeout |
| 1 day | 1440 | Daily ban |
| 1 week | 10080 | Weekly ban |
| 2 weeks | 20160 | Bi-weekly ban |
| 1 month | 43200 | Monthly ban |
| Permanent | 0 | Never expires |
## Player Targeting
All ban commands support advanced targeting:
- `@all` - Target all players
- `@ct` - Target all Counter-Terrorists
- `@t` - Target all Terrorists
- `@spec` - Target all spectators
- `#123` - Target by userid
- `PlayerName` - Target by name (partial match)
## Best Practices
### Banning
1. **Always provide a reason** - Helps with appeals and record keeping
2. **Use appropriate durations** - Don't permaban for minor offenses
3. **Check ban history** - Use `css_who` to see if player has priors
4. **Consider warnings first** - Give players a chance to improve
### Warning System
1. **Be consistent** - Use warnings for minor offenses
2. **Configure thresholds** - Set up auto-escalation in config
3. **Communicate clearly** - Let players know why they're warned
4. **Review regularly** - Check warning history with `css_warns`
### Multi-Account Detection
When `CheckMultiAccountsByIp` is enabled:
- Plugin detects multiple accounts from same IP
- Sends Discord notifications if configured
- Helps identify ban evasion
## Troubleshooting
### Ban doesn't work
**Check:**
- Do you have `@css/ban` permission?
- Is the SteamID format correct?
- Check server console for errors
### Player rejoins after ban
**Check:**
- Is `MultiServerMode` enabled if using multiple servers?
- Is the database shared across servers?
- Check ban type configuration (SteamID vs IP)
### Warning threshold not working
**Check:**
- Is `WarnThreshold` configured correctly?
- Are the command formats correct in config?
- Check server console for execution errors
## Related Commands
- **[Communication Commands](basecomms)** - Mute, gag, silence
- **[Player Commands](playercommands)** - Kick, slay, etc.
- **[Base Commands](basecommands)** - Admin management

View File

@@ -0,0 +1,442 @@
---
sidebar_position: 4
---
# Chat Commands
Admin chat and messaging commands.
## Admin Chat
### Admin Say (Private)
Send a message to all online admins only.
```bash
css_asay <message>
```
**Permission:** `@css/chat`
**Features:**
- Only admins see the message
- Useful for admin coordination
- Colored differently from regular chat
**Examples:**
```bash
css_asay "Player is suspicious, keep an eye on them"
css_asay "I'm going AFK for 5 minutes"
css_asay "Need help with a situation"
```
**Message format:**
```
[ADMIN] YourName: message
```
---
## Public Announcements
### CSS Say (Colored)
Send a colored message to all players.
```bash
css_cssay <message>
```
**Permission:** `@css/chat`
**Features:**
- Colorful formatted message
- Visible to all players
- Stands out from regular chat
**Examples:**
```bash
css_cssay "Server will restart in 5 minutes!"
css_cssay "Welcome to our server!"
css_cssay "Rules: No cheating, be respectful"
```
---
### Say (With Prefix)
Send a message to all players with admin prefix.
```bash
css_say <message>
```
**Permission:** `@css/chat`
**Features:**
- Message shows with "(ADMIN)" prefix
- Visible to all players
- Authority message format
**Examples:**
```bash
css_say "Please be respectful in chat"
css_say "Cheating will result in permanent ban"
css_say "Type !rules for server rules"
```
**Message format:**
```
(ADMIN) YourName: message
```
---
### Private Say (Whisper)
Send a private message to a specific player.
```bash
css_psay <#userid or name> <message>
```
**Permission:** `@css/chat`
**Features:**
- Only the target player sees the message
- Useful for private warnings or help
- Doesn't clutter public chat
**Examples:**
```bash
css_psay #123 "Please stop mic spamming"
css_psay PlayerName "You need to join a team"
css_psay @all "This is a private message to everyone"
```
**Target receives:**
```
(ADMIN to you) AdminName: message
```
---
### Center Say
Display a message in the center of all players' screens.
```bash
css_csay <message>
```
**Permission:** `@css/chat`
**Features:**
- Large text in center of screen
- Impossible to miss
- Useful for important announcements
**Examples:**
```bash
css_csay "ROUND STARTS IN 10 SECONDS"
css_csay "FREEZE! Don't move!"
css_csay "Server restarting NOW"
```
**Display:**
- Shows in center of screen
- Large, bold text
- Auto-fades after a few seconds
---
### HUD Say
Display a message on players' HUD (screen overlay).
```bash
css_hsay <message>
```
**Permission:** `@css/chat`
**Features:**
- Message appears on screen overlay
- Less intrusive than center say
- Stays visible longer
**Examples:**
```bash
css_hsay "Tournament starting soon!"
css_hsay "New map voting available"
css_hsay "Visit our website: example.com"
```
---
## Usage Examples
### Announcements
```bash
# Server restart warning
css_cssay "Server will restart in 10 minutes! Save your progress!"
# Center screen countdown
css_csay "5"
# Wait...
css_csay "4"
css_csay "3"
css_csay "2"
css_csay "1"
css_csay "GO!"
```
### Player Communication
```bash
# Private warning
css_psay PlayerName "This is your first warning for chat spam"
# Public announcement
css_say "Everyone please be quiet for the next round"
# Admin coordination
css_asay "I'm spectating the suspicious player in T spawn"
```
### Event Management
```bash
# Tournament announcement
css_cssay "⚠ TOURNAMENT STARTING IN 5 MINUTES ⚠"
css_hsay "Teams, please get ready!"
css_csay "TOURNAMENT BEGINS NOW!"
```
---
## Color Codes
Many chat commands support color codes:
```
{default} - Default chat color
{white} - White
{darkred} - Dark red
{green} - Green
{lightyellow} - Light yellow
{lightblue} - Light blue
{olive} - Olive
{lime} - Lime
{red} - Red
{purple} - Purple
{grey} - Grey
{yellow} - Yellow
{gold} - Gold
{silver} - Silver
{blue} - Blue
{darkblue} - Dark blue
{bluegrey} - Blue grey
{magenta} - Magenta
{lightred} - Light red
{orange} - Orange
```
**Example:**
```bash
css_say "{red}WARNING: {default}No camping in spawn!"
```
---
## Message Targeting
Some commands support player targeting for private messages:
### Supported Targets
- `@all` - All players (private message to each)
- `@ct` - All Counter-Terrorists
- `@t` - All Terrorists
- `@spec` - All spectators
- `#123` - Specific userid
- `PlayerName` - Player by name
**Examples:**
```bash
# Private message to all CTs
css_psay @ct "Defend bombsite A this round"
# Private message to all terrorists
css_psay @t "Rush B with smoke and flash"
# Message to spectators
css_psay @spec "Type !join to play"
```
---
## Best Practices
### When to Use Each Command
**css_asay** (Admin Say):
- Admin coordination
- Discussing player behavior
- Planning admin actions
- Private admin discussions
**css_cssay** (Colored Say):
- Important server announcements
- Event notifications
- Eye-catching messages
- Server information
**css_say** (Say):
- General admin announcements
- Rule reminders
- Warnings to all players
- Admin presence
**css_psay** (Private Say):
- Private warnings
- Individual help
- Direct player communication
- Discretion needed
**css_csay** (Center Say):
- Emergency announcements
- Cannot-miss messages
- Round starts/events
- Countdowns
**css_hsay** (HUD Say):
- Persistent information
- Less urgent announcements
- Server info
- Website/Discord links
---
## Communication Guidelines
### Professional Communication
1. **Be clear and concise** - Don't spam long messages
2. **Use appropriate command** - Don't center-spam trivial messages
3. **Check for typos** - You represent the server
4. **Avoid excessive colors** - Can be hard to read
### Spam Prevention
1. **Don't overuse center say** - Very intrusive for players
2. **Space out announcements** - Don't flood chat
3. **Use HUD say for persistent info** - Less annoying
4. **Coordinate with other admins** - Avoid duplicate messages
### Effective Messaging
**Good Examples:**
```bash
css_cssay "🎯 New map voting system available! Type !mapvote"
css_psay PlayerName "Hey! Please enable your mic or use team chat"
css_asay "Checking player demos for possible aimbot"
```
**Poor Examples:**
```bash
css_csay "hi" # Don't use center say for trivial messages
css_cssay "a" "b" "c" "d" # Don't spam center messages
css_say "asdfasdfasdf" # Unprofessional
```
---
## Silent Mode
Admins can use silent mode to hide their activity:
```bash
css_hide # Toggle silent mode
```
When in silent mode:
- Chat messages still work
- Admin name might be hidden (depends on config)
- Useful for undercover moderation
---
## Configuration
### Show Activity Type
Controls how admin actions are displayed:
```json
"ShowActivityType": 2
```
**Options:**
- `0` - Hide all admin activity
- `1` - Anonymous ("An admin says...")
- `2` - Show name ("AdminName says...")
---
## Chat Restrictions
### Respecting Gags
Remember that:
- `css_asay` works even if admin is gagged (admin chat)
- Other commands respect communication penalties
- Can't use chat commands while silenced
### Permission Requirements
All chat commands require `@css/chat` permission:
```bash
css_addadmin STEAMID "Name" "@css/chat" 50 0
```
Or add to a group with chat permission:
```bash
css_addgroup "#moderators" "@css/chat,@css/kick" 50
```
---
## Troubleshooting
### Messages not showing
**Check:**
- Do you have `@css/chat` permission?
- Are you silenced/gagged?
- Check console for errors
- Verify player is connected (for css_psay)
### Colors not working
**Check:**
- Use correct color code syntax: `{red}`
- Some commands may not support colors
- Different chat systems handle colors differently
### Players can't see center/HUD messages
**Check:**
- CS2 client-side chat settings
- Conflicting HUD plugins
- Server console for errors
---
## Related Commands
- **[Communication Commands](basecomms)** - Gag, mute, silence players
- **[Base Commands](basecommands)** - Admin management
- **[Ban Commands](basebans)** - Player punishment

View File

@@ -0,0 +1,626 @@
---
sidebar_position: 3
---
# Base Commands
Core admin commands for server management and admin system.
## Player Information
### Show Penalties
View your own active penalties.
```bash
css_penalties
css_mypenalties
css_comms
```
**Permission:** None (all players)
**Shows:**
- Active bans
- Active communication restrictions (gag, mute, silence)
- Warning count
- Duration remaining
---
### Hide Penalty Notifications
Hide penalty notifications when you connect to the server.
```bash
css_hidecomms
```
**Permission:** `@css/kick`
**Notes:**
- Toggle on/off
- Admins won't see penalty notifications on join
- Useful for admin privacy
---
## Admin Menu
### Open Admin Menu
Opens the main admin menu interface.
```bash
css_admin
```
**Permission:** `@css/generic`
**Features:**
- Player management
- Server management
- Ban/kick/mute players via menu
- Map changing
- Custom server commands
---
### Admin Help
Print the admin help file.
```bash
css_adminhelp
```
**Permission:** `@css/generic`
**Shows:**
- Available commands for your permission level
- Command syntax
- Permission requirements
---
## Admin Management
### Add Admin
Add a new admin to the database.
```bash
css_addadmin <steamid> <name> <flags/groups> <immunity> <duration>
```
**Permission:** `@css/root`
**Parameters:**
- `steamid` - Player's SteamID (any format)
- `name` - Admin name (for identification)
- `flags/groups` - Permission flags or group name
- `immunity` - Immunity level (0-100, higher = more protection)
- `duration` - Duration in minutes (0 = permanent)
**Examples:**
```bash
# Add permanent admin with root access
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0
# Add moderator for 30 days
css_addadmin STEAM_1:0:12345678 "ModName" "@css/kick,@css/ban" 50 43200
# Add admin using group
css_addadmin 76561198012345678 "AdminName" "#moderators" 60 0
# Add admin to all servers (-g flag)
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0 -g
```
**Flags:**
- `-g` - Add to all servers (global admin)
---
### Delete Admin
Remove an admin from the database.
```bash
css_deladmin <steamid>
```
**Permission:** `@css/root`
**Examples:**
```bash
# Remove admin from current server
css_deladmin 76561198012345678
# Remove admin from all servers (-g flag)
css_deladmin 76561198012345678 -g
```
---
### Add Admin Group
Create a new admin group.
```bash
css_addgroup <group_name> <flags> <immunity>
```
**Permission:** `@css/root`
**Parameters:**
- `group_name` - Name of the group (e.g., "#moderators")
- `flags` - Permission flags for the group
- `immunity` - Default immunity level for group members
**Examples:**
```bash
# Create moderator group
css_addgroup "#moderators" "@css/kick,@css/ban,@css/chat" 50
# Create VIP group
css_addgroup "#vip" "@css/vip" 10
# Create global group (-g flag)
css_addgroup "#owner" "@css/root" 99 -g
```
**Flags:**
- `-g` - Create group on all servers
---
### Delete Admin Group
Remove an admin group from the database.
```bash
css_delgroup <group_name>
```
**Permission:** `@css/root`
**Examples:**
```bash
# Delete group from current server
css_delgroup "#moderators"
# Delete group from all servers (-g flag)
css_delgroup "#moderators" -g
```
---
### Reload Admins
Reload admin permissions from the database.
```bash
css_reloadadmins
```
**Permission:** `@css/root`
**When to use:**
- After adding/removing admins via database
- After modifying admin permissions
- After group changes
- Troubleshooting permission issues
**Note:** Admins are automatically reloaded periodically and on map change (if configured).
---
## Player Information
### Hide in Scoreboard
Toggle admin stealth mode (hide from scoreboard).
```bash
css_hide
css_stealth
```
**Permission:** `@css/kick`
**Features:**
- Hides you from the scoreboard
- Makes admin actions anonymous
- Useful for undercover moderation
---
### Who is This Player
Show detailed information about a player.
```bash
css_who <#userid or name>
```
**Permission:** `@css/generic`
**Shows:**
- Player name and SteamID
- IP address (if you have `@css/showip` permission)
- Connection time
- Active penalties
- Warning count
- Ban history
**Examples:**
```bash
css_who #123
css_who PlayerName
css_who @me
```
---
### Show Disconnected Players
Show recently disconnected players.
```bash
css_disconnected
css_last
css_last10 # Show last 10 (config value)
```
**Permission:** `@css/kick`
**Shows:**
- Player name
- SteamID
- Disconnect time
- Disconnect reason
**Configuration:**
```json
"DisconnectedPlayersHistoryCount": 10
```
---
### Show Warns for Player
Open warn list for a specific player.
```bash
css_warns <#userid or name>
```
**Permission:** `@css/kick`
**Shows:**
- All warnings for the player
- Warning reasons
- Admins who issued warnings
- Warning timestamps
- Total warning count
**Examples:**
```bash
css_warns #123
css_warns PlayerName
```
---
### Show Online Players
Show information about all online players.
```bash
css_players
```
**Permission:** `@css/generic`
**Shows:**
- List of all connected players
- UserIDs
- Names
- Teams
- Connection status
---
## Server Management
### Kick Player
Kick a player from the server.
```bash
css_kick <#userid or name> [reason]
```
**Permission:** `@css/kick`
**Examples:**
```bash
css_kick #123 "AFK"
css_kick PlayerName "Rule violation"
css_kick @spec "Cleaning spectators"
```
**Configuration:**
```json
"KickTime": 5
```
Delay in seconds before kicking (allows player to see the reason).
---
### Change Map
Change to a different map.
```bash
css_map <mapname>
css_changemap <mapname>
```
**Permission:** `@css/changemap`
**Examples:**
```bash
css_map de_dust2
css_changemap de_mirage
```
**Configuration:**
```json
"DefaultMaps": [
"de_dust2",
"de_mirage",
"de_inferno"
]
```
Maps in this list appear in the map change menu.
---
### Change Workshop Map
Change to a workshop map by ID or name.
```bash
css_wsmap <name or id>
css_changewsmap <name or id>
css_workshop <name or id>
```
**Permission:** `@css/changemap`
**Examples:**
```bash
css_wsmap 123456789
css_wsmap aim_map
```
**Configuration:**
```json
"WorkshopMaps": {
"aim_map": "123456789",
"surf_map": "987654321"
}
```
Maps configured here can be changed by name instead of ID.
---
### Change CVar
Change a server console variable.
```bash
css_cvar <cvar> <value>
```
**Permission:** `@css/cvar`
**Examples:**
```bash
css_cvar sv_cheats 1
css_cvar mp_roundtime 5
css_cvar mp_maxmoney 16000
```
**Warning:** This is a powerful command. Only grant to trusted admins.
---
### Execute RCON Command
Execute any command as the server.
```bash
css_rcon <command>
```
**Permission:** `@css/rcon`
**Examples:**
```bash
css_rcon status
css_rcon changelevel de_dust2
css_rcon sv_cheats 1
```
**Warning:** Extremely powerful command. Only grant to server owners.
**Configuration:**
```json
"DisableDangerousCommands": true
```
When enabled, prevents execution of dangerous commands via css_rcon.
---
### Restart Game
Restart the current game/round.
```bash
css_rr
css_rg
css_restart
css_restartgame
```
**Permission:** `@css/generic`
**Notes:**
- Restarts the current round
- Score is reset
- Players remain connected
---
## Permission Flags
Common permission flags used in CS2-SimpleAdmin:
| Flag | Description | Common Use |
|------|-------------|------------|
| `@css/generic` | Generic admin access | Basic admin menu, info commands |
| `@css/chat` | Chat management | Gag, mute, silence |
| `@css/kick` | Kick players | Kick, warnings, player info |
| `@css/ban` | Ban players | Ban, banip, addban |
| `@css/unban` | Unban players | Remove bans |
| `@css/permban` | Permanent bans | Issue permanent bans |
| `@css/changemap` | Change maps | Map changing |
| `@css/cvar` | Change cvars | Server variable modification |
| `@css/rcon` | Execute rcon | Full server control |
| `@css/root` | Root access | All permissions, admin management |
| `@css/slay` | Slay/respawn | Player manipulation |
| `@css/cheats` | Cheat commands | God mode, noclip, give weapons |
| `@css/showip` | View IPs | See player IP addresses |
---
## Immunity System
Immunity prevents lower-level admins from targeting higher-level admins.
**How it works:**
- Each admin has an immunity value (0-100)
- Higher immunity = more protection
- Admins can only target players with lower immunity
**Example:**
- Admin A has immunity 50
- Admin B has immunity 30
- Admin A can ban Admin B
- Admin B cannot ban Admin A
**Best Practice:**
- Owner: 99
- Senior admins: 80-90
- Moderators: 50-70
- Trial mods: 20-40
- Regular players: 0
---
## Configuration Options
### Reload Admins on Map Change
```json
"ReloadAdminsEveryMapChange": false
```
**Options:**
- `true` - Reload admin permissions every map change
- `false` - Only reload when explicitly requested (better performance)
### Show Activity Type
```json
"ShowActivityType": 2
```
**Options:**
- `0` - Hide all admin activity
- `1` - Show activity anonymously ("An admin banned PlayerName")
- `2` - Show admin name ("AdminName banned PlayerName")
---
## Best Practices
### Admin Management
1. **Use groups** - Easier to manage than individual permissions
2. **Set appropriate immunity** - Prevent abuse
3. **Time-limited admin** - For trial moderators
4. **Document changes** - Keep track of who has what permissions
### Permission Assignment
**Recommended hierarchy:**
```
Root (@css/root, immunity 99):
- Server owners only
Senior Admin (@css/ban,@css/kick,@css/chat,@css/changemap, immunity 80):
- Trusted long-term admins
Moderator (@css/kick,@css/chat, immunity 50):
- Regular moderators
Trial Mod (@css/kick, immunity 20):
- New moderators on probation
```
### Security
1. **Limit @css/rcon** - Only to server owner
2. **Limit @css/cvar** - Only to senior admins
3. **Monitor admin actions** - Review logs regularly
4. **Use time-limited admin** - For temporary staff
---
## Troubleshooting
### Admin permissions not working
**Check:**
1. Is admin correctly added with `css_addadmin`?
2. Run `css_reloadadmins`
3. Check database connection
4. Verify SteamID format
### Can't target another admin
**Check:**
- Your immunity level vs target's immunity
- You need equal or higher immunity to target
### Commands not available
**Check:**
- Your permission flags
- Commands.json for disabled commands
- Server console for errors
---
## Related Commands
- **[Ban Commands](basebans)** - Player punishment
- **[Communication Commands](basecomms)** - Chat/voice management
- **[Player Commands](playercommands)** - Player manipulation

View File

@@ -0,0 +1,396 @@
---
sidebar_position: 2
---
# Communication Commands
Commands for managing player communication (voice and text chat).
## Overview
CS2-SimpleAdmin provides three types of communication restrictions:
- **Gag** - Blocks text chat only
- **Mute** - Blocks voice chat only
- **Silence** - Blocks both text and voice chat
---
## Gag Commands
### Gag Player
Prevent a player from using text chat.
```bash
css_gag <#userid or name> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_gag #123 30 "Chat spam"
css_gag PlayerName 1440 "Advertising"
css_gag @all 5 "Everyone quiet for 5 minutes"
```
### Add Gag (Offline Player)
Gag a player by SteamID even if they're offline.
```bash
css_addgag <steamid> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_addgag 76561198012345678 60 "Chat abuse"
css_addgag STEAM_1:0:12345678 1440 "Spam"
```
### Ungag Player
Remove a gag from a player.
```bash
css_ungag <steamid or name> [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_ungag PlayerName "Appeal accepted"
css_ungag 76561198012345678 "Mistake"
```
---
## Mute Commands
### Mute Player
Prevent a player from using voice chat.
```bash
css_mute <#userid or name> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_mute #123 30 "Mic spam"
css_mute PlayerName 60 "Loud music"
css_mute @t 5 "T team timeout"
```
### Add Mute (Offline Player)
Mute a player by SteamID even if they're offline.
```bash
css_addmute <steamid> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_addmute 76561198012345678 120 "Voice abuse"
css_addmute STEAM_1:0:12345678 1440 "Mic spam"
```
### Unmute Player
Remove a mute from a player.
```bash
css_unmute <steamid or name> [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_unmute PlayerName "Behavior improved"
css_unmute 76561198012345678 "Time served"
```
---
## Silence Commands
### Silence Player
Block both text and voice chat from a player.
```bash
css_silence <#userid or name> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_silence #123 60 "Complete communication ban"
css_silence PlayerName 1440 "Severe abuse"
```
### Add Silence (Offline Player)
Silence a player by SteamID even if they're offline.
```bash
css_addsilence <steamid> [time in minutes/0 perm] [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_addsilence 76561198012345678 120 "Total communication ban"
css_addsilence STEAM_1:0:12345678 0 "Permanent silence"
```
### Unsilence Player
Remove a silence from a player.
```bash
css_unsilence <steamid or name> [reason]
```
**Permission:** `@css/chat`
**Examples:**
```bash
css_unsilence PlayerName "Punishment complete"
css_unsilence 76561198012345678 "Appeal granted"
```
---
## Permission Requirements
All communication commands require the `@css/chat` permission.
| Command | Action | Offline Support |
|---------|--------|----------------|
| `css_gag` | Block text chat | No |
| `css_addgag` | Block text chat | Yes |
| `css_ungag` | Remove text block | Yes |
| `css_mute` | Block voice chat | No |
| `css_addmute` | Block voice chat | Yes |
| `css_unmute` | Remove voice block | Yes |
| `css_silence` | Block both | No |
| `css_addsilence` | Block both | Yes |
| `css_unsilence` | Remove both blocks | Yes |
---
## Communication Penalty Types
### When to Use Each Type
**Gag (Text Only):**
- Chat spam
- Advertising in chat
- Offensive messages
- Spectator camera abuse messages
**Mute (Voice Only):**
- Mic spam
- Loud music/noise
- Voice abuse
- Excessive talking
**Silence (Both):**
- Severe abuse cases
- Players who switch between chat and voice to evade
- Complete communication bans
---
## Configuration Options
### UserMessage Gag Type
In `CS2-SimpleAdmin.json`:
```json
"UserMessageGagChatType": false
```
**Options:**
- `false` - Standard gag implementation (default)
- `true` - Alternative gag using UserMessage system
**Note:** Try switching this if gag commands don't work as expected.
### Notify Penalties on Connect
```json
"NotifyPenaltiesToAdminOnConnect": true
```
When enabled, admins see active communication penalties when they join:
```
[CS2-SimpleAdmin] PlayerName is gagged (30 minutes remaining)
[CS2-SimpleAdmin] PlayerName is muted (1 hour remaining)
```
---
## Checking Penalties
### View Own Penalties
Players can check their own communication penalties:
```bash
css_penalties
css_mypenalties
css_comms
```
Shows:
- Active gags, mutes, and silences
- Duration remaining
- Reason for penalty
- Admin who issued it
### Admin View of Penalties
Use the admin menu or player info command:
```bash
css_who <#userid or name>
```
Shows complete penalty history including communication restrictions.
---
## Time Durations
Common duration values:
| Duration | Minutes | Use Case |
|----------|---------|----------|
| 1 minute | 1 | Quick warning |
| 5 minutes | 5 | Minor spam |
| 15 minutes | 15 | Standard timeout |
| 30 minutes | 30 | Repeated offense |
| 1 hour | 60 | Moderate abuse |
| 6 hours | 360 | Serious abuse |
| 1 day | 1440 | Severe abuse |
| 1 week | 10080 | Extreme cases |
| Permanent | 0 | Reserved for worst cases |
---
## Player Targeting
All communication commands support advanced targeting:
- `@all` - Target all players
- `@ct` - Target all Counter-Terrorists
- `@t` - Target all Terrorists
- `@spec` - Target all spectators
- `#123` - Target by userid
- `PlayerName` - Target by name
**Examples:**
```bash
css_gag @all 1 "Quiet for one minute"
css_mute @t 5 "T team voice timeout"
css_silence @ct 10 "CT team complete silence"
```
---
## Best Practices
### Communication Management
1. **Start with warnings** - Not all chat issues need immediate gag
2. **Use appropriate durations** - Match severity to punishment
3. **Provide reasons** - Helps players understand what they did wrong
4. **Consider silence carefully** - Complete communication ban is harsh
### Gag vs Mute vs Silence
**Progressive Approach:**
1. Verbal warning
2. Gag or mute (specific to offense)
3. Longer gag/mute for repeat offense
4. Silence for continued abuse
5. Temporary ban for extreme cases
### Documentation
1. **Always provide reasons** - Required for appeals
2. **Be specific** - "Mic spam" not just "abuse"
3. **Keep records** - Use admin logs for repeat offenders
---
## Discord Integration
Communication penalties can send Discord notifications when configured:
```json
"DiscordPenaltyGagSettings": [...],
"DiscordPenaltyMuteSettings": [...],
"DiscordPenaltySilenceSettings": [...]
```
Notifications include:
- Player name and SteamID
- Penalty type and duration
- Reason provided
- Admin who issued it
---
## Troubleshooting
### Gag doesn't work
**Try:**
1. Switch `UserMessageGagChatType` in config
2. Ensure player is actually gagged (check with `css_who`)
3. Check for conflicting plugins
### Mute doesn't block voice
**Check:**
- Is sv_talk_enemy_dead configured correctly?
- Are there voice management plugins conflicting?
- Check server console for errors
### Penalties not persistent across maps
**Solution:**
- Penalties should persist automatically
- Check database connection
- Verify MultiServerMode if using multiple servers
### Player can't see their penalties
**Check:**
- Command aliases in Commands.json
- Ensure `css_penalties` is enabled
- Check player chat permissions
---
## Related Commands
- **[Ban Commands](basebans)** - For more serious offenses
- **[Player Commands](playercommands)** - Kick, team switch
- **[Base Commands](basecommands)** - Admin management

View File

@@ -0,0 +1,436 @@
---
sidebar_position: 6
---
# Vote Commands
Commands for creating polls and votes on your server.
## Create Vote
Create a custom poll for players to vote on.
```bash
css_vote <question> [option1] [option2] [option3] ...
```
**Permission:** `@css/generic`
**Parameters:**
- `question` - The question to ask players
- `option1, option2, ...` - Vote options (at least 2 required)
---
## Examples
### Simple Yes/No Vote
```bash
css_vote "Should we change map?" "Yes" "No"
```
**Player sees:**
```
Vote: Should we change map?
1. Yes
2. No
```
---
### Multiple Options
```bash
css_vote "Which map should we play next?" "de_dust2" "de_mirage" "de_inferno" "de_nuke"
```
**Player sees:**
```
Vote: Which map should we play next?
1. de_dust2
2. de_mirage
3. de_inferno
4. de_nuke
```
---
### Rule Vote
```bash
css_vote "Should we allow AWPs?" "Yes" "No" "Only one per team"
```
---
### Activity Vote
```bash
css_vote "What should we do?" "Surf" "Deathrun" "Competitive" "Fun Round"
```
---
## How Voting Works
### Player Participation
Players vote by:
1. Opening their chat
2. Typing the number of their choice
3. Or using vote menu (if available)
**Example:**
```
Player: 1 (votes for option 1)
Player: 2 (votes for option 2)
```
### Vote Duration
- Default vote time: ~30 seconds
- Vote timer shows on screen
- Results shown when vote ends
### Vote Results
After voting ends, results are displayed:
```
Vote Results:
1. Yes - 12 votes (60%)
2. No - 8 votes (40%)
Winner: Yes
```
---
## Use Cases
### Map Voting
```bash
css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno"
```
### Rule Changes
```bash
css_vote "Enable friendly fire?" "Yes" "No"
css_vote "Restart round?" "Yes" "No"
```
### Player Punishment
```bash
css_vote "Ban PlayerName for cheating?" "Yes" "No"
css_vote "Kick AFK player?" "Yes" "No"
```
### Fun Rounds
```bash
css_vote "Fun round type?" "Knife only" "Deagle only" "Zeus only" "Normal"
```
### Server Settings
```bash
css_vote "Round time?" "2 minutes" "3 minutes" "5 minutes"
css_vote "Max players?" "10v10" "5v5" "7v7"
```
---
## Best Practices
### Question Clarity
**Good Questions:**
- Clear and concise
- Specific
- Easy to understand
**Examples:**
```bash
✅ css_vote "Change to de_dust2?" "Yes" "No"
❌ css_vote "Map?" "Yes" "No" # Unclear what map
✅ css_vote "Restart this round?" "Yes" "No"
❌ css_vote "Restart?" "Yes" "No" # Restart what?
```
### Option Limits
**Recommendations:**
- 2-5 options ideal
- Too many options confuse players
- Keep options brief
**Examples:**
```bash
✅ css_vote "Next map?" "dust2" "mirage" "inferno"
❌ css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno" "de_nuke" "de_vertigo" "de_ancient" "de_anubis"
```
### Timing
**When to use votes:**
- End of round
- Between maps
- During downtime
- Not during active gameplay
**When NOT to use votes:**
- Mid-round
- During clutch situations
- Too frequently
### Vote Spam Prevention
Don't spam votes:
```bash
❌ Multiple votes in quick succession
❌ Overlapping votes
❌ Votes every round
```
Wait for current vote to finish before starting another.
---
## Vote Types
### Administrative Votes
**Map change:**
```bash
css_vote "Change map now?" "Yes" "No"
```
**Server restart:**
```bash
css_vote "Restart server?" "Yes" "No"
```
**Rule enforcement:**
```bash
css_vote "Kick PlayerName?" "Yes" "No"
```
### Gameplay Votes
**Weapon restrictions:**
```bash
css_vote "Disable AWP?" "Yes" "No"
```
**Team scramble:**
```bash
css_vote "Scramble teams?" "Yes" "No"
```
**Round rules:**
```bash
css_vote "Knife round first?" "Yes" "No"
```
### Event Votes
**Tournament:**
```bash
css_vote "Start tournament?" "Yes, start now" "Wait 5 minutes" "No, cancel"
```
**Custom game mode:**
```bash
css_vote "Game mode?" "Hide and Seek" "Gungame" "Surf" "Normal"
```
---
## Limitations
### Technical Limits
- Maximum ~10 options (depends on menu system)
- One vote at a time
- Requires active players to participate
### Permission Required
Only admins with `@css/generic` permission can start votes.
To grant permission:
```bash
css_addadmin STEAMID "Name" "@css/generic" 50 0
```
---
## Vote Results Handling
### Manual Enforcement
Votes don't automatically execute actions. Admins must:
1. **See the results**
2. **Manually execute the winning option**
**Example:**
```bash
# Start vote
css_vote "Change to de_dust2?" "Yes" "No"
# If "Yes" wins, manually change map
css_map de_dust2
```
### Why Manual?
- Prevents abuse
- Allows admin oversight
- Gives control over execution
---
## Advanced Usage
### Combining with Commands
Use votes to decide, then execute:
```bash
# Vote on map
css_vote "Next map?" "dust2" "mirage" "inferno"
# If dust2 wins:
css_map de_dust2
# Vote on player kick
css_vote "Kick PlayerName?" "Yes" "No"
# If Yes wins:
css_kick PlayerName "Voted to be kicked"
```
### Sequential Votes
Run multiple votes for complex decisions:
```bash
# First vote: Mode
css_vote "Game mode?" "Competitive" "Casual"
# If Competitive wins, second vote:
css_vote "Round time?" "2 min" "3 min" "5 min"
```
---
## Configuration
Check if vote commands are enabled in:
```
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
```
```json
{
"Commands": {
"css_vote": {
"Aliases": [
"css_vote"
]
}
}
}
```
To disable votes, remove all aliases:
```json
{
"Commands": {
"css_vote": {
"Aliases": []
}
}
}
```
---
## Troubleshooting
### Vote doesn't start
**Check:**
- Do you have `@css/generic` permission?
- Is command enabled in Commands.json?
- Are there at least 2 options?
### Players can't vote
**Check:**
- Vote menu is showing
- Players know how to vote (type number in chat)
- Vote hasn't already ended
### Vote results not showing
**Check:**
- Wait for vote to complete
- Check server console
- Ensure voting system is working
---
## Permission Requirements
| Command | Permission | Description |
|---------|------------|-------------|
| `css_vote` | `@css/generic` | Create votes/polls |
---
## Tips
### Effective Polling
1. **Ask clear questions** - No ambiguity
2. **Limit options** - 2-4 is ideal
3. **Time it right** - Between rounds
4. **Follow through** - Execute winning option
5. **Don't overuse** - Votes lose impact if spammed
### Community Engagement
Use votes to:
- Involve community in decisions
- Gauge player preferences
- Create democratic server atmosphere
- Get feedback on changes
### Example Scenarios
**New map test:**
```bash
css_vote "Try new map cs_office?" "Yes" "No"
```
**Event planning:**
```bash
css_vote "Tournament this weekend?" "Saturday" "Sunday" "No thanks"
```
**Rule feedback:**
```bash
css_vote "Keep no-AWP rule?" "Yes" "No" "Only limit to 2"
```
---
## Related Commands
- **[Base Commands](basecommands)** - Server management
- **[Chat Commands](basechat)** - Announcements
- **[Player Commands](playercommands)** - Player actions

View File

@@ -0,0 +1,516 @@
---
sidebar_position: 5
---
# Player Commands
Commands for managing and manipulating players on your server.
:::note
Many of these commands are included in the base plugin. For extended fun commands (god mode, noclip, freeze, etc.), see the [Fun Commands Module](../../modules/funcommands).
:::
## Player Management
### Slay Player
Kill a player instantly.
```bash
css_slay <#userid or name>
```
**Permission:** `@css/slay`
**Examples:**
```bash
css_slay #123
css_slay PlayerName
css_slay @ct # Slay all CTs
css_slay @t # Slay all terrorists
```
**Use cases:**
- Punishment for rule breaking
- Ending rounds quickly
- Removing camping players
---
### Slap Player
Slap a player, dealing damage and pushing them.
```bash
css_slap <#userid or name> [damage]
```
**Permission:** `@css/slay`
**Parameters:**
- `damage` - HP damage to deal (default: 0)
**Examples:**
```bash
css_slap #123 # Slap with no damage
css_slap PlayerName 10 # Slap for 10 HP damage
css_slap @all 5 # Slap everyone for 5 damage
```
**Effects:**
- Player is pushed in a random direction
- Optional damage dealt
- Makes slap sound
**Use cases:**
- Funny punishment
- Getting player attention
- Moving AFK players
---
## Player Attributes
### Set Player Health
Set a player's health points.
```bash
css_hp <#userid or name> <health>
```
**Permission:** `@css/slay`
**Examples:**
```bash
css_hp #123 100 # Set to full health
css_hp PlayerName 1 # Set to 1 HP
css_hp @ct 200 # Give all CTs 200 HP
```
**Valid range:** 1 - 999+
---
### Set Player Speed
Modify a player's movement speed.
```bash
css_speed <#userid or name> <speed>
```
**Permission:** `@css/slay`
**Parameters:**
- `speed` - Speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed)
**Examples:**
```bash
css_speed #123 1.5 # 150% speed
css_speed PlayerName 0.5 # 50% speed (slow motion)
css_speed @all 2.0 # Double speed for everyone
css_speed #123 1.0 # Reset to normal speed
```
**Common values:**
- `0.5` - Slow motion
- `1.0` - Normal (default)
- `1.5` - Fast
- `2.0` - Very fast
- `3.0` - Extremely fast
**Note:** Speed persists across respawns until reset.
---
### Set Player Gravity
Modify a player's gravity.
```bash
css_gravity <#userid or name> <gravity>
```
**Permission:** `@css/slay`
**Parameters:**
- `gravity` - Gravity multiplier (1.0 = normal, 0.5 = moon jump, 2.0 = heavy)
**Examples:**
```bash
css_gravity #123 0.5 # Moon jump
css_gravity PlayerName 2.0 # Heavy gravity
css_gravity @all 0.1 # Super jump for everyone
css_gravity #123 1.0 # Reset to normal
```
**Common values:**
- `0.1` - Super high jumps
- `0.5` - Moon gravity
- `1.0` - Normal (default)
- `2.0` - Heavy/fast falling
**Note:** Gravity persists across respawns until reset.
---
### Set Player Money
Set a player's money amount.
```bash
css_money <#userid or name> <amount>
```
**Permission:** `@css/slay`
**Examples:**
```bash
css_money #123 16000 # Max money
css_money PlayerName 0 # Remove all money
css_money @ct 10000 # Give all CTs $10,000
```
**Valid range:** 0 - 65535 (CS2 engine limit)
---
## Team Management
### Switch Player Team
Move a player to a different team.
```bash
css_team <#userid or name> [ct/t/spec] [-k]
```
**Permission:** `@css/kick`
**Parameters:**
- `ct` - Counter-Terrorist team
- `t` - Terrorist team
- `spec` - Spectators
- `-k` - Kill player during switch (optional)
**Examples:**
```bash
css_team #123 ct # Move to CT
css_team PlayerName t # Move to T
css_team @spec t # Move all spectators to T
css_team #123 ct -k # Move to CT and kill
```
**Configuration:**
```json
"TeamSwitchType": 1
```
Determines team switch behavior.
---
### Rename Player
Temporarily rename a player.
```bash
css_rename <#userid or name> <new name>
```
**Permission:** `@css/kick`
**Examples:**
```bash
css_rename #123 "NewName"
css_rename PlayerName "RenamedPlayer"
css_rename @all "Everyone"
```
**Notes:**
- Rename is temporary (resets on reconnect)
- For permanent rename, use `css_prename`
---
### Permanent Rename
Permanently force a player's name.
```bash
css_prename <#userid or name> <new name>
```
**Permission:** `@css/ban`
**Examples:**
```bash
css_prename #123 "EnforcedName"
css_prename PlayerName "NewIdentity"
```
**Notes:**
- Name is enforced even after reconnect
- Stored in database
- Player cannot change it
- Useful for offensive names
---
## Weapon Management
### Give Weapon
Give a weapon to a player.
```bash
css_give <#userid or name> <weapon>
```
**Permission:** `@css/cheats`
**Weapon names:**
- Rifles: `ak47`, `m4a1`, `m4a1_silencer`, `aug`, `sg556`, `awp`
- SMGs: `mp5sd`, `mp7`, `mp9`, `p90`, `ump45`
- Heavy: `nova`, `xm1014`, `mag7`, `m249`, `negev`
- Pistols: `deagle`, `elite`, `fiveseven`, `glock`, `hkp2000`, `p250`, `tec9`, `usp_silencer`
- Grenades: `flashbang`, `hegrenade`, `smokegrenade`, `molotov`, `incgrenade`, `decoy`
- Equipment: `kevlar`, `assaultsuit`, `defuser`, `knife`
**Examples:**
```bash
css_give #123 awp
css_give PlayerName ak47
css_give @ct m4a1
css_give @all deagle
```
---
### Strip Weapons
Remove all weapons from a player.
```bash
css_strip <#userid or name>
```
**Permission:** `@css/slay`
**Examples:**
```bash
css_strip #123
css_strip PlayerName
css_strip @t # Disarm all terrorists
```
**Effects:**
- Removes all weapons
- Leaves player with knife only
- Removes grenades and equipment
---
## Teleportation
### Teleport to Player
Teleport yourself to another player.
```bash
css_tp <#userid or name>
css_tpto <#userid or name>
css_goto <#userid or name>
```
**Permission:** `@css/kick`
**Examples:**
```bash
css_tp #123
css_goto PlayerName
```
**Use cases:**
- Checking player behavior
- Admin help
- Spectating suspicious players
---
### Teleport Player to You
Bring a player to your location.
```bash
css_bring <#userid or name>
css_tphere <#userid or name>
```
**Permission:** `@css/kick`
**Examples:**
```bash
css_bring #123
css_tphere PlayerName
css_bring @all # Bring everyone to you
```
**Use cases:**
- Moving stuck players
- Gathering players
- Admin events
---
### Respawn Player
Respawn a dead player.
```bash
css_respawn <#userid or name>
```
**Permission:** `@css/cheats`
**Examples:**
```bash
css_respawn #123
css_respawn PlayerName
css_respawn @ct # Respawn all dead CTs
```
**Notes:**
- Player spawns at spawn point
- Equipped with default weapons
- Can break competitive balance
---
## Player Targeting
All player commands support advanced targeting:
### Target Syntax
- `@all` - All players
- `@ct` - All Counter-Terrorists
- `@t` - All Terrorists
- `@spec` - All spectators
- `@alive` - All alive players
- `@dead` - All dead players
- `@bot` - All bots
- `@human` - All human players
- `@me` - Yourself
- `#123` - Specific user ID
- `PlayerName` - By name (partial match supported)
### Multiple Targets
```bash
css_slay @ct # Kills all CTs
css_hp @all 200 # Give everyone 200 HP
css_speed @t 2.0 # Make all Ts fast
```
---
## Permission Requirements
| Command | Required Permission | Description |
|---------|-------------------|-------------|
| `css_slay` | `@css/slay` | Kill players |
| `css_slap` | `@css/slay` | Slap players |
| `css_hp` | `@css/slay` | Set health |
| `css_speed` | `@css/slay` | Modify speed |
| `css_gravity` | `@css/slay` | Modify gravity |
| `css_money` | `@css/slay` | Set money |
| `css_team` | `@css/kick` | Change team |
| `css_rename` | `@css/kick` | Temporary rename |
| `css_prename` | `@css/ban` | Permanent rename |
| `css_give` | `@css/cheats` | Give weapons |
| `css_strip` | `@css/slay` | Remove weapons |
| `css_tp` | `@css/kick` | Teleport to player |
| `css_bring` | `@css/kick` | Bring player |
| `css_respawn` | `@css/cheats` | Respawn players |
---
## Best Practices
### Punishment Commands
**Slay:**
- Use for rule violations
- Better than kick for minor issues
- Allows player to stay and learn
**Slap:**
- Lighter punishment
- Good for warnings
- Can be funny/entertaining
### Gameplay Modification
**HP/Speed/Gravity:**
- Use for events/fun rounds
- Don't abuse during competitive play
- Reset to normal after use
**Respawn:**
- Very disruptive to gameplay
- Use sparingly
- Good for fixing bugs/mistakes
### Team Management
**Team switching:**
- Balance teams fairly
- Don't abuse for winning
- Use `-k` flag for competitive integrity
---
## Configuration
### Team Switch Behavior
```json
"TeamSwitchType": 1
```
Controls how team switching works.
---
## Troubleshooting
### Speed/Gravity not persisting
**Solution:** These are maintained by a timer. If they reset:
- Check server console for errors
- Ensure plugin is loaded correctly
- Try reapplying the modification
### Can't teleport
**Check:**
- Target player is connected
- You have correct permissions
- Both players are valid
### Give weapon not working
**Check:**
- Weapon name is correct
- Player is alive
- Player has inventory space
---
## Related Commands
- **[Fun Commands Module](../../modules/funcommands)** - Extended fun commands (freeze, god mode, noclip)
- **[Ban Commands](basebans)** - Punishment commands
- **[Base Commands](basecommands)** - Server management

View File

@@ -0,0 +1,397 @@
---
sidebar_position: 3
---
# Configuration
Learn how to configure CS2-SimpleAdmin to suit your server's needs.
## Configuration File Location
The main configuration file is located at:
```
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
```
## Configuration Structure
The configuration file is divided into several sections:
### Database Configuration
Configure your database connection:
```json
"DatabaseConfig": {
"DatabaseType": "SQLite",
"SqliteFilePath": "cs2-simpleadmin.sqlite",
"DatabaseHost": "",
"DatabasePort": 3306,
"DatabaseUser": "",
"DatabasePassword": "",
"DatabaseName": "",
"DatabaseSSlMode": "preferred"
}
```
**Database Types:**
- `SQLite` - Local database file (good for single server)
- `MySQL` - MySQL/MariaDB server (required for multi-server setups)
**MySQL Example:**
```json
"DatabaseConfig": {
"DatabaseType": "MySQL",
"DatabaseHost": "localhost",
"DatabasePort": 3306,
"DatabaseUser": "cs2admin",
"DatabasePassword": "your_password",
"DatabaseName": "cs2_simpleadmin",
"DatabaseSSlMode": "preferred"
}
```
### Other Settings
General plugin settings:
```json
"OtherSettings": {
"ShowActivityType": 2,
"TeamSwitchType": 1,
"KickTime": 5,
"BanType": 1,
"TimeMode": 1,
"DisableDangerousCommands": true,
"MaxBanDuration": 10080,
"MaxMuteDuration": 10080,
"ExpireOldIpBans": 0,
"ReloadAdminsEveryMapChange": false,
"DisconnectedPlayersHistoryCount": 10,
"NotifyPenaltiesToAdminOnConnect": true,
"ShowBanMenuIfNoTime": true,
"UserMessageGagChatType": false,
"CheckMultiAccountsByIp": true,
"AdditionalCommandsToLog": [],
"IgnoredIps": []
}
```
**Settings Explained:**
| Setting | Description | Default |
|---------|-------------|---------|
| `ShowActivityType` | How to display admin actions (0=hide, 1=anonymous, 2=show name) | 2 |
| `TeamSwitchType` | Team switch behavior | 1 |
| `KickTime` | Delay before kicking player (seconds) | 5 |
| `BanType` | Ban type (1=SteamID, 2=IP, 3=Both) | 1 |
| `TimeMode` | Time display mode | 1 |
| `DisableDangerousCommands` | Disable potentially dangerous commands | true |
| `MaxBanDuration` | Maximum ban duration in minutes (0=unlimited) | 10080 |
| `MaxMuteDuration` | Maximum mute duration in minutes (0=unlimited) | 10080 |
| `ExpireOldIpBans` | Auto-expire IP bans after X days (0=disabled) | 0 |
| `ReloadAdminsEveryMapChange` | Reload admin permissions on map change | false |
| `DisconnectedPlayersHistoryCount` | Number of disconnected players to track | 10 |
| `NotifyPenaltiesToAdminOnConnect` | Show penalties to admins when they connect | true |
| `ShowBanMenuIfNoTime` | Show ban menu even without time parameter | true |
| `UserMessageGagChatType` | Use UserMessage for gag (alternative chat blocking) | false |
| `CheckMultiAccountsByIp` | Detect multiple accounts from same IP | true |
| `AdditionalCommandsToLog` | Array of additional commands to log | [] |
| `IgnoredIps` | IPs to ignore in multi-account detection | [] |
### Metrics and Updates
```json
"EnableMetrics": true,
"EnableUpdateCheck": true
```
- `EnableMetrics` - Send anonymous usage statistics
- `EnableUpdateCheck` - Check for plugin updates on load
### Timezone
Set your server's timezone for accurate timestamps:
```json
"Timezone": "UTC"
```
See the [list of timezones](#timezone-list) below.
### Warning Thresholds
Configure automatic actions when players reach warning thresholds:
```json
"WarnThreshold": {
"998": "css_addban STEAMID64 60 \"3/4 Warn\"",
"999": "css_ban #USERID 120 \"4/4 Warn\""
}
```
**Example:** Automatically ban a player for 60 minutes when they receive their 3rd warning.
### Multi-Server Mode
Enable if you're running multiple servers with a shared database:
```json
"MultiServerMode": true
```
When enabled:
- Bans are shared across all servers
- Admin permissions can be global or server-specific
- Player data is synchronized
### Discord Integration
Send notifications to Discord webhooks:
```json
"Discord": {
"DiscordLogWebhook": "https://discord.com/api/webhooks/...",
"DiscordPenaltyBanSettings": [...],
"DiscordPenaltyMuteSettings": [...],
"DiscordPenaltyGagSettings": [...],
"DiscordPenaltySilenceSettings": [...],
"DiscordPenaltyWarnSettings": [...],
"DiscordAssociatedAccountsSettings": [...]
}
```
**Webhook Settings:**
Each penalty type can have its own webhook configuration:
```json
"DiscordPenaltyBanSettings": [
{
"name": "Color",
"value": "#FF0000"
},
{
"name": "Webhook",
"value": "https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE"
},
{
"name": "ThumbnailUrl",
"value": "https://example.com/ban-icon.png"
},
{
"name": "ImageUrl",
"value": ""
},
{
"name": "Footer",
"value": "CS2-SimpleAdmin"
},
{
"name": "Time",
"value": "{relative}"
}
]
```
**Available Placeholders:**
- `{relative}` - Relative timestamp
- `{fixed}` - Fixed timestamp
### Map Configuration
Configure default maps and workshop maps:
```json
"DefaultMaps": [
"de_dust2",
"de_mirage",
"de_inferno"
],
"WorkshopMaps": {
"aim_map": "123456789",
"surf_map": "987654321"
}
```
### Custom Server Commands
Add custom commands to the admin menu:
```json
"CustomServerCommands": [
{
"Flag": "@css/root",
"DisplayName": "Reload Admins",
"Command": "css_reloadadmins"
},
{
"Flag": "@css/cheats",
"DisplayName": "Enable sv_cheats",
"Command": "sv_cheats 1"
}
]
```
### Menu Configuration
Configure menu appearance and options:
```json
"MenuConfig": {
"MenuType": "selectable",
"Durations": [
{ "name": "1 minute", "duration": 1 },
{ "name": "5 minutes", "duration": 5 },
{ "name": "15 minutes", "duration": 15 },
{ "name": "1 hour", "duration": 60 },
{ "name": "1 day", "duration": 1440 },
{ "name": "7 days", "duration": 10080 },
{ "name": "14 days", "duration": 20160 },
{ "name": "30 days", "duration": 43200 },
{ "name": "Permanent", "duration": 0 }
],
"BanReasons": [
"Hacking",
"Voice Abuse",
"Chat Abuse",
"Admin disrespect",
"Other"
],
"KickReasons": [
"Voice Abuse",
"Chat Abuse",
"Admin disrespect",
"Other"
],
"WarnReasons": [
"Voice Abuse",
"Chat Abuse",
"Admin disrespect",
"Other"
],
"MuteReasons": [
"Advertising",
"Spamming",
"Spectator camera abuse",
"Hate",
"Admin disrespect",
"Other"
],
"AdminFlags": [
{ "name": "Generic", "flag": "@css/generic" },
{ "name": "Chat", "flag": "@css/chat" },
{ "name": "Change Map", "flag": "@css/changemap" },
{ "name": "Slay", "flag": "@css/slay" },
{ "name": "Kick", "flag": "@css/kick" },
{ "name": "Ban", "flag": "@css/ban" },
{ "name": "Perm Ban", "flag": "@css/permban" },
{ "name": "Unban", "flag": "@css/unban" },
{ "name": "Show IP", "flag": "@css/showip" },
{ "name": "Cvar", "flag": "@css/cvar" },
{ "name": "Rcon", "flag": "@css/rcon" },
{ "name": "Root (all flags)", "flag": "@css/root" }
]
}
```
## Timezone List
<details>
<summary>Click to expand timezone list</summary>
```
UTC
America/New_York
America/Chicago
America/Denver
America/Los_Angeles
Europe/London
Europe/Paris
Europe/Berlin
Europe/Warsaw
Europe/Moscow
Asia/Tokyo
Asia/Shanghai
Asia/Dubai
Australia/Sydney
Pacific/Auckland
... (and many more)
```
For a complete list, see the info.txt file in the documentation folder.
</details>
## Commands Configuration
You can customize command aliases in:
```
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
```
Example:
```json
{
"Commands": {
"css_ban": {
"Aliases": [
"css_ban",
"css_ban2"
]
}
}
}
```
This allows you to:
- **Disable commands** - Remove all aliases from the array
- **Add aliases** - Add multiple command variations
- **Rename commands** - Change the command name while keeping functionality
## Best Practices
### Security
1. **Use MySQL in production** - SQLite is not suitable for multi-server setups
2. **Set MaxBanDuration** - Prevent accidental permanent bans
3. **Enable DisableDangerousCommands** - Protect against accidental server crashes
4. **Use strong database passwords** - If using MySQL
### Performance
1. **Set ReloadAdminsEveryMapChange to false** - Unless you frequently modify admin permissions
2. **Limit DisconnectedPlayersHistoryCount** - Reduce memory usage
3. **Use database indices** - Migrations create these automatically
### Multi-Server Setup
1. **Enable MultiServerMode** - Share data across servers
2. **Use MySQL** - Required for multi-server
3. **Configure server IDs** - Each server gets a unique ID automatically
4. **Test penalties** - Ensure bans work across all servers
## Troubleshooting
### Changes not taking effect
**Solution:** Reload the plugin or restart the server:
```
css_plugins reload CS2-SimpleAdmin
```
### Discord webhooks not working
**Solution:**
- Verify webhook URL is correct
- Check that the webhook is not deleted in Discord
- Ensure server has internet access
### TimeMode issues
**Solution:** Set your timezone correctly in the configuration
## Next Steps
- **[Learn admin commands](commands/basebans)** - Browse available commands
- **[Set up admins](#)** - Add your admin team
- **[Configure modules](../modules/intro)** - Extend functionality

View File

@@ -0,0 +1,188 @@
---
sidebar_position: 2
---
# Installation
This guide will help you install CS2-SimpleAdmin on your Counter-Strike 2 server.
## Prerequisites
Before installing CS2-SimpleAdmin, ensure you have the following dependencies installed:
### Required Dependencies
1. **[CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/)** (v1.0.340+)
- The core framework for CS2 server plugins
2. **[AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)**
- Required by PlayerSettings
3. **[PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)**
- Required by MenuManager
4. **[MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)**
- Provides the menu system
### Database Requirements
You'll need either:
- **MySQL** server (recommended for production)
- **SQLite** (built-in, good for testing)
## Installation Steps
### 1. Download the Plugin
Download the latest release from the [GitHub Releases page](https://github.com/daffyyyy/CS2-SimpleAdmin/releases).
You can either:
- Download the pre-built release ZIP file
- Clone the repository and build from source
### 2. Extract Files
Extract the downloaded files to your server's CounterStrikeSharp directory:
```
game/csgo/addons/counterstrikesharp/plugins/CS2-SimpleAdmin/
```
Your directory structure should look like this:
```
csgo/
└── addons/
└── counterstrikesharp/
├── plugins/
│ └── CS2-SimpleAdmin/
│ ├── CS2-SimpleAdmin.dll
│ ├── lang/
│ └── ... (other files)
└── shared/
└── CS2-SimpleAdminApi/
└── CS2-SimpleAdminApi.dll
```
### 3. First Launch
Start your server. On the first launch, CS2-SimpleAdmin will:
1. Create a configuration file at:
```
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
```
2. Create a database (if using SQLite):
```
addons/counterstrikesharp/plugins/CS2-SimpleAdmin/cs2-simpleadmin.sqlite
```
3. Apply database migrations automatically
### 4. Configure the Plugin
Edit the generated configuration file to match your server setup.
See the [Configuration Guide](configuration) for detailed information.
### 5. Restart Your Server
After editing the configuration, restart your server or reload the plugin:
```bash
css_plugins reload CS2-SimpleAdmin
```
## Building from Source
If you want to build CS2-SimpleAdmin from source:
### Prerequisites
- .NET 8.0 SDK
- Git
### Build Steps
1. **Clone the repository:**
```bash
git clone https://github.com/daffyyyy/CS2-SimpleAdmin.git
cd CS2-SimpleAdmin
```
2. **Restore dependencies:**
```bash
dotnet restore CS2-SimpleAdmin.sln
```
3. **Build the solution:**
```bash
dotnet build CS2-SimpleAdmin.sln -c Release
```
4. **Build output location:**
```
CS2-SimpleAdmin/bin/Release/net8.0/
CS2-SimpleAdminApi/bin/Release/net8.0/
```
5. **Copy to server:**
- Copy `CS2-SimpleAdmin.dll` and its dependencies to `plugins/CS2-SimpleAdmin/`
- Copy `CS2-SimpleAdminApi.dll` to `shared/CS2-SimpleAdminApi/`
## Verification
To verify the installation was successful:
1. **Check server console** for the plugin load message:
```
[CS2-SimpleAdmin] Plugin loaded successfully
```
2. **Run an admin command** in-game:
```
css_admin
```
3. **Check the logs** at:
```
addons/counterstrikesharp/logs/CS2-SimpleAdmin*.txt
```
## Troubleshooting
### Plugin doesn't load
**Solution:** Ensure all required dependencies are installed:
- CounterStrikeSharp (latest version)
- AnyBaseLibCS2
- PlayerSettings
- MenuManagerCS2
### Database connection errors
**Solution:**
- For MySQL: Verify database credentials in the config file
- For SQLite: Ensure the plugin has write permissions in its directory
### Commands not working
**Solution:**
- Check that you have admin permissions configured
- Verify the commands are enabled in `Commands.json`
- Check server console for error messages
## Next Steps
- **[Configure your plugin](configuration)** - Set up database, permissions, and features
- **[Learn the commands](commands/basebans)** - Browse available admin commands
- **[Add admins](#)** - Set up your admin team
## Need Help?
If you encounter issues:
1. Check the [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues) for similar problems
2. Review server logs for error messages
3. Ask for help on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)

View File

@@ -0,0 +1,81 @@
---
sidebar_position: 1
---
# Introduction
Welcome to **CS2-SimpleAdmin** - a comprehensive administration plugin for Counter-Strike 2 servers built with C# (.NET 8.0) for CounterStrikeSharp.
## What is CS2-SimpleAdmin?
CS2-SimpleAdmin is a powerful server administration tool that provides comprehensive features for managing your Counter-Strike 2 server. It offers:
- **Player Management** - Ban, kick, mute, gag, and warn players
- **Admin System** - Flexible permission system with flags and groups
- **Multi-Server Support** - Manage multiple servers with a shared database
- **Discord Integration** - Send notifications to Discord webhooks
- **Menu System** - Easy-to-use admin menus
- **Extensive Commands** - Over 50 admin commands
- **Module Support** - Extend functionality with custom modules
## Key Features
### 🛡️ Comprehensive Penalty System
- **Bans** - Ban players by SteamID or IP address
- **Mutes** - Mute voice communication
- **Gags** - Gag text chat
- **Silence** - Block both voice and text
- **Warnings** - Progressive warning system with auto-escalation
### 👥 Flexible Admin System
- Permission-based access control using flags
- Admin groups for easy management
- Immunity levels to prevent abuse
- Server-specific or global admin assignments
### 🗄️ Database Support
- **MySQL** - For production environments
- **SQLite** - For quick setup and testing
- Automatic migration system
- Multi-server mode with shared data
### 🎮 User-Friendly Interface
- Interactive admin menus
- In-game admin panel
- Player selection menus
- Duration and reason selection
### 🔧 Extensibility
- Public API for module development
- Event system for custom integrations
- Command registration system
- Menu builder API
## Requirements
Before installing CS2-SimpleAdmin, make sure you have:
- [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/) (v1.0.340+)
- [AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)
- [PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)
- [MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)
- MySQL database (or use SQLite for testing)
## Quick Links
- **[Installation Guide](installation)** - Get started with CS2-SimpleAdmin
- **[Configuration](configuration)** - Learn how to configure the plugin
- **[Commands](commands/basebans)** - Browse all available commands
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code and releases
## Community & Support
Need help or want to contribute?
- **Issues** - Report bugs on [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
- **Discussions** - Ask questions on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
- **Pull Requests** - Contribute improvements
## License
CS2-SimpleAdmin is open-source software. Check the [GitHub repository](https://github.com/daffyyyy/CS2-SimpleAdmin) for license details.

View File

@@ -0,0 +1,174 @@
// @ts-check
// `@type` JSDoc annotations allow editor autocompletion and type checking
// (when paired with `@ts-check`).
// There are various equivalent ways to declare your Docusaurus config.
// See: https://docusaurus.io/docs/api/docusaurus-config
import {themes as prismThemes} from 'prism-react-renderer';
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'CS2-SimpleAdmin',
tagline: 'Comprehensive administration plugin for Counter-Strike 2 servers',
favicon: 'img/favicon.ico',
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
future: {
v4: true, // Improve compatibility with the upcoming Docusaurus v4
},
// Set the production url of your site here
url: 'https://cs2-simpleadmin.daffyy.dev',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'daffyyyy', // Usually your GitHub org/user name.
projectName: 'CS2-SimpleAdmin', // Usually your repo name.
onBrokenLinks: 'throw',
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: './sidebars.js',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
// editUrl:
// '',
},
blog: false, // Disable blog
theme: {
customCss: './src/css/custom.css',
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
metadata: [
{name: 'keywords', content: 'CS2, Counter-Strike 2, admin plugin, server management, bans, mutes, CounterStrikeSharp'},
{name: 'description', content: 'Comprehensive administration plugin for Counter-Strike 2 servers with ban management, multi-server support, and extensible API'},
{name: 'author', content: 'daffyyyy'},
{property: 'og:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
{property: 'og:description', content: 'Comprehensive administration plugin for CS2 servers. Manage bans, mutes, warnings, and permissions with multi-server support.'},
{property: 'og:type', content: 'website'},
{property: 'og:url', content: 'https://cs2-simpleadmin.daffyy.dev'},
{property: 'og:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
{name: 'twitter:card', content: 'summary_large_image'},
{name: 'twitter:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
{name: 'twitter:description', content: 'Comprehensive administration plugin for CS2 servers with ban management and multi-server support.'},
{name: 'twitter:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
],
colorMode: {
respectPrefersColorScheme: true,
},
navbar: {
title: 'CS2-SimpleAdmin',
logo: {
alt: 'CS2-SimpleAdmin Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'docSidebar',
sidebarId: 'userSidebar',
position: 'left',
label: 'User Guide',
},
{
type: 'docSidebar',
sidebarId: 'modulesSidebar',
position: 'left',
label: 'Modules',
},
{
type: 'docSidebar',
sidebarId: 'developerSidebar',
position: 'left',
label: 'Developer',
},
{
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Documentation',
items: [
{
label: 'User Guide',
to: '/docs/user/intro',
},
{
label: 'Modules',
to: '/docs/modules/intro',
},
{
label: 'Developer',
to: '/docs/developer/intro',
},
],
},
{
title: 'Community',
items: [
{
label: 'GitHub Issues',
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/issues',
},
{
label: 'GitHub Discussions',
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/discussions',
},
],
},
{
title: 'More',
items: [
{
label: 'GitHub',
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
},
{
label: 'Releases',
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/releases',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} CS2-SimpleAdmin. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
additionalLanguages: ['csharp', 'json', 'bash'],
},
}),
};
export default config;

File diff suppressed because it is too large Load Diff

17981
CS2-SimpleAdmin-docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
{
"name": "cs-2-simple-admin-docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/types": "3.9.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=20.0"
}
}

View File

@@ -0,0 +1,81 @@
// @ts-check
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
@type {import('@docusaurus/plugin-content-docs').SidebarsConfig}
*/
const sidebars = {
userSidebar: [
'user/intro',
{
type: 'category',
label: 'Getting Started',
items: [
'user/installation',
'user/configuration',
],
},
{
type: 'category',
label: 'Commands',
items: [
'user/commands/basebans',
'user/commands/basecomms',
'user/commands/basecommands',
'user/commands/basechat',
'user/commands/playercommands',
'user/commands/basevotes',
],
},
],
modulesSidebar: [
'modules/intro',
{
type: 'category',
label: 'Official Modules',
items: [
'modules/funcommands',
],
},
'modules/development',
],
developerSidebar: [
'developer/intro',
{
type: 'category',
label: 'API Reference',
items: [
'developer/api/overview',
'developer/api/commands',
'developer/api/menus',
'developer/api/penalties',
'developer/api/events',
'developer/api/utilities',
],
},
{
type: 'category',
label: 'Module Development',
items: [
'developer/module/getting-started',
'developer/module/best-practices',
'developer/module/examples',
],
},
'developer/architecture',
],
};
export default sidebars;

View File

@@ -0,0 +1,64 @@
import clsx from 'clsx';
import Heading from '@theme/Heading';
import styles from './styles.module.css';
const FeatureList = [
{
title: 'Comprehensive Admin Tools',
img: require('@site/static/img/index_1.png').default,
description: (
<>
Full suite of admin commands for managing players, bans, mutes, warnings,
and server settings. Everything you need to moderate your CS2 server.
</>
),
},
{
title: 'Multi-Server Support',
img: require('@site/static/img/index_2.png').default,
description: (
<>
Manage multiple servers with synchronized admin permissions and penalties.
Share bans, mutes, and admin groups across your entire server network.
</>
),
},
{
title: 'Extensible API',
img: require('@site/static/img/index_3.png').default,
description: (
<>
Build custom modules using the public API. Create your own commands,
menus, and integrate with CS2-SimpleAdmin's permission and penalty systems.
</>
),
},
];
function Feature({img, title, description}) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<img src={img} className={styles.featureSvg} alt={title} />
</div>
<div className="text--center padding-horiz--md">
<Heading as="h3">{title}</Heading>
<p>{description}</p>
</div>
</div>
);
}
export default function HomepageFeatures() {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

View File

@@ -0,0 +1,30 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #ff8c00;
--ifm-color-primary-dark: #e67e00;
--ifm-color-primary-darker: #d97700;
--ifm-color-primary-darkest: #b36200;
--ifm-color-primary-light: #ff9a1a;
--ifm-color-primary-lighter: #ffa328;
--ifm-color-primary-lightest: #ffb54d;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #ff9500;
--ifm-color-primary-dark: #e68600;
--ifm-color-primary-darker: #cc7700;
--ifm-color-primary-darkest: #b36200;
--ifm-color-primary-light: #ffa31a;
--ifm-color-primary-lighter: #ffad33;
--ifm-color-primary-lightest: #ffc266;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

View File

@@ -0,0 +1,52 @@
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import Heading from '@theme/Heading';
import styles from './index.module.css';
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<iframe
className={styles.videoBackground}
src="https://www.youtube.com/embed/4qEdIXLdxMo?autoplay=1&mute=1&loop=1&playlist=4qEdIXLdxMo&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1"
title="Background Video"
frameBorder="0"
allow="autoplay; encrypted-media"
allowFullScreen
/>
<div className={styles.videoOverlay}></div>
<div className="container">
<Heading as="h1" className="hero__title">
{siteConfig.title}
</Heading>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/user/intro">
Get Started
</Link>
</div>
</div>
</header>
);
}
export default function Home() {
const {siteConfig} = useDocusaurusContext();
return (
<Layout
title={`${siteConfig.title} - Admin Plugin for CS2`}
description="CS2-SimpleAdmin is a comprehensive administration plugin for Counter-Strike 2 servers. Manage bans, mutes, warnings, and permissions with multi-server support and extensible API.">
<HomepageHeader />
<main>
<HomepageFeatures />
</main>
</Layout>
);
}

View File

@@ -0,0 +1,97 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 8rem 0;
text-align: center;
position: relative;
overflow: hidden;
min-height: 600px;
display: flex;
align-items: center;
justify-content: center;
}
.videoBackground {
position: absolute;
top: 50%;
left: 50%;
width: 100vw;
height: 56.25vw; /* 16:9 Aspect Ratio */
min-height: 100vh;
min-width: 177.77vh; /* 16:9 Aspect Ratio */
transform: translate(-50%, -50%);
z-index: 0;
pointer-events: none;
}
.videoOverlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 1;
}
.heroBanner :global(.container) {
position: relative;
z-index: 999 !important;
}
.heroBanner :global(.hero__title) {
color: white !important;
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9);
font-size: 3.5rem;
font-weight: bold;
margin-bottom: 1rem;
}
.heroBanner :global(.hero__subtitle) {
color: white !important;
text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.9);
font-size: 1.5rem;
margin-bottom: 2rem;
}
.heroBanner .buttons {
position: relative;
z-index: 10;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 4rem 2rem;
min-height: 400px;
}
.videoBackground {
width: 200vw;
height: 112.5vw;
}
.heroBanner :global(.hero__title) {
font-size: 2rem;
}
.heroBanner :global(.hero__subtitle) {
font-size: 1.2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 10;
}
.buttons :global(.button) {
position: relative;
z-index: 10;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}

View File

@@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -233,6 +233,36 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
}
}
public void RegisterMenu(string categoryId, string menuId, string menuName,
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null)
{
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
return;
MenuBuilder BuilderFactory(CCSPlayerController player)
{
var context = new MenuContext(categoryId, menuId, menuName, permission, commandName);
if (menuFactory(player, context) is not MenuBuilder menuBuilder)
throw new InvalidOperationException("Menu factory must return MenuBuilder");
// Dodaj automatyczną obsługę przycisku 'Wróć'
menuBuilder.WithBackAction(p =>
{
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
{
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
}
else
{
Menus.MenuManager.Instance.OpenMainMenu(p);
}
});
return menuBuilder;
}
}
public void UnregisterMenu(string categoryId, string menuId)
{
@@ -257,6 +287,11 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
return builder;
}
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
{
return CreateMenuWithBack(context.MenuTitle, context.CategoryId, player);
}
public List<CCSPlayerController> GetValidPlayers()
{
return Helper.GetValidPlayers();
@@ -283,6 +318,12 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
return menu;
}
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
{
return CreateMenuWithPlayers(context.MenuTitle, context.CategoryId, admin, filter, onSelect);
}
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
string? permission = null)
{

View File

@@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
public override string ModuleAuthor => "daffyy & Dliix66";
public override string ModuleVersion => "1.7.8-beta-2";
public override string ModuleVersion => "1.7.8-beta-3";
public override void Load(bool hotReload)
{

View File

@@ -1 +1 @@
1.7.8-beta-2
1.7.8-beta-3

View File

@@ -151,6 +151,18 @@ public interface ICS2_SimpleAdminApi
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null);
/// <summary>
/// Registers a menu in a category with automatic context passing.
/// RECOMMENDED: Use this overload to eliminate duplication of categoryId and menuName in factory methods.
/// </summary>
/// <param name="categoryId">The category to add this menu to.</param>
/// <param name="menuId">Unique identifier for the menu.</param>
/// <param name="menuName">Display name of the menu.</param>
/// <param name="menuFactory">Factory function that receives player and menu context.</param>
/// <param name="permission">Required permission to access this menu (optional).</param>
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null);
/// <summary>
/// Unregisters a menu from a category.
/// </summary>
@@ -161,11 +173,29 @@ public interface ICS2_SimpleAdminApi
/// </summary>
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player);
/// <summary>
/// Creates a menu with an automatic back button using menu context.
/// RECOMMENDED: Use this overload when calling from a context-aware menu factory to avoid title/category duplication.
/// </summary>
/// <param name="context">Menu context containing title and category information.</param>
/// <param name="player">The player who will see the menu.</param>
object CreateMenuWithBack(MenuContext context, CCSPlayerController player);
/// <summary>
/// Creates a menu with a list of players with filter and action.
/// </summary>
object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect);
/// <summary>
/// Creates a menu with a list of players using menu context.
/// RECOMMENDED: Use this overload when calling from a context-aware menu factory to avoid title/category duplication.
/// </summary>
/// <param name="context">Menu context containing title and category information.</param>
/// <param name="admin">The admin player opening the menu.</param>
/// <param name="filter">Filter function to determine which players to show.</param>
/// <param name="onSelect">Action to execute when a player is selected.</param>
object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect);
/// <summary>
/// Adds an option to the menu (extension method helper).
/// </summary>

View File

@@ -0,0 +1,48 @@
using CounterStrikeSharp.API.Core;
namespace CS2_SimpleAdminApi;
/// <summary>
/// Provides contextual information about a menu to its factory function.
/// This eliminates the need to duplicate category IDs and menu titles when creating menus.
/// </summary>
public class MenuContext
{
/// <summary>
/// The category ID this menu belongs to (e.g., "fun", "players").
/// Used for automatic "Back" button navigation.
/// </summary>
public string CategoryId { get; init; } = string.Empty;
/// <summary>
/// The unique identifier for this menu within its category.
/// </summary>
public string MenuId { get; init; } = string.Empty;
/// <summary>
/// The display title of the menu (from registration).
/// </summary>
public string MenuTitle { get; init; } = string.Empty;
/// <summary>
/// The permission required to access this menu (if any).
/// </summary>
public string? Permission { get; init; }
/// <summary>
/// The command name for permission override checking (if any).
/// </summary>
public string? CommandName { get; init; }
/// <summary>
/// Creates a new MenuContext with the specified values.
/// </summary>
public MenuContext(string categoryId, string menuId, string menuTitle, string? permission = null, string? commandName = null)
{
CategoryId = categoryId;
MenuId = menuId;
MenuTitle = menuTitle;
Permission = permission;
CommandName = commandName;
}
}

View File

@@ -1,59 +0,0 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin_CleanModule;
public class CS2_SimpleAdmin_CleanModule: BasePlugin
{
public override string ModuleName => "[CS2-SimpleAdmin] Clean module";
public override string ModuleDescription => "Module allows you to remove all weapons lying on the ground";
public override string ModuleVersion => "v1.0.0";
public override string ModuleAuthor => "daffyy";
private static ICS2_SimpleAdminApi? _sharedApi;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
public override void OnAllPluginsLoaded(bool hotReload)
{
_sharedApi = _pluginCapability.Get();
if (_sharedApi == null)
{
Logger.LogError("CS2-SimpleAdmin SharedApi not found");
Unload(false);
}
}
[ConsoleCommand("css_clean")]
[ConsoleCommand("css_clear")]
[RequiresPermissions("@css/cheat")]
public void OnCleanCommand(CCSPlayerController? caller, CommandInfo commandInfo)
{
var weapons = Utilities.FindAllEntitiesByDesignerName<CCSWeaponBaseGun>("weapon_");
var defusers = Utilities.FindAllEntitiesByDesignerName<CSceneEntity>("item_cutters");
foreach (var weapon in weapons)
{
if (!weapon.IsValid || weapon.State != CSWeaponState_t.WEAPON_NOT_CARRIED)
continue;
weapon.Remove();
}
foreach (var defuser in defusers)
{
if (!defuser.IsValid || defuser.OwnerEntity.Value != null)
continue;
defuser.Remove();
}
_sharedApi?.LogCommand(caller, commandInfo);
}
}

View File

@@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CS2_SimpleAdmin_CleanModule</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.266" />
</ItemGroup>
<ItemGroup>
<Reference Include="CS2-SimpleAdminApi">
<HintPath>CS2-SimpleAdminApi.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -1,16 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-SimpleAdmin_CleanModule", "CS2-SimpleAdmin_CleanModule.csproj", "{D940F3E9-0E3F-467A-B336-149E3A624FB6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -4,173 +4,532 @@ using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin_ExampleModule;
/// <summary>
/// COMPLETE EXAMPLE MODULE FOR CS2-SIMPLEADMIN
///
/// This module demonstrates:
/// 1. ✅ Getting CS2-SimpleAdmin API via capability system
/// 2. ✅ Using API methods (GetServerId, GetConnectionString, IssuePenalty)
/// 3. ✅ Listening to events (OnPlayerPenaltied, OnPlayerPenaltiedAdded)
/// 4. ✅ Registering console commands
/// 5. ✅ Creating menu categories and menu items
/// 6. ✅ Using NEW MenuContext API to eliminate code duplication
/// 7. ✅ Proper cleanup on module unload
///
/// Study this file to learn how to create your own CS2-SimpleAdmin modules!
/// </summary>
public class CS2_SimpleAdmin_ExampleModule: BasePlugin
{
public override string ModuleName => "[CS2-SimpleAdmin] Example module";
public override string ModuleVersion => "v1.0.1";
public override string ModuleAuthor => "daffyy";
// ========================================
// PLUGIN METADATA
// ========================================
public override string ModuleName => "[CS2-SimpleAdmin] Example Module";
public override string ModuleVersion => "v1.1.0";
public override string ModuleAuthor => "daffyy & Example Contributors";
// ========================================
// PRIVATE FIELDS
// ========================================
/// <summary>
/// Server ID from SimpleAdmin (null for single-server mode)
/// Useful for multi-server setups to identify which server this is
/// </summary>
private int? _serverId;
/// <summary>
/// Database connection string from SimpleAdmin
/// Use this if your module needs direct database access
/// </summary>
private string _dbConnectionString = string.Empty;
/// <summary>
/// Reference to CS2-SimpleAdmin API
/// Use this to call API methods and register menus
/// </summary>
private static ICS2_SimpleAdminApi? _sharedApi;
/// <summary>
/// Capability for getting the SimpleAdmin API
/// This is the recommended way to get access to another plugin's API
/// </summary>
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
/// <summary>
/// Flag to prevent duplicate menu registration
/// Important for hot reload scenarios
/// </summary>
private bool _menusRegistered = false;
// ========================================
// PLUGIN LIFECYCLE
// ========================================
/// <summary>
/// Called when all plugins are loaded (including hot reload)
/// BEST PRACTICE: Use this instead of Load() to ensure all dependencies are available
/// </summary>
public override void OnAllPluginsLoaded(bool hotReload)
{
_sharedApi = _pluginCapability.Get();
if (_sharedApi == null)
// STEP 1: Get the SimpleAdmin API using capability system
try
{
Logger.LogError("CS2-SimpleAdmin SharedApi not found");
_sharedApi = _pluginCapability.Get();
}
catch (Exception)
{
Logger.LogError("CS2-SimpleAdmin API not found - make sure CS2-SimpleAdmin is loaded!");
Unload(false);
return;
}
// STEP 2: Get server information from SimpleAdmin
_serverId = _sharedApi.GetServerId();
_dbConnectionString = _sharedApi.GetConnectionString();
Logger.LogInformation($"{ModuleName} started with serverId {_serverId}");
_sharedApi.OnPlayerPenaltied += OnPlayerPenaltied;
_sharedApi.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
// STEP 3: Subscribe to SimpleAdmin events
// These events fire when penalties (ban, kick, mute, etc.) are issued
_sharedApi.OnPlayerPenaltied += OnPlayerPenaltied; // When penalty is issued to ONLINE player
_sharedApi.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded; // When penalty is issued to OFFLINE player
// STEP 4: Register menus
// BEST PRACTICE: Wait for SimpleAdmin to be ready before registering menus
// This handles both normal load and hot reload scenarios
_sharedApi.OnSimpleAdminReady += RegisterExampleMenus;
RegisterExampleMenus(); // Fallback for hot reload case
}
/// <summary>
/// Called when the plugin is being unloaded
/// BEST PRACTICE: Always clean up your registrations to prevent memory leaks
/// </summary>
public override void Unload(bool hotReload)
{
if (_sharedApi == null) return;
// Unsubscribe from events
_sharedApi.OnPlayerPenaltied -= OnPlayerPenaltied;
_sharedApi.OnPlayerPenaltiedAdded -= OnPlayerPenaltiedAdded;
_sharedApi.OnSimpleAdminReady -= RegisterExampleMenus;
// Unregister menus
_sharedApi.UnregisterMenu("example", "simple_action");
_sharedApi.UnregisterMenu("example", "player_selection");
_sharedApi.UnregisterMenu("example", "nested_menu");
_sharedApi.UnregisterMenu("example", "test_command");
Logger.LogInformation($"{ModuleName} unloaded successfully");
}
// ========================================
// MENU REGISTRATION
// ========================================
/// <summary>
/// Registers all example menus in the admin menu
/// BEST PRACTICE: Use this pattern to prevent duplicate registrations
/// </summary>
private void RegisterExampleMenus()
{
if (_sharedApi == null || _menusRegistered) return;
try
{
// STEP 1: Register a menu category
// This creates a new section in the main admin menu
// Permission: @css/generic means all admins can see it
_sharedApi.RegisterMenuCategory(
"example", // Category ID (unique identifier)
"Example Features", // Display name in admin menu
"@css/generic" // Required permission
);
// STEP 2: Register individual menu items in the category
// 🆕 NEW: These use MenuContext API - factory receives (admin, context) parameters
// Example 1: Simple menu with options
_sharedApi.RegisterMenu(
"example", // Category ID
"simple_action", // Menu ID (unique within category)
"Simple Actions", // Display name
CreateSimpleActionMenu, // Factory method
"@css/generic" // Required permission
);
// Example 2: Player selection menu
_sharedApi.RegisterMenu(
"example",
"player_selection",
"Select Player",
CreatePlayerSelectionMenu,
"@css/kick" // Requires kick permission
);
// Example 3: Nested menu (Player → Value)
_sharedApi.RegisterMenu(
"example",
"nested_menu",
"Give Credits",
CreateGiveCreditsMenu,
"@css/generic"
);
// Example 4: Menu with permission override support
_sharedApi.RegisterMenu(
"example",
"test_command",
"Test Command",
CreateTestCommandMenu,
"@css/root", // Default permission
"css_test" // Command name for override checking
);
_menusRegistered = true;
Logger.LogInformation("Example menus registered successfully!");
}
catch (Exception ex)
{
Logger.LogError($"Failed to register example menus: {ex.Message}");
}
}
// ========================================
// MENU FACTORY METHODS
// ========================================
/// <summary>
/// PATTERN 1: Simple menu with static options
/// 🆕 NEW: Uses MenuContext to eliminate duplication!
/// </summary>
private object CreateSimpleActionMenu(CCSPlayerController admin, MenuContext context)
{
// Create menu with automatic back button
// 🆕 NEW: Use context instead of repeating title and category!
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
// Add menu options
_sharedApi.AddMenuOption(menu, "Print Server Info", player =>
{
player.PrintToChat($"Server ID: {_serverId}");
player.PrintToChat($"Server IP: {_sharedApi?.GetServerAddress()}");
});
_sharedApi.AddMenuOption(menu, "Get My Stats", player =>
{
try
{
var playerInfo = _sharedApi?.GetPlayerInfo(player);
player.PrintToChat($"Total Bans: {playerInfo?.TotalBans ?? 0}");
player.PrintToChat($"Total Kicks: {playerInfo?.TotalKicks ?? 0}");
player.PrintToChat($"Total Warns: {playerInfo?.TotalWarns ?? 0}");
}
catch (Exception ex)
{
Logger.LogError($"Error getting player info: {ex.Message}");
player.PrintToChat("Error retrieving your stats");
}
});
_sharedApi.AddMenuOption(menu, "Check Silent Mode", player =>
{
var isSilent = _sharedApi?.IsAdminSilent(player) ?? false;
player.PrintToChat($"Silent mode: {(isSilent ? "ON" : "OFF")}");
});
return menu;
}
/// <summary>
/// PATTERN 2: Player selection menu with immediate action
/// 🆕 NEW: Uses MenuContext API - cleaner and less error-prone!
/// </summary>
private object CreatePlayerSelectionMenu(CCSPlayerController admin, MenuContext context)
{
// 🆕 NEW: CreateMenuWithPlayers now uses context instead of title/category
return _sharedApi!.CreateMenuWithPlayers(
context, // ← Contains title and category automatically!
admin,
// Filter: Only show valid players that admin can target
player => player.IsValid && admin.CanTarget(player),
// Action: What happens when a player is selected
(adminPlayer, targetPlayer) =>
{
adminPlayer.PrintToChat($"You selected: {targetPlayer.PlayerName}");
// Example: Show player info
try
{
var playerInfo = _sharedApi?.GetPlayerInfo(targetPlayer);
adminPlayer.PrintToChat($"{targetPlayer.PlayerName} - Bans: {playerInfo?.TotalBans}, Warns: {playerInfo?.TotalWarns}");
}
catch (Exception ex)
{
Logger.LogWarning($"Could not get info for {targetPlayer.PlayerName}: {ex.Message}");
}
}
);
}
/// <summary>
/// PATTERN 3: Nested menu (Player → Value selection)
/// 🆕 NEW: First level menu uses MenuContext
/// </summary>
private object CreateGiveCreditsMenu(CCSPlayerController admin, MenuContext context)
{
// Create menu with back button
// 🆕 NEW: Uses context - no more repeating title/category!
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
// Get all valid, targetable players
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26
? player.PlayerName[..26]
: player.PlayerName;
// AddSubMenu automatically adds a "Back" button to the submenu
_sharedApi.AddSubMenu(menu, playerName, p => CreateCreditAmountMenu(admin, player));
}
return menu;
}
/// <summary>
/// Submenu for selecting credit amount
/// Note: Submenus create dynamic titles, so they don't receive MenuContext
/// </summary>
private object CreateCreditAmountMenu(CCSPlayerController admin, CCSPlayerController target)
{
// Dynamic title includes target's name
var menu = _sharedApi!.CreateMenuWithBack(
$"Credits for {target.PlayerName}",
"example", // Category for back navigation
admin
);
// Predefined credit amounts
var creditAmounts = new[] { 100, 500, 1000, 5000, 10000 };
foreach (var amount in creditAmounts)
{
_sharedApi.AddMenuOption(menu, $"{amount} Credits", _ =>
{
// BEST PRACTICE: Always validate player is still valid before action
if (target.IsValid)
{
Server.PrintToChatAll($"{admin.PlayerName} gave {amount} credits to {target.PlayerName}");
Logger.LogInformation($"Admin {admin.PlayerName} gave {amount} credits to {target.PlayerName}");
}
else
{
admin.PrintToChat("Player is no longer available");
}
});
}
return menu;
}
/// <summary>
/// Example menu with permission override support
/// </summary>
private object CreateTestCommandMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
// You can access context properties if needed
_sharedApi.AddMenuOption(menu, "Show Context Info", player =>
{
player.PrintToChat($"Category: {context.CategoryId}");
player.PrintToChat($"Menu ID: {context.MenuId}");
player.PrintToChat($"Title: {context.MenuTitle}");
player.PrintToChat($"Permission: {context.Permission}");
player.PrintToChat($"Command: {context.CommandName}");
});
_sharedApi.AddMenuOption(menu, "Test Action", player =>
{
player.PrintToChat("Test action executed!");
});
return menu;
}
// ========================================
// CONSOLE COMMANDS
// ========================================
/// <summary>
/// Example command: Kick yourself
/// Demonstrates using IssuePenalty API for online players
/// </summary>
[ConsoleCommand("css_kickme")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void KickMeCommand(CCSPlayerController? caller, CommandInfo commandInfo)
{
if (caller == null) return;
_sharedApi?.IssuePenalty(caller, null, PenaltyType.Kick, "test");
// Issue a kick penalty to the caller
// Parameters: player, admin (null = console), penaltyType, reason
_sharedApi?.IssuePenalty(caller, null, PenaltyType.Kick, "You kicked yourself!");
}
[ConsoleCommand("css_serverAddress")]
/// <summary>
/// Example command: Get server address
/// Demonstrates using GetServerAddress API
/// </summary>
[ConsoleCommand("css_serveraddress")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void ServerAddressCommand(CCSPlayerController? caller, CommandInfo commandInfo)
{
commandInfo.ReplyToCommand($"Our server IP: {_sharedApi?.GetServerAddress()}");
commandInfo.ReplyToCommand($"Server IP: {_sharedApi?.GetServerAddress()}");
}
[ConsoleCommand("css_getMyInfo")]
/// <summary>
/// Example command: Get player statistics
/// Demonstrates using GetPlayerInfo API
/// </summary>
[ConsoleCommand("css_getmyinfo")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void GetMyInfoCommand(CCSPlayerController? caller, CommandInfo commandInfo)
{
if (caller == null) return;
try
{
var playerInfo = _sharedApi?.GetPlayerInfo(caller);
commandInfo.ReplyToCommand($"Your total bans: {playerInfo?.TotalBans}");
commandInfo.ReplyToCommand($"Your total gags: {playerInfo?.TotalGags}");
commandInfo.ReplyToCommand($"Your total mutes: {playerInfo?.TotalMutes}");
commandInfo.ReplyToCommand($"Your total silences: {playerInfo?.SteamId}");
commandInfo.ReplyToCommand($"Your Statistics:");
commandInfo.ReplyToCommand($" Total Bans: {playerInfo?.TotalBans ?? 0}");
commandInfo.ReplyToCommand($" Total Kicks: {playerInfo?.TotalKicks ?? 0}");
commandInfo.ReplyToCommand($" Total Gags: {playerInfo?.TotalGags ?? 0}");
commandInfo.ReplyToCommand($" Total Mutes: {playerInfo?.TotalMutes ?? 0}");
commandInfo.ReplyToCommand($" Total Warns: {playerInfo?.TotalWarns ?? 0}");
commandInfo.ReplyToCommand($" SteamID: {playerInfo?.SteamId}");
}
catch (Exception ex)
{
Logger.LogError($"Error in GetMyInfoCommand: {ex.Message}");
commandInfo.ReplyToCommand("Error retrieving your information");
}
}
/// <summary>
/// Example command: Add ban to offline player
/// Demonstrates using IssuePenalty API with SteamID for offline players
/// SERVER ONLY - dangerous command!
/// </summary>
[ConsoleCommand("css_testaddban")]
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo commandInfo)
{
_sharedApi?.IssuePenalty(new SteamID(76561197960287930), null, PenaltyType.Ban, "My super reason", 10);
// Issue a ban to an offline player by SteamID
// Parameters: steamID, admin (null = console), penaltyType, reason, duration (minutes)
_sharedApi?.IssuePenalty(
new SteamID(76561197960287930), // Target SteamID
null, // Admin (null = console)
PenaltyType.Ban, // Penalty type
"Test ban from example module", // Reason
10 // Duration (10 minutes)
);
Logger.LogInformation("Test ban issued via API");
}
private void OnPlayerPenaltied(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType,
string reason, int duration, int? penaltyId, int? serverId)
// ========================================
// EVENT HANDLERS
// ========================================
/// <summary>
/// Called when a penalty is issued to an ONLINE player
/// Use this to react to bans/kicks/mutes happening in real-time
/// </summary>
private void OnPlayerPenaltied(
PlayerInfo player, // The player who received the penalty
PlayerInfo? admin, // The admin who issued it (null = console)
PenaltyType penaltyType,// Type of penalty (Ban, Kick, Mute, etc.)
string reason, // Reason for the penalty
int duration, // Duration in minutes (-1 = permanent)
int? penaltyId, // Database ID of the penalty
int? serverId // Server ID where it was issued
)
{
// Example: Announce bans to all players
if (penaltyType == PenaltyType.Ban)
{
Server.PrintToChatAll($"{player.Name} is a dog");
var adminName = admin?.Name ?? "Console";
var durationText = (duration == -1 || duration == 0) ? "permanently" : $"for {duration} minutes";
Server.PrintToChatAll($"{player.Name} was banned {durationText} by {adminName}");
}
// Log all penalties
var adminNameLog = admin?.Name ?? "Console";
switch (penaltyType)
{
case PenaltyType.Ban:
Logger.LogInformation($"Ban issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m, Reason: {reason})");
break;
case PenaltyType.Kick:
Logger.LogInformation($"Kick issued to {player.Name} by {adminNameLog} (Reason: {reason})");
break;
case PenaltyType.Gag:
Logger.LogInformation($"Gag issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m)");
break;
case PenaltyType.Mute:
Logger.LogInformation($"Mute issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m)");
break;
case PenaltyType.Silence:
Logger.LogInformation($"Silence issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m)");
break;
case PenaltyType.Warn:
Logger.LogInformation($"Warning issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Reason: {reason})");
break;
}
}
/// <summary>
/// Called when a penalty is issued to an OFFLINE player
/// Use this to react to bans/mutes added via SteamID (player not on server)
/// </summary>
private void OnPlayerPenaltiedAdded(
SteamID steamId, // SteamID of the penalized player
PlayerInfo? admin, // The admin who issued it (null = console)
PenaltyType penaltyType,// Type of penalty
string reason, // Reason for the penalty
int duration, // Duration in minutes (-1 = permanent)
int? penaltyId, // Database ID of the penalty
int? serverId // Server ID where it was issued
)
{
// Log offline penalty additions
var adminName = admin?.Name ?? "Console";
switch (penaltyType)
{
case PenaltyType.Ban:
{
Logger.LogInformation("Ban issued");
Logger.LogInformation($"Id = {penaltyId}");
Logger.LogInformation($"Ban added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m, Reason: {reason})");
break;
}
case PenaltyType.Kick:
{
Logger.LogInformation("Kick issued");
break;
}
case PenaltyType.Gag:
{
Logger.LogInformation("Gag issued");
Logger.LogInformation($"Id = {penaltyId}");
Logger.LogInformation($"Gag added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m)");
break;
}
case PenaltyType.Mute:
{
Logger.LogInformation("Mute issued");
Logger.LogInformation($"Mute added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m)");
break;
}
case PenaltyType.Silence:
{
Logger.LogInformation("Silence issued");
Logger.LogInformation($"Silence added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m)");
break;
}
case PenaltyType.Warn:
{
Logger.LogInformation("Warn issued");
Logger.LogInformation($"Warning added for offline player {steamId} by {adminName} (ID: {penaltyId}, Reason: {reason})");
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
Console.WriteLine(player.Name);
Console.WriteLine(admin?.Name ?? "Console");
Console.WriteLine(player.SteamId.ToString());
Console.WriteLine(reason);
}
private void OnPlayerPenaltiedAdded(SteamID steamId, PlayerInfo? admin, PenaltyType penaltyType,
string reason, int duration, int? penaltyId, int? serverId)
{
switch (penaltyType)
{
case PenaltyType.Ban:
{
Logger.LogInformation("Ban added");
Logger.LogInformation($"Id = {penaltyId}");
break;
}
case PenaltyType.Kick:
{
Logger.LogInformation("Kick added");
break;
}
case PenaltyType.Gag:
{
Logger.LogInformation("Gag added");
Logger.LogInformation($"Id = {penaltyId}");
break;
}
case PenaltyType.Mute:
{
Logger.LogInformation("Mute added");
break;
}
case PenaltyType.Silence:
{
Logger.LogInformation("Silence added");
break;
}
case PenaltyType.Warn:
{
Logger.LogInformation("Warn added");
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
Console.WriteLine(admin?.Name ?? "Console");
Console.WriteLine(steamId.ToString());
Console.WriteLine(reason);
}
}

View File

@@ -370,7 +370,7 @@ public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Con
// Register menus with command names for permission override support
// Server admins can override default permissions via CounterStrikeSharp admin system
// Example: If "css_god" is overridden to "@css/vip", only VIPs will see the God Mode menu
// Example: If "css_god" is overdden to "@css/vip", only VIPs will see the God Mode menu
if (Config.GodCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "god",

View File

@@ -27,6 +27,35 @@ namespace CS2_SimpleAdmin_FunCommands;
/// "css_god"); // Command name for override checking
///
/// This means developers don't need to manually check permissions in their menu factory methods!
///
/// MENU CONTEXT API (NEW!):
/// ========================
/// Menu factory methods now receive a MenuContext parameter that contains:
/// - CategoryId: The category this menu belongs to (e.g., "fun")
/// - MenuId: The unique identifier for this menu (e.g., "god")
/// - MenuTitle: The display title from registration (e.g., "God Mode")
/// - Permission: The default permission (e.g., "@css/cheats")
/// - CommandName: The command name for override checking (e.g., "css_god")
///
/// This eliminates duplication when creating menus - you no longer need to repeat
/// the title and category in both RegisterMenu() and CreateMenuWithPlayers()!
///
/// Before (old API):
/// private object CreateGodModeMenu(CCSPlayerController admin)
/// {
/// return _sharedApi.CreateMenuWithPlayers(
/// "God Mode", // ← Duplicated from RegisterMenu
/// "fun", // ← Duplicated from RegisterMenu
/// admin, filter, action);
/// }
///
/// After (new API with MenuContext):
/// private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
/// {
/// return _sharedApi.CreateMenuWithPlayers(
/// context, // ← Contains both title and category automatically!
/// admin, filter, action);
/// }
/// </summary>
public partial class CS2_SimpleAdmin_FunCommands
{
@@ -39,22 +68,21 @@ public partial class CS2_SimpleAdmin_FunCommands
/// <summary>
/// Creates a simple player selection menu for god mode.
/// PATTERN: CreateMenuWithPlayers with method reference
/// IMPROVED: Uses MenuContext to eliminate duplication of title and category
/// </summary>
private object CreateGodModeMenu(CCSPlayerController admin)
private object CreateGodModeMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_god"] ?? "God Mode", // Menu title from translation
"fun", // Category ID (for back button navigation)
context, // ← Context contains title & category automatically!
admin, // Admin opening the menu
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player), // Filter: only alive, targetable players
God); // Action to execute (method reference)
}
private object CreateNoClipMenu(CCSPlayerController admin)
private object CreateNoClipMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_noclip"] ?? "No Clip",
"fun",
context,
admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
NoClip);
@@ -63,13 +91,13 @@ public partial class CS2_SimpleAdmin_FunCommands
/// <summary>
/// Creates a player selection menu for respawn command.
/// PATTERN: CreateMenuWithPlayers with method reference
/// IMPROVED: Uses MenuContext to eliminate duplication
/// </summary>
private object CreateRespawnMenu(CCSPlayerController admin)
private object CreateRespawnMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_respawn"] ?? "Respawn", // Menu title from translation
"fun", // Category ID
admin, // Admin
context,
admin,
admin.CanTarget, // Filter: only targetable players (no LifeState check - can respawn dead players)
Respawn); // Use the Respawn method which includes death position teleport
}
@@ -83,12 +111,12 @@ public partial class CS2_SimpleAdmin_FunCommands
/// <summary>
/// Creates a nested menu: Player selection → Weapon selection.
/// PATTERN: CreateMenuWithBack + foreach + AddSubMenu
/// IMPROVED: Uses MenuContext - no more duplication of title/category!
/// </summary>
private object CreateGiveWeaponMenu(CCSPlayerController admin)
private object CreateGiveWeaponMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_give"] ?? "Give Weapon",
"fun",
context, // ← Context contains title & category!
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
@@ -134,11 +162,10 @@ public partial class CS2_SimpleAdmin_FunCommands
return weaponMenu;
}
private object CreateStripWeaponsMenu(CCSPlayerController admin)
private object CreateStripWeaponsMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
"fun",
context,
admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
(adminPlayer, targetPlayer) =>
@@ -148,11 +175,10 @@ public partial class CS2_SimpleAdmin_FunCommands
});
}
private object CreateFreezeMenu(CCSPlayerController admin)
private object CreateFreezeMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_freeze"] ?? "Freeze",
"fun",
context,
admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
(adminPlayer, targetPlayer) => { Freeze(adminPlayer, targetPlayer, -1); });
@@ -161,13 +187,12 @@ public partial class CS2_SimpleAdmin_FunCommands
/// <summary>
/// Creates a nested menu for setting player HP with predefined values.
/// PATTERN: Same as Give Weapon (player selection → value selection)
/// This is a common pattern you'll use frequently!
/// IMPROVED: Uses MenuContext - cleaner and less error-prone!
/// </summary>
private object CreateSetHpMenu(CCSPlayerController admin)
private object CreateSetHpMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_hp"] ?? "Set HP",
"fun",
context,
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
@@ -212,11 +237,10 @@ public partial class CS2_SimpleAdmin_FunCommands
return hpSelectionMenu;
}
private object CreateSetSpeedMenu(CCSPlayerController admin)
private object CreateSetSpeedMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_speed"] ?? "Set Speed",
"fun",
context,
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
@@ -273,11 +297,10 @@ public partial class CS2_SimpleAdmin_FunCommands
return speedSelectionMenu;
}
private object CreateSetGravityMenu(CCSPlayerController admin)
private object CreateSetGravityMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
"fun",
context,
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
@@ -324,11 +347,10 @@ public partial class CS2_SimpleAdmin_FunCommands
return gravitySelectionMenu;
}
private object CreateSetMoneyMenu(CCSPlayerController admin)
private object CreateSetMoneyMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_money"] ?? "Set Money",
"fun",
context,
admin);
var players = _sharedApi.GetValidPlayers().Where(p => admin.CanTarget(p));
@@ -366,11 +388,10 @@ public partial class CS2_SimpleAdmin_FunCommands
return moneySelectionMenu;
}
private object CreateSetResizeMenu(CCSPlayerController admin)
private object CreateSetResizeMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_resize"] ?? "Resize Player",
"fun",
context,
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));

View File

@@ -168,23 +168,35 @@ private void God(CCSPlayerController? caller, CCSPlayerController player)
**Key Concepts Demonstrated:**
#### Simple Player Selection Menu
#### Simple Player Selection Menu (NEW API with MenuContext!)
```csharp
private object CreateGodModeMenu(CCSPlayerController admin)
// 🆕 NEW: Factory receives MenuContext - no more duplication!
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
{
// ✅ BEST PRACTICE: Use CreateMenuWithPlayers for simple player selection
return _sharedApi!.CreateMenuWithPlayers("God Mode", "fun", admin,
// ✅ BEST PRACTICE: Use context instead of repeating title and category
return _sharedApi!.CreateMenuWithPlayers(
context, // ← Contains "God Mode" title and "fun" category automatically!
admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
God); // Direct method reference
}
```
#### Nested Menu with Value Selection
**Why MenuContext is better:**
-**Before:** You had to type `"God Mode"` and `"fun"` twice (in `RegisterMenu` and `CreateMenuWithPlayers`)
-**After:** Context contains these values automatically - no duplication!
- ✅ Less error-prone (can't accidentally use wrong category)
- ✅ Easier to refactor (change title in one place)
#### Nested Menu with Value Selection (NEW API!)
```csharp
private object CreateSetHpMenu(CCSPlayerController admin)
// 🆕 NEW: Uses MenuContext to eliminate duplication
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
{
// ✅ BEST PRACTICE: Use CreateMenuWithBack for menus with back button
var menu = _sharedApi!.CreateMenuWithBack("Set HP", "fun", admin);
// ✅ BEST PRACTICE: Use context instead of manual title/category
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
@@ -200,6 +212,7 @@ private object CreateSetHpMenu(CCSPlayerController admin)
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
// Note: Submenus don't receive context - they create their own titles dynamically
var hpMenu = _sharedApi!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "fun", admin);
var hpValues = new[] { 1, 10, 25, 50, 100, 200, 500, 999 };
@@ -221,6 +234,31 @@ private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerControl
}
```
**Comparison: Old vs New API**
```csharp
// ❌ OLD API - lots of duplication
_sharedApi.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats", "css_god");
private object CreateGodModeMenu(CCSPlayerController admin)
{
return _sharedApi.CreateMenuWithPlayers(
"God Mode", // ← Repeated from RegisterMenu
"fun", // ← Repeated from RegisterMenu
admin, filter, action);
}
// ✅ NEW API - no duplication!
_sharedApi.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats", "css_god");
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
{
return _sharedApi.CreateMenuWithPlayers(
context, // ← Contains title and category automatically!
admin, filter, action);
}
```
### 6. Translations
**Key Concept:** Module-specific translations

View File

@@ -6,12 +6,12 @@ This guide explains how to create modules for CS2-SimpleAdmin with custom comman
## 📖 Table of Contents
1. [Quick Start](#quick-start)
2. [Learning Resources](#learning-resources)
3. [API Methods Reference](#api-methods)
4. [Menu Patterns](#menu-patterns)
5. [Best Practices](#best-practices)
6. [Common Patterns](#common-patterns)
1. [Quick Start](#-quick-start)
2. [Learning Resources](#-learning-resources)
3. [MenuContext API (New!)](#-menucontext-api-new)
4. [API Methods Reference](#-api-methods-reference)
5. [Menu Patterns](#-menu-patterns)
6. [Best Practices](#best-practices)
7. [Troubleshooting](#troubleshooting)
## 🚀 Quick Start
@@ -44,7 +44,7 @@ YourModule/
└── ru.json
```
### Step 3: Minimal Working Example
### Step 3: Minimal Working Example (NEW MenuContext API!)
```csharp
using CounterStrikeSharp.API.Core;
@@ -78,14 +78,17 @@ public class MyModule : BasePlugin
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
// 2. Register menu items in the category
// 🆕 NEW: Use MenuContext-aware overload (no duplication!)
_api.RegisterMenu("mymodule", "action1", "Action 1", CreateAction1Menu, "@css/generic");
_api.RegisterMenu("mymodule", "action2", "Action 2", CreateAction2Menu, "@css/kick");
}
private object CreateAction1Menu(CCSPlayerController admin)
// 🆕 NEW: Factory now receives MenuContext parameter
private object CreateAction1Menu(CCSPlayerController admin, MenuContext context)
{
// Create a menu with automatic back button
var menu = _api!.CreateMenuWithBack("Action 1 Menu", "mymodule", admin);
// Use context instead of repeating title and category!
var menu = _api!.CreateMenuWithBack(context, admin);
// Add menu options
_api.AddMenuOption(menu, "Option 1", player =>
@@ -101,10 +104,13 @@ public class MyModule : BasePlugin
return menu;
}
private object CreateAction2Menu(CCSPlayerController admin)
// 🆕 NEW: MenuContext eliminates duplication here too!
private object CreateAction2Menu(CCSPlayerController admin, MenuContext context)
{
// Use the built-in player selection menu
return _api!.CreateMenuWithPlayers("Select Player", "mymodule", admin,
// Use the built-in player selection menu with context
return _api!.CreateMenuWithPlayers(
context, // ← Contains title & category automatically!
admin,
player => player.IsValid && admin.CanTarget(player),
(adminPlayer, targetPlayer) =>
{
@@ -158,16 +164,15 @@ public class MyModule : BasePlugin
The FunCommands module demonstrates **3 essential menu patterns** you'll use in every module:
### Pattern 1: Simple Player Selection
### Pattern 1: Simple Player Selection (NEW MenuContext API!)
**When to use:** Select a player and immediately execute an action
```csharp
// Example: God Mode menu
private object CreateGodModeMenu(CCSPlayerController admin)
// 🆕 NEW: Factory receives MenuContext - eliminates duplication!
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
{
return _api.CreateMenuWithPlayers(
"God Mode", // Title
"yourmodule", // Category ID
context, // ← Contains title & category automatically!
admin, // Admin
player => player.IsValid && admin.CanTarget(player), // Filter
(adminPlayer, target) => // Action
@@ -178,16 +183,20 @@ private object CreateGodModeMenu(CCSPlayerController admin)
}
```
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:21-29`
**Why MenuContext is better:**
-**Old way:** `"God Mode"` and `"yourmodule"` had to be typed twice (in RegisterMenu and CreateMenuWithPlayers)
-**New way:** Context contains both automatically - no duplication, no mistakes!
### Pattern 2: Nested Menu (Player → Value)
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:44-51`
### Pattern 2: Nested Menu (Player → Value) - NEW MenuContext API!
**When to use:** Select a player, then select a value/option for that player
```csharp
// Example: Set HP menu (player selection)
private object CreateSetHpMenu(CCSPlayerController admin)
// 🆕 NEW: Uses MenuContext to eliminate duplication
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api.CreateMenuWithBack("Set HP", "yourmodule", admin);
var menu = _api.CreateMenuWithBack(context, admin); // ← No more repeating title & category!
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
foreach (var player in players)
@@ -201,6 +210,7 @@ private object CreateSetHpMenu(CCSPlayerController admin)
}
// Example: Set HP menu (value selection)
// Note: Submenus don't receive context - they create dynamic titles
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api.CreateMenuWithBack($"HP for {target.PlayerName}", "yourmodule", admin);
@@ -221,7 +231,12 @@ private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController
}
```
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:134-173`
**Benefits of MenuContext:**
- ✅ Change menu title in one place (RegisterMenu) and it updates everywhere
- ✅ Can't accidentally mistype category ID
- ✅ Access to permission and command name from context if needed
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:163-178`
### Pattern 3: Nested Menu with Complex Data
**When to use:** Need to display more complex options (like weapons with icons, items with descriptions)
@@ -264,6 +279,73 @@ private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerCon
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:67-109`
## 🆕 MenuContext API (New!)
### What is MenuContext?
`MenuContext` is a new feature that eliminates code duplication when creating menus. When you register a menu, you provide information like title, category, and permissions. Previously, you had to repeat this information in your menu factory method. Now, this information is automatically passed to your factory via `MenuContext`.
### MenuContext Properties
```csharp
public class MenuContext
{
public string CategoryId { get; } // e.g., "fun"
public string MenuId { get; } // e.g., "god"
public string MenuTitle { get; } // e.g., "God Mode"
public string? Permission { get; } // e.g., "@css/cheats"
public string? CommandName { get; } // e.g., "css_god"
}
```
### Before vs After Comparison
```csharp
// ❌ OLD API - Duplication everywhere
_api.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats");
private object CreateGodModeMenu(CCSPlayerController admin)
{
return _api.CreateMenuWithPlayers(
"God Mode", // ← Duplicated from RegisterMenu
"fun", // ← Duplicated from RegisterMenu
admin, filter, action);
}
// ✅ NEW API - No duplication!
_api.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats");
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
{
return _api.CreateMenuWithPlayers(
context, // ← Contains all info automatically!
admin, filter, action);
}
```
### When to Use MenuContext
| Menu Creation Method | Old Signature | New Signature (Recommended) |
|---------------------|---------------|----------------------------|
| `CreateMenuWithBack` | `(string title, string categoryId, ...)` | `(MenuContext context, ...)` |
| `CreateMenuWithPlayers` | `(string title, string categoryId, ...)` | `(MenuContext context, ...)` |
**Rule of thumb:** If you're creating a menu directly from a registered menu factory, use `MenuContext`. For dynamic submenus (e.g., player-specific menus), use the old API.
### Backward Compatibility
The old API still works! Both signatures are supported:
```csharp
// ✅ Old API - still works
_api.RegisterMenu("cat", "id", "Title",
(CCSPlayerController admin) => CreateOldStyleMenu(admin));
// ✅ New API - recommended
_api.RegisterMenu("cat", "id", "Title",
(CCSPlayerController admin, MenuContext ctx) => CreateNewStyleMenu(admin, ctx));
```
## 📋 API Methods Reference
### 1. Category Management
@@ -312,35 +394,57 @@ _api.UnregisterMenu("fun", "godmode");
### 3. Menu Creation
#### `CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)`
#### `CreateMenuWithBack(MenuContext context, CCSPlayerController player)` 🆕 NEW!
Creates a menu with an automatic "Back" button that returns to the category menu.
**Parameters:**
**Parameters (Old API):**
- `title` - Menu title
- `categoryId` - Category this menu belongs to (for back navigation)
- `player` - The admin player viewing the menu
**Parameters (New API - Recommended):**
- `context` - MenuContext containing title, category, and other metadata
- `player` - The admin player viewing the menu
**Returns:** `object` (MenuBuilder instance)
**Example:**
**Example (Old API):**
```csharp
var menu = _api.CreateMenuWithBack("Weapon Selection", "fun", admin);
```
**Example (New API - Recommended):**
```csharp
private object CreateWeaponMenu(CCSPlayerController admin, MenuContext context)
{
var menu = _api.CreateMenuWithBack(context, admin); // ← Uses context!
// ... add options
return menu;
}
```
#### `CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)`
#### `CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)` 🆕 NEW!
Creates a menu with a list of players, filtered and with automatic back button.
**Parameters:**
**Parameters (Old API):**
- `title` - Menu title
- `categoryId` - Category for back navigation
- `admin` - The admin player viewing the menu
- `filter` - Function to filter which players appear in the menu
- `onSelect` - Action to execute when a player is selected (receives admin and target)
**Parameters (New API - Recommended):**
- `context` - MenuContext containing title and category
- `admin` - The admin player viewing the menu
- `filter` - Function to filter which players appear in the menu
- `onSelect` - Action to execute when a player is selected (receives admin and target)
**Returns:** `object` (MenuBuilder instance)
**Example:**
**Example (Old API):**
```csharp
return _api.CreateMenuWithPlayers("Select Player to Kick", "admin", admin,
player => player.IsValid && admin.CanTarget(player),
@@ -351,6 +455,19 @@ return _api.CreateMenuWithPlayers("Select Player to Kick", "admin", admin,
});
```
**Example (New API - Recommended):**
```csharp
private object CreateKickMenu(CCSPlayerController admin, MenuContext context)
{
return _api.CreateMenuWithPlayers(context, admin,
player => player.IsValid && admin.CanTarget(player),
(adminPlayer, targetPlayer) =>
{
Server.ExecuteCommand($"css_kick {targetPlayer.UserId}");
});
}
```
### 4. Menu Manipulation
#### `AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)`