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.
20
CS2-SimpleAdmin-docs/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
41
CS2-SimpleAdmin-docs/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Website
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
## Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```bash
|
||||
USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```bash
|
||||
GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
465
CS2-SimpleAdmin-docs/docs/developer/api/commands.md
Normal file
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,585 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Utilities API
|
||||
|
||||
Helper functions and utility methods for module development.
|
||||
|
||||
## Player Management
|
||||
|
||||
### GetValidPlayers
|
||||
|
||||
Get a list of all valid, connected players.
|
||||
|
||||
```csharp
|
||||
List<CCSPlayerController> GetValidPlayers()
|
||||
```
|
||||
|
||||
**Returns:** List of valid player controllers
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var players = _api!.GetValidPlayers();
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
Console.WriteLine($"Player: {player.PlayerName}");
|
||||
}
|
||||
|
||||
// Filter for specific criteria
|
||||
var alivePlayers = _api.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive)
|
||||
.ToList();
|
||||
|
||||
var ctPlayers = _api.GetValidPlayers()
|
||||
.Where(p => p.Team == CsTeam.CounterTerrorist)
|
||||
.ToList();
|
||||
```
|
||||
|
||||
**Note:** This method filters out invalid and bot players automatically.
|
||||
|
||||
---
|
||||
|
||||
## Admin Status
|
||||
|
||||
### IsAdminSilent
|
||||
|
||||
Check if an admin is in silent mode.
|
||||
|
||||
```csharp
|
||||
bool IsAdminSilent(CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `player` - Player to check
|
||||
|
||||
**Returns:** `true` if player is in silent mode, `false` otherwise
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private void PerformAdminAction(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Do the action
|
||||
DoAction(target);
|
||||
|
||||
// Only show activity if not silent
|
||||
if (!_api!.IsAdminSilent(admin))
|
||||
{
|
||||
Server.PrintToChatAll($"{admin.PlayerName} performed action on {target.PlayerName}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ListSilentAdminsSlots
|
||||
|
||||
Get a list of player slots for all admins currently in silent mode.
|
||||
|
||||
```csharp
|
||||
HashSet<int> ListSilentAdminsSlots()
|
||||
```
|
||||
|
||||
**Returns:** HashSet of player slots
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var silentAdmins = _api!.ListSilentAdminsSlots();
|
||||
|
||||
Console.WriteLine($"Silent admins: {silentAdmins.Count}");
|
||||
|
||||
foreach (var slot in silentAdmins)
|
||||
{
|
||||
var player = Utilities.GetPlayerFromSlot(slot);
|
||||
if (player != null)
|
||||
{
|
||||
Console.WriteLine($"- {player.PlayerName} (slot {slot})");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Activity Messages
|
||||
|
||||
### ShowAdminActivity
|
||||
|
||||
Show an admin activity message to all players.
|
||||
|
||||
```csharp
|
||||
void ShowAdminActivity(
|
||||
string messageKey,
|
||||
string? callerName = null,
|
||||
bool dontPublish = false,
|
||||
params object[] messageArgs
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `messageKey` - Translation key from SimpleAdmin's lang files
|
||||
- `callerName` - Admin name (null for console)
|
||||
- `dontPublish` - If true, don't trigger OnAdminShowActivity event
|
||||
- `messageArgs` - Arguments for message formatting
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Using SimpleAdmin's built-in translations
|
||||
_api!.ShowAdminActivity(
|
||||
"sa_admin_player_kick_message", // Translation key
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
player.PlayerName,
|
||||
reason
|
||||
);
|
||||
```
|
||||
|
||||
**Limitations:**
|
||||
- Only works with SimpleAdmin's own translation keys
|
||||
- For module-specific messages, use `ShowAdminActivityLocalized`
|
||||
|
||||
---
|
||||
|
||||
### ShowAdminActivityTranslated
|
||||
|
||||
Show a pre-translated admin activity message.
|
||||
|
||||
```csharp
|
||||
void ShowAdminActivityTranslated(
|
||||
string translatedMessage,
|
||||
string? callerName = null,
|
||||
bool dontPublish = false
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `translatedMessage` - Already translated message
|
||||
- `callerName` - Admin name
|
||||
- `dontPublish` - If true, don't trigger event
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Use when you've already translated the message
|
||||
var message = Localizer?["my_action_message", player.PlayerName] ?? $"Action on {player.PlayerName}";
|
||||
|
||||
_api!.ShowAdminActivityTranslated(
|
||||
message,
|
||||
admin?.PlayerName,
|
||||
false
|
||||
);
|
||||
```
|
||||
|
||||
**Use Case:**
|
||||
- When you need custom message formatting
|
||||
- When translation is already done
|
||||
|
||||
---
|
||||
|
||||
### ShowAdminActivityLocalized ⭐ RECOMMENDED
|
||||
|
||||
Show admin activity with per-player language support using module's localizer.
|
||||
|
||||
```csharp
|
||||
void ShowAdminActivityLocalized(
|
||||
object moduleLocalizer,
|
||||
string messageKey,
|
||||
string? callerName = null,
|
||||
bool dontPublish = false,
|
||||
params object[] messageArgs
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `moduleLocalizer` - Your module's `IStringLocalizer` instance
|
||||
- `messageKey` - Translation key from your module's lang files
|
||||
- `callerName` - Admin name
|
||||
- `dontPublish` - If true, don't trigger event
|
||||
- `messageArgs` - Message arguments
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Each player sees message in their configured language!
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer, // Your module's localizer
|
||||
"fun_admin_god_message", // From your lang/en.json
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
player.PlayerName
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**lang/en.json:**
|
||||
```json
|
||||
{
|
||||
"fun_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!"
|
||||
}
|
||||
```
|
||||
|
||||
**Why This is Best:**
|
||||
- ✅ Each player sees message in their own language
|
||||
- ✅ Uses your module's translations
|
||||
- ✅ Supports color codes
|
||||
- ✅ Per-player localization
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Action with Activity Message
|
||||
|
||||
```csharp
|
||||
private void ToggleGodMode(CCSPlayerController? admin, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
if (GodPlayers.Contains(target.Slot))
|
||||
{
|
||||
GodPlayers.Remove(target.Slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
GodPlayers.Add(target.Slot);
|
||||
}
|
||||
|
||||
// Show activity (respecting silent mode)
|
||||
if (admin == null || !_api!.IsAdminSilent(admin))
|
||||
{
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"fun_admin_god_message",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Log action
|
||||
_api!.LogCommand(admin, $"css_god {target.PlayerName}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Broadcast to Non-Silent Admins
|
||||
|
||||
```csharp
|
||||
private void NotifyAdmins(string message)
|
||||
{
|
||||
var silentAdmins = _api!.ListSilentAdminsSlots();
|
||||
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// Check if player is admin
|
||||
if (!AdminManager.PlayerHasPermissions(player, "@css/generic"))
|
||||
continue;
|
||||
|
||||
// Skip if admin is in silent mode
|
||||
if (silentAdmins.Contains(player.Slot))
|
||||
continue;
|
||||
|
||||
player.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Filter Players by Criteria
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetTargetablePlayers(CCSPlayerController admin)
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p =>
|
||||
p.IsValid &&
|
||||
!p.IsBot &&
|
||||
p.PawnIsAlive &&
|
||||
admin.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private List<CCSPlayerController> GetAliveEnemies(CCSPlayerController player)
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p =>
|
||||
p.Team != player.Team &&
|
||||
p.PawnIsAlive)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private List<CCSPlayerController> GetAdmins()
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, "@css/generic"))
|
||||
.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use ShowAdminActivityLocalized
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Per-player language
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"my_message_key",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
|
||||
// ❌ Bad - Single language for all
|
||||
Server.PrintToChatAll($"{admin?.PlayerName} did something");
|
||||
```
|
||||
|
||||
### 2. Respect Silent Mode
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Check silent mode
|
||||
if (admin == null || !_api.IsAdminSilent(admin))
|
||||
{
|
||||
ShowActivity();
|
||||
}
|
||||
|
||||
// ❌ Bad - Always show activity
|
||||
ShowActivity(); // Ignores silent mode!
|
||||
```
|
||||
|
||||
### 3. Validate Players from GetValidPlayers
|
||||
|
||||
```csharp
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// Still good to check, especially for async operations
|
||||
if (!player.IsValid) continue;
|
||||
|
||||
DoSomething(player);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Cache Silent Admin List if Checking Multiple Times
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Cache for multiple checks
|
||||
var silentAdmins = _api.ListSilentAdminsSlots();
|
||||
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
if (silentAdmins.Contains(admin.Slot)) continue;
|
||||
NotifyAdmin(admin);
|
||||
}
|
||||
|
||||
// ❌ Bad - Query for each admin
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
if (_api.IsAdminSilent(admin)) continue; // ← Repeated calls
|
||||
NotifyAdmin(admin);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Silent Mode Wrapper
|
||||
|
||||
```csharp
|
||||
private void ShowActivityIfNotSilent(
|
||||
CCSPlayerController? admin,
|
||||
string messageKey,
|
||||
params object[] args)
|
||||
{
|
||||
if (admin != null && _api!.IsAdminSilent(admin))
|
||||
return;
|
||||
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
messageKey,
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
ShowActivityIfNotSilent(admin, "my_action", player.PlayerName);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Online Admins
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetOnlineAdmins(string permission = "@css/generic")
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, permission))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Usage
|
||||
var admins = GetOnlineAdmins("@css/root");
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
admin.PrintToChat("Important admin message");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Notify All Players Except Silent Admins
|
||||
|
||||
```csharp
|
||||
private void BroadcastMessage(string message, bool excludeSilentAdmins = true)
|
||||
{
|
||||
var silentAdmins = excludeSilentAdmins
|
||||
? _api!.ListSilentAdminsSlots()
|
||||
: new HashSet<int>();
|
||||
|
||||
foreach (var player in _api.GetValidPlayers())
|
||||
{
|
||||
if (silentAdmins.Contains(player.Slot))
|
||||
continue;
|
||||
|
||||
player.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Activity Message Formatting
|
||||
|
||||
### Color Codes in Messages
|
||||
|
||||
All activity messages support color codes:
|
||||
|
||||
```json
|
||||
{
|
||||
"my_message": "{lightred}Admin{default} banned {lightred}{0}{default} for {yellow}{1}{default}"
|
||||
}
|
||||
```
|
||||
|
||||
**Available Colors:**
|
||||
- `{default}` - Default color
|
||||
- `{white}` - White
|
||||
- `{darkred}` - Dark red
|
||||
- `{green}` - Green
|
||||
- `{lightyellow}` - Light yellow
|
||||
- `{lightblue}` - Light blue
|
||||
- `{olive}` - Olive
|
||||
- `{lime}` - Lime
|
||||
- `{red}` - Red
|
||||
- `{purple}` - Purple
|
||||
- `{grey}` - Grey
|
||||
- `{yellow}` - Yellow
|
||||
- `{gold}` - Gold
|
||||
- `{silver}` - Silver
|
||||
- `{blue}` - Blue
|
||||
- `{darkblue}` - Dark blue
|
||||
- `{bluegrey}` - Blue grey
|
||||
- `{magenta}` - Magenta
|
||||
- `{lightred}` - Light red
|
||||
- `{orange}` - Orange
|
||||
|
||||
---
|
||||
|
||||
### Message Arguments
|
||||
|
||||
```csharp
|
||||
// lang/en.json
|
||||
{
|
||||
"ban_message": "{lightred}{0}{default} banned {lightred}{1}{default} for {yellow}{2}{default} minutes: {red}{3}"
|
||||
}
|
||||
|
||||
// Code
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"ban_message",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
admin?.PlayerName, // {0}
|
||||
target.PlayerName, // {1}
|
||||
duration, // {2}
|
||||
reason // {3}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### Minimize GetValidPlayers Calls
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Call once, filter multiple times
|
||||
var allPlayers = _api.GetValidPlayers();
|
||||
var alivePlayers = allPlayers.Where(p => p.PawnIsAlive).ToList();
|
||||
var deadPlayers = allPlayers.Where(p => !p.PawnIsAlive).ToList();
|
||||
|
||||
// ❌ Bad - Multiple calls
|
||||
var alivePlayers = _api.GetValidPlayers().Where(p => p.PawnIsAlive).ToList();
|
||||
var deadPlayers = _api.GetValidPlayers().Where(p => !p.PawnIsAlive).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Efficient Filtering
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Single LINQ query
|
||||
var targets = _api.GetValidPlayers()
|
||||
.Where(p => p.Team == CsTeam.Terrorist &&
|
||||
p.PawnIsAlive &&
|
||||
admin.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
// ❌ Bad - Multiple iterations
|
||||
var players = _api.GetValidPlayers();
|
||||
players = players.Where(p => p.Team == CsTeam.Terrorist).ToList();
|
||||
players = players.Where(p => p.PawnIsAlive).ToList();
|
||||
players = players.Where(p => admin.CanTarget(p)).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Activity Messages Not Showing
|
||||
|
||||
**Check:**
|
||||
1. Is `Localizer` not null?
|
||||
2. Does translation key exist in lang files?
|
||||
3. Is message correctly formatted?
|
||||
4. Check `dontPublish` parameter
|
||||
|
||||
### Silent Mode Not Working
|
||||
|
||||
**Check:**
|
||||
1. Is player actually in silent mode? (`css_hide` command)
|
||||
2. Are you checking before showing activity?
|
||||
3. Check slot vs player controller mismatch
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Commands API](commands)** - Log commands
|
||||
- **[Menus API](menus)** - Get players for menus
|
||||
- **[Events API](events)** - Admin activity events
|
||||
- **[Penalties API](penalties)** - Get player info
|
||||
695
CS2-SimpleAdmin-docs/docs/developer/architecture.md
Normal file
@@ -0,0 +1,695 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Plugin Architecture
|
||||
|
||||
Deep dive into CS2-SimpleAdmin's architecture and design patterns.
|
||||
|
||||
## Overview
|
||||
|
||||
CS2-SimpleAdmin follows a **layered architecture** with clear separation of concerns and well-defined responsibilities for each component.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ CounterStrikeSharp Integration Layer │ ← CS2_SimpleAdmin.cs
|
||||
├─────────────────────────────────────────┤
|
||||
│ Manager Layer │ ← /Managers/
|
||||
│ • PermissionManager │
|
||||
│ • BanManager │
|
||||
│ • MuteManager │
|
||||
│ • WarnManager │
|
||||
│ • CacheManager │
|
||||
│ • PlayerManager │
|
||||
│ • ServerManager │
|
||||
│ • DiscordManager │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Database Layer │ ← /Database/
|
||||
│ • IDatabaseProvider (Interface) │
|
||||
│ • MySqlDatabaseProvider │
|
||||
│ • SqliteDatabaseProvider │
|
||||
│ • Migration System │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Menu System │ ← /Menus/
|
||||
│ • MenuManager (Singleton) │
|
||||
│ • MenuBuilder (Factory) │
|
||||
│ • Specific Menu Classes │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Command System │ ← /Commands/
|
||||
│ • RegisterCommands │
|
||||
│ • Command Handlers (basebans, etc.) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Public API │ ← /Api/
|
||||
│ • ICS2_SimpleAdminApi (Interface) │
|
||||
│ • CS2_SimpleAdminApi (Implementation) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. CounterStrikeSharp Integration Layer
|
||||
|
||||
**File:** `CS2_SimpleAdmin.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Plugin lifecycle management (Load/Unload)
|
||||
- Event registration (`player_connect`, `player_disconnect`, etc.)
|
||||
- Command routing
|
||||
- Low-level game operations using `MemoryFunctionVoid`
|
||||
- Timer management
|
||||
|
||||
**Key Methods:**
|
||||
```csharp
|
||||
public override void Load(bool hotReload)
|
||||
public override void Unload(bool hotReload)
|
||||
private HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo info)
|
||||
private HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Manager Layer
|
||||
|
||||
Each manager encapsulates specific domain logic:
|
||||
|
||||
#### PermissionManager
|
||||
|
||||
**File:** `/Managers/PermissionManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Load admin flags and groups from database
|
||||
- Maintain in-memory `AdminCache` with lazy-loading
|
||||
- Time-based cache expiry
|
||||
- Immunity level management
|
||||
|
||||
**Key Patterns:**
|
||||
- Caching for performance
|
||||
- Lazy loading of admin data
|
||||
- Periodic refresh
|
||||
|
||||
#### BanManager
|
||||
|
||||
**File:** `/Managers/BanManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Issue bans (SteamID, IP, or hybrid)
|
||||
- Remove bans (unban)
|
||||
- Handle ban expiration cleanup
|
||||
- Multi-server ban synchronization
|
||||
|
||||
**Key Operations:**
|
||||
```csharp
|
||||
Task BanPlayer(...)
|
||||
Task AddBanBySteamId(...)
|
||||
Task RemoveBan(...)
|
||||
```
|
||||
|
||||
#### MuteManager
|
||||
|
||||
**File:** `/Managers/MuteManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Three mute types: GAG (text), MUTE (voice), SILENCE (both)
|
||||
- Duration-based mutes
|
||||
- Expiration tracking
|
||||
|
||||
#### WarnManager
|
||||
|
||||
**File:** `/Managers/WarnManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Progressive warning system
|
||||
- Auto-escalation to bans based on `WarnThreshold` config
|
||||
- Warning history tracking
|
||||
|
||||
#### CacheManager
|
||||
|
||||
**File:** `/Managers/CacheManager.cs`
|
||||
|
||||
**Purpose:** Performance optimization layer
|
||||
|
||||
**Features:**
|
||||
- In-memory ban cache with O(1) lookups by SteamID and IP
|
||||
- Player IP history tracking for multi-account detection
|
||||
- Reduces database queries on player join
|
||||
|
||||
**Data Structures:**
|
||||
```csharp
|
||||
Dictionary<ulong, BanInfo> _banCacheBySteamId
|
||||
Dictionary<string, List<BanInfo>> _banCacheByIp
|
||||
Dictionary<ulong, List<string>> _playerIpHistory
|
||||
```
|
||||
|
||||
#### PlayerManager
|
||||
|
||||
**File:** `/Managers/PlayerManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Load player data on connect
|
||||
- Check bans against cache
|
||||
- Update IP history
|
||||
- Semaphore limiting (max 5 concurrent loads)
|
||||
|
||||
**Key Pattern:**
|
||||
```csharp
|
||||
private readonly SemaphoreSlim _semaphore = new(5, 5);
|
||||
|
||||
public async Task LoadPlayerData(CCSPlayerController player)
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
// Load player data
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ServerManager
|
||||
|
||||
**File:** `/Managers/ServerManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Load/register server metadata (IP, port, hostname, RCON)
|
||||
- Multi-server mode support
|
||||
- Server ID management
|
||||
|
||||
#### DiscordManager
|
||||
|
||||
**File:** `/Managers/DiscordManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Send webhook notifications for admin actions
|
||||
- Configurable webhooks per penalty type
|
||||
- Embed formatting with placeholders
|
||||
|
||||
---
|
||||
|
||||
### 3. Database Layer
|
||||
|
||||
**Files:** `/Database/`
|
||||
|
||||
**Provider Pattern** for database abstraction:
|
||||
|
||||
```csharp
|
||||
public interface IDatabaseProvider
|
||||
{
|
||||
Task ExecuteAsync(string query, object? parameters = null);
|
||||
Task<T> QueryFirstOrDefaultAsync<T>(string query, object? parameters = null);
|
||||
Task<List<T>> QueryAsync<T>(string query, object? parameters = null);
|
||||
|
||||
// Query generation methods
|
||||
string GetBanQuery(bool multiServer);
|
||||
string GetMuteQuery(bool multiServer);
|
||||
// ... more query methods
|
||||
}
|
||||
```
|
||||
|
||||
**Implementations:**
|
||||
- `MySqlDatabaseProvider` - MySQL-specific SQL syntax
|
||||
- `SqliteDatabaseProvider` - SQLite-specific SQL syntax
|
||||
|
||||
**Benefits:**
|
||||
- Single codebase supports both MySQL and SQLite
|
||||
- Easy to add new database providers
|
||||
- Query methods accept `multiServer` boolean for scoping
|
||||
|
||||
**Migration System:**
|
||||
|
||||
**File:** `Database/Migration.cs`
|
||||
|
||||
- File-based migrations in `/Database/Migrations/{mysql,sqlite}/`
|
||||
- Numbered files: `001_CreateTables.sql`, `002_AddColumn.sql`
|
||||
- Tracking table: `sa_migrations`
|
||||
- Auto-applies on plugin load
|
||||
- Safe for multi-server environments
|
||||
|
||||
---
|
||||
|
||||
### 4. Menu System
|
||||
|
||||
**Files:** `/Menus/`
|
||||
|
||||
**MenuManager (Singleton Pattern):**
|
||||
|
||||
```csharp
|
||||
public class MenuManager
|
||||
{
|
||||
public static MenuManager Instance { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, MenuCategory> _categories = new();
|
||||
private readonly Dictionary<string, Dictionary<string, MenuInfo>> _menus = new();
|
||||
|
||||
public void RegisterCategory(string id, string name, string permission);
|
||||
public void RegisterMenu(string categoryId, string menuId, string name, ...);
|
||||
public MenuBuilder CreateCategoryMenuPublic(MenuCategory category, CCSPlayerController player);
|
||||
}
|
||||
```
|
||||
|
||||
**MenuBuilder (Factory Pattern):**
|
||||
|
||||
```csharp
|
||||
public class MenuBuilder
|
||||
{
|
||||
public MenuBuilder(string title);
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, ...);
|
||||
public MenuBuilder AddSubMenu(string name, Func<CCSPlayerController, MenuBuilder> factory, ...);
|
||||
public MenuBuilder WithBackAction(Action<CCSPlayerController> backAction);
|
||||
public void OpenMenu(CCSPlayerController player);
|
||||
}
|
||||
```
|
||||
|
||||
**Specific Menu Classes:**
|
||||
- `AdminMenu` - Main admin menu with categories
|
||||
- `ManagePlayersMenu` - Player management menus
|
||||
- `ManageServerMenu` - Server settings
|
||||
- `DurationMenu` - Duration selection
|
||||
- `ReasonMenu` - Reason selection
|
||||
|
||||
**Benefits:**
|
||||
- Centralized menu management
|
||||
- Permission-aware rendering
|
||||
- Automatic back button handling
|
||||
- Reusable menu components
|
||||
|
||||
---
|
||||
|
||||
### 5. Command System
|
||||
|
||||
**Files:** `/Commands/`
|
||||
|
||||
**Central Registration:**
|
||||
|
||||
**File:** `RegisterCommands.cs`
|
||||
|
||||
```csharp
|
||||
public static class RegisterCommands
|
||||
{
|
||||
public static Dictionary<string, List<CommandDefinition>> _commandDefinitions = new();
|
||||
|
||||
public static void RegisterCommands(CS2_SimpleAdmin plugin)
|
||||
{
|
||||
// Load Commands.json
|
||||
// Map commands to handler methods
|
||||
// Register with CounterStrikeSharp
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Command Handlers:**
|
||||
|
||||
Organized by category:
|
||||
- `basebans.cs` - Ban, unban, warn commands
|
||||
- `basecomms.cs` - Gag, mute, silence commands
|
||||
- `basecommands.cs` - Admin management, server commands
|
||||
- `basechat.cs` - Chat commands (asay, csay, etc.)
|
||||
- `playercommands.cs` - Player manipulation (slay, hp, etc.)
|
||||
- `funcommands.cs` - Fun commands (god, noclip, etc.)
|
||||
- `basevotes.cs` - Voting system
|
||||
|
||||
**Two-Tier Pattern:**
|
||||
|
||||
```csharp
|
||||
// Entry command - parses arguments
|
||||
[CommandHelper(2, "<#userid> <duration> [reason]")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = GetTarget(command);
|
||||
int duration = ParseDuration(command.GetArg(2));
|
||||
string reason = ParseReason(command);
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
Ban(caller, target, duration, reason); // Core method
|
||||
}
|
||||
}
|
||||
|
||||
// Core method - database writes, events
|
||||
private void Ban(CCSPlayerController? admin, CCSPlayerController target, int duration, string reason)
|
||||
{
|
||||
// Write to database
|
||||
BanManager.BanPlayer(target, admin, duration, reason);
|
||||
|
||||
// Update cache
|
||||
CacheManager.AddBan(target);
|
||||
|
||||
// Trigger events
|
||||
ApiInstance.OnPlayerPenaltiedEvent(target, admin, PenaltyType.Ban, reason, duration);
|
||||
|
||||
// Kick player
|
||||
Server.ExecuteCommand($"kick {target.UserId}");
|
||||
|
||||
// Send Discord notification
|
||||
DiscordManager.SendBanNotification(target, admin, duration, reason);
|
||||
|
||||
// Broadcast action
|
||||
ShowAdminActivity("ban_message", admin?.PlayerName, target.PlayerName, duration, reason);
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Separation of parsing and execution
|
||||
- Reusable core methods
|
||||
- Consistent event triggering
|
||||
- Easy to test
|
||||
|
||||
---
|
||||
|
||||
### 6. Public API
|
||||
|
||||
**Files:** `/Api/`
|
||||
|
||||
**Interface:** `ICS2_SimpleAdminApi.cs` (in CS2-SimpleAdminApi project)
|
||||
|
||||
**Implementation:** `CS2_SimpleAdminApi.cs`
|
||||
|
||||
**Capability System:**
|
||||
|
||||
```csharp
|
||||
// In API interface
|
||||
public static readonly PluginCapability<ICS2_SimpleAdminApi> PluginCapability = new("simpleadmin:api");
|
||||
|
||||
// In module
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
}
|
||||
```
|
||||
|
||||
**Event Publishing:**
|
||||
|
||||
```csharp
|
||||
// API exposes events
|
||||
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, ...>? OnPlayerPenaltied;
|
||||
|
||||
// Core plugin triggers events
|
||||
ApiInstance.OnPlayerPenaltiedEvent(player, admin, type, reason, duration, id);
|
||||
|
||||
// Modules subscribe
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// React to penalty
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Patterns
|
||||
|
||||
### Player Join Flow
|
||||
|
||||
```
|
||||
1. player_connect event
|
||||
↓
|
||||
2. PlayerManager.LoadPlayerData()
|
||||
↓
|
||||
3. Semaphore.WaitAsync() ← Max 5 concurrent
|
||||
↓
|
||||
4. CacheManager.CheckBan(steamId, ip)
|
||||
↓
|
||||
5a. BANNED → Kick player immediately
|
||||
5b. CLEAN → Continue
|
||||
↓
|
||||
6. Load active penalties from DB
|
||||
↓
|
||||
7. Store in PlayersInfo dictionary
|
||||
↓
|
||||
8. Update player IP history
|
||||
```
|
||||
|
||||
### Ban Command Flow
|
||||
|
||||
```
|
||||
1. OnBanCommand() ← Parse arguments
|
||||
↓
|
||||
2. Ban() ← Core method
|
||||
↓
|
||||
3. BanManager.BanPlayer() ← Write to DB
|
||||
↓
|
||||
4. CacheManager.AddBan() ← Update cache
|
||||
↓
|
||||
5. ApiInstance.OnPlayerPenaltiedEvent() ← Trigger event
|
||||
↓
|
||||
6. Server.ExecuteCommand("kick") ← Kick player
|
||||
↓
|
||||
7. DiscordManager.SendNotification() ← Discord webhook
|
||||
↓
|
||||
8. ShowAdminActivity() ← Broadcast action
|
||||
```
|
||||
|
||||
### Admin Permission Check Flow
|
||||
|
||||
```
|
||||
1. Plugin Load
|
||||
↓
|
||||
2. PermissionManager.LoadAdmins()
|
||||
↓
|
||||
3. Build AdminCache ← SteamID → Flags/Immunity
|
||||
↓
|
||||
4. Command Execution
|
||||
↓
|
||||
5. RequiresPermissions attribute check
|
||||
↓
|
||||
6. AdminManager.PlayerHasPermissions() ← Check cache
|
||||
↓
|
||||
7a. HAS PERMISSION → Execute
|
||||
7b. NO PERMISSION → Deny
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Patterns Used
|
||||
|
||||
### Singleton Pattern
|
||||
|
||||
```csharp
|
||||
public class MenuManager
|
||||
{
|
||||
public static MenuManager Instance { get; private set; }
|
||||
|
||||
public static void Initialize(CS2_SimpleAdmin plugin)
|
||||
{
|
||||
Instance = new MenuManager(plugin);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- MenuManager - Single menu registry
|
||||
- Cache management - Single source of truth
|
||||
|
||||
### Factory Pattern
|
||||
|
||||
```csharp
|
||||
public class MenuBuilder
|
||||
{
|
||||
public static MenuBuilder Create(string title) => new MenuBuilder(title);
|
||||
|
||||
public MenuBuilder AddOption(...) { /* ... */ return this; }
|
||||
public MenuBuilder AddSubMenu(...) { /* ... */ return this; }
|
||||
}
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- Menu creation
|
||||
- Database provider creation
|
||||
|
||||
### Strategy Pattern
|
||||
|
||||
```csharp
|
||||
public interface IDatabaseProvider
|
||||
{
|
||||
Task<List<BanInfo>> GetBans(bool multiServer);
|
||||
}
|
||||
|
||||
public class MySqlDatabaseProvider : IDatabaseProvider { /* ... */ }
|
||||
public class SqliteDatabaseProvider : IDatabaseProvider { /* ... */ }
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- Database abstraction
|
||||
- Query generation per DB type
|
||||
|
||||
### Observer Pattern
|
||||
|
||||
```csharp
|
||||
// Publisher
|
||||
public event Action<PlayerInfo, ...>? OnPlayerPenaltied;
|
||||
|
||||
// Trigger
|
||||
OnPlayerPenaltied?.Invoke(player, admin, type, reason, duration, id, sid);
|
||||
|
||||
// Subscribers
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// React
|
||||
};
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- Event system
|
||||
- Module communication
|
||||
|
||||
---
|
||||
|
||||
## Concurrency & Thread Safety
|
||||
|
||||
### Async/Await Patterns
|
||||
|
||||
All database operations use `async`/`await`:
|
||||
|
||||
```csharp
|
||||
public async Task BanPlayer(...)
|
||||
{
|
||||
await _database.ExecuteAsync(query, parameters);
|
||||
}
|
||||
```
|
||||
|
||||
### Semaphore for Rate Limiting
|
||||
|
||||
```csharp
|
||||
private readonly SemaphoreSlim _semaphore = new(5, 5);
|
||||
|
||||
public async Task LoadPlayerData(CCSPlayerController player)
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
// Load data
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Thread-Safe Collections
|
||||
|
||||
```csharp
|
||||
private readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = new();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Memory Management
|
||||
|
||||
### In-Memory Caches
|
||||
|
||||
**AdminCache:**
|
||||
```csharp
|
||||
Dictionary<ulong, (List<string> Flags, int Immunity, DateTime Expiry)> AdminCache
|
||||
```
|
||||
|
||||
**BanCache:**
|
||||
```csharp
|
||||
Dictionary<ulong, BanInfo> _banCacheBySteamId
|
||||
Dictionary<string, List<BanInfo>> _banCacheByIp
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Reduces database load
|
||||
- O(1) lookups
|
||||
- TTL-based expiry
|
||||
|
||||
### Cleanup
|
||||
|
||||
```csharp
|
||||
// On player disconnect
|
||||
PlayersInfo.TryRemove(player.SteamID, out _);
|
||||
|
||||
// Periodic cache cleanup
|
||||
AddTimer(3600f, CleanupExpiredCache, TimerFlags.REPEAT);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration System
|
||||
|
||||
### Multi-Level Configuration
|
||||
|
||||
1. **Main Config:** `CS2-SimpleAdmin.json`
|
||||
2. **Commands Config:** `Commands.json`
|
||||
3. **Module Configs:** Per-module JSON files
|
||||
|
||||
### Hot Reload Support
|
||||
|
||||
```csharp
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
// Reconfigure without restart
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
1. **Caching** - Minimize database queries
|
||||
2. **Lazy Loading** - Load admin data on-demand
|
||||
3. **Semaphore** - Limit concurrent operations
|
||||
4. **Connection Pooling** - Reuse DB connections
|
||||
5. **Indexed Queries** - Fast database lookups
|
||||
6. **Memory Cleanup** - Remove disconnected player data
|
||||
|
||||
---
|
||||
|
||||
## Future Extensibility
|
||||
|
||||
### Plugin Capabilities
|
||||
|
||||
New modules can extend functionality:
|
||||
|
||||
```csharp
|
||||
// New capability
|
||||
var customCapability = new PluginCapability<ICustomFeature>("custom:feature");
|
||||
Capabilities.RegisterPluginCapability(customCapability, () => _customFeature);
|
||||
|
||||
// Other plugins can use it
|
||||
var feature = _customCapability.Get();
|
||||
```
|
||||
|
||||
### Event-Driven Architecture
|
||||
|
||||
New events can be added without breaking changes:
|
||||
|
||||
```csharp
|
||||
public event Action<NewEventArgs>? OnNewEvent;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
### Unit Testing
|
||||
|
||||
- Managers can be tested independently
|
||||
- Mock `IDatabaseProvider` for testing
|
||||
- Test command handlers with mock players
|
||||
|
||||
### Integration Testing
|
||||
|
||||
- Test on actual CS2 server
|
||||
- Multi-server scenarios
|
||||
- Database migration testing
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[API Overview](api/overview)** - Public API details
|
||||
- **[Module Development](module/getting-started)** - Create modules
|
||||
- **[GitHub Source](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Browse code
|
||||
379
CS2-SimpleAdmin-docs/docs/developer/intro.md
Normal file
@@ -0,0 +1,379 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Developer Introduction
|
||||
|
||||
Welcome to the CS2-SimpleAdmin developer documentation!
|
||||
|
||||
## Overview
|
||||
|
||||
This section contains technical documentation for developers who want to:
|
||||
|
||||
- Create modules using the CS2-SimpleAdmin API
|
||||
- Contribute to the core plugin
|
||||
- Integrate with CS2-SimpleAdmin from other plugins
|
||||
- Understand the plugin architecture
|
||||
|
||||
---
|
||||
|
||||
## API Documentation
|
||||
|
||||
The CS2-SimpleAdmin API provides a rich set of features for module developers:
|
||||
|
||||
### Core Features
|
||||
|
||||
- **[Commands](api/commands)** - Register and manage commands
|
||||
- **[Menus](api/menus)** - Create admin menus with player selection
|
||||
- **[Penalties](api/penalties)** - Issue bans, mutes, gags, warnings
|
||||
- **[Events](api/events)** - Subscribe to plugin events
|
||||
- **[Utilities](api/utilities)** - Helper functions and player management
|
||||
|
||||
---
|
||||
|
||||
## Quick Links
|
||||
|
||||
### For Module Developers
|
||||
|
||||
- **[Module Development Guide](module/getting-started)** - Start creating modules
|
||||
- **[Best Practices](module/best-practices)** - Write better code
|
||||
- **[Examples](module/examples)** - Code examples and patterns
|
||||
|
||||
### For Core Contributors
|
||||
|
||||
- **[Architecture](architecture)** - Plugin structure and design
|
||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- C# knowledge (intermediate level)
|
||||
- .NET 8.0 SDK
|
||||
- CounterStrikeSharp understanding
|
||||
- CS2 dedicated server for testing
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Recommended:**
|
||||
- Visual Studio 2022 (Community or higher)
|
||||
- VS Code with C# extension
|
||||
- Git for version control
|
||||
|
||||
---
|
||||
|
||||
## CS2-SimpleAdminApi Interface
|
||||
|
||||
The main API interface provides all functionality:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
// Get the API
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
|
||||
new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the API
|
||||
_api.RegisterCommand("css_mycommand", "Description", OnMyCommand);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Categories
|
||||
|
||||
### Command Management
|
||||
|
||||
Register custom commands that integrate with CS2-SimpleAdmin:
|
||||
|
||||
```csharp
|
||||
_api.RegisterCommand("css_mycommand", "Description", callback);
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
_api.GetTarget(command); // Parse player targets
|
||||
```
|
||||
|
||||
**[Learn more →](api/commands)**
|
||||
|
||||
---
|
||||
|
||||
### Menu System
|
||||
|
||||
Create interactive menus with automatic back button handling:
|
||||
|
||||
```csharp
|
||||
// Register category
|
||||
_api.RegisterMenuCategory("mycategory", "My Category", "@css/generic");
|
||||
|
||||
// Register menu
|
||||
_api.RegisterMenu("mycategory", "mymenu", "My Menu", CreateMenu, "@css/generic");
|
||||
|
||||
// Create menu with players
|
||||
_api.CreateMenuWithPlayers(context, admin, filter, onSelect);
|
||||
```
|
||||
|
||||
**[Learn more →](api/menus)**
|
||||
|
||||
---
|
||||
|
||||
### Penalty System
|
||||
|
||||
Issue and manage player penalties:
|
||||
|
||||
```csharp
|
||||
// Ban player
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Ban, "Reason", 1440);
|
||||
|
||||
// Offline ban
|
||||
_api.IssuePenalty(steamId, admin, PenaltyType.Ban, "Reason", 0);
|
||||
|
||||
// Check penalties
|
||||
var status = _api.GetPlayerMuteStatus(player);
|
||||
```
|
||||
|
||||
**[Learn more →](api/penalties)**
|
||||
|
||||
---
|
||||
|
||||
### Event System
|
||||
|
||||
React to plugin events:
|
||||
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += () => { /* Plugin ready */ };
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, serverId) =>
|
||||
{
|
||||
// Player received penalty
|
||||
};
|
||||
```
|
||||
|
||||
**[Learn more →](api/events)**
|
||||
|
||||
---
|
||||
|
||||
### Utility Functions
|
||||
|
||||
Helper functions for common tasks:
|
||||
|
||||
```csharp
|
||||
// Get player info
|
||||
var playerInfo = _api.GetPlayerInfo(player);
|
||||
|
||||
// Get valid players
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
// Check admin status
|
||||
if (_api.IsAdminSilent(admin)) { /* ... */ }
|
||||
|
||||
// Show admin activity
|
||||
_api.ShowAdminActivity("message_key", callerName, false, args);
|
||||
```
|
||||
|
||||
**[Learn more →](api/utilities)**
|
||||
|
||||
---
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Simple Command
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var target in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
// Do something with target
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
### Simple Menu
|
||||
|
||||
```csharp
|
||||
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) => DoAction(admin, target)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Error Handling
|
||||
|
||||
```csharp
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("API not available!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Resource Cleanup
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister commands
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
|
||||
// Unregister menus
|
||||
_api.UnregisterMenu("mycategory", "mymenu");
|
||||
|
||||
// Unsubscribe events
|
||||
_api.OnSimpleAdminReady -= OnReady;
|
||||
}
|
||||
```
|
||||
|
||||
### Translations
|
||||
|
||||
```csharp
|
||||
// Use per-player language support
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"translation_key",
|
||||
caller?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
The **Fun Commands Module** serves as a complete reference implementation demonstrating all API features:
|
||||
|
||||
- Command registration from config
|
||||
- Menu creation with context
|
||||
- Per-player translations
|
||||
- Proper cleanup
|
||||
- Code organization
|
||||
|
||||
**[View Source Code](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)**
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
CS2-SimpleAdmin follows a layered architecture:
|
||||
|
||||
**Layers:**
|
||||
1. **CounterStrikeSharp Integration** - Game event handling
|
||||
2. **Manager Layer** - Business logic (Bans, Mutes, Permissions)
|
||||
3. **Database Layer** - MySQL/SQLite with migrations
|
||||
4. **Menu System** - MenuManager with factory pattern
|
||||
5. **Command System** - Dynamic registration
|
||||
6. **Public API** - ICS2_SimpleAdminApi interface
|
||||
|
||||
**[Learn more →](architecture)**
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
### Ways to Contribute
|
||||
|
||||
1. **Report Bugs** - [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
2. **Suggest Features** - [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
3. **Submit Pull Requests** - Code contributions
|
||||
4. **Create Modules** - Extend functionality
|
||||
5. **Improve Documentation** - Help others learn
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. Fork the repository
|
||||
2. Create feature branch
|
||||
3. Make changes
|
||||
4. Test thoroughly
|
||||
5. Submit pull request
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- **[API Reference](api/overview)** - Complete API documentation
|
||||
- **[Module Development](module/getting-started)** - Create modules
|
||||
- **[Architecture](architecture)** - Plugin design
|
||||
|
||||
### External Resources
|
||||
|
||||
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS framework
|
||||
- **[CS2 Docs](https://developer.valvesoftware.com/wiki/Counter-Strike_2)** - Game documentation
|
||||
|
||||
### Community
|
||||
|
||||
- **[GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
- **[Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)** - Bug reports
|
||||
- **[Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)** - Questions and ideas
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. **Check Documentation** - Most questions answered here
|
||||
2. **Search Issues** - Someone may have had same problem
|
||||
3. **Ask in Discussions** - Community help
|
||||
4. **Create Issue** - For bugs or feature requests
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
Include:
|
||||
- CS2-SimpleAdmin version
|
||||
- CounterStrikeSharp version
|
||||
- Error messages
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### For New Developers
|
||||
|
||||
1. **[Read API Overview](api/overview)** - Understand available features
|
||||
2. **[Study Examples](module/examples)** - Learn from code
|
||||
3. **[Create First Module](module/getting-started)** - Get hands-on
|
||||
|
||||
### For Advanced Developers
|
||||
|
||||
1. **[Read Architecture](architecture)** - Deep dive into structure
|
||||
2. **[Review Source Code](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Understand implementation
|
||||
3. **[Contribute](https://github.com/daffyyyy/CS2-SimpleAdmin/pulls)** - Help improve the plugin
|
||||
540
CS2-SimpleAdmin-docs/docs/developer/module/best-practices.md
Normal file
@@ -0,0 +1,540 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Best Practices
|
||||
|
||||
Guidelines for writing high-quality CS2-SimpleAdmin modules.
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Use Partial Classes
|
||||
|
||||
Split your code into logical files:
|
||||
|
||||
```
|
||||
MyModule/
|
||||
├── MyModule.cs # Main class, initialization
|
||||
├── Commands.cs # Command handlers
|
||||
├── Menus.cs # Menu creation
|
||||
├── Actions.cs # Core logic
|
||||
└── Config.cs # Configuration
|
||||
```
|
||||
|
||||
```csharp
|
||||
// MyModule.cs
|
||||
public partial class MyModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
// Initialization
|
||||
}
|
||||
|
||||
// Commands.cs
|
||||
public partial class MyModule
|
||||
{
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Command logic
|
||||
}
|
||||
}
|
||||
|
||||
// Menus.cs
|
||||
public partial class MyModule
|
||||
{
|
||||
private object CreateMyMenu(CCSPlayerController player, MenuContext context)
|
||||
{
|
||||
// Menu logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Easy to navigate
|
||||
- ✅ Logical separation
|
||||
- ✅ Better maintainability
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Use Command Lists
|
||||
|
||||
Allow users to customize aliases:
|
||||
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
// ✅ Good - List allows multiple aliases
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
// ❌ Bad - Single string
|
||||
public string MyCommand { get; set; } = "css_mycommand";
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```csharp
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Description", OnMyCommand);
|
||||
}
|
||||
```
|
||||
|
||||
### Provide Sensible Defaults
|
||||
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
// Good defaults
|
||||
public bool EnableFeature { get; set; } = true;
|
||||
public int MaxValue { get; set; } = 100;
|
||||
public List<string> Commands { get; set; } = ["css_default"];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Usage
|
||||
|
||||
### Always Check for Null
|
||||
|
||||
```csharp
|
||||
// ✅ Good
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("API not available!");
|
||||
return;
|
||||
}
|
||||
|
||||
_api.RegisterCommand(...);
|
||||
|
||||
// ❌ Bad
|
||||
_api!.RegisterCommand(...); // Can crash if null
|
||||
```
|
||||
|
||||
### Use OnSimpleAdminReady Pattern
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Handles both normal load and hot reload
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Also call directly
|
||||
|
||||
// ❌ Bad - Only works on normal load
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
```
|
||||
|
||||
### Always Clean Up
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister ALL commands
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
|
||||
// Unregister ALL menus
|
||||
_api.UnregisterMenu("category", "menu");
|
||||
|
||||
// Unsubscribe ALL events
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Player Validation
|
||||
|
||||
### Validate Before Acting
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Multiple checks
|
||||
if (!player.IsValid)
|
||||
{
|
||||
Logger.LogWarning("Player is invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.PawnIsAlive)
|
||||
{
|
||||
caller?.PrintToChat("Target must be alive!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (admin != null && !admin.CanTarget(player))
|
||||
{
|
||||
admin.PrintToChat("Cannot target this player!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Safe to proceed
|
||||
DoAction(player);
|
||||
```
|
||||
|
||||
### Check State Changes
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Validate in callback
|
||||
_api.AddMenuOption(menu, "Action", _ =>
|
||||
{
|
||||
// Validate again - player state may have changed
|
||||
if (!target.IsValid || !target.PawnIsAlive)
|
||||
return;
|
||||
|
||||
DoAction(target);
|
||||
});
|
||||
|
||||
// ❌ Bad - No validation in callback
|
||||
_api.AddMenuOption(menu, "Action", _ =>
|
||||
{
|
||||
DoAction(target); // Might crash!
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
|
||||
### Use MenuContext for Menus
|
||||
|
||||
```csharp
|
||||
// ✅ Good - No duplication
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenu, "@css/generic");
|
||||
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(context, admin, filter, action);
|
||||
}
|
||||
|
||||
// ❌ Bad - Duplicates title and category
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenuOld, "@css/generic");
|
||||
|
||||
private object CreateMenuOld(CCSPlayerController admin)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers("Title", "cat", admin, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### Use Per-Player Translations
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Each player sees their language
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"translation_key",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
// ❌ Bad - Single language for all
|
||||
Server.PrintToChatAll($"{admin?.PlayerName} did something");
|
||||
```
|
||||
|
||||
### Provide English Fallbacks
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Fallback if translation missing
|
||||
_api.RegisterMenuCategory(
|
||||
"mycat",
|
||||
Localizer?["category_name"] ?? "Default Category Name",
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
// ❌ Bad - No fallback
|
||||
_api.RegisterMenuCategory(
|
||||
"mycat",
|
||||
Localizer["category_name"], // Crashes if no translation!
|
||||
"@css/generic"
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
### Cache Expensive Operations
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Cache on first access
|
||||
private static Dictionary<int, string>? _itemCache;
|
||||
|
||||
private static Dictionary<int, string> GetItemCache()
|
||||
{
|
||||
if (_itemCache != null) return _itemCache;
|
||||
|
||||
// Build cache once
|
||||
_itemCache = new Dictionary<int, string>();
|
||||
// ... populate
|
||||
return _itemCache;
|
||||
}
|
||||
|
||||
// ❌ Bad - Rebuild every time
|
||||
private Dictionary<int, string> GetItems()
|
||||
{
|
||||
var items = new Dictionary<int, string>();
|
||||
// ... expensive operation
|
||||
return items;
|
||||
}
|
||||
```
|
||||
|
||||
### Efficient LINQ Queries
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Single query
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.IsValid && !p.IsBot && p.PawnIsAlive)
|
||||
.ToList();
|
||||
|
||||
// ❌ Bad - Multiple iterations
|
||||
var players = _api.GetValidPlayers();
|
||||
players = players.Where(p => p.IsValid).ToList();
|
||||
players = players.Where(p => !p.IsBot).ToList();
|
||||
players = players.Where(p => p.PawnIsAlive).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Log Errors
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Detailed logging
|
||||
try
|
||||
{
|
||||
DoAction();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to perform action: {ex.Message}");
|
||||
Logger.LogError($"Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
|
||||
// ❌ Bad - Silent failure
|
||||
try
|
||||
{
|
||||
DoAction();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
```
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Continue with reduced functionality
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("SimpleAdmin API not found - limited functionality!");
|
||||
// Module still loads, just without SimpleAdmin integration
|
||||
return;
|
||||
}
|
||||
|
||||
// ❌ Bad - Crash the entire module
|
||||
_api = _pluginCapability.Get() ?? throw new Exception("No API!");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
### Validate Admin Permissions
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Check permissions
|
||||
[RequiresPermissions("@css/ban")]
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Already validated by attribute
|
||||
}
|
||||
|
||||
// ❌ Bad - No permission check
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Anyone can use this!
|
||||
}
|
||||
```
|
||||
|
||||
### Check Immunity
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Check immunity
|
||||
if (admin != null && !admin.CanTarget(target))
|
||||
{
|
||||
admin.PrintToChat($"Cannot target {target.PlayerName}!");
|
||||
return;
|
||||
}
|
||||
|
||||
// ❌ Bad - Ignore immunity
|
||||
DoAction(target); // Can target higher immunity!
|
||||
```
|
||||
|
||||
### Sanitize Input
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Validate and sanitize
|
||||
private void OnSetValueCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (!int.TryParse(command.GetArg(1), out int value))
|
||||
{
|
||||
caller?.PrintToChat("Invalid number!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value < 0 || value > 1000)
|
||||
{
|
||||
caller?.PrintToChat("Value must be between 0 and 1000!");
|
||||
return;
|
||||
}
|
||||
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
// ❌ Bad - No validation
|
||||
private void OnSetValueCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var value = int.Parse(command.GetArg(1)); // Can crash!
|
||||
SetValue(value); // No range check!
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
### Comment Complex Logic
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Explain why, not what
|
||||
// We need to check immunity twice because player state can change
|
||||
// between menu creation and action execution
|
||||
if (!admin.CanTarget(player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ❌ Bad - States the obvious
|
||||
// Check if admin can target player
|
||||
if (!admin.CanTarget(player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### XML Documentation
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Toggles god mode for the specified player.
|
||||
/// </summary>
|
||||
/// <param name="admin">Admin performing the action (null for console)</param>
|
||||
/// <param name="target">Player to toggle god mode for</param>
|
||||
/// <returns>True if god mode is now enabled, false otherwise</returns>
|
||||
public bool ToggleGodMode(CCSPlayerController? admin, CCSPlayerController target)
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Edge Cases
|
||||
|
||||
```csharp
|
||||
// Test with:
|
||||
// - Invalid players
|
||||
// - Disconnected players
|
||||
// - Players who changed teams
|
||||
// - Null admins (console)
|
||||
// - Silent admins
|
||||
// - Players with higher immunity
|
||||
```
|
||||
|
||||
### Test Hot Reload
|
||||
|
||||
```bash
|
||||
# Server console
|
||||
css_plugins reload YourModule
|
||||
```
|
||||
|
||||
Make sure everything works after reload!
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### ❌ Forgetting to Unsubscribe
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
// Missing unsubscribe = memory leak!
|
||||
// _api.OnSimpleAdminReady -= RegisterMenus; ← FORGOT THIS
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Not Checking API Availability
|
||||
|
||||
```csharp
|
||||
// Crashes if SimpleAdmin not loaded!
|
||||
_api.RegisterCommand(...); // ← No null check
|
||||
```
|
||||
|
||||
### ❌ Hardcoding Strings
|
||||
|
||||
```csharp
|
||||
// Bad - not translatable
|
||||
player.PrintToChat("You have been banned!");
|
||||
|
||||
// Good - uses translations
|
||||
var message = Localizer?["ban_message"] ?? "You have been banned!";
|
||||
player.PrintToChat(message);
|
||||
```
|
||||
|
||||
### ❌ Blocking Game Thread
|
||||
|
||||
```csharp
|
||||
// Bad - blocks game thread
|
||||
Thread.Sleep(5000);
|
||||
|
||||
// Good - use CounterStrikeSharp timers
|
||||
AddTimer(5.0f, () => DoAction());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
Study the **Fun Commands Module** for best practices:
|
||||
|
||||
**[View Source](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)**
|
||||
|
||||
Shows:
|
||||
- ✅ Proper code organization
|
||||
- ✅ Configuration best practices
|
||||
- ✅ Menu creation with context
|
||||
- ✅ Per-player translations
|
||||
- ✅ Proper cleanup
|
||||
- ✅ Error handling
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Examples](examples)** - More code examples
|
||||
- **[API Reference](../api/overview)** - Full API documentation
|
||||
- **[GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Browse source code
|
||||
552
CS2-SimpleAdmin-docs/docs/developer/module/examples.md
Normal file
@@ -0,0 +1,552 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Code Examples
|
||||
|
||||
Practical examples for common module development scenarios.
|
||||
|
||||
## Complete Mini Module
|
||||
|
||||
A fully working minimal module:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace HelloModule;
|
||||
|
||||
public class HelloModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "Hello Module";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register command
|
||||
foreach (var cmd in Config.HelloCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "Say hello to a player", OnHelloCommand);
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnHelloCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
player.PrintToChat($"Hello {player.PlayerName}!");
|
||||
caller?.PrintToChat($"Said hello to {player.PlayerName}");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config) => Config = config;
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
foreach (var cmd in Config.HelloCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
public List<string> HelloCommands { get; set; } = ["css_hello"];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command Examples
|
||||
|
||||
### Simple Target Command
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
private void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
if (player.PawnIsAlive)
|
||||
{
|
||||
player.PlayerPawn?.Value?.CommitSuicide(false, true);
|
||||
caller?.PrintToChat($"Slayed {player.PlayerName}");
|
||||
}
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
### Command with Value Parameter
|
||||
|
||||
```csharp
|
||||
[CommandHelper(2, "<#userid or name> <value>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Parse HP value
|
||||
if (!int.TryParse(command.GetArg(2), out int hp) || hp < 1 || hp > 999)
|
||||
{
|
||||
caller?.PrintToChat("Invalid HP! Use 1-999");
|
||||
return;
|
||||
}
|
||||
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && p.PawnIsAlive && caller!.CanTarget(p)))
|
||||
{
|
||||
player.PlayerPawn?.Value?.SetHealth(hp);
|
||||
caller?.PrintToChat($"Set {player.PlayerName} HP to {hp}");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, $"css_sethp {hp}");
|
||||
}
|
||||
```
|
||||
|
||||
### Command with Penalty
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name> [duration] [reason]")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Parse duration (default: 60 minutes)
|
||||
int duration = 60;
|
||||
if (command.ArgCount > 2)
|
||||
{
|
||||
int.TryParse(command.GetArg(2), out duration);
|
||||
}
|
||||
|
||||
// Get reason (default: "Banned")
|
||||
string reason = command.ArgCount > 3
|
||||
? string.Join(" ", command.ArgString.Split(' ').Skip(2))
|
||||
: "Banned";
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
_api.IssuePenalty(player, caller, PenaltyType.Ban, reason, duration);
|
||||
caller?.PrintToChat($"Banned {player.PlayerName} for {duration} minutes");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Examples
|
||||
|
||||
### Simple Player Selection Menu
|
||||
|
||||
```csharp
|
||||
private void RegisterMenus()
|
||||
{
|
||||
_api!.RegisterMenuCategory("actions", "Player Actions", "@css/generic");
|
||||
|
||||
_api.RegisterMenu(
|
||||
"actions",
|
||||
"kick",
|
||||
"Kick Player",
|
||||
CreateKickMenu,
|
||||
"@css/kick"
|
||||
);
|
||||
}
|
||||
|
||||
private object CreateKickMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
Server.ExecuteCommand($"css_kick #{target.UserId} Kicked via menu");
|
||||
admin.PrintToChat($"Kicked {target.PlayerName}");
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Menu (Player → Action)
|
||||
|
||||
```csharp
|
||||
private object CreatePlayerActionsMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
foreach (var player in _api.GetValidPlayers().Where(p => admin.CanTarget(p)))
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateActionSelectMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateActionSelectMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Actions: {target.PlayerName}", "actions", admin);
|
||||
|
||||
_api.AddMenuOption(menu, "Slay", _ =>
|
||||
{
|
||||
if (target.IsValid && target.PawnIsAlive)
|
||||
{
|
||||
target.PlayerPawn?.Value?.CommitSuicide(false, true);
|
||||
admin.PrintToChat($"Slayed {target.PlayerName}");
|
||||
}
|
||||
});
|
||||
|
||||
_api.AddMenuOption(menu, "Kick", _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
Server.ExecuteCommand($"css_kick #{target.UserId}");
|
||||
}
|
||||
});
|
||||
|
||||
_api.AddMenuOption(menu, "Ban", _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
_api.IssuePenalty(target, admin, PenaltyType.Ban, "Banned via menu", 1440);
|
||||
}
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
### Menu with Value Selection
|
||||
|
||||
```csharp
|
||||
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateHpValueMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "actions", admin);
|
||||
|
||||
var hpValues = new[] { 1, 10, 50, 100, 200, 500 };
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
_api.AddMenuOption(menu, $"{hp} HP", _ =>
|
||||
{
|
||||
if (target.IsValid && target.PawnIsAlive)
|
||||
{
|
||||
target.PlayerPawn?.Value?.SetHealth(hp);
|
||||
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Event Examples
|
||||
|
||||
### React to Bans
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
Logger.LogInformation($"Ban: {adminName} -> {player.PlayerName} ({duration}m): {reason}");
|
||||
|
||||
// Log to file
|
||||
File.AppendAllText("bans.log",
|
||||
$"[{DateTime.Now}] {player.PlayerName} banned by {adminName} for {duration}m: {reason}\n");
|
||||
}
|
||||
```
|
||||
|
||||
### Warning Escalation
|
||||
|
||||
```csharp
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Warn) return;
|
||||
|
||||
Logger.LogInformation($"{player.PlayerName} has {player.Warnings} warnings");
|
||||
|
||||
// Auto-ban at 3 warnings
|
||||
if (player.Warnings >= 3)
|
||||
{
|
||||
var controller = Utilities.GetPlayers()
|
||||
.FirstOrDefault(p => p.SteamID == player.SteamId);
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
_api!.IssuePenalty(
|
||||
controller,
|
||||
null,
|
||||
PenaltyType.Ban,
|
||||
"Automatic: 3 warnings",
|
||||
1440 // 1 day
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translation Examples
|
||||
|
||||
### Module with Translations
|
||||
|
||||
**lang/en.json:**
|
||||
```json
|
||||
{
|
||||
"category_name": "My Module",
|
||||
"menu_name": "My Action",
|
||||
"action_message": "{lightred}{0}{default} performed action on {lightred}{1}{default}!",
|
||||
"error_invalid_player": "{red}Error:{default} Invalid player!",
|
||||
"success": "{green}Success!{default} Action completed."
|
||||
}
|
||||
```
|
||||
|
||||
**Code:**
|
||||
```csharp
|
||||
private void PerformAction(CCSPlayerController? admin, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
DoSomething(target);
|
||||
|
||||
// Show activity with translation
|
||||
if (admin == null || !_api!.IsAdminSilent(admin))
|
||||
{
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"action_message",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
admin?.PlayerName ?? "Console",
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Send success message
|
||||
admin?.PrintToChat(Localizer?["success"] ?? "Success!");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Utility Examples
|
||||
|
||||
### Get Players by Team
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetTeamPlayers(CsTeam team)
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => p.Team == team)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Usage
|
||||
var ctPlayers = GetTeamPlayers(CsTeam.CounterTerrorist);
|
||||
var tPlayers = GetTeamPlayers(CsTeam.Terrorist);
|
||||
```
|
||||
|
||||
### Get Alive Players
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetAlivePlayers()
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive)
|
||||
.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
### Notify Admins
|
||||
|
||||
```csharp
|
||||
private void NotifyAdmins(string message, string permission = "@css/generic")
|
||||
{
|
||||
var admins = _api!.GetValidPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, permission));
|
||||
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
admin.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
NotifyAdmins("⚠ Important admin message", "@css/root");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Timer Examples
|
||||
|
||||
### Delayed Action
|
||||
|
||||
```csharp
|
||||
private void DelayedAction(CCSPlayerController player, float delay)
|
||||
{
|
||||
AddTimer(delay, () =>
|
||||
{
|
||||
if (player.IsValid && player.PawnIsAlive)
|
||||
{
|
||||
DoAction(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Repeating Timer
|
||||
|
||||
```csharp
|
||||
private void StartRepeatingAction()
|
||||
{
|
||||
AddTimer(1.0f, () =>
|
||||
{
|
||||
foreach (var player in _api!.GetValidPlayers())
|
||||
{
|
||||
if (player.PawnIsAlive)
|
||||
{
|
||||
UpdatePlayer(player);
|
||||
}
|
||||
}
|
||||
}, TimerFlags.REPEAT);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Multiple Feature Toggles
|
||||
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[JsonPropertyName("EnableFeature1")]
|
||||
public bool EnableFeature1 { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("EnableFeature2")]
|
||||
public bool EnableFeature2 { get; set; } = false;
|
||||
|
||||
[JsonPropertyName("Feature1Commands")]
|
||||
public List<string> Feature1Commands { get; set; } = ["css_feature1"];
|
||||
|
||||
[JsonPropertyName("Feature2Commands")]
|
||||
public List<string> Feature2Commands { get; set; } = ["css_feature2"];
|
||||
|
||||
[JsonPropertyName("MaxValue")]
|
||||
public int MaxValue { get; set; } = 100;
|
||||
}
|
||||
|
||||
// Usage
|
||||
private void RegisterCommands()
|
||||
{
|
||||
if (Config.EnableFeature1)
|
||||
{
|
||||
foreach (var cmd in Config.Feature1Commands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Feature 1", OnFeature1Command);
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.EnableFeature2)
|
||||
{
|
||||
foreach (var cmd in Config.Feature2Commands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Feature 2", OnFeature2Command);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Best Practices](best-practices)** - Write better code
|
||||
- **[Getting Started](getting-started)** - Create your first module
|
||||
- **[API Reference](../api/overview)** - Full API documentation
|
||||
- **[Fun Commands Source](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Complete reference implementation
|
||||
282
CS2-SimpleAdmin-docs/docs/developer/module/getting-started.md
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Getting Started with Module Development
|
||||
|
||||
Step-by-step guide to creating your first CS2-SimpleAdmin module.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin:
|
||||
|
||||
- C# knowledge (intermediate level)
|
||||
- .NET 8.0 SDK installed
|
||||
- Visual Studio 2022 or VS Code
|
||||
- Basic understanding of CounterStrikeSharp
|
||||
- CS2 dedicated server for testing
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Project
|
||||
|
||||
### Using .NET CLI
|
||||
|
||||
```bash
|
||||
dotnet new classlib -n MyModule -f net8.0
|
||||
cd MyModule
|
||||
```
|
||||
|
||||
### Using Visual Studio
|
||||
|
||||
1. File → New → Project
|
||||
2. Select "Class Library (.NET 8.0)"
|
||||
3. Name: `MyModule`
|
||||
4. Click Create
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add References
|
||||
|
||||
Edit `MyModule.csproj`:
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="CounterStrikeSharp.API">
|
||||
<HintPath>path/to/CounterStrikeSharp.API.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>path/to/CS2-SimpleAdminApi.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Create Main Plugin Class
|
||||
|
||||
Create `MyModule.cs`:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace MyModule;
|
||||
|
||||
public class MyModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "My Module";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "Your Name";
|
||||
public override string ModuleDescription => "My awesome module";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
|
||||
new("simpleadmin:api");
|
||||
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
// Get SimpleAdmin API
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInformation("MyModule loaded successfully!");
|
||||
|
||||
// Register features
|
||||
RegisterCommands();
|
||||
|
||||
// Register menus when ready
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Also call for hot reload
|
||||
}
|
||||
|
||||
private void RegisterCommands()
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "My command description", OnMyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterMenus()
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "mymenu", "My Menu", CreateMyMenu, "@css/generic");
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
player.PrintToChat($"Hello from MyModule!");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
target.PrintToChat("You were selected!");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister commands
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
|
||||
// Unregister menus
|
||||
_api.UnregisterMenu("mymodule", "mymenu");
|
||||
|
||||
// Unsubscribe events
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Create Configuration
|
||||
|
||||
Create `Config.cs`:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("Version")]
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[JsonPropertyName("MyCommands")]
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
[JsonPropertyName("EnableFeature")]
|
||||
public bool EnableFeature { get; set; } = true;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Build and Deploy
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
Copy files to server:
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/MyModule/
|
||||
└── MyModule.dll
|
||||
```
|
||||
|
||||
### Restart Server
|
||||
|
||||
```bash
|
||||
# Server console
|
||||
css_plugins reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Test
|
||||
|
||||
1. Join your server
|
||||
2. Open admin menu: `css_admin`
|
||||
3. Look for "My Module" category
|
||||
4. Test command: `css_mycommand @me`
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Best Practices](best-practices)** - Write better code
|
||||
- **[Examples](examples)** - More code examples
|
||||
- **[API Reference](../api/overview)** - Full API documentation
|
||||
- **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Reference implementation
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### API Not Found
|
||||
|
||||
**Error:** `CS2-SimpleAdmin API not found!`
|
||||
|
||||
**Solution:**
|
||||
- Ensure CS2-SimpleAdmin is installed
|
||||
- Check that CS2-SimpleAdminApi.dll is in shared folder
|
||||
- Verify CS2-SimpleAdmin loads before your module
|
||||
|
||||
### Commands Not Working
|
||||
|
||||
**Check:**
|
||||
- Command registered in `RegisterCommands()`
|
||||
- Permission is correct
|
||||
- Player has required permission
|
||||
|
||||
### Menu Not Showing
|
||||
|
||||
**Check:**
|
||||
- `OnSimpleAdminReady` event subscribed
|
||||
- Menu registered in category
|
||||
- Permission is correct
|
||||
- SimpleAdmin loaded successfully
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **[Module Development Guide](../../modules/development)** - Detailed guide
|
||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS framework
|
||||
47
CS2-SimpleAdmin-docs/docs/intro.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Tutorial Intro
|
||||
|
||||
Let's discover **Docusaurus in less than 5 minutes**.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Get started by **creating a new site**.
|
||||
|
||||
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
|
||||
|
||||
### What you'll need
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above:
|
||||
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||
|
||||
## Generate a new site
|
||||
|
||||
Generate a new Docusaurus site using the **classic template**.
|
||||
|
||||
The classic template will automatically be added to your project after you run the command:
|
||||
|
||||
```bash
|
||||
npm init docusaurus@latest my-website classic
|
||||
```
|
||||
|
||||
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
|
||||
|
||||
The command also installs all necessary dependencies you need to run Docusaurus.
|
||||
|
||||
## Start your site
|
||||
|
||||
Run the development server:
|
||||
|
||||
```bash
|
||||
cd my-website
|
||||
npm run start
|
||||
```
|
||||
|
||||
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
|
||||
|
||||
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
|
||||
|
||||
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
|
||||
799
CS2-SimpleAdmin-docs/docs/modules/development.md
Normal file
@@ -0,0 +1,799 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Module Development
|
||||
|
||||
Learn how to create your own CS2-SimpleAdmin modules.
|
||||
|
||||
## Introduction
|
||||
|
||||
Creating modules for CS2-SimpleAdmin allows you to extend the plugin's functionality while keeping your code separate and maintainable.
|
||||
|
||||
:::tip Reference Implementation
|
||||
The **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** serves as a complete reference implementation. Study its code to learn best practices!
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Knowledge Required
|
||||
|
||||
- C# programming (intermediate level)
|
||||
- .NET 8.0
|
||||
- CounterStrikeSharp basics
|
||||
- Understanding of CS2-SimpleAdmin structure
|
||||
|
||||
### Tools Needed
|
||||
|
||||
- Visual Studio 2022 or VS Code
|
||||
- .NET 8.0 SDK
|
||||
- CS2 server for testing
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create Project
|
||||
|
||||
```bash
|
||||
dotnet new classlib -n YourModuleName -f net8.0
|
||||
cd YourModuleName
|
||||
```
|
||||
|
||||
### 2. Add References
|
||||
|
||||
Edit your `.csproj` file:
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- CounterStrikeSharp -->
|
||||
<Reference Include="CounterStrikeSharp.API">
|
||||
<HintPath>path/to/CounterStrikeSharp.API.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
|
||||
<!-- CS2-SimpleAdmin API -->
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>path/to/CS2-SimpleAdminApi.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
### 3. Create Main Plugin Class
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace YourModuleName;
|
||||
|
||||
public class YourModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "Your Module Name";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "Your Name";
|
||||
public override string ModuleDescription => "Description";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
// Get SimpleAdmin API
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register your commands and menus
|
||||
RegisterCommands();
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Fallback for hot reload
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
private void RegisterCommands()
|
||||
{
|
||||
// Register commands here
|
||||
}
|
||||
|
||||
private void RegisterMenus()
|
||||
{
|
||||
// Register menus here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Structure
|
||||
|
||||
### Recommended File Organization
|
||||
|
||||
```
|
||||
YourModuleName/
|
||||
├── YourModule.cs # Main plugin class
|
||||
├── Config.cs # Configuration
|
||||
├── Commands.cs # Command handlers (partial class)
|
||||
├── Menus.cs # Menu creation (partial class)
|
||||
├── Actions.cs # Core logic (partial class)
|
||||
├── lang/ # Translations
|
||||
│ ├── en.json
|
||||
│ ├── pl.json
|
||||
│ └── ...
|
||||
└── YourModuleName.csproj
|
||||
```
|
||||
|
||||
### Using Partial Classes
|
||||
|
||||
Split your code for better organization:
|
||||
|
||||
```csharp
|
||||
// YourModule.cs
|
||||
public partial class YourModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
// Plugin initialization
|
||||
}
|
||||
|
||||
// Commands.cs
|
||||
public partial class YourModule
|
||||
{
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Command logic
|
||||
}
|
||||
}
|
||||
|
||||
// Menus.cs
|
||||
public partial class YourModule
|
||||
{
|
||||
private object CreateMyMenu(CCSPlayerController player, MenuContext context)
|
||||
{
|
||||
// Menu creation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Create Config Class
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("Version")]
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[JsonPropertyName("MyCommands")]
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
[JsonPropertyName("EnableFeature")]
|
||||
public bool EnableFeature { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("MaxValue")]
|
||||
public int MaxValue { get; set; } = 100;
|
||||
}
|
||||
```
|
||||
|
||||
### Config Best Practices
|
||||
|
||||
1. **Use command lists** - Allow users to add aliases or disable features
|
||||
2. **Provide defaults** - Sensible default values
|
||||
3. **Version your config** - Track config changes
|
||||
4. **Document settings** - Clear property names
|
||||
|
||||
---
|
||||
|
||||
## Registering Commands
|
||||
|
||||
### Basic Command Registration
|
||||
|
||||
```csharp
|
||||
private void RegisterCommands()
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "Command description", OnMyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Get target players
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Filter for valid players
|
||||
var players = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot)
|
||||
.ToList();
|
||||
|
||||
// Process each player
|
||||
foreach (var player in players)
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
DoSomething(caller, player);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the command
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
### Command Cleanup
|
||||
|
||||
Always unregister commands when unloading:
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating Menus
|
||||
|
||||
### Register Menu Category
|
||||
|
||||
```csharp
|
||||
private void RegisterMenus()
|
||||
{
|
||||
if (_api == null || _menusRegistered) return;
|
||||
|
||||
// Register category
|
||||
_api.RegisterMenuCategory(
|
||||
"mycategory",
|
||||
Localizer?["category_name"] ?? "My Category",
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
// Register menu
|
||||
_api.RegisterMenu(
|
||||
"mycategory",
|
||||
"mymenu",
|
||||
Localizer?["menu_name"] ?? "My Menu",
|
||||
CreateMyMenu,
|
||||
"@css/generic",
|
||||
"css_mycommand" // For permission override
|
||||
);
|
||||
|
||||
_menusRegistered = true;
|
||||
}
|
||||
```
|
||||
|
||||
### Menu with Player Selection (NEW API)
|
||||
|
||||
```csharp
|
||||
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// Context contains: CategoryId, MenuId, MenuTitle, Permission, CommandName
|
||||
// No need to repeat "mycategory" and "My Menu" here!
|
||||
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // ← Automatically uses menu title and category
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) => DoSomethingToPlayer(admin, target)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Menu with Custom Options
|
||||
|
||||
```csharp
|
||||
private object CreateValueSelectionMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var values = new[] { 10, 25, 50, 100, 200 };
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
_api.AddMenuOption(menu, $"{value} points", player =>
|
||||
{
|
||||
GivePoints(player, value);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Menus
|
||||
|
||||
```csharp
|
||||
private object CreatePlayerSelectionMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateValueMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Select value for {target.PlayerName}", "mycategory", admin);
|
||||
|
||||
// Add options...
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
|
||||
### Create Translation Files
|
||||
|
||||
Create `lang/en.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"command_success": "{green}Success! {default}Action performed on {lightred}{0}",
|
||||
"command_failed": "{red}Failed! {default}Could not perform action",
|
||||
"menu_title": "My Custom Menu"
|
||||
}
|
||||
```
|
||||
|
||||
### Use Translations in Code
|
||||
|
||||
```csharp
|
||||
// In commands
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Using module's own localizer for per-player language
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"command_success",
|
||||
caller?.PlayerName,
|
||||
false,
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Language Support
|
||||
|
||||
Create files for each language:
|
||||
- `lang/en.json` - English
|
||||
- `lang/pl.json` - Polish
|
||||
- `lang/ru.json` - Russian
|
||||
- `lang/de.json` - German
|
||||
- etc.
|
||||
|
||||
---
|
||||
|
||||
## Working with API
|
||||
|
||||
### Issue Penalties
|
||||
|
||||
```csharp
|
||||
// Ban online player
|
||||
_api!.IssuePenalty(
|
||||
player,
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
"Cheating",
|
||||
1440 // 1 day in minutes
|
||||
);
|
||||
|
||||
// Ban offline player by SteamID
|
||||
_api!.IssuePenalty(
|
||||
new SteamID(76561198012345678),
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
"Ban evasion",
|
||||
0 // Permanent
|
||||
);
|
||||
|
||||
// Other penalty types
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Gag, "Chat spam", 30);
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Mute, "Mic spam", 60);
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Silence, "Total abuse", 120);
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Warn, "Rule break");
|
||||
```
|
||||
|
||||
### Get Player Information
|
||||
|
||||
```csharp
|
||||
// Get player info with penalty data
|
||||
var playerInfo = _api!.GetPlayerInfo(player);
|
||||
|
||||
Console.WriteLine($"Player: {playerInfo.PlayerName}");
|
||||
Console.WriteLine($"SteamID: {playerInfo.SteamId}");
|
||||
Console.WriteLine($"Warnings: {playerInfo.Warnings}");
|
||||
|
||||
// Get player mute status
|
||||
var muteStatus = _api!.GetPlayerMuteStatus(player);
|
||||
|
||||
if (muteStatus.ContainsKey(PenaltyType.Gag))
|
||||
{
|
||||
Console.WriteLine("Player is gagged");
|
||||
}
|
||||
```
|
||||
|
||||
### Check Admin Status
|
||||
|
||||
```csharp
|
||||
// Check if admin is in silent mode
|
||||
if (_api!.IsAdminSilent(admin))
|
||||
{
|
||||
// Don't broadcast this action
|
||||
}
|
||||
|
||||
// Get all silent admins
|
||||
var silentAdmins = _api!.ListSilentAdminsSlots();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Events
|
||||
|
||||
### Subscribe to Events
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
|
||||
// Subscribe to events
|
||||
_api.OnSimpleAdminReady += OnSimpleAdminReady;
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
_api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
}
|
||||
|
||||
private void OnSimpleAdminReady()
|
||||
{
|
||||
Logger.LogInformation("SimpleAdmin is ready!");
|
||||
RegisterMenus();
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(PlayerInfo player, PlayerInfo? admin,
|
||||
PenaltyType type, string reason, int duration, int? penaltyId, int? serverId)
|
||||
{
|
||||
Logger.LogInformation($"{player.PlayerName} received {type} for {reason}");
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltiedAdded(SteamID steamId, PlayerInfo? admin,
|
||||
PenaltyType type, string reason, int duration, int? penaltyId, int? serverId)
|
||||
{
|
||||
Logger.LogInformation($"Offline ban added to {steamId}");
|
||||
}
|
||||
|
||||
private void OnAdminShowActivity(string messageKey, string? callerName,
|
||||
bool dontPublish, object messageArgs)
|
||||
{
|
||||
// React to admin activity
|
||||
}
|
||||
```
|
||||
|
||||
### Unsubscribe on Unload
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.OnSimpleAdminReady -= OnSimpleAdminReady;
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
// ... unsubscribe all events
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Check for Null
|
||||
|
||||
```csharp
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("API not available!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Validate Player State
|
||||
|
||||
```csharp
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Check Target Permissions
|
||||
|
||||
```csharp
|
||||
if (!caller.CanTarget(target))
|
||||
{
|
||||
// caller can't target this player (immunity)
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Log Commands
|
||||
|
||||
```csharp
|
||||
_api.LogCommand(caller, command);
|
||||
// or
|
||||
_api.LogCommand(caller, $"css_mycommand {player.PlayerName}");
|
||||
```
|
||||
|
||||
### 5. Use Per-Player Translations
|
||||
|
||||
```csharp
|
||||
// Each player sees message in their language!
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"translation_key",
|
||||
callerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
```
|
||||
|
||||
### 6. Clean Up Resources
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
// Unregister commands
|
||||
// Unregister menus
|
||||
// Unsubscribe events
|
||||
// Dispose resources
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Player Targeting Helper
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetTargets(CommandInfo command, CCSPlayerController? caller)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return new List<CCSPlayerController>();
|
||||
|
||||
return targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
### Menu Context Pattern (NEW!)
|
||||
|
||||
```csharp
|
||||
// ✅ NEW: Use context to avoid duplication
|
||||
private object CreateMenu(CCSPlayerController player, MenuContext context)
|
||||
{
|
||||
// context.MenuTitle, context.CategoryId already set!
|
||||
return _api!.CreateMenuWithPlayers(context, player, filter, action);
|
||||
}
|
||||
|
||||
// ❌ OLD: Had to repeat title and category
|
||||
private object CreateMenu(CCSPlayerController player)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers("My Menu", "mycategory", player, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### Action with Activity Message
|
||||
|
||||
```csharp
|
||||
private void DoAction(CCSPlayerController? caller, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
// ...
|
||||
|
||||
// Show activity
|
||||
if (caller == null || !_api!.IsAdminSilent(caller))
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"action_message",
|
||||
caller?.PlayerName,
|
||||
false,
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
|
||||
// Log action
|
||||
_api!.LogCommand(caller, $"css_action {target.PlayerName}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Module
|
||||
|
||||
### 1. Build
|
||||
|
||||
```bash
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
### 2. Copy to Server
|
||||
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/YourModuleName/
|
||||
```
|
||||
|
||||
### 3. Test
|
||||
|
||||
- Start server
|
||||
- Check console for load messages
|
||||
- Test commands
|
||||
- Test menus
|
||||
- Check translations
|
||||
|
||||
### 4. Debug
|
||||
|
||||
Enable detailed logging:
|
||||
```csharp
|
||||
Logger.LogInformation("Debug: ...");
|
||||
Logger.LogWarning("Warning: ...");
|
||||
Logger.LogError("Error: ...");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example: Complete Mini-Module
|
||||
|
||||
Here's a complete working example:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace ExampleModule;
|
||||
|
||||
public class ExampleModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "Example Module";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register command
|
||||
if (Config.ExampleCommands.Count > 0)
|
||||
{
|
||||
foreach (var cmd in Config.ExampleCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "Example command", OnExampleCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnExampleCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var target in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
// Do something to target
|
||||
caller?.PrintToChat($"Performed action on {target.PlayerName}");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.ExampleCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
public List<string> ExampleCommands { get; set; } = ["css_example"];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Study Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Complete reference
|
||||
- **[Read API Documentation](../developer/api/overview)** - Full API reference
|
||||
- **[Check Examples](../developer/module/examples)** - More code examples
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **[CS2-SimpleAdmin GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS documentation
|
||||
- **[Module Development Guide](../developer/module/getting-started)** - Detailed guide
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- **Issues:** [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
- **Discussions:** [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
- **Examples:** Study official modules for reference
|
||||
691
CS2-SimpleAdmin-docs/docs/modules/funcommands.md
Normal file
@@ -0,0 +1,691 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Fun Commands Module
|
||||
|
||||
Add entertaining and powerful player manipulation commands to your server.
|
||||
|
||||
## Overview
|
||||
|
||||
The Fun Commands module extends CS2-SimpleAdmin with commands for god mode, noclip, freeze, respawn, weapon management, and player attribute modification.
|
||||
|
||||
**Module Name:** `CS2-SimpleAdmin_FunCommands`
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- ⭐ God Mode - Make players invincible
|
||||
- 👻 No Clip - Allow players to fly through walls
|
||||
- 🧊 Freeze/Unfreeze - Freeze players in place
|
||||
- 🔄 Respawn - Bring dead players back
|
||||
- 🔫 Give Weapons - Provide any weapon to players
|
||||
- 🗑️ Strip Weapons - Remove all weapons
|
||||
- ❤️ Set HP - Modify player health
|
||||
- ⚡ Set Speed - Change movement speed
|
||||
- 🌙 Set Gravity - Modify gravity
|
||||
- 💰 Set Money - Adjust player money
|
||||
- 📏 Resize Player - Change player model size
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CS2-SimpleAdmin installed and working
|
||||
- CS2-SimpleAdminApi.dll in shared folder
|
||||
|
||||
### Install Steps
|
||||
|
||||
1. **Download** the module from releases
|
||||
|
||||
2. **Extract** to your server:
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/CS2-SimpleAdmin_FunCommands/
|
||||
```
|
||||
|
||||
3. **Restart** your server or reload plugins:
|
||||
```
|
||||
css_plugins reload
|
||||
```
|
||||
|
||||
4. **Verify** the module loaded:
|
||||
- Check server console for load message
|
||||
- Try `css_admin` and look for "Fun Commands" menu
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### God Mode
|
||||
|
||||
Toggle god mode (invincibility) for a player.
|
||||
|
||||
```bash
|
||||
css_god <#userid or name>
|
||||
css_godmode <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_god #123
|
||||
css_god PlayerName
|
||||
css_god @all # Toggle god mode for everyone
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player takes no damage
|
||||
- Toggles on/off with each use
|
||||
|
||||
---
|
||||
|
||||
### No Clip
|
||||
|
||||
Enable noclip mode (fly through walls).
|
||||
|
||||
```bash
|
||||
css_noclip <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_noclip #123
|
||||
css_noclip PlayerName
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player can fly
|
||||
- Can pass through walls
|
||||
- Gravity disabled
|
||||
- Toggles on/off with each use
|
||||
|
||||
---
|
||||
|
||||
### Freeze
|
||||
|
||||
Freeze a player in place.
|
||||
|
||||
```bash
|
||||
css_freeze <#userid or name> [duration]
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `duration` - Freeze duration in seconds (optional, default: permanent until unfreeze)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_freeze #123 # Freeze permanently
|
||||
css_freeze PlayerName 30 # Freeze for 30 seconds
|
||||
css_freeze @t 10 # Freeze all terrorists for 10 seconds
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player cannot move
|
||||
- Player cannot shoot
|
||||
- Auto-unfreezes after duration (if specified)
|
||||
|
||||
---
|
||||
|
||||
### Unfreeze
|
||||
|
||||
Unfreeze a frozen player.
|
||||
|
||||
```bash
|
||||
css_unfreeze <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unfreeze #123
|
||||
css_unfreeze PlayerName
|
||||
css_unfreeze @all # Unfreeze everyone
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Respawn
|
||||
|
||||
Respawn a dead player at last death position.
|
||||
|
||||
```bash
|
||||
css_respawn <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_respawn #123
|
||||
css_respawn PlayerName
|
||||
css_respawn @dead # Respawn all dead players
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player spawns at death point
|
||||
- Gets default weapons
|
||||
- Joins their team
|
||||
|
||||
---
|
||||
|
||||
### Give Weapon
|
||||
|
||||
Give a weapon to a player.
|
||||
|
||||
```bash
|
||||
css_give <#userid or name> <weapon>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Weapon names:**
|
||||
|
||||
**Rifles:**
|
||||
- `weapon_ak47` or `ak47`
|
||||
- `weapon_m4a1` or `m4a1`
|
||||
- `weapon_m4a1_silencer` or `m4a1_silencer`
|
||||
- `weapon_awp` or `awp`
|
||||
- `weapon_aug` or `aug`
|
||||
- `weapon_sg556` or `sg556`
|
||||
- `weapon_ssg08` or `ssg08` (Scout)
|
||||
- `weapon_g3sg1` or `g3sg1`
|
||||
- `weapon_scar20` or `scar20`
|
||||
|
||||
**SMGs:**
|
||||
- `weapon_mp5sd` or `mp5sd`
|
||||
- `weapon_mp7` or `mp7`
|
||||
- `weapon_mp9` or `mp9`
|
||||
- `weapon_mac10` or `mac10`
|
||||
- `weapon_p90` or `p90`
|
||||
- `weapon_ump45` or `ump45`
|
||||
- `weapon_bizon` or `bizon`
|
||||
|
||||
**Heavy:**
|
||||
- `weapon_nova` or `nova`
|
||||
- `weapon_xm1014` or `xm1014`
|
||||
- `weapon_mag7` or `mag7`
|
||||
- `weapon_sawedoff` or `sawedoff`
|
||||
- `weapon_m249` or `m249`
|
||||
- `weapon_negev` or `negev`
|
||||
|
||||
**Pistols:**
|
||||
- `weapon_deagle` or `deagle`
|
||||
- `weapon_elite` or `elite` (Dual Berettas)
|
||||
- `weapon_fiveseven` or `fiveseven`
|
||||
- `weapon_glock` or `glock`
|
||||
- `weapon_hkp2000` or `hkp2000`
|
||||
- `weapon_p250` or `p250`
|
||||
- `weapon_usp_silencer` or `usp_silencer`
|
||||
- `weapon_tec9` or `tec9`
|
||||
- `weapon_cz75a` or `cz75a`
|
||||
- `weapon_revolver` or `revolver`
|
||||
|
||||
**Grenades:**
|
||||
- `weapon_flashbang` or `flashbang`
|
||||
- `weapon_hegrenade` or `hegrenade`
|
||||
- `weapon_smokegrenade` or `smokegrenade`
|
||||
- `weapon_molotov` or `molotov`
|
||||
- `weapon_incgrenade` or `incgrenade`
|
||||
- `weapon_decoy` or `decoy`
|
||||
|
||||
**Equipment:**
|
||||
- `weapon_knife` or `knife`
|
||||
- `weapon_taser` or `taser`
|
||||
- `item_defuser` or `defuser`
|
||||
- `item_kevlar` or `kevlar`
|
||||
- `item_assaultsuit` or `assaultsuit`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_give #123 awp
|
||||
css_give PlayerName ak47
|
||||
css_give @ct m4a1
|
||||
css_give @all deagle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Strip Weapons
|
||||
|
||||
Remove all weapons from a player.
|
||||
|
||||
```bash
|
||||
css_strip <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_strip #123
|
||||
css_strip PlayerName
|
||||
css_strip @t # Disarm all terrorists
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Set HP
|
||||
|
||||
Set a player's health.
|
||||
|
||||
```bash
|
||||
css_hp <#userid or name> <health>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `health` - Health amount (1-999+)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_hp #123 100 # Full health
|
||||
css_hp PlayerName 200 # 200 HP
|
||||
css_hp @all 1 # 1 HP everyone
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `1` - 1 HP (one-shot mode)
|
||||
- `100` - Normal health
|
||||
- `200` - Double health
|
||||
- `500` - Tank mode
|
||||
|
||||
---
|
||||
|
||||
### Set Speed
|
||||
|
||||
Modify a player's movement speed.
|
||||
|
||||
```bash
|
||||
css_speed <#userid or name> <speed>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `speed` - Speed multiplier (0.1 - 10.0)
|
||||
- `1.0` = Normal speed
|
||||
- `2.0` = Double speed
|
||||
- `0.5` = Half speed
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_speed #123 1.5 # 50% faster
|
||||
css_speed PlayerName 0.5 # Slow motion
|
||||
css_speed @all 2.0 # Everyone fast
|
||||
css_speed #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.5` - Slow motion mode
|
||||
- `1.0` - Normal (reset)
|
||||
- `1.5` - Fast mode
|
||||
- `2.0` - Super fast
|
||||
- `3.0` - Extremely fast
|
||||
|
||||
---
|
||||
|
||||
### Set Gravity
|
||||
|
||||
Modify a player's gravity.
|
||||
|
||||
```bash
|
||||
css_gravity <#userid or name> <gravity>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `gravity` - Gravity multiplier (0.1 - 10.0)
|
||||
- `1.0` = Normal gravity
|
||||
- `0.5` = Moon jump
|
||||
- `2.0` = Heavy
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gravity #123 0.5 # Moon jump
|
||||
css_gravity PlayerName 0.1 # Super jump
|
||||
css_gravity @all 2.0 # Heavy gravity
|
||||
css_gravity #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.1` - Super high jumps
|
||||
- `0.5` - Moon gravity
|
||||
- `1.0` - Normal (reset)
|
||||
- `2.0` - Heavy/fast falling
|
||||
|
||||
---
|
||||
|
||||
### Set Money
|
||||
|
||||
Set a player's money amount.
|
||||
|
||||
```bash
|
||||
css_money <#userid or name> <amount>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `amount` - Money amount (0-65535)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_money #123 16000 # Max money
|
||||
css_money PlayerName 0 # Remove all money
|
||||
css_money @ct 10000 # Give all CTs $10,000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Resize Player
|
||||
|
||||
Change a player's model size.
|
||||
|
||||
```bash
|
||||
css_resize <#userid or name> <scale>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `scale` - Size scale (0.1 - 10.0)
|
||||
- `1.0` = Normal size
|
||||
- `0.5` = Half size
|
||||
- `2.0` = Double size
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_resize #123 0.5 # Tiny player
|
||||
css_resize PlayerName 2.0 # Giant player
|
||||
css_resize #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.5` - Tiny mode
|
||||
- `1.0` - Normal (reset)
|
||||
- `1.5` - Big
|
||||
- `2.0` - Giant
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration file location:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.json
|
||||
```
|
||||
|
||||
### Default Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": 1,
|
||||
"GodCommands": ["css_god", "css_godmode"],
|
||||
"NoclipCommands": ["css_noclip"],
|
||||
"FreezeCommands": ["css_freeze"],
|
||||
"UnfreezeCommands": ["css_unfreeze"],
|
||||
"RespawnCommands": ["css_respawn"],
|
||||
"GiveCommands": ["css_give"],
|
||||
"StripCommands": ["css_strip"],
|
||||
"HpCommands": ["css_hp"],
|
||||
"SpeedCommands": ["css_speed"],
|
||||
"GravityCommands": ["css_gravity"],
|
||||
"MoneyCommands": ["css_money"],
|
||||
"ResizeCommands": ["css_resize"]
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing Commands
|
||||
|
||||
**Add aliases:**
|
||||
```json
|
||||
"GodCommands": ["css_god", "css_godmode", "css_immortal"]
|
||||
```
|
||||
|
||||
**Disable feature:**
|
||||
```json
|
||||
"GodCommands": []
|
||||
```
|
||||
|
||||
**Rename command:**
|
||||
```json
|
||||
"NoclipCommands": ["css_fly"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Menu Integration
|
||||
|
||||
The module automatically adds a "Fun Commands" category to the admin menu with these options:
|
||||
|
||||
- God Mode
|
||||
- No Clip
|
||||
- Freeze
|
||||
- Respawn
|
||||
- Give Weapon
|
||||
- Strip Weapons
|
||||
- Set HP
|
||||
- Set Speed
|
||||
- Set Gravity
|
||||
- Set Money
|
||||
- Resize Player
|
||||
|
||||
**Access menu:**
|
||||
```bash
|
||||
css_admin # Navigate to "Fun Commands"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission System
|
||||
|
||||
### Permission Override
|
||||
|
||||
Admins can override command permissions using CounterStrikeSharp's admin system.
|
||||
|
||||
**Example:**
|
||||
If you want VIPs to use god mode:
|
||||
|
||||
1. **In admin config**, add permission override for `css_god`:
|
||||
```json
|
||||
{
|
||||
"css_god": ["@css/vip"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **VIPs will now see God Mode** in the menu
|
||||
|
||||
---
|
||||
|
||||
## Permissions Required
|
||||
|
||||
| Command | Default Permission | Description |
|
||||
|---------|------------------|-------------|
|
||||
| `css_god` | `@css/cheats` | God mode |
|
||||
| `css_noclip` | `@css/cheats` | No clip |
|
||||
| `css_freeze` | `@css/slay` | Freeze players |
|
||||
| `css_unfreeze` | `@css/slay` | Unfreeze players |
|
||||
| `css_respawn` | `@css/cheats` | Respawn players |
|
||||
| `css_give` | `@css/cheats` | Give weapons |
|
||||
| `css_strip` | `@css/slay` | Strip weapons |
|
||||
| `css_hp` | `@css/slay` | Set health |
|
||||
| `css_speed` | `@css/slay` | Set speed |
|
||||
| `css_gravity` | `@css/slay` | Set gravity |
|
||||
| `css_money` | `@css/slay` | Set money |
|
||||
| `css_resize` | `@css/slay` | Resize player |
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Fun Rounds
|
||||
|
||||
```bash
|
||||
# Low gravity, high speed round
|
||||
css_gravity @all 0.3
|
||||
css_speed @all 1.5
|
||||
|
||||
# One-shot mode
|
||||
css_hp @all 1
|
||||
css_give @all deagle
|
||||
|
||||
# Tiny players
|
||||
css_resize @all 0.5
|
||||
```
|
||||
|
||||
### Admin Events
|
||||
|
||||
```bash
|
||||
# Hide and seek (seekers)
|
||||
css_speed @ct 1.5
|
||||
css_hp @ct 200
|
||||
|
||||
# Hide and seek (hiders)
|
||||
css_resize @t 0.5
|
||||
css_speed @t 0.8
|
||||
```
|
||||
|
||||
### Testing & Debug
|
||||
|
||||
```bash
|
||||
# Test map navigation
|
||||
css_noclip @me
|
||||
css_god @me
|
||||
|
||||
# Test weapon balance
|
||||
css_give @me awp
|
||||
css_hp @me 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Competitive Balance
|
||||
|
||||
1. **Don't use during serious matches** - Breaks game balance
|
||||
2. **Announce fun rounds** - Let players know it's for fun
|
||||
3. **Reset after use** - Return to normal settings
|
||||
4. **Save for appropriate times** - End of night, special events
|
||||
|
||||
### Reset Commands
|
||||
|
||||
Always reset modifications after fun rounds:
|
||||
|
||||
```bash
|
||||
css_speed @all 1.0
|
||||
css_gravity @all 1.0
|
||||
css_resize @all 1.0
|
||||
```
|
||||
|
||||
### Permission Management
|
||||
|
||||
1. **Limit @css/cheats** - Only trusted admins
|
||||
2. **@css/slay is safer** - For HP/speed/gravity
|
||||
3. **Monitor usage** - Check logs for abuse
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Speed/Gravity not persisting
|
||||
|
||||
**Solution:**
|
||||
- These are maintained by a repeating timer
|
||||
- If they reset, reapply them
|
||||
- Check server console for timer errors
|
||||
|
||||
### God mode not working
|
||||
|
||||
**Check:**
|
||||
- Is player alive?
|
||||
- Check console for errors
|
||||
- Try toggling off and on
|
||||
|
||||
### Can't give weapons
|
||||
|
||||
**Check:**
|
||||
- Correct weapon name
|
||||
- Player is alive
|
||||
- Player has inventory space
|
||||
|
||||
### Noclip doesn't work
|
||||
|
||||
**Check:**
|
||||
- Player must be alive
|
||||
- sv_cheats doesn't need to be enabled
|
||||
- Check console for errors
|
||||
|
||||
---
|
||||
|
||||
## Module Development
|
||||
|
||||
This module serves as a **reference implementation** for creating CS2-SimpleAdmin modules.
|
||||
|
||||
**Key concepts demonstrated:**
|
||||
- Command registration from configuration
|
||||
- Menu creation with SimpleAdmin API
|
||||
- Per-player translation support
|
||||
- Proper cleanup on module unload
|
||||
- Code organization using partial classes
|
||||
|
||||
**[View source code](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** for implementation details.
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
|
||||
The module includes translations for 13 languages:
|
||||
|
||||
- English (en)
|
||||
- Polish (pl)
|
||||
- Russian (ru)
|
||||
- Portuguese (pt)
|
||||
- And 9 more...
|
||||
|
||||
Translation files location:
|
||||
```
|
||||
plugins/CS2-SimpleAdmin_FunCommands/lang/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[Player Commands](../user/commands/playercommands)** - Core player commands
|
||||
- **[Module Development](development)** - Create your own modules
|
||||
- **[API Reference](../developer/api/overview)** - CS2-SimpleAdmin API
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
**v1.0.0** - Initial release
|
||||
- God mode
|
||||
- Noclip
|
||||
- Freeze/Unfreeze
|
||||
- Respawn
|
||||
- Give/Strip weapons
|
||||
- HP/Speed/Gravity/Money
|
||||
- Resize player
|
||||
- Admin menu integration
|
||||
- 13 language support
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
**Issues:** [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
|
||||
**Questions:** [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
260
CS2-SimpleAdmin-docs/docs/modules/intro.md
Normal file
@@ -0,0 +1,260 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Modules Introduction
|
||||
|
||||
Extend CS2-SimpleAdmin functionality with powerful modules.
|
||||
|
||||
## What are Modules?
|
||||
|
||||
Modules are extensions that add new features to CS2-SimpleAdmin. They use the CS2-SimpleAdmin API to integrate seamlessly with the core plugin.
|
||||
|
||||
## Official Modules
|
||||
|
||||
### Fun Commands Module
|
||||
|
||||
Adds entertainment and player manipulation commands like god mode, noclip, freeze, and more.
|
||||
|
||||
**[Learn more →](funcommands)**
|
||||
|
||||
---
|
||||
|
||||
## Benefits of Modules
|
||||
|
||||
### 🔌 Easy Integration
|
||||
- Built on CS2-SimpleAdmin API
|
||||
- Automatic menu registration
|
||||
- Command system integration
|
||||
|
||||
### 🎨 Feature Separation
|
||||
- Keep core plugin lightweight
|
||||
- Add only features you need
|
||||
- Easy to enable/disable
|
||||
|
||||
### 🔧 Customizable
|
||||
- Configure each module independently
|
||||
- Disable unwanted commands
|
||||
- Customize permissions
|
||||
|
||||
### 📦 Simple Installation
|
||||
- Drop module files in folder
|
||||
- Restart server
|
||||
- Module auto-loads
|
||||
|
||||
---
|
||||
|
||||
## Installing Modules
|
||||
|
||||
### Standard Installation
|
||||
|
||||
1. **Download the module** from releases or build from source
|
||||
|
||||
2. **Extract to plugins folder:**
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/ModuleName/
|
||||
```
|
||||
|
||||
3. **Restart server** or reload plugins:
|
||||
```
|
||||
css_plugins reload
|
||||
```
|
||||
|
||||
4. **Configure** (if needed):
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/ModuleName/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Structure
|
||||
|
||||
Typical module structure:
|
||||
|
||||
```
|
||||
plugins/
|
||||
└── CS2-SimpleAdmin_ModuleName/
|
||||
├── CS2-SimpleAdmin_ModuleName.dll
|
||||
├── CS2-SimpleAdmin_ModuleName.json (config)
|
||||
└── lang/ (translations)
|
||||
├── en.json
|
||||
├── pl.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Configuration
|
||||
|
||||
Each module has its own configuration file:
|
||||
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/ModuleName/ModuleName.json
|
||||
```
|
||||
|
||||
### Common Configuration Pattern
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": 1,
|
||||
"CommandName": ["css_command", "css_alias"],
|
||||
"OtherSettings": {
|
||||
"EnableFeature": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Command lists allow multiple aliases
|
||||
- Empty command list = feature disabled
|
||||
- Module-specific settings
|
||||
|
||||
---
|
||||
|
||||
## Available Modules
|
||||
|
||||
### Core Modules
|
||||
|
||||
| Module | Description | Status |
|
||||
|--------|-------------|--------|
|
||||
| **[Fun Commands](funcommands)** | God mode, noclip, freeze, speed, gravity | ✅ Official |
|
||||
|
||||
### Community Modules
|
||||
|
||||
Check the [GitHub repository](https://github.com/daffyyyy/CS2-SimpleAdmin) for community-contributed modules.
|
||||
|
||||
---
|
||||
|
||||
## Developing Modules
|
||||
|
||||
Want to create your own module?
|
||||
|
||||
**[See Module Development Guide →](development)**
|
||||
|
||||
**[See Developer Documentation →](../developer/intro)**
|
||||
|
||||
---
|
||||
|
||||
## Module vs Core Plugin
|
||||
|
||||
### When to use Core Plugin:
|
||||
- Essential admin functions
|
||||
- Punishment system
|
||||
- Permission management
|
||||
- Database operations
|
||||
|
||||
### When to use Modules:
|
||||
- Optional features
|
||||
- Server-specific functionality
|
||||
- Experimental features
|
||||
- Custom integrations
|
||||
|
||||
---
|
||||
|
||||
## Module Dependencies
|
||||
|
||||
### Required for All Modules:
|
||||
- CS2-SimpleAdmin (core plugin)
|
||||
- CS2-SimpleAdminApi.dll
|
||||
|
||||
### Module-Specific:
|
||||
Check each module's documentation for specific requirements.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Modules
|
||||
|
||||
### Module doesn't load
|
||||
|
||||
**Check:**
|
||||
1. Is CS2-SimpleAdmin loaded?
|
||||
2. Is CS2-SimpleAdminApi.dll in shared folder?
|
||||
3. Check server console for errors
|
||||
4. Verify module files are complete
|
||||
|
||||
### Module commands not working
|
||||
|
||||
**Check:**
|
||||
1. Is command enabled in module config?
|
||||
2. Do you have required permissions?
|
||||
3. Check Commands.json for conflicts
|
||||
4. Verify module loaded successfully
|
||||
|
||||
### Module conflicts
|
||||
|
||||
**Check:**
|
||||
- Multiple modules providing same command
|
||||
- Check server console for warnings
|
||||
- Disable conflicting module
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Module Management
|
||||
|
||||
1. **Use only needed modules** - Don't overload server
|
||||
2. **Keep modules updated** - Check for updates regularly
|
||||
3. **Test before production** - Test modules on dev server first
|
||||
4. **Review permissions** - Understand what each module can do
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Monitor resource usage** - Some modules may impact performance
|
||||
2. **Configure wisely** - Disable unused features
|
||||
3. **Check logs** - Monitor for errors
|
||||
|
||||
---
|
||||
|
||||
## Module Updates
|
||||
|
||||
### Updating Modules
|
||||
|
||||
1. **Backup current version**
|
||||
2. **Download new version**
|
||||
3. **Replace files** in plugins folder
|
||||
4. **Check configuration** - New config options may exist
|
||||
5. **Restart server**
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
Some updates may have breaking changes:
|
||||
- Check module changelog
|
||||
- Review new configuration options
|
||||
- Test thoroughly
|
||||
|
||||
---
|
||||
|
||||
## Community Contributions
|
||||
|
||||
### Sharing Modules
|
||||
|
||||
Created a module? Share it with the community!
|
||||
|
||||
1. **Publish on GitHub**
|
||||
2. **Document thoroughly**
|
||||
3. **Provide examples**
|
||||
4. **Include README**
|
||||
|
||||
### Using Community Modules
|
||||
|
||||
1. **Review code** - Ensure it's safe
|
||||
2. **Check compatibility** - Verify CS2-SimpleAdmin version
|
||||
3. **Test thoroughly** - Don't trust blindly
|
||||
4. **Report issues** - Help improve modules
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Explore Fun Commands Module](funcommands)** - Add entertainment features
|
||||
- **[Learn Module Development](development)** - Create your own modules
|
||||
- **[Read API Documentation](../developer/intro)** - Understand the API
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- **Issues** - [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
- **Discussions** - [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
- **Examples** - Check official modules for reference
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Tutorial - Basics",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "5 minutes to learn the most important Docusaurus concepts."
|
||||
}
|
||||
}
|
||||
23
CS2-SimpleAdmin-docs/docs/tutorial-basics/congratulations.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Congratulations!
|
||||
|
||||
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
|
||||
|
||||
Docusaurus has **much more to offer**!
|
||||
|
||||
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
|
||||
|
||||
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
|
||||
|
||||
## What's next?
|
||||
|
||||
- Read the [official documentation](https://docusaurus.io/)
|
||||
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
|
||||
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
|
||||
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
|
||||
- Add a [search bar](https://docusaurus.io/docs/search)
|
||||
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
|
||||
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Create a Blog Post
|
||||
|
||||
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
|
||||
|
||||
## Create your first Post
|
||||
|
||||
Create a file at `blog/2021-02-28-greetings.md`:
|
||||
|
||||
```md title="blog/2021-02-28-greetings.md"
|
||||
---
|
||||
slug: greetings
|
||||
title: Greetings!
|
||||
authors:
|
||||
- name: Joel Marcey
|
||||
title: Co-creator of Docusaurus 1
|
||||
url: https://github.com/JoelMarcey
|
||||
image_url: https://github.com/JoelMarcey.png
|
||||
- name: Sébastien Lorber
|
||||
title: Docusaurus maintainer
|
||||
url: https://sebastienlorber.com
|
||||
image_url: https://github.com/slorber.png
|
||||
tags: [greetings]
|
||||
---
|
||||
|
||||
Congratulations, you have made your first post!
|
||||
|
||||
Feel free to play around and edit this post as much as you like.
|
||||
```
|
||||
|
||||
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Create a Document
|
||||
|
||||
Documents are **groups of pages** connected through:
|
||||
|
||||
- a **sidebar**
|
||||
- **previous/next navigation**
|
||||
- **versioning**
|
||||
|
||||
## Create your first Doc
|
||||
|
||||
Create a Markdown file at `docs/hello.md`:
|
||||
|
||||
```md title="docs/hello.md"
|
||||
# Hello
|
||||
|
||||
This is my **first Docusaurus document**!
|
||||
```
|
||||
|
||||
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
|
||||
|
||||
## Configure the Sidebar
|
||||
|
||||
Docusaurus automatically **creates a sidebar** from the `docs` folder.
|
||||
|
||||
Add metadata to customize the sidebar label and position:
|
||||
|
||||
```md title="docs/hello.md" {1-4}
|
||||
---
|
||||
sidebar_label: 'Hi!'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Hello
|
||||
|
||||
This is my **first Docusaurus document**!
|
||||
```
|
||||
|
||||
It is also possible to create your sidebar explicitly in `sidebars.js`:
|
||||
|
||||
```js title="sidebars.js"
|
||||
export default {
|
||||
tutorialSidebar: [
|
||||
'intro',
|
||||
// highlight-next-line
|
||||
'hello',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['tutorial-basics/create-a-document'],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
43
CS2-SimpleAdmin-docs/docs/tutorial-basics/create-a-page.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Create a Page
|
||||
|
||||
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
|
||||
|
||||
- `src/pages/index.js` → `localhost:3000/`
|
||||
- `src/pages/foo.md` → `localhost:3000/foo`
|
||||
- `src/pages/foo/bar.js` → `localhost:3000/foo/bar`
|
||||
|
||||
## Create your first React Page
|
||||
|
||||
Create a file at `src/pages/my-react-page.js`:
|
||||
|
||||
```jsx title="src/pages/my-react-page.js"
|
||||
import React from 'react';
|
||||
import Layout from '@theme/Layout';
|
||||
|
||||
export default function MyReactPage() {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>My React page</h1>
|
||||
<p>This is a React page</p>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
|
||||
|
||||
## Create your first Markdown Page
|
||||
|
||||
Create a file at `src/pages/my-markdown-page.md`:
|
||||
|
||||
```mdx title="src/pages/my-markdown-page.md"
|
||||
# My Markdown page
|
||||
|
||||
This is a Markdown page
|
||||
```
|
||||
|
||||
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Deploy your site
|
||||
|
||||
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
|
||||
|
||||
It builds your site as simple **static HTML, JavaScript and CSS files**.
|
||||
|
||||
## Build your site
|
||||
|
||||
Build your site **for production**:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
The static files are generated in the `build` folder.
|
||||
|
||||
## Deploy your site
|
||||
|
||||
Test your production build locally:
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
|
||||
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
|
||||
|
||||
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).
|
||||
152
CS2-SimpleAdmin-docs/docs/tutorial-basics/markdown-features.mdx
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Markdown Features
|
||||
|
||||
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
|
||||
|
||||
## Front Matter
|
||||
|
||||
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
|
||||
|
||||
```text title="my-doc.md"
|
||||
// highlight-start
|
||||
---
|
||||
id: my-doc-id
|
||||
title: My document title
|
||||
description: My document description
|
||||
slug: /my-custom-url
|
||||
---
|
||||
// highlight-end
|
||||
|
||||
## Markdown heading
|
||||
|
||||
Markdown text with [links](./hello.md)
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
Regular Markdown links are supported, using url paths or relative file paths.
|
||||
|
||||
```md
|
||||
Let's see how to [Create a page](/create-a-page).
|
||||
```
|
||||
|
||||
```md
|
||||
Let's see how to [Create a page](./create-a-page.md).
|
||||
```
|
||||
|
||||
**Result:** Let's see how to [Create a page](./create-a-page.md).
|
||||
|
||||
## Images
|
||||
|
||||
Regular Markdown images are supported.
|
||||
|
||||
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||

|
||||
|
||||
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||
## Code Blocks
|
||||
|
||||
Markdown code blocks are supported with Syntax highlighting.
|
||||
|
||||
````md
|
||||
```jsx title="src/components/HelloDocusaurus.js"
|
||||
function HelloDocusaurus() {
|
||||
return <h1>Hello, Docusaurus!</h1>;
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
```jsx title="src/components/HelloDocusaurus.js"
|
||||
function HelloDocusaurus() {
|
||||
return <h1>Hello, Docusaurus!</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
## Admonitions
|
||||
|
||||
Docusaurus has a special syntax to create admonitions and callouts:
|
||||
|
||||
```md
|
||||
:::tip My tip
|
||||
|
||||
Use this awesome feature option
|
||||
|
||||
:::
|
||||
|
||||
:::danger Take care
|
||||
|
||||
This action is dangerous
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
:::tip My tip
|
||||
|
||||
Use this awesome feature option
|
||||
|
||||
:::
|
||||
|
||||
:::danger Take care
|
||||
|
||||
This action is dangerous
|
||||
|
||||
:::
|
||||
|
||||
## MDX and React Components
|
||||
|
||||
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
|
||||
|
||||
```jsx
|
||||
export const Highlight = ({children, color}) => (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '20px',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
alert(`You clicked the color ${color} with label ${children}`)
|
||||
}}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||
|
||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||
```
|
||||
|
||||
export const Highlight = ({children, color}) => (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '20px',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
alert(`You clicked the color ${color} with label ${children}`);
|
||||
}}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||
|
||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Tutorial - Extras",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
BIN
CS2-SimpleAdmin-docs/docs/tutorial-extras/img/localeDropdown.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,55 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Manage Docs Versions
|
||||
|
||||
Docusaurus can manage multiple versions of your docs.
|
||||
|
||||
## Create a docs version
|
||||
|
||||
Release a version 1.0 of your project:
|
||||
|
||||
```bash
|
||||
npm run docusaurus docs:version 1.0
|
||||
```
|
||||
|
||||
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
|
||||
|
||||
Your docs now have 2 versions:
|
||||
|
||||
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
|
||||
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
|
||||
|
||||
## Add a Version Dropdown
|
||||
|
||||
To navigate seamlessly across versions, add a version dropdown.
|
||||
|
||||
Modify the `docusaurus.config.js` file:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
export default {
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
items: [
|
||||
// highlight-start
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
},
|
||||
// highlight-end
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The docs version dropdown appears in your navbar:
|
||||
|
||||

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

|
||||
|
||||
## Build your localized site
|
||||
|
||||
Build your site for a specific locale:
|
||||
|
||||
```bash
|
||||
npm run build -- --locale fr
|
||||
```
|
||||
|
||||
Or build your site to include all the locales at once:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
262
CS2-SimpleAdmin-docs/docs/user/commands/basebans.md
Normal file
@@ -0,0 +1,262 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Ban Commands
|
||||
|
||||
Commands for managing player bans.
|
||||
|
||||
## Ban Player
|
||||
|
||||
Ban a player currently on the server.
|
||||
|
||||
```bash
|
||||
css_ban <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_ban @all 60 "Timeout for everyone"
|
||||
css_ban #123 1440 "Hacking - 1 day ban"
|
||||
css_ban PlayerName 0 "Permanent ban for cheating"
|
||||
css_ban @ct 30 "CT team timeout"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Time in minutes (0 = permanent)
|
||||
- Supports player targeting (@all, @ct, @t, #userid, name)
|
||||
- Reason is optional but recommended
|
||||
|
||||
---
|
||||
|
||||
## Add Ban (Offline Player)
|
||||
|
||||
Ban a player by SteamID even if they're not online.
|
||||
|
||||
```bash
|
||||
css_addban <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addban STEAM_1:0:12345678 1440 "Ban evasion"
|
||||
css_addban 76561198012345678 10080 "Hacking - 7 day ban"
|
||||
css_addban STEAM_1:1:87654321 0 "Permanent ban"
|
||||
```
|
||||
|
||||
**Supported SteamID formats:**
|
||||
- SteamID64: `76561198012345678`
|
||||
- SteamID: `STEAM_1:0:12345678`
|
||||
- SteamID3: `[U:1:12345678]`
|
||||
|
||||
---
|
||||
|
||||
## Ban IP Address
|
||||
|
||||
Ban an IP address.
|
||||
|
||||
```bash
|
||||
css_banip <ip> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_banip 192.168.1.100 1440 "Ban evasion attempt"
|
||||
css_banip 10.0.0.5 0 "Persistent troublemaker"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Useful for preventing ban evasion
|
||||
- Can be combined with SteamID bans
|
||||
- Check config for `BanType` setting (SteamID, IP, or Both)
|
||||
|
||||
---
|
||||
|
||||
## Unban Player
|
||||
|
||||
Remove a ban from a player.
|
||||
|
||||
```bash
|
||||
css_unban <steamid or name or ip> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/unban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unban 76561198012345678 "Appeal accepted"
|
||||
css_unban STEAM_1:0:12345678 "Ban lifted"
|
||||
css_unban 192.168.1.100 "Wrong person banned"
|
||||
css_unban PlayerName "Mistake"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Works with SteamID, IP, or player name
|
||||
- Unban reason is logged
|
||||
- Can unban offline players
|
||||
|
||||
---
|
||||
|
||||
## Warn Player
|
||||
|
||||
Issue a warning to a player.
|
||||
|
||||
```bash
|
||||
css_warn <#userid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_warn #123 "Mic spam"
|
||||
css_warn PlayerName "Language"
|
||||
css_warn @all "Final warning"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Warnings can accumulate
|
||||
- Auto-escalation to bans based on `WarnThreshold` config
|
||||
- Example: 3 warnings = 1 hour ban, 4 warnings = 2 hour ban
|
||||
|
||||
**Warning Threshold Configuration:**
|
||||
```json
|
||||
"WarnThreshold": {
|
||||
"3": "css_addban STEAMID64 60 \"3 warnings\"",
|
||||
"4": "css_ban #USERID 120 \"4 warnings\""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unwarn Player
|
||||
|
||||
Remove a warning from a player.
|
||||
|
||||
```bash
|
||||
css_unwarn <steamid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unwarn 76561198012345678
|
||||
css_unwarn PlayerName
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Removes the most recent warning
|
||||
- Helps manage warning thresholds
|
||||
- Can be used for offline players
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
| Command | Required Permission | Description |
|
||||
|---------|-------------------|-------------|
|
||||
| `css_ban` | `@css/ban` | Ban online players |
|
||||
| `css_addban` | `@css/ban` | Ban offline players by SteamID |
|
||||
| `css_banip` | `@css/ban` | Ban IP addresses |
|
||||
| `css_unban` | `@css/unban` | Remove bans |
|
||||
| `css_warn` | `@css/kick` | Issue warnings |
|
||||
| `css_unwarn` | `@css/kick` | Remove warnings |
|
||||
|
||||
## Ban Types
|
||||
|
||||
Configure ban behavior in `CS2-SimpleAdmin.json`:
|
||||
|
||||
```json
|
||||
"BanType": 1
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `1` - SteamID only (default)
|
||||
- `2` - IP only
|
||||
- `3` - Both SteamID and IP
|
||||
|
||||
## Time Durations
|
||||
|
||||
Common time values:
|
||||
|
||||
| Duration | Minutes | Description |
|
||||
|----------|---------|-------------|
|
||||
| 1 minute | 1 | Very short timeout |
|
||||
| 5 minutes | 5 | Short timeout |
|
||||
| 15 minutes | 15 | Medium timeout |
|
||||
| 1 hour | 60 | Standard timeout |
|
||||
| 1 day | 1440 | Daily ban |
|
||||
| 1 week | 10080 | Weekly ban |
|
||||
| 2 weeks | 20160 | Bi-weekly ban |
|
||||
| 1 month | 43200 | Monthly ban |
|
||||
| Permanent | 0 | Never expires |
|
||||
|
||||
## Player Targeting
|
||||
|
||||
All ban commands support advanced targeting:
|
||||
|
||||
- `@all` - Target all players
|
||||
- `@ct` - Target all Counter-Terrorists
|
||||
- `@t` - Target all Terrorists
|
||||
- `@spec` - Target all spectators
|
||||
- `#123` - Target by userid
|
||||
- `PlayerName` - Target by name (partial match)
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Banning
|
||||
|
||||
1. **Always provide a reason** - Helps with appeals and record keeping
|
||||
2. **Use appropriate durations** - Don't permaban for minor offenses
|
||||
3. **Check ban history** - Use `css_who` to see if player has priors
|
||||
4. **Consider warnings first** - Give players a chance to improve
|
||||
|
||||
### Warning System
|
||||
|
||||
1. **Be consistent** - Use warnings for minor offenses
|
||||
2. **Configure thresholds** - Set up auto-escalation in config
|
||||
3. **Communicate clearly** - Let players know why they're warned
|
||||
4. **Review regularly** - Check warning history with `css_warns`
|
||||
|
||||
### Multi-Account Detection
|
||||
|
||||
When `CheckMultiAccountsByIp` is enabled:
|
||||
- Plugin detects multiple accounts from same IP
|
||||
- Sends Discord notifications if configured
|
||||
- Helps identify ban evasion
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Ban doesn't work
|
||||
|
||||
**Check:**
|
||||
- Do you have `@css/ban` permission?
|
||||
- Is the SteamID format correct?
|
||||
- Check server console for errors
|
||||
|
||||
### Player rejoins after ban
|
||||
|
||||
**Check:**
|
||||
- Is `MultiServerMode` enabled if using multiple servers?
|
||||
- Is the database shared across servers?
|
||||
- Check ban type configuration (SteamID vs IP)
|
||||
|
||||
### Warning threshold not working
|
||||
|
||||
**Check:**
|
||||
- Is `WarnThreshold` configured correctly?
|
||||
- Are the command formats correct in config?
|
||||
- Check server console for execution errors
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Communication Commands](basecomms)** - Mute, gag, silence
|
||||
- **[Player Commands](playercommands)** - Kick, slay, etc.
|
||||
- **[Base Commands](basecommands)** - Admin management
|
||||
442
CS2-SimpleAdmin-docs/docs/user/commands/basechat.md
Normal file
@@ -0,0 +1,442 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Chat Commands
|
||||
|
||||
Admin chat and messaging commands.
|
||||
|
||||
## Admin Chat
|
||||
|
||||
### Admin Say (Private)
|
||||
|
||||
Send a message to all online admins only.
|
||||
|
||||
```bash
|
||||
css_asay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Only admins see the message
|
||||
- Useful for admin coordination
|
||||
- Colored differently from regular chat
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_asay "Player is suspicious, keep an eye on them"
|
||||
css_asay "I'm going AFK for 5 minutes"
|
||||
css_asay "Need help with a situation"
|
||||
```
|
||||
|
||||
**Message format:**
|
||||
```
|
||||
[ADMIN] YourName: message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Public Announcements
|
||||
|
||||
### CSS Say (Colored)
|
||||
|
||||
Send a colored message to all players.
|
||||
|
||||
```bash
|
||||
css_cssay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Colorful formatted message
|
||||
- Visible to all players
|
||||
- Stands out from regular chat
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_cssay "Server will restart in 5 minutes!"
|
||||
css_cssay "Welcome to our server!"
|
||||
css_cssay "Rules: No cheating, be respectful"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Say (With Prefix)
|
||||
|
||||
Send a message to all players with admin prefix.
|
||||
|
||||
```bash
|
||||
css_say <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Message shows with "(ADMIN)" prefix
|
||||
- Visible to all players
|
||||
- Authority message format
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_say "Please be respectful in chat"
|
||||
css_say "Cheating will result in permanent ban"
|
||||
css_say "Type !rules for server rules"
|
||||
```
|
||||
|
||||
**Message format:**
|
||||
```
|
||||
(ADMIN) YourName: message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Private Say (Whisper)
|
||||
|
||||
Send a private message to a specific player.
|
||||
|
||||
```bash
|
||||
css_psay <#userid or name> <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Only the target player sees the message
|
||||
- Useful for private warnings or help
|
||||
- Doesn't clutter public chat
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_psay #123 "Please stop mic spamming"
|
||||
css_psay PlayerName "You need to join a team"
|
||||
css_psay @all "This is a private message to everyone"
|
||||
```
|
||||
|
||||
**Target receives:**
|
||||
```
|
||||
(ADMIN to you) AdminName: message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Center Say
|
||||
|
||||
Display a message in the center of all players' screens.
|
||||
|
||||
```bash
|
||||
css_csay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Large text in center of screen
|
||||
- Impossible to miss
|
||||
- Useful for important announcements
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_csay "ROUND STARTS IN 10 SECONDS"
|
||||
css_csay "FREEZE! Don't move!"
|
||||
css_csay "Server restarting NOW"
|
||||
```
|
||||
|
||||
**Display:**
|
||||
- Shows in center of screen
|
||||
- Large, bold text
|
||||
- Auto-fades after a few seconds
|
||||
|
||||
---
|
||||
|
||||
### HUD Say
|
||||
|
||||
Display a message on players' HUD (screen overlay).
|
||||
|
||||
```bash
|
||||
css_hsay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Message appears on screen overlay
|
||||
- Less intrusive than center say
|
||||
- Stays visible longer
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_hsay "Tournament starting soon!"
|
||||
css_hsay "New map voting available"
|
||||
css_hsay "Visit our website: example.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Announcements
|
||||
|
||||
```bash
|
||||
# Server restart warning
|
||||
css_cssay "Server will restart in 10 minutes! Save your progress!"
|
||||
|
||||
# Center screen countdown
|
||||
css_csay "5"
|
||||
# Wait...
|
||||
css_csay "4"
|
||||
css_csay "3"
|
||||
css_csay "2"
|
||||
css_csay "1"
|
||||
css_csay "GO!"
|
||||
```
|
||||
|
||||
### Player Communication
|
||||
|
||||
```bash
|
||||
# Private warning
|
||||
css_psay PlayerName "This is your first warning for chat spam"
|
||||
|
||||
# Public announcement
|
||||
css_say "Everyone please be quiet for the next round"
|
||||
|
||||
# Admin coordination
|
||||
css_asay "I'm spectating the suspicious player in T spawn"
|
||||
```
|
||||
|
||||
### Event Management
|
||||
|
||||
```bash
|
||||
# Tournament announcement
|
||||
css_cssay "⚠ TOURNAMENT STARTING IN 5 MINUTES ⚠"
|
||||
css_hsay "Teams, please get ready!"
|
||||
css_csay "TOURNAMENT BEGINS NOW!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Codes
|
||||
|
||||
Many chat commands support color codes:
|
||||
|
||||
```
|
||||
{default} - Default chat color
|
||||
{white} - White
|
||||
{darkred} - Dark red
|
||||
{green} - Green
|
||||
{lightyellow} - Light yellow
|
||||
{lightblue} - Light blue
|
||||
{olive} - Olive
|
||||
{lime} - Lime
|
||||
{red} - Red
|
||||
{purple} - Purple
|
||||
{grey} - Grey
|
||||
{yellow} - Yellow
|
||||
{gold} - Gold
|
||||
{silver} - Silver
|
||||
{blue} - Blue
|
||||
{darkblue} - Dark blue
|
||||
{bluegrey} - Blue grey
|
||||
{magenta} - Magenta
|
||||
{lightred} - Light red
|
||||
{orange} - Orange
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
css_say "{red}WARNING: {default}No camping in spawn!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Message Targeting
|
||||
|
||||
Some commands support player targeting for private messages:
|
||||
|
||||
### Supported Targets
|
||||
|
||||
- `@all` - All players (private message to each)
|
||||
- `@ct` - All Counter-Terrorists
|
||||
- `@t` - All Terrorists
|
||||
- `@spec` - All spectators
|
||||
- `#123` - Specific userid
|
||||
- `PlayerName` - Player by name
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Private message to all CTs
|
||||
css_psay @ct "Defend bombsite A this round"
|
||||
|
||||
# Private message to all terrorists
|
||||
css_psay @t "Rush B with smoke and flash"
|
||||
|
||||
# Message to spectators
|
||||
css_psay @spec "Type !join to play"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use Each Command
|
||||
|
||||
**css_asay** (Admin Say):
|
||||
- Admin coordination
|
||||
- Discussing player behavior
|
||||
- Planning admin actions
|
||||
- Private admin discussions
|
||||
|
||||
**css_cssay** (Colored Say):
|
||||
- Important server announcements
|
||||
- Event notifications
|
||||
- Eye-catching messages
|
||||
- Server information
|
||||
|
||||
**css_say** (Say):
|
||||
- General admin announcements
|
||||
- Rule reminders
|
||||
- Warnings to all players
|
||||
- Admin presence
|
||||
|
||||
**css_psay** (Private Say):
|
||||
- Private warnings
|
||||
- Individual help
|
||||
- Direct player communication
|
||||
- Discretion needed
|
||||
|
||||
**css_csay** (Center Say):
|
||||
- Emergency announcements
|
||||
- Cannot-miss messages
|
||||
- Round starts/events
|
||||
- Countdowns
|
||||
|
||||
**css_hsay** (HUD Say):
|
||||
- Persistent information
|
||||
- Less urgent announcements
|
||||
- Server info
|
||||
- Website/Discord links
|
||||
|
||||
---
|
||||
|
||||
## Communication Guidelines
|
||||
|
||||
### Professional Communication
|
||||
|
||||
1. **Be clear and concise** - Don't spam long messages
|
||||
2. **Use appropriate command** - Don't center-spam trivial messages
|
||||
3. **Check for typos** - You represent the server
|
||||
4. **Avoid excessive colors** - Can be hard to read
|
||||
|
||||
### Spam Prevention
|
||||
|
||||
1. **Don't overuse center say** - Very intrusive for players
|
||||
2. **Space out announcements** - Don't flood chat
|
||||
3. **Use HUD say for persistent info** - Less annoying
|
||||
4. **Coordinate with other admins** - Avoid duplicate messages
|
||||
|
||||
### Effective Messaging
|
||||
|
||||
**Good Examples:**
|
||||
```bash
|
||||
css_cssay "🎯 New map voting system available! Type !mapvote"
|
||||
css_psay PlayerName "Hey! Please enable your mic or use team chat"
|
||||
css_asay "Checking player demos for possible aimbot"
|
||||
```
|
||||
|
||||
**Poor Examples:**
|
||||
```bash
|
||||
css_csay "hi" # Don't use center say for trivial messages
|
||||
css_cssay "a" "b" "c" "d" # Don't spam center messages
|
||||
css_say "asdfasdfasdf" # Unprofessional
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Silent Mode
|
||||
|
||||
Admins can use silent mode to hide their activity:
|
||||
|
||||
```bash
|
||||
css_hide # Toggle silent mode
|
||||
```
|
||||
|
||||
When in silent mode:
|
||||
- Chat messages still work
|
||||
- Admin name might be hidden (depends on config)
|
||||
- Useful for undercover moderation
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Show Activity Type
|
||||
|
||||
Controls how admin actions are displayed:
|
||||
|
||||
```json
|
||||
"ShowActivityType": 2
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `0` - Hide all admin activity
|
||||
- `1` - Anonymous ("An admin says...")
|
||||
- `2` - Show name ("AdminName says...")
|
||||
|
||||
---
|
||||
|
||||
## Chat Restrictions
|
||||
|
||||
### Respecting Gags
|
||||
|
||||
Remember that:
|
||||
- `css_asay` works even if admin is gagged (admin chat)
|
||||
- Other commands respect communication penalties
|
||||
- Can't use chat commands while silenced
|
||||
|
||||
### Permission Requirements
|
||||
|
||||
All chat commands require `@css/chat` permission:
|
||||
|
||||
```bash
|
||||
css_addadmin STEAMID "Name" "@css/chat" 50 0
|
||||
```
|
||||
|
||||
Or add to a group with chat permission:
|
||||
```bash
|
||||
css_addgroup "#moderators" "@css/chat,@css/kick" 50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Messages not showing
|
||||
|
||||
**Check:**
|
||||
- Do you have `@css/chat` permission?
|
||||
- Are you silenced/gagged?
|
||||
- Check console for errors
|
||||
- Verify player is connected (for css_psay)
|
||||
|
||||
### Colors not working
|
||||
|
||||
**Check:**
|
||||
- Use correct color code syntax: `{red}`
|
||||
- Some commands may not support colors
|
||||
- Different chat systems handle colors differently
|
||||
|
||||
### Players can't see center/HUD messages
|
||||
|
||||
**Check:**
|
||||
- CS2 client-side chat settings
|
||||
- Conflicting HUD plugins
|
||||
- Server console for errors
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Communication Commands](basecomms)** - Gag, mute, silence players
|
||||
- **[Base Commands](basecommands)** - Admin management
|
||||
- **[Ban Commands](basebans)** - Player punishment
|
||||
626
CS2-SimpleAdmin-docs/docs/user/commands/basecommands.md
Normal file
@@ -0,0 +1,626 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Base Commands
|
||||
|
||||
Core admin commands for server management and admin system.
|
||||
|
||||
## Player Information
|
||||
|
||||
### Show Penalties
|
||||
|
||||
View your own active penalties.
|
||||
|
||||
```bash
|
||||
css_penalties
|
||||
css_mypenalties
|
||||
css_comms
|
||||
```
|
||||
|
||||
**Permission:** None (all players)
|
||||
|
||||
**Shows:**
|
||||
- Active bans
|
||||
- Active communication restrictions (gag, mute, silence)
|
||||
- Warning count
|
||||
- Duration remaining
|
||||
|
||||
---
|
||||
|
||||
### Hide Penalty Notifications
|
||||
|
||||
Hide penalty notifications when you connect to the server.
|
||||
|
||||
```bash
|
||||
css_hidecomms
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Notes:**
|
||||
- Toggle on/off
|
||||
- Admins won't see penalty notifications on join
|
||||
- Useful for admin privacy
|
||||
|
||||
---
|
||||
|
||||
## Admin Menu
|
||||
|
||||
### Open Admin Menu
|
||||
|
||||
Opens the main admin menu interface.
|
||||
|
||||
```bash
|
||||
css_admin
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Features:**
|
||||
- Player management
|
||||
- Server management
|
||||
- Ban/kick/mute players via menu
|
||||
- Map changing
|
||||
- Custom server commands
|
||||
|
||||
---
|
||||
|
||||
### Admin Help
|
||||
|
||||
Print the admin help file.
|
||||
|
||||
```bash
|
||||
css_adminhelp
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Shows:**
|
||||
- Available commands for your permission level
|
||||
- Command syntax
|
||||
- Permission requirements
|
||||
|
||||
---
|
||||
|
||||
## Admin Management
|
||||
|
||||
### Add Admin
|
||||
|
||||
Add a new admin to the database.
|
||||
|
||||
```bash
|
||||
css_addadmin <steamid> <name> <flags/groups> <immunity> <duration>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Parameters:**
|
||||
- `steamid` - Player's SteamID (any format)
|
||||
- `name` - Admin name (for identification)
|
||||
- `flags/groups` - Permission flags or group name
|
||||
- `immunity` - Immunity level (0-100, higher = more protection)
|
||||
- `duration` - Duration in minutes (0 = permanent)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Add permanent admin with root access
|
||||
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0
|
||||
|
||||
# Add moderator for 30 days
|
||||
css_addadmin STEAM_1:0:12345678 "ModName" "@css/kick,@css/ban" 50 43200
|
||||
|
||||
# Add admin using group
|
||||
css_addadmin 76561198012345678 "AdminName" "#moderators" 60 0
|
||||
|
||||
# Add admin to all servers (-g flag)
|
||||
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0 -g
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
- `-g` - Add to all servers (global admin)
|
||||
|
||||
---
|
||||
|
||||
### Delete Admin
|
||||
|
||||
Remove an admin from the database.
|
||||
|
||||
```bash
|
||||
css_deladmin <steamid>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Remove admin from current server
|
||||
css_deladmin 76561198012345678
|
||||
|
||||
# Remove admin from all servers (-g flag)
|
||||
css_deladmin 76561198012345678 -g
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Add Admin Group
|
||||
|
||||
Create a new admin group.
|
||||
|
||||
```bash
|
||||
css_addgroup <group_name> <flags> <immunity>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Parameters:**
|
||||
- `group_name` - Name of the group (e.g., "#moderators")
|
||||
- `flags` - Permission flags for the group
|
||||
- `immunity` - Default immunity level for group members
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Create moderator group
|
||||
css_addgroup "#moderators" "@css/kick,@css/ban,@css/chat" 50
|
||||
|
||||
# Create VIP group
|
||||
css_addgroup "#vip" "@css/vip" 10
|
||||
|
||||
# Create global group (-g flag)
|
||||
css_addgroup "#owner" "@css/root" 99 -g
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
- `-g` - Create group on all servers
|
||||
|
||||
---
|
||||
|
||||
### Delete Admin Group
|
||||
|
||||
Remove an admin group from the database.
|
||||
|
||||
```bash
|
||||
css_delgroup <group_name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Delete group from current server
|
||||
css_delgroup "#moderators"
|
||||
|
||||
# Delete group from all servers (-g flag)
|
||||
css_delgroup "#moderators" -g
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reload Admins
|
||||
|
||||
Reload admin permissions from the database.
|
||||
|
||||
```bash
|
||||
css_reloadadmins
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**When to use:**
|
||||
- After adding/removing admins via database
|
||||
- After modifying admin permissions
|
||||
- After group changes
|
||||
- Troubleshooting permission issues
|
||||
|
||||
**Note:** Admins are automatically reloaded periodically and on map change (if configured).
|
||||
|
||||
---
|
||||
|
||||
## Player Information
|
||||
|
||||
### Hide in Scoreboard
|
||||
|
||||
Toggle admin stealth mode (hide from scoreboard).
|
||||
|
||||
```bash
|
||||
css_hide
|
||||
css_stealth
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Features:**
|
||||
- Hides you from the scoreboard
|
||||
- Makes admin actions anonymous
|
||||
- Useful for undercover moderation
|
||||
|
||||
---
|
||||
|
||||
### Who is This Player
|
||||
|
||||
Show detailed information about a player.
|
||||
|
||||
```bash
|
||||
css_who <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Shows:**
|
||||
- Player name and SteamID
|
||||
- IP address (if you have `@css/showip` permission)
|
||||
- Connection time
|
||||
- Active penalties
|
||||
- Warning count
|
||||
- Ban history
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_who #123
|
||||
css_who PlayerName
|
||||
css_who @me
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Show Disconnected Players
|
||||
|
||||
Show recently disconnected players.
|
||||
|
||||
```bash
|
||||
css_disconnected
|
||||
css_last
|
||||
css_last10 # Show last 10 (config value)
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Shows:**
|
||||
- Player name
|
||||
- SteamID
|
||||
- Disconnect time
|
||||
- Disconnect reason
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"DisconnectedPlayersHistoryCount": 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Show Warns for Player
|
||||
|
||||
Open warn list for a specific player.
|
||||
|
||||
```bash
|
||||
css_warns <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Shows:**
|
||||
- All warnings for the player
|
||||
- Warning reasons
|
||||
- Admins who issued warnings
|
||||
- Warning timestamps
|
||||
- Total warning count
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_warns #123
|
||||
css_warns PlayerName
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Show Online Players
|
||||
|
||||
Show information about all online players.
|
||||
|
||||
```bash
|
||||
css_players
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Shows:**
|
||||
- List of all connected players
|
||||
- UserIDs
|
||||
- Names
|
||||
- Teams
|
||||
- Connection status
|
||||
|
||||
---
|
||||
|
||||
## Server Management
|
||||
|
||||
### Kick Player
|
||||
|
||||
Kick a player from the server.
|
||||
|
||||
```bash
|
||||
css_kick <#userid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_kick #123 "AFK"
|
||||
css_kick PlayerName "Rule violation"
|
||||
css_kick @spec "Cleaning spectators"
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"KickTime": 5
|
||||
```
|
||||
Delay in seconds before kicking (allows player to see the reason).
|
||||
|
||||
---
|
||||
|
||||
### Change Map
|
||||
|
||||
Change to a different map.
|
||||
|
||||
```bash
|
||||
css_map <mapname>
|
||||
css_changemap <mapname>
|
||||
```
|
||||
|
||||
**Permission:** `@css/changemap`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_map de_dust2
|
||||
css_changemap de_mirage
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"DefaultMaps": [
|
||||
"de_dust2",
|
||||
"de_mirage",
|
||||
"de_inferno"
|
||||
]
|
||||
```
|
||||
|
||||
Maps in this list appear in the map change menu.
|
||||
|
||||
---
|
||||
|
||||
### Change Workshop Map
|
||||
|
||||
Change to a workshop map by ID or name.
|
||||
|
||||
```bash
|
||||
css_wsmap <name or id>
|
||||
css_changewsmap <name or id>
|
||||
css_workshop <name or id>
|
||||
```
|
||||
|
||||
**Permission:** `@css/changemap`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_wsmap 123456789
|
||||
css_wsmap aim_map
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"WorkshopMaps": {
|
||||
"aim_map": "123456789",
|
||||
"surf_map": "987654321"
|
||||
}
|
||||
```
|
||||
|
||||
Maps configured here can be changed by name instead of ID.
|
||||
|
||||
---
|
||||
|
||||
### Change CVar
|
||||
|
||||
Change a server console variable.
|
||||
|
||||
```bash
|
||||
css_cvar <cvar> <value>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cvar`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_cvar sv_cheats 1
|
||||
css_cvar mp_roundtime 5
|
||||
css_cvar mp_maxmoney 16000
|
||||
```
|
||||
|
||||
**Warning:** This is a powerful command. Only grant to trusted admins.
|
||||
|
||||
---
|
||||
|
||||
### Execute RCON Command
|
||||
|
||||
Execute any command as the server.
|
||||
|
||||
```bash
|
||||
css_rcon <command>
|
||||
```
|
||||
|
||||
**Permission:** `@css/rcon`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_rcon status
|
||||
css_rcon changelevel de_dust2
|
||||
css_rcon sv_cheats 1
|
||||
```
|
||||
|
||||
**Warning:** Extremely powerful command. Only grant to server owners.
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"DisableDangerousCommands": true
|
||||
```
|
||||
|
||||
When enabled, prevents execution of dangerous commands via css_rcon.
|
||||
|
||||
---
|
||||
|
||||
### Restart Game
|
||||
|
||||
Restart the current game/round.
|
||||
|
||||
```bash
|
||||
css_rr
|
||||
css_rg
|
||||
css_restart
|
||||
css_restartgame
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Notes:**
|
||||
- Restarts the current round
|
||||
- Score is reset
|
||||
- Players remain connected
|
||||
|
||||
---
|
||||
|
||||
## Permission Flags
|
||||
|
||||
Common permission flags used in CS2-SimpleAdmin:
|
||||
|
||||
| Flag | Description | Common Use |
|
||||
|------|-------------|------------|
|
||||
| `@css/generic` | Generic admin access | Basic admin menu, info commands |
|
||||
| `@css/chat` | Chat management | Gag, mute, silence |
|
||||
| `@css/kick` | Kick players | Kick, warnings, player info |
|
||||
| `@css/ban` | Ban players | Ban, banip, addban |
|
||||
| `@css/unban` | Unban players | Remove bans |
|
||||
| `@css/permban` | Permanent bans | Issue permanent bans |
|
||||
| `@css/changemap` | Change maps | Map changing |
|
||||
| `@css/cvar` | Change cvars | Server variable modification |
|
||||
| `@css/rcon` | Execute rcon | Full server control |
|
||||
| `@css/root` | Root access | All permissions, admin management |
|
||||
| `@css/slay` | Slay/respawn | Player manipulation |
|
||||
| `@css/cheats` | Cheat commands | God mode, noclip, give weapons |
|
||||
| `@css/showip` | View IPs | See player IP addresses |
|
||||
|
||||
---
|
||||
|
||||
## Immunity System
|
||||
|
||||
Immunity prevents lower-level admins from targeting higher-level admins.
|
||||
|
||||
**How it works:**
|
||||
- Each admin has an immunity value (0-100)
|
||||
- Higher immunity = more protection
|
||||
- Admins can only target players with lower immunity
|
||||
|
||||
**Example:**
|
||||
- Admin A has immunity 50
|
||||
- Admin B has immunity 30
|
||||
- Admin A can ban Admin B
|
||||
- Admin B cannot ban Admin A
|
||||
|
||||
**Best Practice:**
|
||||
- Owner: 99
|
||||
- Senior admins: 80-90
|
||||
- Moderators: 50-70
|
||||
- Trial mods: 20-40
|
||||
- Regular players: 0
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Reload Admins on Map Change
|
||||
|
||||
```json
|
||||
"ReloadAdminsEveryMapChange": false
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `true` - Reload admin permissions every map change
|
||||
- `false` - Only reload when explicitly requested (better performance)
|
||||
|
||||
### Show Activity Type
|
||||
|
||||
```json
|
||||
"ShowActivityType": 2
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `0` - Hide all admin activity
|
||||
- `1` - Show activity anonymously ("An admin banned PlayerName")
|
||||
- `2` - Show admin name ("AdminName banned PlayerName")
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Admin Management
|
||||
|
||||
1. **Use groups** - Easier to manage than individual permissions
|
||||
2. **Set appropriate immunity** - Prevent abuse
|
||||
3. **Time-limited admin** - For trial moderators
|
||||
4. **Document changes** - Keep track of who has what permissions
|
||||
|
||||
### Permission Assignment
|
||||
|
||||
**Recommended hierarchy:**
|
||||
```
|
||||
Root (@css/root, immunity 99):
|
||||
- Server owners only
|
||||
|
||||
Senior Admin (@css/ban,@css/kick,@css/chat,@css/changemap, immunity 80):
|
||||
- Trusted long-term admins
|
||||
|
||||
Moderator (@css/kick,@css/chat, immunity 50):
|
||||
- Regular moderators
|
||||
|
||||
Trial Mod (@css/kick, immunity 20):
|
||||
- New moderators on probation
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
1. **Limit @css/rcon** - Only to server owner
|
||||
2. **Limit @css/cvar** - Only to senior admins
|
||||
3. **Monitor admin actions** - Review logs regularly
|
||||
4. **Use time-limited admin** - For temporary staff
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Admin permissions not working
|
||||
|
||||
**Check:**
|
||||
1. Is admin correctly added with `css_addadmin`?
|
||||
2. Run `css_reloadadmins`
|
||||
3. Check database connection
|
||||
4. Verify SteamID format
|
||||
|
||||
### Can't target another admin
|
||||
|
||||
**Check:**
|
||||
- Your immunity level vs target's immunity
|
||||
- You need equal or higher immunity to target
|
||||
|
||||
### Commands not available
|
||||
|
||||
**Check:**
|
||||
- Your permission flags
|
||||
- Commands.json for disabled commands
|
||||
- Server console for errors
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Ban Commands](basebans)** - Player punishment
|
||||
- **[Communication Commands](basecomms)** - Chat/voice management
|
||||
- **[Player Commands](playercommands)** - Player manipulation
|
||||
396
CS2-SimpleAdmin-docs/docs/user/commands/basecomms.md
Normal file
@@ -0,0 +1,396 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Communication Commands
|
||||
|
||||
Commands for managing player communication (voice and text chat).
|
||||
|
||||
## Overview
|
||||
|
||||
CS2-SimpleAdmin provides three types of communication restrictions:
|
||||
|
||||
- **Gag** - Blocks text chat only
|
||||
- **Mute** - Blocks voice chat only
|
||||
- **Silence** - Blocks both text and voice chat
|
||||
|
||||
---
|
||||
|
||||
## Gag Commands
|
||||
|
||||
### Gag Player
|
||||
|
||||
Prevent a player from using text chat.
|
||||
|
||||
```bash
|
||||
css_gag <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gag #123 30 "Chat spam"
|
||||
css_gag PlayerName 1440 "Advertising"
|
||||
css_gag @all 5 "Everyone quiet for 5 minutes"
|
||||
```
|
||||
|
||||
### Add Gag (Offline Player)
|
||||
|
||||
Gag a player by SteamID even if they're offline.
|
||||
|
||||
```bash
|
||||
css_addgag <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addgag 76561198012345678 60 "Chat abuse"
|
||||
css_addgag STEAM_1:0:12345678 1440 "Spam"
|
||||
```
|
||||
|
||||
### Ungag Player
|
||||
|
||||
Remove a gag from a player.
|
||||
|
||||
```bash
|
||||
css_ungag <steamid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_ungag PlayerName "Appeal accepted"
|
||||
css_ungag 76561198012345678 "Mistake"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mute Commands
|
||||
|
||||
### Mute Player
|
||||
|
||||
Prevent a player from using voice chat.
|
||||
|
||||
```bash
|
||||
css_mute <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_mute #123 30 "Mic spam"
|
||||
css_mute PlayerName 60 "Loud music"
|
||||
css_mute @t 5 "T team timeout"
|
||||
```
|
||||
|
||||
### Add Mute (Offline Player)
|
||||
|
||||
Mute a player by SteamID even if they're offline.
|
||||
|
||||
```bash
|
||||
css_addmute <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addmute 76561198012345678 120 "Voice abuse"
|
||||
css_addmute STEAM_1:0:12345678 1440 "Mic spam"
|
||||
```
|
||||
|
||||
### Unmute Player
|
||||
|
||||
Remove a mute from a player.
|
||||
|
||||
```bash
|
||||
css_unmute <steamid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unmute PlayerName "Behavior improved"
|
||||
css_unmute 76561198012345678 "Time served"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Silence Commands
|
||||
|
||||
### Silence Player
|
||||
|
||||
Block both text and voice chat from a player.
|
||||
|
||||
```bash
|
||||
css_silence <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_silence #123 60 "Complete communication ban"
|
||||
css_silence PlayerName 1440 "Severe abuse"
|
||||
```
|
||||
|
||||
### Add Silence (Offline Player)
|
||||
|
||||
Silence a player by SteamID even if they're offline.
|
||||
|
||||
```bash
|
||||
css_addsilence <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addsilence 76561198012345678 120 "Total communication ban"
|
||||
css_addsilence STEAM_1:0:12345678 0 "Permanent silence"
|
||||
```
|
||||
|
||||
### Unsilence Player
|
||||
|
||||
Remove a silence from a player.
|
||||
|
||||
```bash
|
||||
css_unsilence <steamid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unsilence PlayerName "Punishment complete"
|
||||
css_unsilence 76561198012345678 "Appeal granted"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
All communication commands require the `@css/chat` permission.
|
||||
|
||||
| Command | Action | Offline Support |
|
||||
|---------|--------|----------------|
|
||||
| `css_gag` | Block text chat | No |
|
||||
| `css_addgag` | Block text chat | Yes |
|
||||
| `css_ungag` | Remove text block | Yes |
|
||||
| `css_mute` | Block voice chat | No |
|
||||
| `css_addmute` | Block voice chat | Yes |
|
||||
| `css_unmute` | Remove voice block | Yes |
|
||||
| `css_silence` | Block both | No |
|
||||
| `css_addsilence` | Block both | Yes |
|
||||
| `css_unsilence` | Remove both blocks | Yes |
|
||||
|
||||
---
|
||||
|
||||
## Communication Penalty Types
|
||||
|
||||
### When to Use Each Type
|
||||
|
||||
**Gag (Text Only):**
|
||||
- Chat spam
|
||||
- Advertising in chat
|
||||
- Offensive messages
|
||||
- Spectator camera abuse messages
|
||||
|
||||
**Mute (Voice Only):**
|
||||
- Mic spam
|
||||
- Loud music/noise
|
||||
- Voice abuse
|
||||
- Excessive talking
|
||||
|
||||
**Silence (Both):**
|
||||
- Severe abuse cases
|
||||
- Players who switch between chat and voice to evade
|
||||
- Complete communication bans
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### UserMessage Gag Type
|
||||
|
||||
In `CS2-SimpleAdmin.json`:
|
||||
|
||||
```json
|
||||
"UserMessageGagChatType": false
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `false` - Standard gag implementation (default)
|
||||
- `true` - Alternative gag using UserMessage system
|
||||
|
||||
**Note:** Try switching this if gag commands don't work as expected.
|
||||
|
||||
### Notify Penalties on Connect
|
||||
|
||||
```json
|
||||
"NotifyPenaltiesToAdminOnConnect": true
|
||||
```
|
||||
|
||||
When enabled, admins see active communication penalties when they join:
|
||||
```
|
||||
[CS2-SimpleAdmin] PlayerName is gagged (30 minutes remaining)
|
||||
[CS2-SimpleAdmin] PlayerName is muted (1 hour remaining)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checking Penalties
|
||||
|
||||
### View Own Penalties
|
||||
|
||||
Players can check their own communication penalties:
|
||||
|
||||
```bash
|
||||
css_penalties
|
||||
css_mypenalties
|
||||
css_comms
|
||||
```
|
||||
|
||||
Shows:
|
||||
- Active gags, mutes, and silences
|
||||
- Duration remaining
|
||||
- Reason for penalty
|
||||
- Admin who issued it
|
||||
|
||||
### Admin View of Penalties
|
||||
|
||||
Use the admin menu or player info command:
|
||||
|
||||
```bash
|
||||
css_who <#userid or name>
|
||||
```
|
||||
|
||||
Shows complete penalty history including communication restrictions.
|
||||
|
||||
---
|
||||
|
||||
## Time Durations
|
||||
|
||||
Common duration values:
|
||||
|
||||
| Duration | Minutes | Use Case |
|
||||
|----------|---------|----------|
|
||||
| 1 minute | 1 | Quick warning |
|
||||
| 5 minutes | 5 | Minor spam |
|
||||
| 15 minutes | 15 | Standard timeout |
|
||||
| 30 minutes | 30 | Repeated offense |
|
||||
| 1 hour | 60 | Moderate abuse |
|
||||
| 6 hours | 360 | Serious abuse |
|
||||
| 1 day | 1440 | Severe abuse |
|
||||
| 1 week | 10080 | Extreme cases |
|
||||
| Permanent | 0 | Reserved for worst cases |
|
||||
|
||||
---
|
||||
|
||||
## Player Targeting
|
||||
|
||||
All communication commands support advanced targeting:
|
||||
|
||||
- `@all` - Target all players
|
||||
- `@ct` - Target all Counter-Terrorists
|
||||
- `@t` - Target all Terrorists
|
||||
- `@spec` - Target all spectators
|
||||
- `#123` - Target by userid
|
||||
- `PlayerName` - Target by name
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gag @all 1 "Quiet for one minute"
|
||||
css_mute @t 5 "T team voice timeout"
|
||||
css_silence @ct 10 "CT team complete silence"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Communication Management
|
||||
|
||||
1. **Start with warnings** - Not all chat issues need immediate gag
|
||||
2. **Use appropriate durations** - Match severity to punishment
|
||||
3. **Provide reasons** - Helps players understand what they did wrong
|
||||
4. **Consider silence carefully** - Complete communication ban is harsh
|
||||
|
||||
### Gag vs Mute vs Silence
|
||||
|
||||
**Progressive Approach:**
|
||||
1. Verbal warning
|
||||
2. Gag or mute (specific to offense)
|
||||
3. Longer gag/mute for repeat offense
|
||||
4. Silence for continued abuse
|
||||
5. Temporary ban for extreme cases
|
||||
|
||||
### Documentation
|
||||
|
||||
1. **Always provide reasons** - Required for appeals
|
||||
2. **Be specific** - "Mic spam" not just "abuse"
|
||||
3. **Keep records** - Use admin logs for repeat offenders
|
||||
|
||||
---
|
||||
|
||||
## Discord Integration
|
||||
|
||||
Communication penalties can send Discord notifications when configured:
|
||||
|
||||
```json
|
||||
"DiscordPenaltyGagSettings": [...],
|
||||
"DiscordPenaltyMuteSettings": [...],
|
||||
"DiscordPenaltySilenceSettings": [...]
|
||||
```
|
||||
|
||||
Notifications include:
|
||||
- Player name and SteamID
|
||||
- Penalty type and duration
|
||||
- Reason provided
|
||||
- Admin who issued it
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gag doesn't work
|
||||
|
||||
**Try:**
|
||||
1. Switch `UserMessageGagChatType` in config
|
||||
2. Ensure player is actually gagged (check with `css_who`)
|
||||
3. Check for conflicting plugins
|
||||
|
||||
### Mute doesn't block voice
|
||||
|
||||
**Check:**
|
||||
- Is sv_talk_enemy_dead configured correctly?
|
||||
- Are there voice management plugins conflicting?
|
||||
- Check server console for errors
|
||||
|
||||
### Penalties not persistent across maps
|
||||
|
||||
**Solution:**
|
||||
- Penalties should persist automatically
|
||||
- Check database connection
|
||||
- Verify MultiServerMode if using multiple servers
|
||||
|
||||
### Player can't see their penalties
|
||||
|
||||
**Check:**
|
||||
- Command aliases in Commands.json
|
||||
- Ensure `css_penalties` is enabled
|
||||
- Check player chat permissions
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Ban Commands](basebans)** - For more serious offenses
|
||||
- **[Player Commands](playercommands)** - Kick, team switch
|
||||
- **[Base Commands](basecommands)** - Admin management
|
||||
436
CS2-SimpleAdmin-docs/docs/user/commands/basevotes.md
Normal file
@@ -0,0 +1,436 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Vote Commands
|
||||
|
||||
Commands for creating polls and votes on your server.
|
||||
|
||||
## Create Vote
|
||||
|
||||
Create a custom poll for players to vote on.
|
||||
|
||||
```bash
|
||||
css_vote <question> [option1] [option2] [option3] ...
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Parameters:**
|
||||
- `question` - The question to ask players
|
||||
- `option1, option2, ...` - Vote options (at least 2 required)
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple Yes/No Vote
|
||||
|
||||
```bash
|
||||
css_vote "Should we change map?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Player sees:**
|
||||
```
|
||||
Vote: Should we change map?
|
||||
1. Yes
|
||||
2. No
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Multiple Options
|
||||
|
||||
```bash
|
||||
css_vote "Which map should we play next?" "de_dust2" "de_mirage" "de_inferno" "de_nuke"
|
||||
```
|
||||
|
||||
**Player sees:**
|
||||
```
|
||||
Vote: Which map should we play next?
|
||||
1. de_dust2
|
||||
2. de_mirage
|
||||
3. de_inferno
|
||||
4. de_nuke
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Rule Vote
|
||||
|
||||
```bash
|
||||
css_vote "Should we allow AWPs?" "Yes" "No" "Only one per team"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Activity Vote
|
||||
|
||||
```bash
|
||||
css_vote "What should we do?" "Surf" "Deathrun" "Competitive" "Fun Round"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How Voting Works
|
||||
|
||||
### Player Participation
|
||||
|
||||
Players vote by:
|
||||
1. Opening their chat
|
||||
2. Typing the number of their choice
|
||||
3. Or using vote menu (if available)
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Player: 1 (votes for option 1)
|
||||
Player: 2 (votes for option 2)
|
||||
```
|
||||
|
||||
### Vote Duration
|
||||
|
||||
- Default vote time: ~30 seconds
|
||||
- Vote timer shows on screen
|
||||
- Results shown when vote ends
|
||||
|
||||
### Vote Results
|
||||
|
||||
After voting ends, results are displayed:
|
||||
```
|
||||
Vote Results:
|
||||
1. Yes - 12 votes (60%)
|
||||
2. No - 8 votes (40%)
|
||||
|
||||
Winner: Yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Map Voting
|
||||
|
||||
```bash
|
||||
css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno"
|
||||
```
|
||||
|
||||
### Rule Changes
|
||||
|
||||
```bash
|
||||
css_vote "Enable friendly fire?" "Yes" "No"
|
||||
css_vote "Restart round?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Player Punishment
|
||||
|
||||
```bash
|
||||
css_vote "Ban PlayerName for cheating?" "Yes" "No"
|
||||
css_vote "Kick AFK player?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Fun Rounds
|
||||
|
||||
```bash
|
||||
css_vote "Fun round type?" "Knife only" "Deagle only" "Zeus only" "Normal"
|
||||
```
|
||||
|
||||
### Server Settings
|
||||
|
||||
```bash
|
||||
css_vote "Round time?" "2 minutes" "3 minutes" "5 minutes"
|
||||
css_vote "Max players?" "10v10" "5v5" "7v7"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Question Clarity
|
||||
|
||||
**Good Questions:**
|
||||
- Clear and concise
|
||||
- Specific
|
||||
- Easy to understand
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
✅ css_vote "Change to de_dust2?" "Yes" "No"
|
||||
❌ css_vote "Map?" "Yes" "No" # Unclear what map
|
||||
|
||||
✅ css_vote "Restart this round?" "Yes" "No"
|
||||
❌ css_vote "Restart?" "Yes" "No" # Restart what?
|
||||
```
|
||||
|
||||
### Option Limits
|
||||
|
||||
**Recommendations:**
|
||||
- 2-5 options ideal
|
||||
- Too many options confuse players
|
||||
- Keep options brief
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
✅ css_vote "Next map?" "dust2" "mirage" "inferno"
|
||||
❌ css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno" "de_nuke" "de_vertigo" "de_ancient" "de_anubis"
|
||||
```
|
||||
|
||||
### Timing
|
||||
|
||||
**When to use votes:**
|
||||
- End of round
|
||||
- Between maps
|
||||
- During downtime
|
||||
- Not during active gameplay
|
||||
|
||||
**When NOT to use votes:**
|
||||
- Mid-round
|
||||
- During clutch situations
|
||||
- Too frequently
|
||||
|
||||
### Vote Spam Prevention
|
||||
|
||||
Don't spam votes:
|
||||
```bash
|
||||
❌ Multiple votes in quick succession
|
||||
❌ Overlapping votes
|
||||
❌ Votes every round
|
||||
```
|
||||
|
||||
Wait for current vote to finish before starting another.
|
||||
|
||||
---
|
||||
|
||||
## Vote Types
|
||||
|
||||
### Administrative Votes
|
||||
|
||||
**Map change:**
|
||||
```bash
|
||||
css_vote "Change map now?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Server restart:**
|
||||
```bash
|
||||
css_vote "Restart server?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Rule enforcement:**
|
||||
```bash
|
||||
css_vote "Kick PlayerName?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Gameplay Votes
|
||||
|
||||
**Weapon restrictions:**
|
||||
```bash
|
||||
css_vote "Disable AWP?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Team scramble:**
|
||||
```bash
|
||||
css_vote "Scramble teams?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Round rules:**
|
||||
```bash
|
||||
css_vote "Knife round first?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Event Votes
|
||||
|
||||
**Tournament:**
|
||||
```bash
|
||||
css_vote "Start tournament?" "Yes, start now" "Wait 5 minutes" "No, cancel"
|
||||
```
|
||||
|
||||
**Custom game mode:**
|
||||
```bash
|
||||
css_vote "Game mode?" "Hide and Seek" "Gungame" "Surf" "Normal"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Limitations
|
||||
|
||||
### Technical Limits
|
||||
|
||||
- Maximum ~10 options (depends on menu system)
|
||||
- One vote at a time
|
||||
- Requires active players to participate
|
||||
|
||||
### Permission Required
|
||||
|
||||
Only admins with `@css/generic` permission can start votes.
|
||||
|
||||
To grant permission:
|
||||
```bash
|
||||
css_addadmin STEAMID "Name" "@css/generic" 50 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vote Results Handling
|
||||
|
||||
### Manual Enforcement
|
||||
|
||||
Votes don't automatically execute actions. Admins must:
|
||||
|
||||
1. **See the results**
|
||||
2. **Manually execute the winning option**
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Start vote
|
||||
css_vote "Change to de_dust2?" "Yes" "No"
|
||||
|
||||
# If "Yes" wins, manually change map
|
||||
css_map de_dust2
|
||||
```
|
||||
|
||||
### Why Manual?
|
||||
|
||||
- Prevents abuse
|
||||
- Allows admin oversight
|
||||
- Gives control over execution
|
||||
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Combining with Commands
|
||||
|
||||
Use votes to decide, then execute:
|
||||
|
||||
```bash
|
||||
# Vote on map
|
||||
css_vote "Next map?" "dust2" "mirage" "inferno"
|
||||
# If dust2 wins:
|
||||
css_map de_dust2
|
||||
|
||||
# Vote on player kick
|
||||
css_vote "Kick PlayerName?" "Yes" "No"
|
||||
# If Yes wins:
|
||||
css_kick PlayerName "Voted to be kicked"
|
||||
```
|
||||
|
||||
### Sequential Votes
|
||||
|
||||
Run multiple votes for complex decisions:
|
||||
|
||||
```bash
|
||||
# First vote: Mode
|
||||
css_vote "Game mode?" "Competitive" "Casual"
|
||||
|
||||
# If Competitive wins, second vote:
|
||||
css_vote "Round time?" "2 min" "3 min" "5 min"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Check if vote commands are enabled in:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"Commands": {
|
||||
"css_vote": {
|
||||
"Aliases": [
|
||||
"css_vote"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To disable votes, remove all aliases:
|
||||
```json
|
||||
{
|
||||
"Commands": {
|
||||
"css_vote": {
|
||||
"Aliases": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Vote doesn't start
|
||||
|
||||
**Check:**
|
||||
- Do you have `@css/generic` permission?
|
||||
- Is command enabled in Commands.json?
|
||||
- Are there at least 2 options?
|
||||
|
||||
### Players can't vote
|
||||
|
||||
**Check:**
|
||||
- Vote menu is showing
|
||||
- Players know how to vote (type number in chat)
|
||||
- Vote hasn't already ended
|
||||
|
||||
### Vote results not showing
|
||||
|
||||
**Check:**
|
||||
- Wait for vote to complete
|
||||
- Check server console
|
||||
- Ensure voting system is working
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
| Command | Permission | Description |
|
||||
|---------|------------|-------------|
|
||||
| `css_vote` | `@css/generic` | Create votes/polls |
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
### Effective Polling
|
||||
|
||||
1. **Ask clear questions** - No ambiguity
|
||||
2. **Limit options** - 2-4 is ideal
|
||||
3. **Time it right** - Between rounds
|
||||
4. **Follow through** - Execute winning option
|
||||
5. **Don't overuse** - Votes lose impact if spammed
|
||||
|
||||
### Community Engagement
|
||||
|
||||
Use votes to:
|
||||
- Involve community in decisions
|
||||
- Gauge player preferences
|
||||
- Create democratic server atmosphere
|
||||
- Get feedback on changes
|
||||
|
||||
### Example Scenarios
|
||||
|
||||
**New map test:**
|
||||
```bash
|
||||
css_vote "Try new map cs_office?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Event planning:**
|
||||
```bash
|
||||
css_vote "Tournament this weekend?" "Saturday" "Sunday" "No thanks"
|
||||
```
|
||||
|
||||
**Rule feedback:**
|
||||
```bash
|
||||
css_vote "Keep no-AWP rule?" "Yes" "No" "Only limit to 2"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Base Commands](basecommands)** - Server management
|
||||
- **[Chat Commands](basechat)** - Announcements
|
||||
- **[Player Commands](playercommands)** - Player actions
|
||||
516
CS2-SimpleAdmin-docs/docs/user/commands/playercommands.md
Normal file
@@ -0,0 +1,516 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Player Commands
|
||||
|
||||
Commands for managing and manipulating players on your server.
|
||||
|
||||
:::note
|
||||
Many of these commands are included in the base plugin. For extended fun commands (god mode, noclip, freeze, etc.), see the [Fun Commands Module](../../modules/funcommands).
|
||||
:::
|
||||
|
||||
## Player Management
|
||||
|
||||
### Slay Player
|
||||
|
||||
Kill a player instantly.
|
||||
|
||||
```bash
|
||||
css_slay <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_slay #123
|
||||
css_slay PlayerName
|
||||
css_slay @ct # Slay all CTs
|
||||
css_slay @t # Slay all terrorists
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Punishment for rule breaking
|
||||
- Ending rounds quickly
|
||||
- Removing camping players
|
||||
|
||||
---
|
||||
|
||||
### Slap Player
|
||||
|
||||
Slap a player, dealing damage and pushing them.
|
||||
|
||||
```bash
|
||||
css_slap <#userid or name> [damage]
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `damage` - HP damage to deal (default: 0)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_slap #123 # Slap with no damage
|
||||
css_slap PlayerName 10 # Slap for 10 HP damage
|
||||
css_slap @all 5 # Slap everyone for 5 damage
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player is pushed in a random direction
|
||||
- Optional damage dealt
|
||||
- Makes slap sound
|
||||
|
||||
**Use cases:**
|
||||
- Funny punishment
|
||||
- Getting player attention
|
||||
- Moving AFK players
|
||||
|
||||
---
|
||||
|
||||
## Player Attributes
|
||||
|
||||
### Set Player Health
|
||||
|
||||
Set a player's health points.
|
||||
|
||||
```bash
|
||||
css_hp <#userid or name> <health>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_hp #123 100 # Set to full health
|
||||
css_hp PlayerName 1 # Set to 1 HP
|
||||
css_hp @ct 200 # Give all CTs 200 HP
|
||||
```
|
||||
|
||||
**Valid range:** 1 - 999+
|
||||
|
||||
---
|
||||
|
||||
### Set Player Speed
|
||||
|
||||
Modify a player's movement speed.
|
||||
|
||||
```bash
|
||||
css_speed <#userid or name> <speed>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `speed` - Speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_speed #123 1.5 # 150% speed
|
||||
css_speed PlayerName 0.5 # 50% speed (slow motion)
|
||||
css_speed @all 2.0 # Double speed for everyone
|
||||
css_speed #123 1.0 # Reset to normal speed
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.5` - Slow motion
|
||||
- `1.0` - Normal (default)
|
||||
- `1.5` - Fast
|
||||
- `2.0` - Very fast
|
||||
- `3.0` - Extremely fast
|
||||
|
||||
**Note:** Speed persists across respawns until reset.
|
||||
|
||||
---
|
||||
|
||||
### Set Player Gravity
|
||||
|
||||
Modify a player's gravity.
|
||||
|
||||
```bash
|
||||
css_gravity <#userid or name> <gravity>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `gravity` - Gravity multiplier (1.0 = normal, 0.5 = moon jump, 2.0 = heavy)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gravity #123 0.5 # Moon jump
|
||||
css_gravity PlayerName 2.0 # Heavy gravity
|
||||
css_gravity @all 0.1 # Super jump for everyone
|
||||
css_gravity #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.1` - Super high jumps
|
||||
- `0.5` - Moon gravity
|
||||
- `1.0` - Normal (default)
|
||||
- `2.0` - Heavy/fast falling
|
||||
|
||||
**Note:** Gravity persists across respawns until reset.
|
||||
|
||||
---
|
||||
|
||||
### Set Player Money
|
||||
|
||||
Set a player's money amount.
|
||||
|
||||
```bash
|
||||
css_money <#userid or name> <amount>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_money #123 16000 # Max money
|
||||
css_money PlayerName 0 # Remove all money
|
||||
css_money @ct 10000 # Give all CTs $10,000
|
||||
```
|
||||
|
||||
**Valid range:** 0 - 65535 (CS2 engine limit)
|
||||
|
||||
---
|
||||
|
||||
## Team Management
|
||||
|
||||
### Switch Player Team
|
||||
|
||||
Move a player to a different team.
|
||||
|
||||
```bash
|
||||
css_team <#userid or name> [ct/t/spec] [-k]
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Parameters:**
|
||||
- `ct` - Counter-Terrorist team
|
||||
- `t` - Terrorist team
|
||||
- `spec` - Spectators
|
||||
- `-k` - Kill player during switch (optional)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_team #123 ct # Move to CT
|
||||
css_team PlayerName t # Move to T
|
||||
css_team @spec t # Move all spectators to T
|
||||
css_team #123 ct -k # Move to CT and kill
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"TeamSwitchType": 1
|
||||
```
|
||||
|
||||
Determines team switch behavior.
|
||||
|
||||
---
|
||||
|
||||
### Rename Player
|
||||
|
||||
Temporarily rename a player.
|
||||
|
||||
```bash
|
||||
css_rename <#userid or name> <new name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_rename #123 "NewName"
|
||||
css_rename PlayerName "RenamedPlayer"
|
||||
css_rename @all "Everyone"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Rename is temporary (resets on reconnect)
|
||||
- For permanent rename, use `css_prename`
|
||||
|
||||
---
|
||||
|
||||
### Permanent Rename
|
||||
|
||||
Permanently force a player's name.
|
||||
|
||||
```bash
|
||||
css_prename <#userid or name> <new name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_prename #123 "EnforcedName"
|
||||
css_prename PlayerName "NewIdentity"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Name is enforced even after reconnect
|
||||
- Stored in database
|
||||
- Player cannot change it
|
||||
- Useful for offensive names
|
||||
|
||||
---
|
||||
|
||||
## Weapon Management
|
||||
|
||||
### Give Weapon
|
||||
|
||||
Give a weapon to a player.
|
||||
|
||||
```bash
|
||||
css_give <#userid or name> <weapon>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Weapon names:**
|
||||
- Rifles: `ak47`, `m4a1`, `m4a1_silencer`, `aug`, `sg556`, `awp`
|
||||
- SMGs: `mp5sd`, `mp7`, `mp9`, `p90`, `ump45`
|
||||
- Heavy: `nova`, `xm1014`, `mag7`, `m249`, `negev`
|
||||
- Pistols: `deagle`, `elite`, `fiveseven`, `glock`, `hkp2000`, `p250`, `tec9`, `usp_silencer`
|
||||
- Grenades: `flashbang`, `hegrenade`, `smokegrenade`, `molotov`, `incgrenade`, `decoy`
|
||||
- Equipment: `kevlar`, `assaultsuit`, `defuser`, `knife`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_give #123 awp
|
||||
css_give PlayerName ak47
|
||||
css_give @ct m4a1
|
||||
css_give @all deagle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Strip Weapons
|
||||
|
||||
Remove all weapons from a player.
|
||||
|
||||
```bash
|
||||
css_strip <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_strip #123
|
||||
css_strip PlayerName
|
||||
css_strip @t # Disarm all terrorists
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Removes all weapons
|
||||
- Leaves player with knife only
|
||||
- Removes grenades and equipment
|
||||
|
||||
---
|
||||
|
||||
## Teleportation
|
||||
|
||||
### Teleport to Player
|
||||
|
||||
Teleport yourself to another player.
|
||||
|
||||
```bash
|
||||
css_tp <#userid or name>
|
||||
css_tpto <#userid or name>
|
||||
css_goto <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_tp #123
|
||||
css_goto PlayerName
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Checking player behavior
|
||||
- Admin help
|
||||
- Spectating suspicious players
|
||||
|
||||
---
|
||||
|
||||
### Teleport Player to You
|
||||
|
||||
Bring a player to your location.
|
||||
|
||||
```bash
|
||||
css_bring <#userid or name>
|
||||
css_tphere <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_bring #123
|
||||
css_tphere PlayerName
|
||||
css_bring @all # Bring everyone to you
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Moving stuck players
|
||||
- Gathering players
|
||||
- Admin events
|
||||
|
||||
---
|
||||
|
||||
### Respawn Player
|
||||
|
||||
Respawn a dead player.
|
||||
|
||||
```bash
|
||||
css_respawn <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_respawn #123
|
||||
css_respawn PlayerName
|
||||
css_respawn @ct # Respawn all dead CTs
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Player spawns at spawn point
|
||||
- Equipped with default weapons
|
||||
- Can break competitive balance
|
||||
|
||||
---
|
||||
|
||||
## Player Targeting
|
||||
|
||||
All player commands support advanced targeting:
|
||||
|
||||
### Target Syntax
|
||||
|
||||
- `@all` - All players
|
||||
- `@ct` - All Counter-Terrorists
|
||||
- `@t` - All Terrorists
|
||||
- `@spec` - All spectators
|
||||
- `@alive` - All alive players
|
||||
- `@dead` - All dead players
|
||||
- `@bot` - All bots
|
||||
- `@human` - All human players
|
||||
- `@me` - Yourself
|
||||
- `#123` - Specific user ID
|
||||
- `PlayerName` - By name (partial match supported)
|
||||
|
||||
### Multiple Targets
|
||||
|
||||
```bash
|
||||
css_slay @ct # Kills all CTs
|
||||
css_hp @all 200 # Give everyone 200 HP
|
||||
css_speed @t 2.0 # Make all Ts fast
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
| Command | Required Permission | Description |
|
||||
|---------|-------------------|-------------|
|
||||
| `css_slay` | `@css/slay` | Kill players |
|
||||
| `css_slap` | `@css/slay` | Slap players |
|
||||
| `css_hp` | `@css/slay` | Set health |
|
||||
| `css_speed` | `@css/slay` | Modify speed |
|
||||
| `css_gravity` | `@css/slay` | Modify gravity |
|
||||
| `css_money` | `@css/slay` | Set money |
|
||||
| `css_team` | `@css/kick` | Change team |
|
||||
| `css_rename` | `@css/kick` | Temporary rename |
|
||||
| `css_prename` | `@css/ban` | Permanent rename |
|
||||
| `css_give` | `@css/cheats` | Give weapons |
|
||||
| `css_strip` | `@css/slay` | Remove weapons |
|
||||
| `css_tp` | `@css/kick` | Teleport to player |
|
||||
| `css_bring` | `@css/kick` | Bring player |
|
||||
| `css_respawn` | `@css/cheats` | Respawn players |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Punishment Commands
|
||||
|
||||
**Slay:**
|
||||
- Use for rule violations
|
||||
- Better than kick for minor issues
|
||||
- Allows player to stay and learn
|
||||
|
||||
**Slap:**
|
||||
- Lighter punishment
|
||||
- Good for warnings
|
||||
- Can be funny/entertaining
|
||||
|
||||
### Gameplay Modification
|
||||
|
||||
**HP/Speed/Gravity:**
|
||||
- Use for events/fun rounds
|
||||
- Don't abuse during competitive play
|
||||
- Reset to normal after use
|
||||
|
||||
**Respawn:**
|
||||
- Very disruptive to gameplay
|
||||
- Use sparingly
|
||||
- Good for fixing bugs/mistakes
|
||||
|
||||
### Team Management
|
||||
|
||||
**Team switching:**
|
||||
- Balance teams fairly
|
||||
- Don't abuse for winning
|
||||
- Use `-k` flag for competitive integrity
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Team Switch Behavior
|
||||
|
||||
```json
|
||||
"TeamSwitchType": 1
|
||||
```
|
||||
|
||||
Controls how team switching works.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Speed/Gravity not persisting
|
||||
|
||||
**Solution:** These are maintained by a timer. If they reset:
|
||||
- Check server console for errors
|
||||
- Ensure plugin is loaded correctly
|
||||
- Try reapplying the modification
|
||||
|
||||
### Can't teleport
|
||||
|
||||
**Check:**
|
||||
- Target player is connected
|
||||
- You have correct permissions
|
||||
- Both players are valid
|
||||
|
||||
### Give weapon not working
|
||||
|
||||
**Check:**
|
||||
- Weapon name is correct
|
||||
- Player is alive
|
||||
- Player has inventory space
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Fun Commands Module](../../modules/funcommands)** - Extended fun commands (freeze, god mode, noclip)
|
||||
- **[Ban Commands](basebans)** - Punishment commands
|
||||
- **[Base Commands](basecommands)** - Server management
|
||||
397
CS2-SimpleAdmin-docs/docs/user/configuration.md
Normal file
@@ -0,0 +1,397 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Configuration
|
||||
|
||||
Learn how to configure CS2-SimpleAdmin to suit your server's needs.
|
||||
|
||||
## Configuration File Location
|
||||
|
||||
The main configuration file is located at:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
||||
```
|
||||
|
||||
## Configuration Structure
|
||||
|
||||
The configuration file is divided into several sections:
|
||||
|
||||
### Database Configuration
|
||||
|
||||
Configure your database connection:
|
||||
|
||||
```json
|
||||
"DatabaseConfig": {
|
||||
"DatabaseType": "SQLite",
|
||||
"SqliteFilePath": "cs2-simpleadmin.sqlite",
|
||||
"DatabaseHost": "",
|
||||
"DatabasePort": 3306,
|
||||
"DatabaseUser": "",
|
||||
"DatabasePassword": "",
|
||||
"DatabaseName": "",
|
||||
"DatabaseSSlMode": "preferred"
|
||||
}
|
||||
```
|
||||
|
||||
**Database Types:**
|
||||
- `SQLite` - Local database file (good for single server)
|
||||
- `MySQL` - MySQL/MariaDB server (required for multi-server setups)
|
||||
|
||||
**MySQL Example:**
|
||||
```json
|
||||
"DatabaseConfig": {
|
||||
"DatabaseType": "MySQL",
|
||||
"DatabaseHost": "localhost",
|
||||
"DatabasePort": 3306,
|
||||
"DatabaseUser": "cs2admin",
|
||||
"DatabasePassword": "your_password",
|
||||
"DatabaseName": "cs2_simpleadmin",
|
||||
"DatabaseSSlMode": "preferred"
|
||||
}
|
||||
```
|
||||
|
||||
### Other Settings
|
||||
|
||||
General plugin settings:
|
||||
|
||||
```json
|
||||
"OtherSettings": {
|
||||
"ShowActivityType": 2,
|
||||
"TeamSwitchType": 1,
|
||||
"KickTime": 5,
|
||||
"BanType": 1,
|
||||
"TimeMode": 1,
|
||||
"DisableDangerousCommands": true,
|
||||
"MaxBanDuration": 10080,
|
||||
"MaxMuteDuration": 10080,
|
||||
"ExpireOldIpBans": 0,
|
||||
"ReloadAdminsEveryMapChange": false,
|
||||
"DisconnectedPlayersHistoryCount": 10,
|
||||
"NotifyPenaltiesToAdminOnConnect": true,
|
||||
"ShowBanMenuIfNoTime": true,
|
||||
"UserMessageGagChatType": false,
|
||||
"CheckMultiAccountsByIp": true,
|
||||
"AdditionalCommandsToLog": [],
|
||||
"IgnoredIps": []
|
||||
}
|
||||
```
|
||||
|
||||
**Settings Explained:**
|
||||
|
||||
| Setting | Description | Default |
|
||||
|---------|-------------|---------|
|
||||
| `ShowActivityType` | How to display admin actions (0=hide, 1=anonymous, 2=show name) | 2 |
|
||||
| `TeamSwitchType` | Team switch behavior | 1 |
|
||||
| `KickTime` | Delay before kicking player (seconds) | 5 |
|
||||
| `BanType` | Ban type (1=SteamID, 2=IP, 3=Both) | 1 |
|
||||
| `TimeMode` | Time display mode | 1 |
|
||||
| `DisableDangerousCommands` | Disable potentially dangerous commands | true |
|
||||
| `MaxBanDuration` | Maximum ban duration in minutes (0=unlimited) | 10080 |
|
||||
| `MaxMuteDuration` | Maximum mute duration in minutes (0=unlimited) | 10080 |
|
||||
| `ExpireOldIpBans` | Auto-expire IP bans after X days (0=disabled) | 0 |
|
||||
| `ReloadAdminsEveryMapChange` | Reload admin permissions on map change | false |
|
||||
| `DisconnectedPlayersHistoryCount` | Number of disconnected players to track | 10 |
|
||||
| `NotifyPenaltiesToAdminOnConnect` | Show penalties to admins when they connect | true |
|
||||
| `ShowBanMenuIfNoTime` | Show ban menu even without time parameter | true |
|
||||
| `UserMessageGagChatType` | Use UserMessage for gag (alternative chat blocking) | false |
|
||||
| `CheckMultiAccountsByIp` | Detect multiple accounts from same IP | true |
|
||||
| `AdditionalCommandsToLog` | Array of additional commands to log | [] |
|
||||
| `IgnoredIps` | IPs to ignore in multi-account detection | [] |
|
||||
|
||||
### Metrics and Updates
|
||||
|
||||
```json
|
||||
"EnableMetrics": true,
|
||||
"EnableUpdateCheck": true
|
||||
```
|
||||
|
||||
- `EnableMetrics` - Send anonymous usage statistics
|
||||
- `EnableUpdateCheck` - Check for plugin updates on load
|
||||
|
||||
### Timezone
|
||||
|
||||
Set your server's timezone for accurate timestamps:
|
||||
|
||||
```json
|
||||
"Timezone": "UTC"
|
||||
```
|
||||
|
||||
See the [list of timezones](#timezone-list) below.
|
||||
|
||||
### Warning Thresholds
|
||||
|
||||
Configure automatic actions when players reach warning thresholds:
|
||||
|
||||
```json
|
||||
"WarnThreshold": {
|
||||
"998": "css_addban STEAMID64 60 \"3/4 Warn\"",
|
||||
"999": "css_ban #USERID 120 \"4/4 Warn\""
|
||||
}
|
||||
```
|
||||
|
||||
**Example:** Automatically ban a player for 60 minutes when they receive their 3rd warning.
|
||||
|
||||
### Multi-Server Mode
|
||||
|
||||
Enable if you're running multiple servers with a shared database:
|
||||
|
||||
```json
|
||||
"MultiServerMode": true
|
||||
```
|
||||
|
||||
When enabled:
|
||||
- Bans are shared across all servers
|
||||
- Admin permissions can be global or server-specific
|
||||
- Player data is synchronized
|
||||
|
||||
### Discord Integration
|
||||
|
||||
Send notifications to Discord webhooks:
|
||||
|
||||
```json
|
||||
"Discord": {
|
||||
"DiscordLogWebhook": "https://discord.com/api/webhooks/...",
|
||||
"DiscordPenaltyBanSettings": [...],
|
||||
"DiscordPenaltyMuteSettings": [...],
|
||||
"DiscordPenaltyGagSettings": [...],
|
||||
"DiscordPenaltySilenceSettings": [...],
|
||||
"DiscordPenaltyWarnSettings": [...],
|
||||
"DiscordAssociatedAccountsSettings": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**Webhook Settings:**
|
||||
Each penalty type can have its own webhook configuration:
|
||||
|
||||
```json
|
||||
"DiscordPenaltyBanSettings": [
|
||||
{
|
||||
"name": "Color",
|
||||
"value": "#FF0000"
|
||||
},
|
||||
{
|
||||
"name": "Webhook",
|
||||
"value": "https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE"
|
||||
},
|
||||
{
|
||||
"name": "ThumbnailUrl",
|
||||
"value": "https://example.com/ban-icon.png"
|
||||
},
|
||||
{
|
||||
"name": "ImageUrl",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "Footer",
|
||||
"value": "CS2-SimpleAdmin"
|
||||
},
|
||||
{
|
||||
"name": "Time",
|
||||
"value": "{relative}"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Available Placeholders:**
|
||||
- `{relative}` - Relative timestamp
|
||||
- `{fixed}` - Fixed timestamp
|
||||
|
||||
### Map Configuration
|
||||
|
||||
Configure default maps and workshop maps:
|
||||
|
||||
```json
|
||||
"DefaultMaps": [
|
||||
"de_dust2",
|
||||
"de_mirage",
|
||||
"de_inferno"
|
||||
],
|
||||
"WorkshopMaps": {
|
||||
"aim_map": "123456789",
|
||||
"surf_map": "987654321"
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Server Commands
|
||||
|
||||
Add custom commands to the admin menu:
|
||||
|
||||
```json
|
||||
"CustomServerCommands": [
|
||||
{
|
||||
"Flag": "@css/root",
|
||||
"DisplayName": "Reload Admins",
|
||||
"Command": "css_reloadadmins"
|
||||
},
|
||||
{
|
||||
"Flag": "@css/cheats",
|
||||
"DisplayName": "Enable sv_cheats",
|
||||
"Command": "sv_cheats 1"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Menu Configuration
|
||||
|
||||
Configure menu appearance and options:
|
||||
|
||||
```json
|
||||
"MenuConfig": {
|
||||
"MenuType": "selectable",
|
||||
"Durations": [
|
||||
{ "name": "1 minute", "duration": 1 },
|
||||
{ "name": "5 minutes", "duration": 5 },
|
||||
{ "name": "15 minutes", "duration": 15 },
|
||||
{ "name": "1 hour", "duration": 60 },
|
||||
{ "name": "1 day", "duration": 1440 },
|
||||
{ "name": "7 days", "duration": 10080 },
|
||||
{ "name": "14 days", "duration": 20160 },
|
||||
{ "name": "30 days", "duration": 43200 },
|
||||
{ "name": "Permanent", "duration": 0 }
|
||||
],
|
||||
"BanReasons": [
|
||||
"Hacking",
|
||||
"Voice Abuse",
|
||||
"Chat Abuse",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"KickReasons": [
|
||||
"Voice Abuse",
|
||||
"Chat Abuse",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"WarnReasons": [
|
||||
"Voice Abuse",
|
||||
"Chat Abuse",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"MuteReasons": [
|
||||
"Advertising",
|
||||
"Spamming",
|
||||
"Spectator camera abuse",
|
||||
"Hate",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"AdminFlags": [
|
||||
{ "name": "Generic", "flag": "@css/generic" },
|
||||
{ "name": "Chat", "flag": "@css/chat" },
|
||||
{ "name": "Change Map", "flag": "@css/changemap" },
|
||||
{ "name": "Slay", "flag": "@css/slay" },
|
||||
{ "name": "Kick", "flag": "@css/kick" },
|
||||
{ "name": "Ban", "flag": "@css/ban" },
|
||||
{ "name": "Perm Ban", "flag": "@css/permban" },
|
||||
{ "name": "Unban", "flag": "@css/unban" },
|
||||
{ "name": "Show IP", "flag": "@css/showip" },
|
||||
{ "name": "Cvar", "flag": "@css/cvar" },
|
||||
{ "name": "Rcon", "flag": "@css/rcon" },
|
||||
{ "name": "Root (all flags)", "flag": "@css/root" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Timezone List
|
||||
|
||||
<details>
|
||||
<summary>Click to expand timezone list</summary>
|
||||
|
||||
```
|
||||
UTC
|
||||
America/New_York
|
||||
America/Chicago
|
||||
America/Denver
|
||||
America/Los_Angeles
|
||||
Europe/London
|
||||
Europe/Paris
|
||||
Europe/Berlin
|
||||
Europe/Warsaw
|
||||
Europe/Moscow
|
||||
Asia/Tokyo
|
||||
Asia/Shanghai
|
||||
Asia/Dubai
|
||||
Australia/Sydney
|
||||
Pacific/Auckland
|
||||
... (and many more)
|
||||
```
|
||||
|
||||
For a complete list, see the info.txt file in the documentation folder.
|
||||
|
||||
</details>
|
||||
|
||||
## Commands Configuration
|
||||
|
||||
You can customize command aliases in:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
|
||||
```
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"Commands": {
|
||||
"css_ban": {
|
||||
"Aliases": [
|
||||
"css_ban",
|
||||
"css_ban2"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to:
|
||||
- **Disable commands** - Remove all aliases from the array
|
||||
- **Add aliases** - Add multiple command variations
|
||||
- **Rename commands** - Change the command name while keeping functionality
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
1. **Use MySQL in production** - SQLite is not suitable for multi-server setups
|
||||
2. **Set MaxBanDuration** - Prevent accidental permanent bans
|
||||
3. **Enable DisableDangerousCommands** - Protect against accidental server crashes
|
||||
4. **Use strong database passwords** - If using MySQL
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Set ReloadAdminsEveryMapChange to false** - Unless you frequently modify admin permissions
|
||||
2. **Limit DisconnectedPlayersHistoryCount** - Reduce memory usage
|
||||
3. **Use database indices** - Migrations create these automatically
|
||||
|
||||
### Multi-Server Setup
|
||||
|
||||
1. **Enable MultiServerMode** - Share data across servers
|
||||
2. **Use MySQL** - Required for multi-server
|
||||
3. **Configure server IDs** - Each server gets a unique ID automatically
|
||||
4. **Test penalties** - Ensure bans work across all servers
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Changes not taking effect
|
||||
|
||||
**Solution:** Reload the plugin or restart the server:
|
||||
```
|
||||
css_plugins reload CS2-SimpleAdmin
|
||||
```
|
||||
|
||||
### Discord webhooks not working
|
||||
|
||||
**Solution:**
|
||||
- Verify webhook URL is correct
|
||||
- Check that the webhook is not deleted in Discord
|
||||
- Ensure server has internet access
|
||||
|
||||
### TimeMode issues
|
||||
|
||||
**Solution:** Set your timezone correctly in the configuration
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Learn admin commands](commands/basebans)** - Browse available commands
|
||||
- **[Set up admins](#)** - Add your admin team
|
||||
- **[Configure modules](../modules/intro)** - Extend functionality
|
||||
188
CS2-SimpleAdmin-docs/docs/user/installation.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
This guide will help you install CS2-SimpleAdmin on your Counter-Strike 2 server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before installing CS2-SimpleAdmin, ensure you have the following dependencies installed:
|
||||
|
||||
### Required Dependencies
|
||||
|
||||
1. **[CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/)** (v1.0.340+)
|
||||
- The core framework for CS2 server plugins
|
||||
|
||||
2. **[AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)**
|
||||
- Required by PlayerSettings
|
||||
|
||||
3. **[PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)**
|
||||
- Required by MenuManager
|
||||
|
||||
4. **[MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)**
|
||||
- Provides the menu system
|
||||
|
||||
### Database Requirements
|
||||
|
||||
You'll need either:
|
||||
- **MySQL** server (recommended for production)
|
||||
- **SQLite** (built-in, good for testing)
|
||||
|
||||
## Installation Steps
|
||||
|
||||
### 1. Download the Plugin
|
||||
|
||||
Download the latest release from the [GitHub Releases page](https://github.com/daffyyyy/CS2-SimpleAdmin/releases).
|
||||
|
||||
You can either:
|
||||
- Download the pre-built release ZIP file
|
||||
- Clone the repository and build from source
|
||||
|
||||
### 2. Extract Files
|
||||
|
||||
Extract the downloaded files to your server's CounterStrikeSharp directory:
|
||||
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/CS2-SimpleAdmin/
|
||||
```
|
||||
|
||||
Your directory structure should look like this:
|
||||
|
||||
```
|
||||
csgo/
|
||||
└── addons/
|
||||
└── counterstrikesharp/
|
||||
├── plugins/
|
||||
│ └── CS2-SimpleAdmin/
|
||||
│ ├── CS2-SimpleAdmin.dll
|
||||
│ ├── lang/
|
||||
│ └── ... (other files)
|
||||
└── shared/
|
||||
└── CS2-SimpleAdminApi/
|
||||
└── CS2-SimpleAdminApi.dll
|
||||
```
|
||||
|
||||
### 3. First Launch
|
||||
|
||||
Start your server. On the first launch, CS2-SimpleAdmin will:
|
||||
|
||||
1. Create a configuration file at:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
||||
```
|
||||
|
||||
2. Create a database (if using SQLite):
|
||||
```
|
||||
addons/counterstrikesharp/plugins/CS2-SimpleAdmin/cs2-simpleadmin.sqlite
|
||||
```
|
||||
|
||||
3. Apply database migrations automatically
|
||||
|
||||
### 4. Configure the Plugin
|
||||
|
||||
Edit the generated configuration file to match your server setup.
|
||||
|
||||
See the [Configuration Guide](configuration) for detailed information.
|
||||
|
||||
### 5. Restart Your Server
|
||||
|
||||
After editing the configuration, restart your server or reload the plugin:
|
||||
|
||||
```bash
|
||||
css_plugins reload CS2-SimpleAdmin
|
||||
```
|
||||
|
||||
## Building from Source
|
||||
|
||||
If you want to build CS2-SimpleAdmin from source:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- .NET 8.0 SDK
|
||||
- Git
|
||||
|
||||
### Build Steps
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone https://github.com/daffyyyy/CS2-SimpleAdmin.git
|
||||
cd CS2-SimpleAdmin
|
||||
```
|
||||
|
||||
2. **Restore dependencies:**
|
||||
```bash
|
||||
dotnet restore CS2-SimpleAdmin.sln
|
||||
```
|
||||
|
||||
3. **Build the solution:**
|
||||
```bash
|
||||
dotnet build CS2-SimpleAdmin.sln -c Release
|
||||
```
|
||||
|
||||
4. **Build output location:**
|
||||
```
|
||||
CS2-SimpleAdmin/bin/Release/net8.0/
|
||||
CS2-SimpleAdminApi/bin/Release/net8.0/
|
||||
```
|
||||
|
||||
5. **Copy to server:**
|
||||
- Copy `CS2-SimpleAdmin.dll` and its dependencies to `plugins/CS2-SimpleAdmin/`
|
||||
- Copy `CS2-SimpleAdminApi.dll` to `shared/CS2-SimpleAdminApi/`
|
||||
|
||||
## Verification
|
||||
|
||||
To verify the installation was successful:
|
||||
|
||||
1. **Check server console** for the plugin load message:
|
||||
```
|
||||
[CS2-SimpleAdmin] Plugin loaded successfully
|
||||
```
|
||||
|
||||
2. **Run an admin command** in-game:
|
||||
```
|
||||
css_admin
|
||||
```
|
||||
|
||||
3. **Check the logs** at:
|
||||
```
|
||||
addons/counterstrikesharp/logs/CS2-SimpleAdmin*.txt
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin doesn't load
|
||||
|
||||
**Solution:** Ensure all required dependencies are installed:
|
||||
- CounterStrikeSharp (latest version)
|
||||
- AnyBaseLibCS2
|
||||
- PlayerSettings
|
||||
- MenuManagerCS2
|
||||
|
||||
### Database connection errors
|
||||
|
||||
**Solution:**
|
||||
- For MySQL: Verify database credentials in the config file
|
||||
- For SQLite: Ensure the plugin has write permissions in its directory
|
||||
|
||||
### Commands not working
|
||||
|
||||
**Solution:**
|
||||
- Check that you have admin permissions configured
|
||||
- Verify the commands are enabled in `Commands.json`
|
||||
- Check server console for error messages
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Configure your plugin](configuration)** - Set up database, permissions, and features
|
||||
- **[Learn the commands](commands/basebans)** - Browse available admin commands
|
||||
- **[Add admins](#)** - Set up your admin team
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Check the [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues) for similar problems
|
||||
2. Review server logs for error messages
|
||||
3. Ask for help on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
81
CS2-SimpleAdmin-docs/docs/user/intro.md
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Welcome to **CS2-SimpleAdmin** - a comprehensive administration plugin for Counter-Strike 2 servers built with C# (.NET 8.0) for CounterStrikeSharp.
|
||||
|
||||
## What is CS2-SimpleAdmin?
|
||||
|
||||
CS2-SimpleAdmin is a powerful server administration tool that provides comprehensive features for managing your Counter-Strike 2 server. It offers:
|
||||
|
||||
- **Player Management** - Ban, kick, mute, gag, and warn players
|
||||
- **Admin System** - Flexible permission system with flags and groups
|
||||
- **Multi-Server Support** - Manage multiple servers with a shared database
|
||||
- **Discord Integration** - Send notifications to Discord webhooks
|
||||
- **Menu System** - Easy-to-use admin menus
|
||||
- **Extensive Commands** - Over 50 admin commands
|
||||
- **Module Support** - Extend functionality with custom modules
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🛡️ Comprehensive Penalty System
|
||||
- **Bans** - Ban players by SteamID or IP address
|
||||
- **Mutes** - Mute voice communication
|
||||
- **Gags** - Gag text chat
|
||||
- **Silence** - Block both voice and text
|
||||
- **Warnings** - Progressive warning system with auto-escalation
|
||||
|
||||
### 👥 Flexible Admin System
|
||||
- Permission-based access control using flags
|
||||
- Admin groups for easy management
|
||||
- Immunity levels to prevent abuse
|
||||
- Server-specific or global admin assignments
|
||||
|
||||
### 🗄️ Database Support
|
||||
- **MySQL** - For production environments
|
||||
- **SQLite** - For quick setup and testing
|
||||
- Automatic migration system
|
||||
- Multi-server mode with shared data
|
||||
|
||||
### 🎮 User-Friendly Interface
|
||||
- Interactive admin menus
|
||||
- In-game admin panel
|
||||
- Player selection menus
|
||||
- Duration and reason selection
|
||||
|
||||
### 🔧 Extensibility
|
||||
- Public API for module development
|
||||
- Event system for custom integrations
|
||||
- Command registration system
|
||||
- Menu builder API
|
||||
|
||||
## Requirements
|
||||
|
||||
Before installing CS2-SimpleAdmin, make sure you have:
|
||||
|
||||
- [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/) (v1.0.340+)
|
||||
- [AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)
|
||||
- [PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)
|
||||
- [MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)
|
||||
- MySQL database (or use SQLite for testing)
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **[Installation Guide](installation)** - Get started with CS2-SimpleAdmin
|
||||
- **[Configuration](configuration)** - Learn how to configure the plugin
|
||||
- **[Commands](commands/basebans)** - Browse all available commands
|
||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code and releases
|
||||
|
||||
## Community & Support
|
||||
|
||||
Need help or want to contribute?
|
||||
|
||||
- **Issues** - Report bugs on [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
- **Discussions** - Ask questions on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
- **Pull Requests** - Contribute improvements
|
||||
|
||||
## License
|
||||
|
||||
CS2-SimpleAdmin is open-source software. Check the [GitHub repository](https://github.com/daffyyyy/CS2-SimpleAdmin) for license details.
|
||||
174
CS2-SimpleAdmin-docs/docusaurus.config.js
Normal file
@@ -0,0 +1,174 @@
|
||||
// @ts-check
|
||||
// `@type` JSDoc annotations allow editor autocompletion and type checking
|
||||
// (when paired with `@ts-check`).
|
||||
// There are various equivalent ways to declare your Docusaurus config.
|
||||
// See: https://docusaurus.io/docs/api/docusaurus-config
|
||||
|
||||
import {themes as prismThemes} from 'prism-react-renderer';
|
||||
|
||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'CS2-SimpleAdmin',
|
||||
tagline: 'Comprehensive administration plugin for Counter-Strike 2 servers',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
||||
future: {
|
||||
v4: true, // Improve compatibility with the upcoming Docusaurus v4
|
||||
},
|
||||
|
||||
// Set the production url of your site here
|
||||
url: 'https://cs2-simpleadmin.daffyy.dev',
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'daffyyyy', // Usually your GitHub org/user name.
|
||||
projectName: 'CS2-SimpleAdmin', // Usually your repo name.
|
||||
|
||||
onBrokenLinks: 'throw',
|
||||
|
||||
// Even if you don't use internationalization, you can use this field to set
|
||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||
// may want to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
sidebarPath: './sidebars.js',
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
// editUrl:
|
||||
// '',
|
||||
},
|
||||
blog: false, // Disable blog
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
// Replace with your project's social card
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
metadata: [
|
||||
{name: 'keywords', content: 'CS2, Counter-Strike 2, admin plugin, server management, bans, mutes, CounterStrikeSharp'},
|
||||
{name: 'description', content: 'Comprehensive administration plugin for Counter-Strike 2 servers with ban management, multi-server support, and extensible API'},
|
||||
{name: 'author', content: 'daffyyyy'},
|
||||
{property: 'og:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
|
||||
{property: 'og:description', content: 'Comprehensive administration plugin for CS2 servers. Manage bans, mutes, warnings, and permissions with multi-server support.'},
|
||||
{property: 'og:type', content: 'website'},
|
||||
{property: 'og:url', content: 'https://cs2-simpleadmin.daffyy.dev'},
|
||||
{property: 'og:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
|
||||
{name: 'twitter:card', content: 'summary_large_image'},
|
||||
{name: 'twitter:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
|
||||
{name: 'twitter:description', content: 'Comprehensive administration plugin for CS2 servers with ban management and multi-server support.'},
|
||||
{name: 'twitter:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
|
||||
],
|
||||
colorMode: {
|
||||
respectPrefersColorScheme: true,
|
||||
},
|
||||
navbar: {
|
||||
title: 'CS2-SimpleAdmin',
|
||||
logo: {
|
||||
alt: 'CS2-SimpleAdmin Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'userSidebar',
|
||||
position: 'left',
|
||||
label: 'User Guide',
|
||||
},
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'modulesSidebar',
|
||||
position: 'left',
|
||||
label: 'Modules',
|
||||
},
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'developerSidebar',
|
||||
position: 'left',
|
||||
label: 'Developer',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Documentation',
|
||||
items: [
|
||||
{
|
||||
label: 'User Guide',
|
||||
to: '/docs/user/intro',
|
||||
},
|
||||
{
|
||||
label: 'Modules',
|
||||
to: '/docs/modules/intro',
|
||||
},
|
||||
{
|
||||
label: 'Developer',
|
||||
to: '/docs/developer/intro',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
items: [
|
||||
{
|
||||
label: 'GitHub Issues',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/issues',
|
||||
},
|
||||
{
|
||||
label: 'GitHub Discussions',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/discussions',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'More',
|
||||
items: [
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
|
||||
},
|
||||
{
|
||||
label: 'Releases',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/releases',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} CS2-SimpleAdmin. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
additionalLanguages: ['csharp', 'json', 'bash'],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
1575
CS2-SimpleAdmin-docs/info.txt
Normal file
17981
CS2-SimpleAdmin-docs/package-lock.json
generated
Normal file
44
CS2-SimpleAdmin-docs/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "cs-2-simple-admin-docs",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/preset-classic": "3.9.2",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.9.2",
|
||||
"@docusaurus/types": "3.9.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 3 chrome version",
|
||||
"last 3 firefox version",
|
||||
"last 5 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0"
|
||||
}
|
||||
}
|
||||
81
CS2-SimpleAdmin-docs/sidebars.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// @ts-check
|
||||
|
||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
|
||||
@type {import('@docusaurus/plugin-content-docs').SidebarsConfig}
|
||||
*/
|
||||
const sidebars = {
|
||||
userSidebar: [
|
||||
'user/intro',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
'user/installation',
|
||||
'user/configuration',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Commands',
|
||||
items: [
|
||||
'user/commands/basebans',
|
||||
'user/commands/basecomms',
|
||||
'user/commands/basecommands',
|
||||
'user/commands/basechat',
|
||||
'user/commands/playercommands',
|
||||
'user/commands/basevotes',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
modulesSidebar: [
|
||||
'modules/intro',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Official Modules',
|
||||
items: [
|
||||
'modules/funcommands',
|
||||
],
|
||||
},
|
||||
'modules/development',
|
||||
],
|
||||
|
||||
developerSidebar: [
|
||||
'developer/intro',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'API Reference',
|
||||
items: [
|
||||
'developer/api/overview',
|
||||
'developer/api/commands',
|
||||
'developer/api/menus',
|
||||
'developer/api/penalties',
|
||||
'developer/api/events',
|
||||
'developer/api/utilities',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Module Development',
|
||||
items: [
|
||||
'developer/module/getting-started',
|
||||
'developer/module/best-practices',
|
||||
'developer/module/examples',
|
||||
],
|
||||
},
|
||||
'developer/architecture',
|
||||
],
|
||||
};
|
||||
|
||||
export default sidebars;
|
||||
@@ -0,0 +1,64 @@
|
||||
import clsx from 'clsx';
|
||||
import Heading from '@theme/Heading';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const FeatureList = [
|
||||
{
|
||||
title: 'Comprehensive Admin Tools',
|
||||
img: require('@site/static/img/index_1.png').default,
|
||||
description: (
|
||||
<>
|
||||
Full suite of admin commands for managing players, bans, mutes, warnings,
|
||||
and server settings. Everything you need to moderate your CS2 server.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Multi-Server Support',
|
||||
img: require('@site/static/img/index_2.png').default,
|
||||
description: (
|
||||
<>
|
||||
Manage multiple servers with synchronized admin permissions and penalties.
|
||||
Share bans, mutes, and admin groups across your entire server network.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Extensible API',
|
||||
img: require('@site/static/img/index_3.png').default,
|
||||
description: (
|
||||
<>
|
||||
Build custom modules using the public API. Create your own commands,
|
||||
menus, and integrate with CS2-SimpleAdmin's permission and penalty systems.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Feature({img, title, description}) {
|
||||
return (
|
||||
<div className={clsx('col col--4')}>
|
||||
<div className="text--center">
|
||||
<img src={img} className={styles.featureSvg} alt={title} />
|
||||
</div>
|
||||
<div className="text--center padding-horiz--md">
|
||||
<Heading as="h3">{title}</Heading>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomepageFeatures() {
|
||||
return (
|
||||
<section className={styles.features}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{FeatureList.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureSvg {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
30
CS2-SimpleAdmin-docs/src/css/custom.css
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #ff8c00;
|
||||
--ifm-color-primary-dark: #e67e00;
|
||||
--ifm-color-primary-darker: #d97700;
|
||||
--ifm-color-primary-darkest: #b36200;
|
||||
--ifm-color-primary-light: #ff9a1a;
|
||||
--ifm-color-primary-lighter: #ffa328;
|
||||
--ifm-color-primary-lightest: #ffb54d;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #ff9500;
|
||||
--ifm-color-primary-dark: #e68600;
|
||||
--ifm-color-primary-darker: #cc7700;
|
||||
--ifm-color-primary-darkest: #b36200;
|
||||
--ifm-color-primary-light: #ffa31a;
|
||||
--ifm-color-primary-lighter: #ffad33;
|
||||
--ifm-color-primary-lightest: #ffc266;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
52
CS2-SimpleAdmin-docs/src/pages/index.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import HomepageFeatures from '@site/src/components/HomepageFeatures';
|
||||
|
||||
import Heading from '@theme/Heading';
|
||||
import styles from './index.module.css';
|
||||
|
||||
function HomepageHeader() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
||||
<iframe
|
||||
className={styles.videoBackground}
|
||||
src="https://www.youtube.com/embed/4qEdIXLdxMo?autoplay=1&mute=1&loop=1&playlist=4qEdIXLdxMo&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1"
|
||||
title="Background Video"
|
||||
frameBorder="0"
|
||||
allow="autoplay; encrypted-media"
|
||||
allowFullScreen
|
||||
/>
|
||||
<div className={styles.videoOverlay}></div>
|
||||
<div className="container">
|
||||
<Heading as="h1" className="hero__title">
|
||||
{siteConfig.title}
|
||||
</Heading>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="/docs/user/intro">
|
||||
Get Started
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`${siteConfig.title} - Admin Plugin for CS2`}
|
||||
description="CS2-SimpleAdmin is a comprehensive administration plugin for Counter-Strike 2 servers. Manage bans, mutes, warnings, and permissions with multi-server support and extensible API.">
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
<HomepageFeatures />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
97
CS2-SimpleAdmin-docs/src/pages/index.module.css
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
*/
|
||||
|
||||
.heroBanner {
|
||||
padding: 8rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 600px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.videoBackground {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100vw;
|
||||
height: 56.25vw; /* 16:9 Aspect Ratio */
|
||||
min-height: 100vh;
|
||||
min-width: 177.77vh; /* 16:9 Aspect Ratio */
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.videoOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.heroBanner :global(.container) {
|
||||
position: relative;
|
||||
z-index: 999 !important;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__title) {
|
||||
color: white !important;
|
||||
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9);
|
||||
font-size: 3.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__subtitle) {
|
||||
color: white !important;
|
||||
text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.9);
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.heroBanner .buttons {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.heroBanner {
|
||||
padding: 4rem 2rem;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.videoBackground {
|
||||
width: 200vw;
|
||||
height: 112.5vw;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__title) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__subtitle) {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.buttons :global(.button) {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
7
CS2-SimpleAdmin-docs/src/pages/markdown-page.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Markdown page example
|
||||
---
|
||||
|
||||
# Markdown page example
|
||||
|
||||
You don't need React to write simple standalone pages.
|
||||
0
CS2-SimpleAdmin-docs/static/.nojekyll
Normal file
BIN
CS2-SimpleAdmin-docs/static/img/docusaurus-social-card.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/docusaurus.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/index_1.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/index_2.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/index_3.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
1
CS2-SimpleAdmin-docs/static/img/logo.svg
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
@@ -233,6 +233,36 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
{
|
||||
var context = new MenuContext(categoryId, menuId, menuName, permission, commandName);
|
||||
|
||||
if (menuFactory(player, context) is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
||||
|
||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
||||
menuBuilder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return menuBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UnregisterMenu(string categoryId, string menuId)
|
||||
{
|
||||
@@ -257,6 +287,11 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
return builder;
|
||||
}
|
||||
|
||||
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
||||
{
|
||||
return CreateMenuWithBack(context.MenuTitle, context.CategoryId, player);
|
||||
}
|
||||
|
||||
public List<CCSPlayerController> GetValidPlayers()
|
||||
{
|
||||
return Helper.GetValidPlayers();
|
||||
@@ -283,6 +318,12 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
return menu;
|
||||
}
|
||||
|
||||
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||
{
|
||||
return CreateMenuWithPlayers(context.MenuTitle, context.CategoryId, admin, filter, onSelect);
|
||||
}
|
||||
|
||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
||||
string? permission = null)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
||||
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
||||
public override string ModuleAuthor => "daffyy & Dliix66";
|
||||
public override string ModuleVersion => "1.7.8-beta-2";
|
||||
public override string ModuleVersion => "1.7.8-beta-3";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.8-beta-2
|
||||
1.7.8-beta-3
|
||||
@@ -151,6 +151,18 @@ public interface ICS2_SimpleAdminApi
|
||||
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu in a category with automatic context passing.
|
||||
/// RECOMMENDED: Use this overload to eliminate duplication of categoryId and menuName in factory methods.
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuName">Display name of the menu.</param>
|
||||
/// <param name="menuFactory">Factory function that receives player and menu context.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
/// </summary>
|
||||
@@ -161,11 +173,29 @@ public interface ICS2_SimpleAdminApi
|
||||
/// </summary>
|
||||
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a menu with an automatic back button using menu context.
|
||||
/// RECOMMENDED: Use this overload when calling from a context-aware menu factory to avoid title/category duplication.
|
||||
/// </summary>
|
||||
/// <param name="context">Menu context containing title and category information.</param>
|
||||
/// <param name="player">The player who will see the menu.</param>
|
||||
object CreateMenuWithBack(MenuContext context, CCSPlayerController player);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a menu with a list of players with filter and action.
|
||||
/// </summary>
|
||||
object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a menu with a list of players using menu context.
|
||||
/// RECOMMENDED: Use this overload when calling from a context-aware menu factory to avoid title/category duplication.
|
||||
/// </summary>
|
||||
/// <param name="context">Menu context containing title and category information.</param>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <param name="filter">Filter function to determine which players to show.</param>
|
||||
/// <param name="onSelect">Action to execute when a player is selected.</param>
|
||||
object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an option to the menu (extension method helper).
|
||||
/// </summary>
|
||||
|
||||
48
CS2-SimpleAdminApi/MenuContext.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace CS2_SimpleAdminApi;
|
||||
|
||||
/// <summary>
|
||||
/// Provides contextual information about a menu to its factory function.
|
||||
/// This eliminates the need to duplicate category IDs and menu titles when creating menus.
|
||||
/// </summary>
|
||||
public class MenuContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The category ID this menu belongs to (e.g., "fun", "players").
|
||||
/// Used for automatic "Back" button navigation.
|
||||
/// </summary>
|
||||
public string CategoryId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier for this menu within its category.
|
||||
/// </summary>
|
||||
public string MenuId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The display title of the menu (from registration).
|
||||
/// </summary>
|
||||
public string MenuTitle { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The permission required to access this menu (if any).
|
||||
/// </summary>
|
||||
public string? Permission { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The command name for permission override checking (if any).
|
||||
/// </summary>
|
||||
public string? CommandName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new MenuContext with the specified values.
|
||||
/// </summary>
|
||||
public MenuContext(string categoryId, string menuId, string menuTitle, string? permission = null, string? commandName = null)
|
||||
{
|
||||
CategoryId = categoryId;
|
||||
MenuId = menuId;
|
||||
MenuTitle = menuTitle;
|
||||
Permission = permission;
|
||||
CommandName = commandName;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CS2_SimpleAdmin_CleanModule;
|
||||
|
||||
public class CS2_SimpleAdmin_CleanModule: BasePlugin
|
||||
{
|
||||
public override string ModuleName => "[CS2-SimpleAdmin] Clean module";
|
||||
public override string ModuleDescription => "Module allows you to remove all weapons lying on the ground";
|
||||
public override string ModuleVersion => "v1.0.0";
|
||||
public override string ModuleAuthor => "daffyy";
|
||||
|
||||
private static ICS2_SimpleAdminApi? _sharedApi;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_sharedApi = _pluginCapability.Get();
|
||||
|
||||
if (_sharedApi == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin SharedApi not found");
|
||||
Unload(false);
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_clean")]
|
||||
[ConsoleCommand("css_clear")]
|
||||
[RequiresPermissions("@css/cheat")]
|
||||
public void OnCleanCommand(CCSPlayerController? caller, CommandInfo commandInfo)
|
||||
{
|
||||
var weapons = Utilities.FindAllEntitiesByDesignerName<CCSWeaponBaseGun>("weapon_");
|
||||
var defusers = Utilities.FindAllEntitiesByDesignerName<CSceneEntity>("item_cutters");
|
||||
|
||||
foreach (var weapon in weapons)
|
||||
{
|
||||
if (!weapon.IsValid || weapon.State != CSWeaponState_t.WEAPON_NOT_CARRIED)
|
||||
continue;
|
||||
|
||||
weapon.Remove();
|
||||
}
|
||||
|
||||
foreach (var defuser in defusers)
|
||||
{
|
||||
if (!defuser.IsValid || defuser.OwnerEntity.Value != null)
|
||||
continue;
|
||||
|
||||
defuser.Remove();
|
||||
}
|
||||
|
||||
_sharedApi?.LogCommand(caller, commandInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>CS2_SimpleAdmin_CleanModule</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.266" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>CS2-SimpleAdminApi.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-SimpleAdmin_CleanModule", "CS2-SimpleAdmin_CleanModule.csproj", "{D940F3E9-0E3F-467A-B336-149E3A624FB6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -4,173 +4,532 @@ using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CS2_SimpleAdmin_ExampleModule;
|
||||
|
||||
/// <summary>
|
||||
/// COMPLETE EXAMPLE MODULE FOR CS2-SIMPLEADMIN
|
||||
///
|
||||
/// This module demonstrates:
|
||||
/// 1. ✅ Getting CS2-SimpleAdmin API via capability system
|
||||
/// 2. ✅ Using API methods (GetServerId, GetConnectionString, IssuePenalty)
|
||||
/// 3. ✅ Listening to events (OnPlayerPenaltied, OnPlayerPenaltiedAdded)
|
||||
/// 4. ✅ Registering console commands
|
||||
/// 5. ✅ Creating menu categories and menu items
|
||||
/// 6. ✅ Using NEW MenuContext API to eliminate code duplication
|
||||
/// 7. ✅ Proper cleanup on module unload
|
||||
///
|
||||
/// Study this file to learn how to create your own CS2-SimpleAdmin modules!
|
||||
/// </summary>
|
||||
public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
||||
{
|
||||
public override string ModuleName => "[CS2-SimpleAdmin] Example module";
|
||||
public override string ModuleVersion => "v1.0.1";
|
||||
public override string ModuleAuthor => "daffyy";
|
||||
// ========================================
|
||||
// PLUGIN METADATA
|
||||
// ========================================
|
||||
public override string ModuleName => "[CS2-SimpleAdmin] Example Module";
|
||||
public override string ModuleVersion => "v1.1.0";
|
||||
public override string ModuleAuthor => "daffyy & Example Contributors";
|
||||
|
||||
// ========================================
|
||||
// PRIVATE FIELDS
|
||||
// ========================================
|
||||
|
||||
/// <summary>
|
||||
/// Server ID from SimpleAdmin (null for single-server mode)
|
||||
/// Useful for multi-server setups to identify which server this is
|
||||
/// </summary>
|
||||
private int? _serverId;
|
||||
|
||||
/// <summary>
|
||||
/// Database connection string from SimpleAdmin
|
||||
/// Use this if your module needs direct database access
|
||||
/// </summary>
|
||||
private string _dbConnectionString = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to CS2-SimpleAdmin API
|
||||
/// Use this to call API methods and register menus
|
||||
/// </summary>
|
||||
private static ICS2_SimpleAdminApi? _sharedApi;
|
||||
|
||||
/// <summary>
|
||||
/// Capability for getting the SimpleAdmin API
|
||||
/// This is the recommended way to get access to another plugin's API
|
||||
/// </summary>
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
/// <summary>
|
||||
/// Flag to prevent duplicate menu registration
|
||||
/// Important for hot reload scenarios
|
||||
/// </summary>
|
||||
private bool _menusRegistered = false;
|
||||
|
||||
// ========================================
|
||||
// PLUGIN LIFECYCLE
|
||||
// ========================================
|
||||
|
||||
/// <summary>
|
||||
/// Called when all plugins are loaded (including hot reload)
|
||||
/// BEST PRACTICE: Use this instead of Load() to ensure all dependencies are available
|
||||
/// </summary>
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_sharedApi = _pluginCapability.Get();
|
||||
|
||||
if (_sharedApi == null)
|
||||
// STEP 1: Get the SimpleAdmin API using capability system
|
||||
try
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin SharedApi not found");
|
||||
_sharedApi = _pluginCapability.Get();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found - make sure CS2-SimpleAdmin is loaded!");
|
||||
Unload(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// STEP 2: Get server information from SimpleAdmin
|
||||
_serverId = _sharedApi.GetServerId();
|
||||
_dbConnectionString = _sharedApi.GetConnectionString();
|
||||
Logger.LogInformation($"{ModuleName} started with serverId {_serverId}");
|
||||
|
||||
_sharedApi.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
_sharedApi.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
// STEP 3: Subscribe to SimpleAdmin events
|
||||
// These events fire when penalties (ban, kick, mute, etc.) are issued
|
||||
_sharedApi.OnPlayerPenaltied += OnPlayerPenaltied; // When penalty is issued to ONLINE player
|
||||
_sharedApi.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded; // When penalty is issued to OFFLINE player
|
||||
|
||||
// STEP 4: Register menus
|
||||
// BEST PRACTICE: Wait for SimpleAdmin to be ready before registering menus
|
||||
// This handles both normal load and hot reload scenarios
|
||||
_sharedApi.OnSimpleAdminReady += RegisterExampleMenus;
|
||||
RegisterExampleMenus(); // Fallback for hot reload case
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the plugin is being unloaded
|
||||
/// BEST PRACTICE: Always clean up your registrations to prevent memory leaks
|
||||
/// </summary>
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_sharedApi == null) return;
|
||||
|
||||
// Unsubscribe from events
|
||||
_sharedApi.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
_sharedApi.OnPlayerPenaltiedAdded -= OnPlayerPenaltiedAdded;
|
||||
_sharedApi.OnSimpleAdminReady -= RegisterExampleMenus;
|
||||
|
||||
// Unregister menus
|
||||
_sharedApi.UnregisterMenu("example", "simple_action");
|
||||
_sharedApi.UnregisterMenu("example", "player_selection");
|
||||
_sharedApi.UnregisterMenu("example", "nested_menu");
|
||||
_sharedApi.UnregisterMenu("example", "test_command");
|
||||
|
||||
Logger.LogInformation($"{ModuleName} unloaded successfully");
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MENU REGISTRATION
|
||||
// ========================================
|
||||
|
||||
/// <summary>
|
||||
/// Registers all example menus in the admin menu
|
||||
/// BEST PRACTICE: Use this pattern to prevent duplicate registrations
|
||||
/// </summary>
|
||||
private void RegisterExampleMenus()
|
||||
{
|
||||
if (_sharedApi == null || _menusRegistered) return;
|
||||
|
||||
try
|
||||
{
|
||||
// STEP 1: Register a menu category
|
||||
// This creates a new section in the main admin menu
|
||||
// Permission: @css/generic means all admins can see it
|
||||
_sharedApi.RegisterMenuCategory(
|
||||
"example", // Category ID (unique identifier)
|
||||
"Example Features", // Display name in admin menu
|
||||
"@css/generic" // Required permission
|
||||
);
|
||||
|
||||
// STEP 2: Register individual menu items in the category
|
||||
// 🆕 NEW: These use MenuContext API - factory receives (admin, context) parameters
|
||||
|
||||
// Example 1: Simple menu with options
|
||||
_sharedApi.RegisterMenu(
|
||||
"example", // Category ID
|
||||
"simple_action", // Menu ID (unique within category)
|
||||
"Simple Actions", // Display name
|
||||
CreateSimpleActionMenu, // Factory method
|
||||
"@css/generic" // Required permission
|
||||
);
|
||||
|
||||
// Example 2: Player selection menu
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"player_selection",
|
||||
"Select Player",
|
||||
CreatePlayerSelectionMenu,
|
||||
"@css/kick" // Requires kick permission
|
||||
);
|
||||
|
||||
// Example 3: Nested menu (Player → Value)
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"nested_menu",
|
||||
"Give Credits",
|
||||
CreateGiveCreditsMenu,
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
// Example 4: Menu with permission override support
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"test_command",
|
||||
"Test Command",
|
||||
CreateTestCommandMenu,
|
||||
"@css/root", // Default permission
|
||||
"css_test" // Command name for override checking
|
||||
);
|
||||
|
||||
_menusRegistered = true;
|
||||
Logger.LogInformation("Example menus registered successfully!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to register example menus: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MENU FACTORY METHODS
|
||||
// ========================================
|
||||
|
||||
/// <summary>
|
||||
/// PATTERN 1: Simple menu with static options
|
||||
/// 🆕 NEW: Uses MenuContext to eliminate duplication!
|
||||
/// </summary>
|
||||
private object CreateSimpleActionMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// Create menu with automatic back button
|
||||
// 🆕 NEW: Use context instead of repeating title and category!
|
||||
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
|
||||
|
||||
// Add menu options
|
||||
_sharedApi.AddMenuOption(menu, "Print Server Info", player =>
|
||||
{
|
||||
player.PrintToChat($"Server ID: {_serverId}");
|
||||
player.PrintToChat($"Server IP: {_sharedApi?.GetServerAddress()}");
|
||||
});
|
||||
|
||||
_sharedApi.AddMenuOption(menu, "Get My Stats", player =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var playerInfo = _sharedApi?.GetPlayerInfo(player);
|
||||
player.PrintToChat($"Total Bans: {playerInfo?.TotalBans ?? 0}");
|
||||
player.PrintToChat($"Total Kicks: {playerInfo?.TotalKicks ?? 0}");
|
||||
player.PrintToChat($"Total Warns: {playerInfo?.TotalWarns ?? 0}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error getting player info: {ex.Message}");
|
||||
player.PrintToChat("Error retrieving your stats");
|
||||
}
|
||||
});
|
||||
|
||||
_sharedApi.AddMenuOption(menu, "Check Silent Mode", player =>
|
||||
{
|
||||
var isSilent = _sharedApi?.IsAdminSilent(player) ?? false;
|
||||
player.PrintToChat($"Silent mode: {(isSilent ? "ON" : "OFF")}");
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PATTERN 2: Player selection menu with immediate action
|
||||
/// 🆕 NEW: Uses MenuContext API - cleaner and less error-prone!
|
||||
/// </summary>
|
||||
private object CreatePlayerSelectionMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// 🆕 NEW: CreateMenuWithPlayers now uses context instead of title/category
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
context, // ← Contains title and category automatically!
|
||||
admin,
|
||||
// Filter: Only show valid players that admin can target
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
// Action: What happens when a player is selected
|
||||
(adminPlayer, targetPlayer) =>
|
||||
{
|
||||
adminPlayer.PrintToChat($"You selected: {targetPlayer.PlayerName}");
|
||||
|
||||
// Example: Show player info
|
||||
try
|
||||
{
|
||||
var playerInfo = _sharedApi?.GetPlayerInfo(targetPlayer);
|
||||
adminPlayer.PrintToChat($"{targetPlayer.PlayerName} - Bans: {playerInfo?.TotalBans}, Warns: {playerInfo?.TotalWarns}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Could not get info for {targetPlayer.PlayerName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PATTERN 3: Nested menu (Player → Value selection)
|
||||
/// 🆕 NEW: First level menu uses MenuContext
|
||||
/// </summary>
|
||||
private object CreateGiveCreditsMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// Create menu with back button
|
||||
// 🆕 NEW: Uses context - no more repeating title/category!
|
||||
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
|
||||
|
||||
// Get all valid, targetable players
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26
|
||||
? player.PlayerName[..26]
|
||||
: player.PlayerName;
|
||||
|
||||
// AddSubMenu automatically adds a "Back" button to the submenu
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateCreditAmountMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submenu for selecting credit amount
|
||||
/// Note: Submenus create dynamic titles, so they don't receive MenuContext
|
||||
/// </summary>
|
||||
private object CreateCreditAmountMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Dynamic title includes target's name
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
$"Credits for {target.PlayerName}",
|
||||
"example", // Category for back navigation
|
||||
admin
|
||||
);
|
||||
|
||||
// Predefined credit amounts
|
||||
var creditAmounts = new[] { 100, 500, 1000, 5000, 10000 };
|
||||
|
||||
foreach (var amount in creditAmounts)
|
||||
{
|
||||
_sharedApi.AddMenuOption(menu, $"{amount} Credits", _ =>
|
||||
{
|
||||
// BEST PRACTICE: Always validate player is still valid before action
|
||||
if (target.IsValid)
|
||||
{
|
||||
Server.PrintToChatAll($"{admin.PlayerName} gave {amount} credits to {target.PlayerName}");
|
||||
Logger.LogInformation($"Admin {admin.PlayerName} gave {amount} credits to {target.PlayerName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
admin.PrintToChat("Player is no longer available");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example menu with permission override support
|
||||
/// </summary>
|
||||
private object CreateTestCommandMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
|
||||
|
||||
// You can access context properties if needed
|
||||
_sharedApi.AddMenuOption(menu, "Show Context Info", player =>
|
||||
{
|
||||
player.PrintToChat($"Category: {context.CategoryId}");
|
||||
player.PrintToChat($"Menu ID: {context.MenuId}");
|
||||
player.PrintToChat($"Title: {context.MenuTitle}");
|
||||
player.PrintToChat($"Permission: {context.Permission}");
|
||||
player.PrintToChat($"Command: {context.CommandName}");
|
||||
});
|
||||
|
||||
_sharedApi.AddMenuOption(menu, "Test Action", player =>
|
||||
{
|
||||
player.PrintToChat("Test action executed!");
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CONSOLE COMMANDS
|
||||
// ========================================
|
||||
|
||||
/// <summary>
|
||||
/// Example command: Kick yourself
|
||||
/// Demonstrates using IssuePenalty API for online players
|
||||
/// </summary>
|
||||
[ConsoleCommand("css_kickme")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
public void KickMeCommand(CCSPlayerController? caller, CommandInfo commandInfo)
|
||||
{
|
||||
if (caller == null) return;
|
||||
|
||||
_sharedApi?.IssuePenalty(caller, null, PenaltyType.Kick, "test");
|
||||
// Issue a kick penalty to the caller
|
||||
// Parameters: player, admin (null = console), penaltyType, reason
|
||||
_sharedApi?.IssuePenalty(caller, null, PenaltyType.Kick, "You kicked yourself!");
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_serverAddress")]
|
||||
/// <summary>
|
||||
/// Example command: Get server address
|
||||
/// Demonstrates using GetServerAddress API
|
||||
/// </summary>
|
||||
[ConsoleCommand("css_serveraddress")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
public void ServerAddressCommand(CCSPlayerController? caller, CommandInfo commandInfo)
|
||||
{
|
||||
commandInfo.ReplyToCommand($"Our server IP: {_sharedApi?.GetServerAddress()}");
|
||||
commandInfo.ReplyToCommand($"Server IP: {_sharedApi?.GetServerAddress()}");
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_getMyInfo")]
|
||||
/// <summary>
|
||||
/// Example command: Get player statistics
|
||||
/// Demonstrates using GetPlayerInfo API
|
||||
/// </summary>
|
||||
[ConsoleCommand("css_getmyinfo")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
public void GetMyInfoCommand(CCSPlayerController? caller, CommandInfo commandInfo)
|
||||
{
|
||||
if (caller == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var playerInfo = _sharedApi?.GetPlayerInfo(caller);
|
||||
commandInfo.ReplyToCommand($"Your total bans: {playerInfo?.TotalBans}");
|
||||
commandInfo.ReplyToCommand($"Your total gags: {playerInfo?.TotalGags}");
|
||||
commandInfo.ReplyToCommand($"Your total mutes: {playerInfo?.TotalMutes}");
|
||||
commandInfo.ReplyToCommand($"Your total silences: {playerInfo?.SteamId}");
|
||||
commandInfo.ReplyToCommand($"Your Statistics:");
|
||||
commandInfo.ReplyToCommand($" Total Bans: {playerInfo?.TotalBans ?? 0}");
|
||||
commandInfo.ReplyToCommand($" Total Kicks: {playerInfo?.TotalKicks ?? 0}");
|
||||
commandInfo.ReplyToCommand($" Total Gags: {playerInfo?.TotalGags ?? 0}");
|
||||
commandInfo.ReplyToCommand($" Total Mutes: {playerInfo?.TotalMutes ?? 0}");
|
||||
commandInfo.ReplyToCommand($" Total Warns: {playerInfo?.TotalWarns ?? 0}");
|
||||
commandInfo.ReplyToCommand($" SteamID: {playerInfo?.SteamId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error in GetMyInfoCommand: {ex.Message}");
|
||||
commandInfo.ReplyToCommand("Error retrieving your information");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example command: Add ban to offline player
|
||||
/// Demonstrates using IssuePenalty API with SteamID for offline players
|
||||
/// SERVER ONLY - dangerous command!
|
||||
/// </summary>
|
||||
[ConsoleCommand("css_testaddban")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
|
||||
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo commandInfo)
|
||||
{
|
||||
_sharedApi?.IssuePenalty(new SteamID(76561197960287930), null, PenaltyType.Ban, "My super reason", 10);
|
||||
// Issue a ban to an offline player by SteamID
|
||||
// Parameters: steamID, admin (null = console), penaltyType, reason, duration (minutes)
|
||||
_sharedApi?.IssuePenalty(
|
||||
new SteamID(76561197960287930), // Target SteamID
|
||||
null, // Admin (null = console)
|
||||
PenaltyType.Ban, // Penalty type
|
||||
"Test ban from example module", // Reason
|
||||
10 // Duration (10 minutes)
|
||||
);
|
||||
|
||||
Logger.LogInformation("Test ban issued via API");
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType,
|
||||
string reason, int duration, int? penaltyId, int? serverId)
|
||||
// ========================================
|
||||
// EVENT HANDLERS
|
||||
// ========================================
|
||||
|
||||
/// <summary>
|
||||
/// Called when a penalty is issued to an ONLINE player
|
||||
/// Use this to react to bans/kicks/mutes happening in real-time
|
||||
/// </summary>
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player, // The player who received the penalty
|
||||
PlayerInfo? admin, // The admin who issued it (null = console)
|
||||
PenaltyType penaltyType,// Type of penalty (Ban, Kick, Mute, etc.)
|
||||
string reason, // Reason for the penalty
|
||||
int duration, // Duration in minutes (-1 = permanent)
|
||||
int? penaltyId, // Database ID of the penalty
|
||||
int? serverId // Server ID where it was issued
|
||||
)
|
||||
{
|
||||
// Example: Announce bans to all players
|
||||
if (penaltyType == PenaltyType.Ban)
|
||||
{
|
||||
Server.PrintToChatAll($"{player.Name} is a dog");
|
||||
var adminName = admin?.Name ?? "Console";
|
||||
var durationText = (duration == -1 || duration == 0) ? "permanently" : $"for {duration} minutes";
|
||||
Server.PrintToChatAll($"{player.Name} was banned {durationText} by {adminName}");
|
||||
}
|
||||
|
||||
// Log all penalties
|
||||
var adminNameLog = admin?.Name ?? "Console";
|
||||
switch (penaltyType)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
Logger.LogInformation($"Ban issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m, Reason: {reason})");
|
||||
break;
|
||||
case PenaltyType.Kick:
|
||||
Logger.LogInformation($"Kick issued to {player.Name} by {adminNameLog} (Reason: {reason})");
|
||||
break;
|
||||
case PenaltyType.Gag:
|
||||
Logger.LogInformation($"Gag issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m)");
|
||||
break;
|
||||
case PenaltyType.Mute:
|
||||
Logger.LogInformation($"Mute issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m)");
|
||||
break;
|
||||
case PenaltyType.Silence:
|
||||
Logger.LogInformation($"Silence issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Duration: {duration}m)");
|
||||
break;
|
||||
case PenaltyType.Warn:
|
||||
Logger.LogInformation($"Warning issued to {player.Name} by {adminNameLog} (ID: {penaltyId}, Reason: {reason})");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a penalty is issued to an OFFLINE player
|
||||
/// Use this to react to bans/mutes added via SteamID (player not on server)
|
||||
/// </summary>
|
||||
private void OnPlayerPenaltiedAdded(
|
||||
SteamID steamId, // SteamID of the penalized player
|
||||
PlayerInfo? admin, // The admin who issued it (null = console)
|
||||
PenaltyType penaltyType,// Type of penalty
|
||||
string reason, // Reason for the penalty
|
||||
int duration, // Duration in minutes (-1 = permanent)
|
||||
int? penaltyId, // Database ID of the penalty
|
||||
int? serverId // Server ID where it was issued
|
||||
)
|
||||
{
|
||||
// Log offline penalty additions
|
||||
var adminName = admin?.Name ?? "Console";
|
||||
|
||||
switch (penaltyType)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
{
|
||||
Logger.LogInformation("Ban issued");
|
||||
Logger.LogInformation($"Id = {penaltyId}");
|
||||
Logger.LogInformation($"Ban added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m, Reason: {reason})");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Kick:
|
||||
{
|
||||
Logger.LogInformation("Kick issued");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Gag:
|
||||
{
|
||||
Logger.LogInformation("Gag issued");
|
||||
Logger.LogInformation($"Id = {penaltyId}");
|
||||
Logger.LogInformation($"Gag added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m)");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Mute:
|
||||
{
|
||||
Logger.LogInformation("Mute issued");
|
||||
Logger.LogInformation($"Mute added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m)");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Silence:
|
||||
{
|
||||
Logger.LogInformation("Silence issued");
|
||||
Logger.LogInformation($"Silence added for offline player {steamId} by {adminName} (ID: {penaltyId}, Duration: {duration}m)");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Warn:
|
||||
{
|
||||
Logger.LogInformation("Warn issued");
|
||||
Logger.LogInformation($"Warning added for offline player {steamId} by {adminName} (ID: {penaltyId}, Reason: {reason})");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
|
||||
}
|
||||
|
||||
Console.WriteLine(player.Name);
|
||||
Console.WriteLine(admin?.Name ?? "Console");
|
||||
Console.WriteLine(player.SteamId.ToString());
|
||||
Console.WriteLine(reason);
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltiedAdded(SteamID steamId, PlayerInfo? admin, PenaltyType penaltyType,
|
||||
string reason, int duration, int? penaltyId, int? serverId)
|
||||
{
|
||||
switch (penaltyType)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
{
|
||||
Logger.LogInformation("Ban added");
|
||||
Logger.LogInformation($"Id = {penaltyId}");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Kick:
|
||||
{
|
||||
Logger.LogInformation("Kick added");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Gag:
|
||||
{
|
||||
Logger.LogInformation("Gag added");
|
||||
Logger.LogInformation($"Id = {penaltyId}");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Mute:
|
||||
{
|
||||
Logger.LogInformation("Mute added");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Silence:
|
||||
{
|
||||
Logger.LogInformation("Silence added");
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Warn:
|
||||
{
|
||||
Logger.LogInformation("Warn added");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
|
||||
}
|
||||
|
||||
Console.WriteLine(admin?.Name ?? "Console");
|
||||
Console.WriteLine(steamId.ToString());
|
||||
Console.WriteLine(reason);
|
||||
}
|
||||
}
|
||||
@@ -370,7 +370,7 @@ public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Con
|
||||
|
||||
// Register menus with command names for permission override support
|
||||
// Server admins can override default permissions via CounterStrikeSharp admin system
|
||||
// Example: If "css_god" is overridden to "@css/vip", only VIPs will see the God Mode menu
|
||||
// Example: If "css_god" is overdden to "@css/vip", only VIPs will see the God Mode menu
|
||||
|
||||
if (Config.GodCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "god",
|
||||
|
||||
@@ -27,6 +27,35 @@ namespace CS2_SimpleAdmin_FunCommands;
|
||||
/// "css_god"); // Command name for override checking
|
||||
///
|
||||
/// This means developers don't need to manually check permissions in their menu factory methods!
|
||||
///
|
||||
/// MENU CONTEXT API (NEW!):
|
||||
/// ========================
|
||||
/// Menu factory methods now receive a MenuContext parameter that contains:
|
||||
/// - CategoryId: The category this menu belongs to (e.g., "fun")
|
||||
/// - MenuId: The unique identifier for this menu (e.g., "god")
|
||||
/// - MenuTitle: The display title from registration (e.g., "God Mode")
|
||||
/// - Permission: The default permission (e.g., "@css/cheats")
|
||||
/// - CommandName: The command name for override checking (e.g., "css_god")
|
||||
///
|
||||
/// This eliminates duplication when creating menus - you no longer need to repeat
|
||||
/// the title and category in both RegisterMenu() and CreateMenuWithPlayers()!
|
||||
///
|
||||
/// Before (old API):
|
||||
/// private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
/// {
|
||||
/// return _sharedApi.CreateMenuWithPlayers(
|
||||
/// "God Mode", // ← Duplicated from RegisterMenu
|
||||
/// "fun", // ← Duplicated from RegisterMenu
|
||||
/// admin, filter, action);
|
||||
/// }
|
||||
///
|
||||
/// After (new API with MenuContext):
|
||||
/// private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
|
||||
/// {
|
||||
/// return _sharedApi.CreateMenuWithPlayers(
|
||||
/// context, // ← Contains both title and category automatically!
|
||||
/// admin, filter, action);
|
||||
/// }
|
||||
/// </summary>
|
||||
public partial class CS2_SimpleAdmin_FunCommands
|
||||
{
|
||||
@@ -39,22 +68,21 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// <summary>
|
||||
/// Creates a simple player selection menu for god mode.
|
||||
/// PATTERN: CreateMenuWithPlayers with method reference
|
||||
/// IMPROVED: Uses MenuContext to eliminate duplication of title and category
|
||||
/// </summary>
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
private object CreateGodModeMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_god"] ?? "God Mode", // Menu title from translation
|
||||
"fun", // Category ID (for back button navigation)
|
||||
context, // ← Context contains title & category automatically!
|
||||
admin, // Admin opening the menu
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player), // Filter: only alive, targetable players
|
||||
God); // Action to execute (method reference)
|
||||
}
|
||||
|
||||
private object CreateNoClipMenu(CCSPlayerController admin)
|
||||
private object CreateNoClipMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_noclip"] ?? "No Clip",
|
||||
"fun",
|
||||
context,
|
||||
admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
NoClip);
|
||||
@@ -63,13 +91,13 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// <summary>
|
||||
/// Creates a player selection menu for respawn command.
|
||||
/// PATTERN: CreateMenuWithPlayers with method reference
|
||||
/// IMPROVED: Uses MenuContext to eliminate duplication
|
||||
/// </summary>
|
||||
private object CreateRespawnMenu(CCSPlayerController admin)
|
||||
private object CreateRespawnMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_respawn"] ?? "Respawn", // Menu title from translation
|
||||
"fun", // Category ID
|
||||
admin, // Admin
|
||||
context,
|
||||
admin,
|
||||
admin.CanTarget, // Filter: only targetable players (no LifeState check - can respawn dead players)
|
||||
Respawn); // Use the Respawn method which includes death position teleport
|
||||
}
|
||||
@@ -83,12 +111,12 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// <summary>
|
||||
/// Creates a nested menu: Player selection → Weapon selection.
|
||||
/// PATTERN: CreateMenuWithBack + foreach + AddSubMenu
|
||||
/// IMPROVED: Uses MenuContext - no more duplication of title/category!
|
||||
/// </summary>
|
||||
private object CreateGiveWeaponMenu(CCSPlayerController admin)
|
||||
private object CreateGiveWeaponMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_give"] ?? "Give Weapon",
|
||||
"fun",
|
||||
context, // ← Context contains title & category!
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
@@ -134,11 +162,10 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
return weaponMenu;
|
||||
}
|
||||
|
||||
private object CreateStripWeaponsMenu(CCSPlayerController admin)
|
||||
private object CreateStripWeaponsMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
|
||||
"fun",
|
||||
context,
|
||||
admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) =>
|
||||
@@ -148,11 +175,10 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
});
|
||||
}
|
||||
|
||||
private object CreateFreezeMenu(CCSPlayerController admin)
|
||||
private object CreateFreezeMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_freeze"] ?? "Freeze",
|
||||
"fun",
|
||||
context,
|
||||
admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) => { Freeze(adminPlayer, targetPlayer, -1); });
|
||||
@@ -161,13 +187,12 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// <summary>
|
||||
/// Creates a nested menu for setting player HP with predefined values.
|
||||
/// PATTERN: Same as Give Weapon (player selection → value selection)
|
||||
/// This is a common pattern you'll use frequently!
|
||||
/// IMPROVED: Uses MenuContext - cleaner and less error-prone!
|
||||
/// </summary>
|
||||
private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
private object CreateSetHpMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_hp"] ?? "Set HP",
|
||||
"fun",
|
||||
context,
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
@@ -212,11 +237,10 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
return hpSelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetSpeedMenu(CCSPlayerController admin)
|
||||
private object CreateSetSpeedMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_speed"] ?? "Set Speed",
|
||||
"fun",
|
||||
context,
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
@@ -273,11 +297,10 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
return speedSelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetGravityMenu(CCSPlayerController admin)
|
||||
private object CreateSetGravityMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
|
||||
"fun",
|
||||
context,
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
@@ -324,11 +347,10 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
return gravitySelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetMoneyMenu(CCSPlayerController admin)
|
||||
private object CreateSetMoneyMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_money"] ?? "Set Money",
|
||||
"fun",
|
||||
context,
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
@@ -366,11 +388,10 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
return moneySelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetResizeMenu(CCSPlayerController admin)
|
||||
private object CreateSetResizeMenu(CCSPlayerController admin, CS2_SimpleAdminApi.MenuContext context)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_resize"] ?? "Resize Player",
|
||||
"fun",
|
||||
context,
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
@@ -168,23 +168,35 @@ private void God(CCSPlayerController? caller, CCSPlayerController player)
|
||||
|
||||
**Key Concepts Demonstrated:**
|
||||
|
||||
#### Simple Player Selection Menu
|
||||
#### Simple Player Selection Menu (NEW API with MenuContext!)
|
||||
|
||||
```csharp
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
// 🆕 NEW: Factory receives MenuContext - no more duplication!
|
||||
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// ✅ BEST PRACTICE: Use CreateMenuWithPlayers for simple player selection
|
||||
return _sharedApi!.CreateMenuWithPlayers("God Mode", "fun", admin,
|
||||
// ✅ BEST PRACTICE: Use context instead of repeating title and category
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
context, // ← Contains "God Mode" title and "fun" category automatically!
|
||||
admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
God); // Direct method reference
|
||||
}
|
||||
```
|
||||
|
||||
#### Nested Menu with Value Selection
|
||||
**Why MenuContext is better:**
|
||||
- ❌ **Before:** You had to type `"God Mode"` and `"fun"` twice (in `RegisterMenu` and `CreateMenuWithPlayers`)
|
||||
- ✅ **After:** Context contains these values automatically - no duplication!
|
||||
- ✅ Less error-prone (can't accidentally use wrong category)
|
||||
- ✅ Easier to refactor (change title in one place)
|
||||
|
||||
#### Nested Menu with Value Selection (NEW API!)
|
||||
|
||||
```csharp
|
||||
private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
// 🆕 NEW: Uses MenuContext to eliminate duplication
|
||||
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// ✅ BEST PRACTICE: Use CreateMenuWithBack for menus with back button
|
||||
var menu = _sharedApi!.CreateMenuWithBack("Set HP", "fun", admin);
|
||||
// ✅ BEST PRACTICE: Use context instead of manual title/category
|
||||
var menu = _sharedApi!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
@@ -200,6 +212,7 @@ private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
|
||||
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Note: Submenus don't receive context - they create their own titles dynamically
|
||||
var hpMenu = _sharedApi!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "fun", admin);
|
||||
var hpValues = new[] { 1, 10, 25, 50, 100, 200, 500, 999 };
|
||||
|
||||
@@ -221,6 +234,31 @@ private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerControl
|
||||
}
|
||||
```
|
||||
|
||||
**Comparison: Old vs New API**
|
||||
|
||||
```csharp
|
||||
// ❌ OLD API - lots of duplication
|
||||
_sharedApi.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats", "css_god");
|
||||
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _sharedApi.CreateMenuWithPlayers(
|
||||
"God Mode", // ← Repeated from RegisterMenu
|
||||
"fun", // ← Repeated from RegisterMenu
|
||||
admin, filter, action);
|
||||
}
|
||||
|
||||
// ✅ NEW API - no duplication!
|
||||
_sharedApi.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats", "css_god");
|
||||
|
||||
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _sharedApi.CreateMenuWithPlayers(
|
||||
context, // ← Contains title and category automatically!
|
||||
admin, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Translations
|
||||
|
||||
**Key Concept:** Module-specific translations
|
||||
|
||||
@@ -6,12 +6,12 @@ This guide explains how to create modules for CS2-SimpleAdmin with custom comman
|
||||
|
||||
## 📖 Table of Contents
|
||||
|
||||
1. [Quick Start](#quick-start)
|
||||
2. [Learning Resources](#learning-resources)
|
||||
3. [API Methods Reference](#api-methods)
|
||||
4. [Menu Patterns](#menu-patterns)
|
||||
5. [Best Practices](#best-practices)
|
||||
6. [Common Patterns](#common-patterns)
|
||||
1. [Quick Start](#-quick-start)
|
||||
2. [Learning Resources](#-learning-resources)
|
||||
3. [MenuContext API (New!)](#-menucontext-api-new)
|
||||
4. [API Methods Reference](#-api-methods-reference)
|
||||
5. [Menu Patterns](#-menu-patterns)
|
||||
6. [Best Practices](#best-practices)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
|
||||
## 🚀 Quick Start
|
||||
@@ -44,7 +44,7 @@ YourModule/
|
||||
└── ru.json
|
||||
```
|
||||
|
||||
### Step 3: Minimal Working Example
|
||||
### Step 3: Minimal Working Example (NEW MenuContext API!)
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
@@ -78,14 +78,17 @@ public class MyModule : BasePlugin
|
||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||
|
||||
// 2. Register menu items in the category
|
||||
// 🆕 NEW: Use MenuContext-aware overload (no duplication!)
|
||||
_api.RegisterMenu("mymodule", "action1", "Action 1", CreateAction1Menu, "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "action2", "Action 2", CreateAction2Menu, "@css/kick");
|
||||
}
|
||||
|
||||
private object CreateAction1Menu(CCSPlayerController admin)
|
||||
// 🆕 NEW: Factory now receives MenuContext parameter
|
||||
private object CreateAction1Menu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// Create a menu with automatic back button
|
||||
var menu = _api!.CreateMenuWithBack("Action 1 Menu", "mymodule", admin);
|
||||
// Use context instead of repeating title and category!
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
// Add menu options
|
||||
_api.AddMenuOption(menu, "Option 1", player =>
|
||||
@@ -101,10 +104,13 @@ public class MyModule : BasePlugin
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateAction2Menu(CCSPlayerController admin)
|
||||
// 🆕 NEW: MenuContext eliminates duplication here too!
|
||||
private object CreateAction2Menu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// Use the built-in player selection menu
|
||||
return _api!.CreateMenuWithPlayers("Select Player", "mymodule", admin,
|
||||
// Use the built-in player selection menu with context
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // ← Contains title & category automatically!
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) =>
|
||||
{
|
||||
@@ -158,16 +164,15 @@ public class MyModule : BasePlugin
|
||||
|
||||
The FunCommands module demonstrates **3 essential menu patterns** you'll use in every module:
|
||||
|
||||
### Pattern 1: Simple Player Selection
|
||||
### Pattern 1: Simple Player Selection (NEW MenuContext API!)
|
||||
**When to use:** Select a player and immediately execute an action
|
||||
|
||||
```csharp
|
||||
// Example: God Mode menu
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
// 🆕 NEW: Factory receives MenuContext - eliminates duplication!
|
||||
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(
|
||||
"God Mode", // Title
|
||||
"yourmodule", // Category ID
|
||||
context, // ← Contains title & category automatically!
|
||||
admin, // Admin
|
||||
player => player.IsValid && admin.CanTarget(player), // Filter
|
||||
(adminPlayer, target) => // Action
|
||||
@@ -178,16 +183,20 @@ private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
}
|
||||
```
|
||||
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:21-29`
|
||||
**Why MenuContext is better:**
|
||||
- ❌ **Old way:** `"God Mode"` and `"yourmodule"` had to be typed twice (in RegisterMenu and CreateMenuWithPlayers)
|
||||
- ✅ **New way:** Context contains both automatically - no duplication, no mistakes!
|
||||
|
||||
### Pattern 2: Nested Menu (Player → Value)
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:44-51`
|
||||
|
||||
### Pattern 2: Nested Menu (Player → Value) - NEW MenuContext API!
|
||||
**When to use:** Select a player, then select a value/option for that player
|
||||
|
||||
```csharp
|
||||
// Example: Set HP menu (player selection)
|
||||
private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
// 🆕 NEW: Uses MenuContext to eliminate duplication
|
||||
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack("Set HP", "yourmodule", admin);
|
||||
var menu = _api.CreateMenuWithBack(context, admin); // ← No more repeating title & category!
|
||||
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
@@ -201,6 +210,7 @@ private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
}
|
||||
|
||||
// Example: Set HP menu (value selection)
|
||||
// Note: Submenus don't receive context - they create dynamic titles
|
||||
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack($"HP for {target.PlayerName}", "yourmodule", admin);
|
||||
@@ -221,7 +231,12 @@ private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController
|
||||
}
|
||||
```
|
||||
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:134-173`
|
||||
**Benefits of MenuContext:**
|
||||
- ✅ Change menu title in one place (RegisterMenu) and it updates everywhere
|
||||
- ✅ Can't accidentally mistype category ID
|
||||
- ✅ Access to permission and command name from context if needed
|
||||
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:163-178`
|
||||
|
||||
### Pattern 3: Nested Menu with Complex Data
|
||||
**When to use:** Need to display more complex options (like weapons with icons, items with descriptions)
|
||||
@@ -264,6 +279,73 @@ private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerCon
|
||||
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:67-109`
|
||||
|
||||
## 🆕 MenuContext API (New!)
|
||||
|
||||
### What is MenuContext?
|
||||
|
||||
`MenuContext` is a new feature that eliminates code duplication when creating menus. When you register a menu, you provide information like title, category, and permissions. Previously, you had to repeat this information in your menu factory method. Now, this information is automatically passed to your factory via `MenuContext`.
|
||||
|
||||
### MenuContext Properties
|
||||
|
||||
```csharp
|
||||
public class MenuContext
|
||||
{
|
||||
public string CategoryId { get; } // e.g., "fun"
|
||||
public string MenuId { get; } // e.g., "god"
|
||||
public string MenuTitle { get; } // e.g., "God Mode"
|
||||
public string? Permission { get; } // e.g., "@css/cheats"
|
||||
public string? CommandName { get; } // e.g., "css_god"
|
||||
}
|
||||
```
|
||||
|
||||
### Before vs After Comparison
|
||||
|
||||
```csharp
|
||||
// ❌ OLD API - Duplication everywhere
|
||||
_api.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats");
|
||||
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(
|
||||
"God Mode", // ← Duplicated from RegisterMenu
|
||||
"fun", // ← Duplicated from RegisterMenu
|
||||
admin, filter, action);
|
||||
}
|
||||
|
||||
// ✅ NEW API - No duplication!
|
||||
_api.RegisterMenu("fun", "god", "God Mode", CreateGodModeMenu, "@css/cheats");
|
||||
|
||||
private object CreateGodModeMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(
|
||||
context, // ← Contains all info automatically!
|
||||
admin, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### When to Use MenuContext
|
||||
|
||||
| Menu Creation Method | Old Signature | New Signature (Recommended) |
|
||||
|---------------------|---------------|----------------------------|
|
||||
| `CreateMenuWithBack` | `(string title, string categoryId, ...)` | `(MenuContext context, ...)` |
|
||||
| `CreateMenuWithPlayers` | `(string title, string categoryId, ...)` | `(MenuContext context, ...)` |
|
||||
|
||||
**Rule of thumb:** If you're creating a menu directly from a registered menu factory, use `MenuContext`. For dynamic submenus (e.g., player-specific menus), use the old API.
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
The old API still works! Both signatures are supported:
|
||||
|
||||
```csharp
|
||||
// ✅ Old API - still works
|
||||
_api.RegisterMenu("cat", "id", "Title",
|
||||
(CCSPlayerController admin) => CreateOldStyleMenu(admin));
|
||||
|
||||
// ✅ New API - recommended
|
||||
_api.RegisterMenu("cat", "id", "Title",
|
||||
(CCSPlayerController admin, MenuContext ctx) => CreateNewStyleMenu(admin, ctx));
|
||||
```
|
||||
|
||||
## 📋 API Methods Reference
|
||||
|
||||
### 1. Category Management
|
||||
@@ -312,35 +394,57 @@ _api.UnregisterMenu("fun", "godmode");
|
||||
### 3. Menu Creation
|
||||
|
||||
#### `CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)`
|
||||
#### `CreateMenuWithBack(MenuContext context, CCSPlayerController player)` 🆕 NEW!
|
||||
|
||||
Creates a menu with an automatic "Back" button that returns to the category menu.
|
||||
|
||||
**Parameters:**
|
||||
**Parameters (Old API):**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category this menu belongs to (for back navigation)
|
||||
- `player` - The admin player viewing the menu
|
||||
|
||||
**Parameters (New API - Recommended):**
|
||||
- `context` - MenuContext containing title, category, and other metadata
|
||||
- `player` - The admin player viewing the menu
|
||||
|
||||
**Returns:** `object` (MenuBuilder instance)
|
||||
|
||||
**Example:**
|
||||
**Example (Old API):**
|
||||
```csharp
|
||||
var menu = _api.CreateMenuWithBack("Weapon Selection", "fun", admin);
|
||||
```
|
||||
|
||||
**Example (New API - Recommended):**
|
||||
```csharp
|
||||
private object CreateWeaponMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack(context, admin); // ← Uses context!
|
||||
// ... add options
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
#### `CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)`
|
||||
#### `CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)` 🆕 NEW!
|
||||
|
||||
Creates a menu with a list of players, filtered and with automatic back button.
|
||||
|
||||
**Parameters:**
|
||||
**Parameters (Old API):**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category for back navigation
|
||||
- `admin` - The admin player viewing the menu
|
||||
- `filter` - Function to filter which players appear in the menu
|
||||
- `onSelect` - Action to execute when a player is selected (receives admin and target)
|
||||
|
||||
**Parameters (New API - Recommended):**
|
||||
- `context` - MenuContext containing title and category
|
||||
- `admin` - The admin player viewing the menu
|
||||
- `filter` - Function to filter which players appear in the menu
|
||||
- `onSelect` - Action to execute when a player is selected (receives admin and target)
|
||||
|
||||
**Returns:** `object` (MenuBuilder instance)
|
||||
|
||||
**Example:**
|
||||
**Example (Old API):**
|
||||
```csharp
|
||||
return _api.CreateMenuWithPlayers("Select Player to Kick", "admin", admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
@@ -351,6 +455,19 @@ return _api.CreateMenuWithPlayers("Select Player to Kick", "admin", admin,
|
||||
});
|
||||
```
|
||||
|
||||
**Example (New API - Recommended):**
|
||||
```csharp
|
||||
private object CreateKickMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(context, admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) =>
|
||||
{
|
||||
Server.ExecuteCommand($"css_kick {targetPlayer.UserId}");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Menu Manipulation
|
||||
|
||||
#### `AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)`
|
||||
|
||||