mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-22 11:42:26 +00:00
Add CS2-SimpleAdmin documentation site
Introduces a new documentation site for CS2-SimpleAdmin using Docusaurus, including developer API references, tutorials, user guides, and module documentation. Removes the CleanModule example and updates FunCommands and ExampleModule. Also updates main plugin and API files to support new documentation and module structure.
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user