mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-03-10 00:22:31 +00:00
Compare commits
15 Commits
build-1.7.
...
bdada2df1e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdada2df1e | ||
|
|
310a43fcd9 | ||
|
|
58243e813a | ||
|
|
d53446e0fe | ||
|
|
2404c1bc03 | ||
|
|
665962565e | ||
|
|
c2e8b4a898 | ||
|
|
9723a4faee | ||
|
|
4865b76262 | ||
|
|
0dded66e5d | ||
|
|
038641dbdf | ||
|
|
a03964c08a | ||
|
|
b0d8696756 | ||
|
|
21a5de6b3d | ||
|
|
718536eaef |
20
CS2-SimpleAdmin-docs/.gitignore
vendored
Normal file
20
CS2-SimpleAdmin-docs/.gitignore
vendored
Normal 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*
|
||||||
41
CS2-SimpleAdmin-docs/README.md
Normal file
41
CS2-SimpleAdmin-docs/README.md
Normal 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.
|
||||||
465
CS2-SimpleAdmin-docs/docs/developer/api/commands.md
Normal file
465
CS2-SimpleAdmin-docs/docs/developer/api/commands.md
Normal 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
|
||||||
642
CS2-SimpleAdmin-docs/docs/developer/api/events.md
Normal file
642
CS2-SimpleAdmin-docs/docs/developer/api/events.md
Normal 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
|
||||||
706
CS2-SimpleAdmin-docs/docs/developer/api/menus.md
Normal file
706
CS2-SimpleAdmin-docs/docs/developer/api/menus.md
Normal 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
|
||||||
621
CS2-SimpleAdmin-docs/docs/developer/api/overview.md
Normal file
621
CS2-SimpleAdmin-docs/docs/developer/api/overview.md
Normal 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
|
||||||
610
CS2-SimpleAdmin-docs/docs/developer/api/penalties.md
Normal file
610
CS2-SimpleAdmin-docs/docs/developer/api/penalties.md
Normal 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
|
||||||
585
CS2-SimpleAdmin-docs/docs/developer/api/utilities.md
Normal file
585
CS2-SimpleAdmin-docs/docs/developer/api/utilities.md
Normal 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
|
||||||
695
CS2-SimpleAdmin-docs/docs/developer/architecture.md
Normal file
695
CS2-SimpleAdmin-docs/docs/developer/architecture.md
Normal 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
|
||||||
379
CS2-SimpleAdmin-docs/docs/developer/intro.md
Normal file
379
CS2-SimpleAdmin-docs/docs/developer/intro.md
Normal 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
|
||||||
|
|||||||