Compare commits
26 Commits
build-1.7.
...
build-1.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91615ffc67 | ||
|
|
eb9b438315 | ||
|
|
3d23b8981b | ||
|
|
39cbfdab1e | ||
|
|
4599f08fd7 | ||
|
|
4c43f14c82 | ||
|
|
f16f8cf1a5 | ||
|
|
193685826c | ||
|
|
fe73fa9917 | ||
|
|
bdada2df1e | ||
|
|
310a43fcd9 | ||
|
|
58243e813a | ||
|
|
d53446e0fe | ||
|
|
2404c1bc03 | ||
|
|
665962565e | ||
|
|
c2e8b4a898 | ||
|
|
9723a4faee | ||
|
|
4865b76262 | ||
|
|
0dded66e5d | ||
|
|
038641dbdf | ||
|
|
a03964c08a | ||
|
|
b0d8696756 | ||
|
|
21a5de6b3d | ||
|
|
718536eaef | ||
|
|
099e91b43b | ||
|
|
206c18db66 |
6
.github/workflows/build.yml
vendored
@@ -16,6 +16,8 @@ env:
|
||||
PROJECT_NAME_CS2_SIMPLEADMIN: "CS2-SimpleAdmin"
|
||||
PROJECT_PATH_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj"
|
||||
PROJECT_NAME_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi"
|
||||
PROJECT_PATH_FUNCOMMANDSMODULE: "Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj"
|
||||
PROJECT_NAME_FUNCOMMANDSMODULE: "CS2-SimpleAdmin_FunCommands"
|
||||
PROJECT_PATH_STEALTHMODULE: "Modules/CS2-SimpleAdmin_StealthModule/CS2-SimpleAdmin_StealthModule.csproj"
|
||||
PROJECT_NAME_STEALTHMODULE: "CS2-SimpleAdmin_StealthModule"
|
||||
OUTPUT_PATH: "./counterstrikesharp"
|
||||
@@ -45,16 +47,20 @@ jobs:
|
||||
dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}
|
||||
dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }}
|
||||
dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}
|
||||
dotnet restore ${{ env.PROJECT_PATH_FUNCOMMANDSMODULE }}
|
||||
dotnet build ${{ env.PROJECT_PATH_FUNCOMMANDSMODULE }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_FUNCOMMANDSMODULE }}
|
||||
dotnet restore ${{ env.PROJECT_PATH_STEALTHMODULE }}
|
||||
dotnet build ${{ env.PROJECT_PATH_STEALTHMODULE }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_STEALTHMODULE }}
|
||||
|
||||
- name: Combine projects
|
||||
run: |
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_FunCommands
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_StealthModule
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/shared/CS2-SimpleAdminApi
|
||||
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin/
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_FUNCOMMANDSMODULE }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_FunCommands
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_STEALTHMODULE }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_StealthModule
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/* ${{ env.OUTPUT_PATH }}/shared/CS2-SimpleAdminApi/
|
||||
|
||||
|
||||
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 |
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
@@ -205,10 +206,18 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||
Func<CCSPlayerController, object> menuFactory, string? permission = null)
|
||||
public void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer)
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission);
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryNameKey, permission, localizer);
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||
Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
@@ -233,6 +242,69 @@ 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 RegisterMenu(string categoryId, string menuId, string menuNameKey,
|
||||
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer)
|
||||
{
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuNameKey, BuilderFactory, permission, commandName, localizer);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
{
|
||||
var context = new MenuContext(categoryId, menuId, menuNameKey, 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 +329,34 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
return builder;
|
||||
}
|
||||
|
||||
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
||||
{
|
||||
// Get translated title if module has localizer
|
||||
string title = context.MenuTitle;
|
||||
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||
{
|
||||
// Check if this specific menu has a localizer
|
||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||
{
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
// Fallback to category localizer
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMenuWithBack(title, context.CategoryId, player);
|
||||
}
|
||||
|
||||
public List<CCSPlayerController> GetValidPlayers()
|
||||
{
|
||||
return Helper.GetValidPlayers();
|
||||
@@ -283,6 +383,35 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
return menu;
|
||||
}
|
||||
|
||||
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||
{
|
||||
// Get translated title if module has localizer
|
||||
string title = context.MenuTitle;
|
||||
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||
{
|
||||
// Check if this specific menu has a localizer
|
||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||
{
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
// Fallback to category localizer
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMenuWithPlayers(title, context.CategoryId, admin, filter, onSelect);
|
||||
}
|
||||
|
||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
||||
string? permission = null)
|
||||
{
|
||||
|
||||
@@ -21,13 +21,12 @@ 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-1";
|
||||
public override string ModuleAuthor => "daffyy";
|
||||
public override string ModuleVersion => "1.7.8-beta-10b";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
if (hotReload)
|
||||
{
|
||||
ServerLoaded = false;
|
||||
@@ -47,7 +46,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
CachedPlayers.Clear();
|
||||
BotPlayers.Clear();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
|
||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected && !p.IsHLTV).ToArray())
|
||||
{
|
||||
if (!player.IsBot)
|
||||
PlayerManager.LoadPlayerData(player, true);
|
||||
@@ -84,9 +83,9 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
Unload(false);
|
||||
}
|
||||
|
||||
AddTimer(6.0f, () => ReloadAdmins(null));
|
||||
RegisterEvents();
|
||||
AddTimer(0.5f, RegisterCommands.InitializeCommands);
|
||||
AddTimer(3.0f, () => ReloadAdmins(null));
|
||||
|
||||
if (!CoreConfig.UnlockConCommands)
|
||||
{
|
||||
@@ -261,6 +260,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
CacheManager = null;
|
||||
PlayersTimer?.Kill();
|
||||
PlayersTimer = null;
|
||||
|
||||
UnregisterEvents();
|
||||
|
||||
if (hotReload)
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340">
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.361">
|
||||
<PrivateAssets>none</PrivateAssets>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
||||
<PackageReference Include="MySqlConnector" Version="2.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
|
||||
<PackageReference Include="ZLinq" Version="1.5.2" />
|
||||
<PackageReference Include="System.Linq.Async" Version="7.0.0" />
|
||||
<PackageReference Include="ZLinq" Version="1.5.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -507,7 +507,7 @@ public partial class CS2_SimpleAdmin
|
||||
var adminsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/admins.json");
|
||||
var groupsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/groups.json");
|
||||
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
AddTimer(1, () =>
|
||||
{
|
||||
@@ -521,7 +521,7 @@ public partial class CS2_SimpleAdmin
|
||||
_logger?.LogInformation("Loaded admins!");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
//_ = _adminManager.GiveAllGroupsFlags();
|
||||
//_ = _adminManager.GiveAllFlags();
|
||||
|
||||
@@ -52,6 +52,7 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
// Make the player commit suicide
|
||||
player.CommitSuicide(false, true);
|
||||
player.EmitSound("Player.Death");
|
||||
|
||||
// Determine message keys and arguments for the slay notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
@@ -120,6 +121,7 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
// Apply slap damage to the player
|
||||
player.Pawn.Value?.Slap(damage);
|
||||
player.EmitSound("Player.DamageFall");
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
|
||||
@@ -12,6 +12,9 @@ public interface IDatabaseProvider
|
||||
string GetBanSelectQuery(bool multiServer);
|
||||
string GetIpHistoryQuery();
|
||||
string GetBanUpdateQuery(bool multiServer);
|
||||
|
||||
// PlayerManager
|
||||
string GetUpsertPlayerIpQuery();
|
||||
|
||||
// PermissionManager
|
||||
string GetAdminsQuery();
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
-- Migration 016: Optimize tables and indexes
|
||||
-- Add proper indexes for all tables to improve query performance
|
||||
-- -- Migration 016: Optimize tables and indexes
|
||||
-- -- Add proper indexes for all tables to improve query performance
|
||||
|
||||
-- Optimize sa_players_ips table indexes
|
||||
-- Add index on used_at for efficient date-based queries
|
||||
ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
||||
-- -- Optimize sa_players_ips table indexes
|
||||
-- -- Add index on used_at for efficient date-based queries
|
||||
-- ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
||||
|
||||
-- Optimize sa_bans table indexes
|
||||
-- Add composite indexes for common query patterns
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||
-- -- Optimize sa_bans table indexes
|
||||
-- -- Add composite indexes for common query patterns
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||
|
||||
-- Optimize sa_admins table indexes
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||
-- -- Optimize sa_admins table indexes
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||
|
||||
-- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||
-- Add index for expire queries
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||
-- -- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||
-- -- Add index for expire queries
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||
|
||||
-- Optimize sa_warns table indexes (if exists)
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
||||
-- -- Optimize sa_warns table indexes (if exists)
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
||||
|
||||
-- Add index on sa_servers for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||
-- -- Add index on sa_servers for faster lookups
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
||||
`steamid` INTEGER NOT NULL,
|
||||
`address` VARCHAR(64) NOT NULL,
|
||||
`address` INTEGER NOT NULL,
|
||||
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`steamid`, `address`)
|
||||
);
|
||||
@@ -1,9 +1,4 @@
|
||||
DELETE FROM `sa_players_ips`
|
||||
WHERE `id` NOT IN (
|
||||
SELECT MIN(`id`)
|
||||
FROM `sa_players_ips`
|
||||
GROUP BY `steamid`
|
||||
);
|
||||
DELETE FROM sa_players_ips;
|
||||
|
||||
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL;
|
||||
CREATE INDEX IF NOT EXISTS `idx_sa_players_ips_used_at` ON `sa_players_ips` (`used_at` DESC);
|
||||
@@ -1,33 +0,0 @@
|
||||
-- Migration 016: Optimize tables and indexes
|
||||
-- Add proper indexes for all tables to improve query performance
|
||||
|
||||
-- Optimize sa_players_ips table indexes
|
||||
-- Add index on used_at for efficient date-based queries
|
||||
CREATE INDEX IF NOT EXISTS `idx_used_at` ON `sa_players_ips` (`used_at` DESC);
|
||||
|
||||
-- Optimize sa_bans table indexes
|
||||
-- Add composite indexes for common query patterns
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||
|
||||
-- Optimize sa_admins table indexes
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||
|
||||
-- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||
-- Add index for expire queries
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||
|
||||
-- Optimize sa_warns table indexes (if exists)
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
||||
|
||||
-- Add index on sa_servers for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||
|
||||
@@ -15,7 +15,7 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
|
||||
cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
cmd.CommandText = "SET time_zone = '+00:00';";
|
||||
// cmd.CommandText = "SET time_zone = '+00:00';";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
return connection;
|
||||
@@ -86,6 +86,17 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
|
||||
return "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
||||
}
|
||||
|
||||
public string GetUpsertPlayerIpQuery()
|
||||
{
|
||||
return """
|
||||
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
}
|
||||
|
||||
public string GetBanUpdateQuery(bool multiServer)
|
||||
{
|
||||
return multiServer ? """
|
||||
|
||||
@@ -83,6 +83,17 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider
|
||||
public string GetIpHistoryQuery() =>
|
||||
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
||||
|
||||
public string GetUpsertPlayerIpQuery()
|
||||
{
|
||||
return """
|
||||
INSERT INTO sa_players_ips (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(steamid, address) DO UPDATE SET
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
}
|
||||
|
||||
public string GetBanUpdateQuery(bool multiServer) =>
|
||||
multiServer
|
||||
? """
|
||||
|
||||
@@ -23,7 +23,7 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
RegisterListener<Listeners.OnMapStart>(OnMapStart);
|
||||
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||
RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
|
||||
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
|
||||
if (Config.OtherSettings.UserMessageGagChatType)
|
||||
@@ -77,7 +77,7 @@ public partial class CS2_SimpleAdmin
|
||||
new ServerManager().LoadServerData();
|
||||
}
|
||||
|
||||
[GameEventHandler(HookMode.Pre)]
|
||||
[GameEventHandler]
|
||||
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||
{
|
||||
if (@event.Reason is 149 or 6)
|
||||
@@ -91,14 +91,15 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
if (player == null || !player.IsValid || player.IsHLTV)
|
||||
return HookResult.Continue;
|
||||
|
||||
BotPlayers.Remove(player);
|
||||
CachedPlayers.Remove(player);
|
||||
|
||||
CachedPlayers.Remove(player);
|
||||
BotPlayers.Remove(player);
|
||||
SilentPlayers.Remove(player.Slot);
|
||||
|
||||
if (player.IsBot)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnClientDisconnect] After Check");
|
||||
@@ -176,6 +177,9 @@ public partial class CS2_SimpleAdmin
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
return;
|
||||
|
||||
if (!CachedPlayers.Contains(player))
|
||||
CachedPlayers.Add(player);
|
||||
|
||||
PlayerManager.LoadPlayerData(player);
|
||||
}
|
||||
|
||||
@@ -444,13 +448,13 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
private void OnMapStart(string mapName)
|
||||
{
|
||||
if (!ServerLoaded || ServerId == null)
|
||||
AddTimer(2.0f, OnGameServerSteamAPIActivated);
|
||||
|
||||
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
|
||||
AddTimer(5.0f, () => ReloadAdmins(null));
|
||||
ReloadAdmins(null);
|
||||
|
||||
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
|
||||
|
||||
if (!ServerLoaded || ServerId == null)
|
||||
AddTimer(1.5f, OnGameServerSteamAPIActivated);
|
||||
|
||||
// AddTimer(34, () =>
|
||||
// {
|
||||
@@ -458,28 +462,11 @@ public partial class CS2_SimpleAdmin
|
||||
// OnGameServerSteamAPIActivated();
|
||||
// });
|
||||
|
||||
GodPlayers.Clear();
|
||||
SilentPlayers.Clear();
|
||||
|
||||
PlayerPenaltyManager.RemoveAllPenalties();
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
|
||||
if (player is null || @event.Attacker is null || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.PlayerPawn.Value == null)
|
||||
return HookResult.Continue;
|
||||
|
||||
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
|
||||
|
||||
player.PlayerPawn.Value.Health = player.PlayerPawn.Value.MaxHealth;
|
||||
player.PlayerPawn.Value.ArmorValue = 100;
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
@@ -512,17 +499,13 @@ public partial class CS2_SimpleAdmin
|
||||
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
if (player == null || !player.IsValid || player.IsBot || !SilentPlayers.Contains(player.Slot))
|
||||
return HookResult.Continue;
|
||||
|
||||
if (!SilentPlayers.Contains(player.Slot))
|
||||
return HookResult.Continue;
|
||||
|
||||
if (@event is { Oldteam: <= 1, Team: >= 1 })
|
||||
{
|
||||
SilentPlayers.Remove(player.Slot);
|
||||
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
|
||||
}
|
||||
if (@event is not { Oldteam: <= 1, Team: >= 1 }) return HookResult.Continue;
|
||||
|
||||
SilentPlayers.Remove(player.Slot);
|
||||
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ internal static class Helper
|
||||
|
||||
public static List<CCSPlayerController> GetValidPlayers()
|
||||
{
|
||||
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().ToList();
|
||||
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected).ToList();
|
||||
}
|
||||
|
||||
public static List<CCSPlayerController> GetValidPlayersWithBots()
|
||||
@@ -426,7 +426,7 @@ internal static class Helper
|
||||
|
||||
var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : "<https://steamcommunity.com/profiles/0>";
|
||||
var callerName = caller != null ? caller.PlayerName : CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
|
||||
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(Helper.GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString]));
|
||||
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString]));
|
||||
}
|
||||
|
||||
private static void SendDiscordLogMessage(CCSPlayerController? caller, string command, IStringLocalizer? localizer)
|
||||
@@ -583,27 +583,32 @@ internal static class Helper
|
||||
}
|
||||
|
||||
public static void DisplayCenterMessage(
|
||||
CCSPlayerController player,
|
||||
string messageKey,
|
||||
string? callerName = null,
|
||||
CCSPlayerController player,
|
||||
string messageKey,
|
||||
string? callerName = null,
|
||||
params object[] messageArgs)
|
||||
{
|
||||
if (CS2_SimpleAdmin._localizer == null) return;
|
||||
|
||||
// Determine the localized message key
|
||||
var localizedMessageKey = $"{messageKey}";
|
||||
|
||||
var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();
|
||||
|
||||
// Replace placeholder based on showActivityType
|
||||
for (var i = 0; i < formattedMessageArgs.Length; i++)
|
||||
{
|
||||
var arg = formattedMessageArgs[i];
|
||||
var arg = formattedMessageArgs[i]; // Convert argument to string if not null
|
||||
// Replace "CALLER" placeholder in the argument string
|
||||
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||
{
|
||||
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
2 => arg.Replace("CALLER", callerName ?? "Console"),
|
||||
_ => arg
|
||||
};
|
||||
}
|
||||
|
||||
// Print the localized message to the center of the screen for the player
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
|
||||
@@ -1026,7 +1031,9 @@ public static class Time
|
||||
{
|
||||
public static DateTime ActualDateTime()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
if (CS2_SimpleAdmin.Instance.Config.DatabaseConfig.DatabaseType.ToLower().Equals("sqlite"))
|
||||
return DateTime.UtcNow;
|
||||
|
||||
string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone;
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdmin.Models;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZLinq;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
@@ -16,6 +17,7 @@ internal class CacheManager: IDisposable
|
||||
private HashSet<uint> _cachedIgnoredIps = [];
|
||||
|
||||
private DateTime _lastUpdateTime = DateTime.MinValue;
|
||||
private DateTime? _lastDatabaseTime = null; // Track actual time from database
|
||||
private bool _isInitialized;
|
||||
private bool _disposed;
|
||||
|
||||
@@ -156,13 +158,20 @@ internal class CacheManager: IDisposable
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
IEnumerable<BanRecord> updatedBans;
|
||||
|
||||
// Get current time from database in local timezone (CURRENT_TIMESTAMP uses session timezone, not UTC)
|
||||
var currentDatabaseTime = await connection.QueryFirstAsync<DateTime>("SELECT CURRENT_TIMESTAMP");
|
||||
|
||||
// Optimization: Only get IDs for comparison if we need to check for deletions
|
||||
// Most of the time bans are just added/updated, not deleted
|
||||
HashSet<int>? allIds = null;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
{
|
||||
updatedBans = (await connection.QueryAsync<BanRecord>(
|
||||
// Use previous database time or start from far past if first run
|
||||
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||
|
||||
// Get recently updated bans by timestamp (using database time to avoid timezone issues)
|
||||
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||
"""
|
||||
SELECT id AS Id,
|
||||
player_name AS PlayerName,
|
||||
@@ -171,33 +180,68 @@ internal class CacheManager: IDisposable
|
||||
status AS Status
|
||||
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
||||
""",
|
||||
new { lastUpdate = _lastUpdateTime }
|
||||
));
|
||||
new { lastUpdate = lastCheckTime }
|
||||
)).ToList();
|
||||
|
||||
// Detect changes: new bans or status changes
|
||||
var updatedList = new List<BanRecord>();
|
||||
foreach (var ban in updatedBans_Query)
|
||||
{
|
||||
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||
{
|
||||
// New ban
|
||||
updatedList.Add(ban);
|
||||
}
|
||||
else if (cachedBan.Status != ban.Status)
|
||||
{
|
||||
// Status changed
|
||||
updatedList.Add(ban);
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization: Only fetch all IDs if there were updates
|
||||
var updatedList = updatedBans.ToList();
|
||||
if (updatedList.Count > 0)
|
||||
{
|
||||
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
||||
}
|
||||
updatedBans = updatedList;
|
||||
|
||||
// Update last check time to current database time
|
||||
_lastDatabaseTime = currentDatabaseTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
updatedBans = (await connection.QueryAsync<BanRecord>(
|
||||
// Use previous database time or start from far past if first run
|
||||
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||
|
||||
// Get recently updated bans for this server by timestamp (using database time to avoid timezone issues)
|
||||
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||
"""
|
||||
SELECT id AS Id,
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
|
||||
FROM `sa_bans` WHERE server_id = @serverId AND (updated_at > @lastUpdate OR created > @lastUpdate) ORDER BY updated_at DESC
|
||||
""",
|
||||
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
|
||||
));
|
||||
new { serverId = CS2_SimpleAdmin.ServerId, lastUpdate = lastCheckTime }
|
||||
)).ToList();
|
||||
|
||||
// Detect changes: new bans or status changes
|
||||
var updatedList = new List<BanRecord>();
|
||||
foreach (var ban in updatedBans_Query)
|
||||
{
|
||||
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||
{
|
||||
// New ban
|
||||
updatedList.Add(ban);
|
||||
}
|
||||
else if (cachedBan.Status != ban.Status)
|
||||
{
|
||||
// Status changed
|
||||
updatedList.Add(ban);
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization: Only fetch all IDs if there were updates
|
||||
var updatedList = updatedBans.ToList();
|
||||
if (updatedList.Count > 0)
|
||||
{
|
||||
allIds = (await connection.QueryAsync<int>(
|
||||
@@ -206,6 +250,9 @@ internal class CacheManager: IDisposable
|
||||
)).ToHashSet();
|
||||
}
|
||||
updatedBans = updatedList;
|
||||
|
||||
// Update last check time to current database time
|
||||
_lastDatabaseTime = currentDatabaseTime;
|
||||
}
|
||||
|
||||
// Optimization: Only process deletions if we have the full ID list
|
||||
@@ -276,16 +323,19 @@ internal class CacheManager: IDisposable
|
||||
}
|
||||
|
||||
// Update cache with new/modified bans
|
||||
var hasUpdates = false;
|
||||
var needsRebuild = false;
|
||||
foreach (var ban in updatedBans)
|
||||
{
|
||||
if (_banCache.TryGetValue(ban.Id, out var oldBan) && oldBan.Status != ban.Status)
|
||||
{
|
||||
// Ban status changed (e.g., ACTIVE -> EXPIRED/UNBANNED), need to rebuild indexes
|
||||
needsRebuild = true;
|
||||
}
|
||||
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
||||
hasUpdates = true;
|
||||
}
|
||||
|
||||
// Always rebuild indexes if there were any updates
|
||||
// This ensures status changes (ACTIVE -> UNBANNED) are reflected
|
||||
if (hasUpdates)
|
||||
// Rebuild indexes if there were updates or status changes
|
||||
if (updatedBans.Any() || needsRebuild)
|
||||
{
|
||||
RebuildIndexes();
|
||||
}
|
||||
@@ -436,32 +486,41 @@ internal class CacheManager: IDisposable
|
||||
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (record != null)
|
||||
{
|
||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||
(!record.PlayerSteamId.HasValue))
|
||||
// Double-check the ban is still active in cache (handle race conditions)
|
||||
if (_banCache.TryGetValue(record.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||
{
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||
(!record.PlayerSteamId.HasValue))
|
||||
{
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress) ||
|
||||
!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) ||
|
||||
_cachedIgnoredIps.Contains(ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false;
|
||||
|
||||
|
||||
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (record == null) return false;
|
||||
|
||||
// Double-check the ban is still active in cache (handle race conditions)
|
||||
if (!_banCache.TryGetValue(record.Id, out var cachedBanIp) || cachedBanIp.StatusEnum != BanStatus.ACTIVE)
|
||||
return false;
|
||||
|
||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||
(!record.PlayerSteamId.HasValue && steamId.HasValue))
|
||||
{
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -547,53 +606,54 @@ internal class CacheManager: IDisposable
|
||||
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp))
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
|
||||
return true;
|
||||
// Double-check the ban is still active in cache (handle race conditions)
|
||||
if (_banCache.TryGetValue(activeBan.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||
{
|
||||
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||
return false;
|
||||
|
||||
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
|
||||
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
|
||||
return false;
|
||||
|
||||
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
||||
if (_cachedIgnoredIps.Contains(ipUInt))
|
||||
return false;
|
||||
|
||||
if (!_ipIndex.TryGetValue(ipUInt, out var ipBanRecords))
|
||||
return false;
|
||||
|
||||
var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (ipBan == null)
|
||||
return false;
|
||||
|
||||
if (!_banCache.TryGetValue(ipBan.Id, out var cachedIpBan) || cachedIpBan.StatusEnum != BanStatus.ACTIVE)
|
||||
return false;
|
||||
|
||||
var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans;
|
||||
if (expireOldIpBans > 0)
|
||||
{
|
||||
var cutoff = Time.ActualDateTime().AddDays(-expireOldIpBans);
|
||||
if (ipBan.Created < cutoff)
|
||||
return false;
|
||||
}
|
||||
|
||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
|
||||
{
|
||||
if (!_cachedIgnoredIps.Contains(ipAsUint))
|
||||
{
|
||||
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty(ipBan.PlayerName))
|
||||
ipBan.PlayerName = playerName;
|
||||
|
||||
foreach (var ipRecord in ipData)
|
||||
{
|
||||
if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
|
||||
continue;
|
||||
ipBan.PlayerSteamId ??= steamId;
|
||||
|
||||
if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords))
|
||||
continue;
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
|
||||
var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan == null)
|
||||
continue;
|
||||
|
||||
if (string.IsNullOrEmpty(activeBan.PlayerName))
|
||||
activeBan.PlayerName = unknownName;
|
||||
|
||||
activeBan.PlayerSteamId ??= steamId;
|
||||
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
internal class PlayerManager
|
||||
{
|
||||
private readonly SemaphoreSlim _loadPlayerSemaphore = new(5);
|
||||
private readonly SemaphoreSlim _loadPlayerSemaphore = new(10);
|
||||
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
|
||||
|
||||
/// <summary>
|
||||
@@ -51,7 +51,6 @@ internal class PlayerManager
|
||||
try
|
||||
{
|
||||
await _loadPlayerSemaphore.WaitAsync();
|
||||
|
||||
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
||||
{
|
||||
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
@@ -81,13 +80,7 @@ internal class PlayerManager
|
||||
{
|
||||
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
||||
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
||||
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
if (!CS2_SimpleAdmin.CachedPlayers.Contains(player))
|
||||
CS2_SimpleAdmin.CachedPlayers.Add(player);
|
||||
});
|
||||
|
||||
|
||||
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null &&
|
||||
CS2_SimpleAdmin.PlayersInfo[steamId] != null)
|
||||
{
|
||||
@@ -99,14 +92,8 @@ internal class PlayerManager
|
||||
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
|
||||
var ipUint = IpHelper.IpToUint(ipAddress);
|
||||
|
||||
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE pattern
|
||||
const string upsertQuery = """
|
||||
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
// Use database-specific UPSERT query (handles MySQL vs SQLite syntax differences)
|
||||
var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery();
|
||||
|
||||
await connection.ExecuteAsync(upsertQuery, new
|
||||
{
|
||||
@@ -260,6 +247,7 @@ internal class PlayerManager
|
||||
_loadPlayerSemaphore.Release();
|
||||
}
|
||||
});
|
||||
|
||||
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
|
||||
{
|
||||
player.Rename(name);
|
||||
@@ -293,9 +281,6 @@ internal class PlayerManager
|
||||
|
||||
// Optimization: Get players once and avoid allocating anonymous types
|
||||
var validPlayers = Helper.GetValidPlayers();
|
||||
if (validPlayers.Count == 0)
|
||||
return;
|
||||
|
||||
// Use ValueTuple instead of anonymous type - better performance and less allocations
|
||||
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
||||
foreach (var p in validPlayers)
|
||||
@@ -370,7 +355,12 @@ internal class PlayerManager
|
||||
foreach (var player in bannedPlayers)
|
||||
{
|
||||
if (!player.UserId.HasValue) continue;
|
||||
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
if (Helper.GetPlayerFromSteamid64(player.SteamID) != null)
|
||||
Helper.KickPlayer((int)player.UserId,
|
||||
NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class ServerManager
|
||||
/// </summary>
|
||||
public void LoadServerData()
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddTimer(2.0f, () =>
|
||||
CS2_SimpleAdmin.Instance.AddTimer(1.0f, () =>
|
||||
{
|
||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
|
||||
@@ -103,14 +103,12 @@ public class ServerManager
|
||||
CS2_SimpleAdmin.ServerId = serverId;
|
||||
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
|
||||
|
||||
if (CS2_SimpleAdmin.ServerId != null)
|
||||
{
|
||||
await Server.NextWorldUpdateAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
|
||||
}
|
||||
CS2_SimpleAdmin.ServerLoaded = true;
|
||||
|
||||
CS2_SimpleAdmin.ServerLoaded = true;
|
||||
if (CS2_SimpleAdmin.Instance.CacheManager != null)
|
||||
{
|
||||
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
||||
playerName = player.Name,
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
muteReason = reason,
|
||||
warnReason = reason,
|
||||
duration = time,
|
||||
ends = futureTime,
|
||||
created = now,
|
||||
@@ -42,7 +42,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
||||
|
||||
return warnId;
|
||||
}
|
||||
catch
|
||||
catch(Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
||||
playerSteamid = playerSteamId,
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
muteReason = reason,
|
||||
warnReason = reason,
|
||||
duration = time,
|
||||
ends = futureTime,
|
||||
created = now,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
@@ -12,32 +13,33 @@ public abstract class BasicMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes all menus in the system by registering them with the MenuManager.
|
||||
/// Register with translation keys instead of static names - translation happens per-player.
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
var manager = MenuManager.Instance;
|
||||
|
||||
// Players category menus
|
||||
manager.RegisterMenu("players", "slap", "Slap Player", CreateSlapMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "slay", "Slay Player", CreateSlayMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "warn", "Warn Player", CreateWarnMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
|
||||
manager.RegisterMenu("players", "gag", "Gag Player", CreateGagMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "mute", "Mute Player", CreateMuteMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "silence", "Silence Player", CreateSilenceMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "team", "Force Team", CreateForceTeamMenu, "@css/kick");
|
||||
// Players category menus - using translation keys
|
||||
manager.RegisterMenu("players", "slap", "sa_slap", CreateSlapMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "slay", "sa_slay", CreateSlayMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "kick", "sa_kick", CreateKickMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "warn", "sa_warn", CreateWarnMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "ban", "sa_ban", CreateBanMenu, "@css/ban");
|
||||
manager.RegisterMenu("players", "gag", "sa_gag", CreateGagMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "mute", "sa_mute", CreateMuteMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "silence", "sa_silence", CreateSilenceMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "team", "sa_team_force", CreateForceTeamMenu, "@css/kick");
|
||||
|
||||
// Server category menus
|
||||
manager.RegisterMenu("server", "plugins", "Manage Plugins", CreatePluginsMenu, "@css/root");
|
||||
manager.RegisterMenu("server", "changemap", "Change Map", CreateChangeMapMenu, "@css/changemap");
|
||||
manager.RegisterMenu("server", "restart", "Restart Game", CreateRestartGameMenu, "@css/generic");
|
||||
manager.RegisterMenu("server", "custom", "Custom Commands", CreateCustomCommandsMenu, "@css/generic");
|
||||
// Server category menus - using translation keys
|
||||
manager.RegisterMenu("server", "plugins", "sa_menu_pluginsmanager_title", CreatePluginsMenu, "@css/root");
|
||||
manager.RegisterMenu("server", "changemap", "sa_changemap", CreateChangeMapMenu, "@css/changemap");
|
||||
manager.RegisterMenu("server", "restart", "sa_restart_game", CreateRestartGameMenu, "@css/generic");
|
||||
manager.RegisterMenu("server", "custom", "sa_menu_custom_commands", CreateCustomCommandsMenu, "@css/generic");
|
||||
|
||||
// Admin category menus
|
||||
manager.RegisterMenu("admin", "add", "Add Admin", CreateAddAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "remove", "Remove Admin", CreateRemoveAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "reload", "Reload Admins", CreateReloadAdminsMenu, "@css/root");
|
||||
// Admin category menus - using translation keys
|
||||
manager.RegisterMenu("admin", "add", "sa_admin_add", CreateAddAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "remove", "sa_admin_remove", CreateRemoveAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "reload", "sa_admin_reload", CreateReloadAdminsMenu, "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,14 +51,15 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateSlapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slapMenu = new MenuBuilder(localizer?["sa_slap"] ?? "Slap Player");
|
||||
var slapMenu = new MenuBuilder("sa_slap", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, player));
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, capturedPlayer));
|
||||
}
|
||||
|
||||
return slapMenu.WithBackButton();
|
||||
@@ -70,18 +73,25 @@ public abstract class BasicMenu
|
||||
/// <returns>A MenuBuilder instance for the slap damage menu.</returns>
|
||||
private static MenuBuilder CreateSlapDamageMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var slapDamageMenu = new MenuBuilder($"Slap: {target.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
string localizedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedTitle = $"{localizer?["sa_slap"] ?? "Slap"}: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var slapDamageMenu = new MenuBuilder(localizedTitle);
|
||||
var damages = new[] { 0, 1, 5, 10, 50, 100 };
|
||||
|
||||
foreach (var damage in damages)
|
||||
{
|
||||
slapDamageMenu.AddOption($"{damage} HP", _ =>
|
||||
slapDamageMenu.AddOption($"{damage} HP", currentAdmin =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Slap(admin, target, damage);
|
||||
// Keep menu open for consecutive slaps
|
||||
CreateSlapDamageMenu(admin, target).OpenMenu(admin);
|
||||
CS2_SimpleAdmin.Slap(currentAdmin, target, damage);
|
||||
// Reopen the same menu (not create new one) to keep back button working
|
||||
slapDamageMenu.OpenMenu(currentAdmin);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -97,18 +107,19 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateSlayMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slayMenu = new MenuBuilder(localizer?["sa_slay"] ?? "Slay Player");
|
||||
var slayMenu = new MenuBuilder("sa_slay", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
slayMenu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Slay(admin, player);
|
||||
CS2_SimpleAdmin.Slay(admin, capturedPlayer);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -124,19 +135,20 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateKickMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var kickMenu = new MenuBuilder(localizer?["sa_kick"] ?? "Kick Player");
|
||||
var kickMenu = new MenuBuilder("sa_kick", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, capturedPlayer, "Kick", PenaltyType.Kick,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
|
||||
CS2_SimpleAdmin.Instance.Kick(admin, capturedPlayer, reason, admin.PlayerName);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -152,20 +164,21 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateWarnMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var warnMenu = new MenuBuilder(localizer?["sa_warn"] ?? "Warn Player");
|
||||
var warnMenu = new MenuBuilder("sa_warn", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Warn",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Warn", PenaltyType.Warn,
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Warn",
|
||||
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Warn", PenaltyType.Warn,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
|
||||
CS2_SimpleAdmin.Instance.Warn(admin, capturedPlayer, duration, reason, admin.PlayerName);
|
||||
}
|
||||
})));
|
||||
}
|
||||
@@ -181,20 +194,21 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateBanMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var banMenu = new MenuBuilder(localizer?["sa_ban"] ?? "Ban Player");
|
||||
var banMenu = new MenuBuilder("sa_ban", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Ban",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Ban", PenaltyType.Ban,
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Ban",
|
||||
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Ban", PenaltyType.Ban,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
|
||||
CS2_SimpleAdmin.Instance.Ban(admin, capturedPlayer, duration, reason, admin.PlayerName);
|
||||
}
|
||||
})));
|
||||
}
|
||||
@@ -210,20 +224,21 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateGagMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var gagMenu = new MenuBuilder(localizer?["sa_gag"] ?? "Gag Player");
|
||||
var gagMenu = new MenuBuilder("sa_gag", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Gag",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Gag", PenaltyType.Gag,
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Gag",
|
||||
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Gag", PenaltyType.Gag,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
|
||||
CS2_SimpleAdmin.Instance.Gag(admin, capturedPlayer, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
@@ -239,20 +254,21 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateMuteMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var muteMenu = new MenuBuilder(localizer?["sa_mute"] ?? "Mute Player");
|
||||
var muteMenu = new MenuBuilder("sa_mute", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Mute",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Mute", PenaltyType.Mute,
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Mute",
|
||||
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Mute", PenaltyType.Mute,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
|
||||
CS2_SimpleAdmin.Instance.Mute(admin, capturedPlayer, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
@@ -268,20 +284,21 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateSilenceMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var silenceMenu = new MenuBuilder(localizer?["sa_silence"] ?? "Silence Player");
|
||||
var silenceMenu = new MenuBuilder("sa_silence", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Silence",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Silence", PenaltyType.Silence,
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Silence",
|
||||
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Silence", PenaltyType.Silence,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
|
||||
CS2_SimpleAdmin.Instance.Silence(admin, capturedPlayer, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
@@ -297,14 +314,15 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateForceTeamMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamMenu = new MenuBuilder(localizer?["sa_team_force"] ?? "Force Team");
|
||||
var teamMenu = new MenuBuilder("sa_team_force", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, player));
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, capturedPlayer));
|
||||
}
|
||||
|
||||
return teamMenu.WithBackButton();
|
||||
@@ -319,14 +337,32 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateTeamSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamSelectionMenu = new MenuBuilder($"Force Team: {target.PlayerName}");
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedTitle = $"{localizer?["sa_team_force"] ?? "Force Team"}: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var teamSelectionMenu = new MenuBuilder(localizedTitle);
|
||||
|
||||
// Localize team options for admin's language
|
||||
string ctName, tName, swapName, specName;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
ctName = localizer?["sa_team_ct"] ?? "CT";
|
||||
tName = localizer?["sa_team_t"] ?? "T";
|
||||
swapName = localizer?["sa_team_swap"] ?? "Swap";
|
||||
specName = localizer?["sa_team_spec"] ?? "Spec";
|
||||
}
|
||||
|
||||
var teams = new[]
|
||||
{
|
||||
(localizer?["sa_team_ct"] ?? "CT", "ct", CsTeam.CounterTerrorist),
|
||||
(localizer?["sa_team_t"] ?? "T", "t", CsTeam.Terrorist),
|
||||
(localizer?["sa_team_swap"] ?? "Swap", "swap", CsTeam.Spectator),
|
||||
(localizer?["sa_team_spec"] ?? "Spec", "spec", CsTeam.Spectator)
|
||||
(ctName, "ct", CsTeam.CounterTerrorist),
|
||||
(tName, "t", CsTeam.Terrorist),
|
||||
(swapName, "swap", CsTeam.Spectator),
|
||||
(specName, "spec", CsTeam.Spectator)
|
||||
};
|
||||
|
||||
foreach (var (name, teamName, teamNum) in teams)
|
||||
@@ -351,7 +387,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreatePluginsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var pluginsMenu = new MenuBuilder(localizer?["sa_menu_pluginsmanager_title"] ?? "Manage Plugins");
|
||||
var pluginsMenu = new MenuBuilder("sa_menu_pluginsmanager_title", admin, localizer);
|
||||
|
||||
pluginsMenu.AddOption("Open Plugins Manager", _ =>
|
||||
{
|
||||
@@ -369,7 +405,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateChangeMapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mapMenu = new MenuBuilder(localizer?["sa_changemap"] ?? "Change Map");
|
||||
var mapMenu = new MenuBuilder("sa_changemap", admin, localizer);
|
||||
|
||||
// Add default maps
|
||||
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
|
||||
@@ -402,7 +438,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateRestartGameMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var restartMenu = new MenuBuilder(localizer?["sa_restart_game"] ?? "Restart Game");
|
||||
var restartMenu = new MenuBuilder("sa_restart_game", admin, localizer);
|
||||
|
||||
restartMenu.AddOption("Restart Round", _ =>
|
||||
{
|
||||
@@ -420,7 +456,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateCustomCommandsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var customMenu = new MenuBuilder(localizer?["sa_menu_custom_commands"] ?? "Custom Commands");
|
||||
var customMenu = new MenuBuilder("sa_menu_custom_commands", admin, localizer);
|
||||
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
|
||||
@@ -455,14 +491,15 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateAddAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var addAdminMenu = new MenuBuilder(localizer?["sa_admin_add"] ?? "Add Admin");
|
||||
var addAdminMenu = new MenuBuilder("sa_admin_add", admin, localizer);
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, player));
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, capturedPlayer));
|
||||
}
|
||||
|
||||
return addAdminMenu.WithBackButton();
|
||||
@@ -476,7 +513,16 @@ public abstract class BasicMenu
|
||||
/// <returns>A MenuBuilder instance for the admin flags menu.</returns>
|
||||
private static MenuBuilder CreateAdminFlagsMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var flagsMenu = new MenuBuilder($"Add Admin: {target.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedTitle = $"{localizer?["sa_admin_add"] ?? "Add Admin"}: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var flagsMenu = new MenuBuilder(localizedTitle);
|
||||
|
||||
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
|
||||
{
|
||||
@@ -501,21 +547,22 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateRemoveAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var removeAdminMenu = new MenuBuilder(localizer?["sa_admin_remove"] ?? "Remove Admin");
|
||||
var removeAdminMenu = new MenuBuilder("sa_admin_remove", admin, localizer);
|
||||
|
||||
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
||||
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
||||
p != admin &&
|
||||
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
||||
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
||||
p != admin &&
|
||||
admin.CanTarget(p));
|
||||
|
||||
|
||||
foreach (var player in adminPlayers)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||
removeAdminMenu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
if (capturedPlayer.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
|
||||
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, capturedPlayer.SteamID.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -531,7 +578,7 @@ public abstract class BasicMenu
|
||||
private static MenuBuilder CreateReloadAdminsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var reloadMenu = new MenuBuilder(localizer?["sa_admin_reload"] ?? "Reload Admins");
|
||||
var reloadMenu = new MenuBuilder("sa_admin_reload", admin, localizer);
|
||||
|
||||
reloadMenu.AddOption("Reload Admins", _ =>
|
||||
{
|
||||
@@ -546,20 +593,40 @@ public abstract class BasicMenu
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting duration.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
|
||||
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
||||
/// <param name="onSelectAction">Callback function that returns the next menu when duration is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
||||
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||
Func<CCSPlayerController, CCSPlayerController, int, MenuBuilder> onSelectAction)
|
||||
{
|
||||
var durationMenu = new MenuBuilder($"{actionName} Duration: {player.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Convert action name to translation key (e.g., "Ban" -> "sa_ban")
|
||||
var actionKey = actionName.ToLower() switch
|
||||
{
|
||||
"kick" => "sa_kick",
|
||||
"ban" => "sa_ban",
|
||||
"warn" => "sa_warn",
|
||||
"gag" => "sa_gag",
|
||||
"mute" => "sa_mute",
|
||||
"silence" => "sa_silence",
|
||||
_ => actionName
|
||||
};
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedAction, durationText;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedAction = localizer?[actionKey] ?? actionName;
|
||||
durationText = localizer?["sa_duration"] ?? "Duration";
|
||||
}
|
||||
|
||||
var durationMenu = new MenuBuilder($"{localizedAction} {durationText}: {player.PlayerName}");
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
durationMenu.AddOption(durationItem.Name, _ =>
|
||||
{
|
||||
onSelectAction(admin, player, durationItem.Duration);
|
||||
});
|
||||
var capturedDuration = durationItem.Duration; // Capture to avoid closure issue
|
||||
durationMenu.AddSubMenu(durationItem.Name, () => onSelectAction(admin, player, capturedDuration));
|
||||
}
|
||||
|
||||
return durationMenu.WithBackButton();
|
||||
@@ -570,14 +637,36 @@ public abstract class BasicMenu
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting reason.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
||||
/// <param name="penaltyType">The type of penalty to determine which reason list to use.</param>
|
||||
/// <param name="onSelectAction">Callback action executed when reason is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the reason menu.</returns>
|
||||
private static MenuBuilder CreateReasonMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
PenaltyType penaltyType, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
|
||||
{
|
||||
var reasonMenu = new MenuBuilder($"{actionName} Reason: {player.PlayerName}");
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Convert action name to translation key
|
||||
var actionKey = actionName.ToLower() switch
|
||||
{
|
||||
"kick" => "sa_kick",
|
||||
"ban" => "sa_ban",
|
||||
"warn" => "sa_warn",
|
||||
"gag" => "sa_gag",
|
||||
"mute" => "sa_mute",
|
||||
"silence" => "sa_silence",
|
||||
_ => actionName
|
||||
};
|
||||
|
||||
// Localize title for admin's language
|
||||
string localizedAction, reasonText;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
localizedAction = localizer?[actionKey] ?? actionName;
|
||||
reasonText = localizer?["sa_reason"] ?? "Reason";
|
||||
}
|
||||
|
||||
var reasonMenu = new MenuBuilder($"{localizedAction} {reasonText}: {player.PlayerName}");
|
||||
|
||||
var reasons = penaltyType switch
|
||||
{
|
||||
|
||||
@@ -8,12 +8,28 @@ public static class DurationMenu
|
||||
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||
{
|
||||
var menu = AdminMenu.CreateMenu(menuName);
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
if (menu == null)
|
||||
return;
|
||||
|
||||
var durations = CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations;
|
||||
|
||||
// Capture admin and player to avoid closure issues
|
||||
var capturedAdmin = admin;
|
||||
var capturedPlayer = player;
|
||||
var capturedAction = onSelectAction;
|
||||
|
||||
foreach (var durationItem in durations)
|
||||
{
|
||||
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
|
||||
var duration = durationItem.Duration; // Capture in local variable
|
||||
var name = durationItem.Name;
|
||||
|
||||
menu.AddMenuOption(name, (controller, option) =>
|
||||
{
|
||||
capturedAction(capturedAdmin, capturedPlayer, duration);
|
||||
});
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
|
||||
|
||||
@@ -49,25 +49,27 @@ public static class ManagePlayersMenu
|
||||
if (AdminManager.CommandIsOverriden("css_warn")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, WarnMenu))));
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_warn"] ?? "Warn"}: {p.PlayerName}", p, WarnMenu))));
|
||||
|
||||
if (hasBan)
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player, BanMenu))));
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () =>
|
||||
PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (a, p) =>
|
||||
DurationMenu.OpenMenu(a, $"{localizer?["sa_ban"] ?? "Ban"}: {p.PlayerName}", p, BanMenu))));
|
||||
|
||||
if (hasChat)
|
||||
{
|
||||
if (AdminManager.CommandIsOverriden("css_gag")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, GagMenu))));
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_gag"] ?? "Gag"}: {p.PlayerName}", p, GagMenu))));
|
||||
if (AdminManager.CommandIsOverriden("css_mute")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player, MuteMenu))));
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_mute"] ?? "Mute"}: {p.PlayerName}", p, MuteMenu))));
|
||||
if (AdminManager.CommandIsOverriden("css_silence")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, SilenceMenu))));
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_silence"] ?? "Silence"}: {p.PlayerName}", p, SilenceMenu))));
|
||||
}
|
||||
|
||||
if (AdminManager.CommandIsOverriden("css_team")
|
||||
@@ -159,22 +161,9 @@ public static class ManagePlayersMenu
|
||||
{
|
||||
if (player is { IsValid: true })
|
||||
Ban(admin, player, duration, reason);
|
||||
|
||||
|
||||
CS2_SimpleAdmin.MenuApi?.CloseMenu(admin);
|
||||
});
|
||||
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player?.PlayerName}");
|
||||
//
|
||||
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons)
|
||||
// {
|
||||
// menu?.AddMenuOption(option, (_, _) =>
|
||||
// {
|
||||
// if (player is { IsValid: true })
|
||||
// Ban(admin, player, duration, option);
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void Ban(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
|
||||
|
||||
@@ -1,28 +1,89 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
public class MenuBuilder(string title)
|
||||
public class MenuBuilder
|
||||
{
|
||||
private readonly string _title;
|
||||
private readonly CCSPlayerController? _player;
|
||||
private readonly IStringLocalizer? _localizer;
|
||||
private readonly List<MenuOption> _options = [];
|
||||
private MenuBuilder? _parentMenu;
|
||||
private Action<CCSPlayerController>? _backAction;
|
||||
private Action<CCSPlayerController>? _resetAction;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for player-localized menu with translation key
|
||||
/// </summary>
|
||||
public MenuBuilder(string titleKey, CCSPlayerController player, IStringLocalizer? localizer = null)
|
||||
{
|
||||
_title = titleKey;
|
||||
_player = player;
|
||||
_localizer = localizer ?? CS2_SimpleAdmin._localizer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for static title (backward compatibility)
|
||||
/// </summary>
|
||||
public MenuBuilder(string title)
|
||||
{
|
||||
_title = title;
|
||||
_player = null;
|
||||
_localizer = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localized title for the player
|
||||
/// </summary>
|
||||
private string GetLocalizedTitle()
|
||||
{
|
||||
if (_player != null && _localizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(_player.GetLanguage()))
|
||||
{
|
||||
return _localizer[_title];
|
||||
}
|
||||
}
|
||||
return _title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option with an action.
|
||||
/// </summary>
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)
|
||||
/// <param name="name">Display name or translation key</param>
|
||||
/// <param name="action">Action to perform when selected</param>
|
||||
/// <param name="disabled">Whether the option is disabled</param>
|
||||
/// <param name="permission">Required permission</param>
|
||||
/// <param name="isTranslationKey">If true, name is a translation key to be localized</param>
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null, bool isTranslationKey = false)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = action,
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
Permission = permission,
|
||||
IsTranslationKey = isTranslationKey
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localized name for a menu option
|
||||
/// </summary>
|
||||
private string GetLocalizedOptionName(MenuOption option)
|
||||
{
|
||||
if (option.IsTranslationKey && _player != null && _localizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(_player.GetLanguage()))
|
||||
{
|
||||
return _localizer[option.Name];
|
||||
}
|
||||
}
|
||||
return option.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option that opens a submenu.
|
||||
/// </summary>
|
||||
@@ -99,8 +160,11 @@ public class MenuBuilder(string title)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
|
||||
// Get localized title
|
||||
var localizedTitle = GetLocalizedTitle();
|
||||
|
||||
// Use MenuManager dependency
|
||||
var menu = Helper.CreateMenu(title, _backAction);
|
||||
var menu = Helper.CreateMenu(localizedTitle, _backAction);
|
||||
if (menu == null) return;
|
||||
|
||||
foreach (var option in _options)
|
||||
@@ -115,7 +179,10 @@ public class MenuBuilder(string title)
|
||||
}
|
||||
}
|
||||
|
||||
menu.AddMenuOption(option.Name, (menuPlayer, menuOption) =>
|
||||
// Get localized option name
|
||||
var localizedName = GetLocalizedOptionName(option);
|
||||
|
||||
menu.AddMenuOption(localizedName, (menuPlayer, menuOption) =>
|
||||
{
|
||||
option.Action?.Invoke(menuPlayer);
|
||||
}, option.Disabled);
|
||||
@@ -166,5 +233,6 @@ public class MenuOption
|
||||
public Action<CCSPlayerController>? Action { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string? Permission { get; set; }
|
||||
public bool IsTranslationKey { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
@@ -38,6 +39,26 @@ public class MenuManager
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new menu category with per-player localization support for modules.
|
||||
/// 🆕 NEW: Enables modules to provide localized category names based on each player's css_lang!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">Unique identifier for the category.</param>
|
||||
/// <param name="categoryNameKey">Translation key from module's lang files.</param>
|
||||
/// <param name="permission">Required permission to access this category.</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
public void RegisterCategory(string categoryId, string categoryNameKey, string permission, Microsoft.Extensions.Localization.IStringLocalizer moduleLocalizer)
|
||||
{
|
||||
_menuCategories[categoryId] = new MenuCategory
|
||||
{
|
||||
Id = categoryId,
|
||||
Name = categoryNameKey, // Store the key, not translated text
|
||||
Permission = permission,
|
||||
MenuFactories = new Dictionary<string, Func<CCSPlayerController, MenuBuilder>>(),
|
||||
ModuleLocalizer = moduleLocalizer // Store module's localizer
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu within a category (API for other plugins).
|
||||
/// </summary>
|
||||
@@ -46,7 +67,8 @@ public class MenuManager
|
||||
/// <param name="menuName">Display name of the menu.</param>
|
||||
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission = null)
|
||||
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
if (!_menuCategories.ContainsKey(categoryId))
|
||||
{
|
||||
@@ -59,6 +81,41 @@ public class MenuManager
|
||||
{
|
||||
_menuCategories[categoryId].MenuPermissions[menuId] = permission;
|
||||
}
|
||||
if (commandName != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuCommandNames[menuId] = commandName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu with per-player localization support for modules.
|
||||
/// 🆕 NEW: Enables modules to provide localized menu names based on each player's css_lang!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuNameKey">Translation key from module's lang files.</param>
|
||||
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
/// <param name="commandName">Command name for permission override checking (optional).</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuNameKey, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission, string? commandName, Microsoft.Extensions.Localization.IStringLocalizer moduleLocalizer)
|
||||
{
|
||||
if (!_menuCategories.ContainsKey(categoryId))
|
||||
{
|
||||
RegisterCategory(categoryId, categoryId); // Auto-create category if it doesn't exist
|
||||
}
|
||||
|
||||
_menuCategories[categoryId].MenuFactories[menuId] = menuFactory;
|
||||
_menuCategories[categoryId].MenuNames[menuId] = menuNameKey; // Store the key
|
||||
_menuCategories[categoryId].MenuLocalizers[menuId] = moduleLocalizer; // Store localizer
|
||||
if (permission != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuPermissions[menuId] = permission;
|
||||
}
|
||||
if (commandName != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuCommandNames[menuId] = commandName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,6 +129,7 @@ public class MenuManager
|
||||
category.MenuFactories.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuNames.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuPermissions.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuCommandNames.Remove(menuId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,7 +140,7 @@ public class MenuManager
|
||||
public MenuBuilder CreateMainMenu(CCSPlayerController player)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mainMenu = new MenuBuilder(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
var mainMenu = new MenuBuilder("sa_title", player, localizer);
|
||||
|
||||
foreach (var category in _menuCategories.Values)
|
||||
{
|
||||
@@ -92,8 +150,23 @@ public class MenuManager
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, category.Permission))
|
||||
continue;
|
||||
|
||||
// Get localized category name for this player
|
||||
// If category has a module localizer, use it; otherwise use main plugin localizer
|
||||
string localizedCategoryName;
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
if (category.ModuleLocalizer != null)
|
||||
{
|
||||
localizedCategoryName = category.ModuleLocalizer[category.Name] ?? category.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
localizedCategoryName = localizer?[category.Name] ?? category.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass player to CreateCategoryMenu
|
||||
mainMenu.AddSubMenu(category.Name, () => CreateCategoryMenu(category, player),
|
||||
mainMenu.AddSubMenu(localizedCategoryName, () => CreateCategoryMenu(category, player),
|
||||
permission: category.Permission);
|
||||
}
|
||||
|
||||
@@ -108,7 +181,24 @@ public class MenuManager
|
||||
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
||||
private MenuBuilder CreateCategoryMenu(MenuCategory category, CCSPlayerController player)
|
||||
{
|
||||
var categoryMenu = new MenuBuilder(category.Name);
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
// Get localized category name for this player
|
||||
// If category has a module localizer, use it; otherwise use main plugin localizer
|
||||
string localizedCategoryName;
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
if (category.ModuleLocalizer != null)
|
||||
{
|
||||
localizedCategoryName = category.ModuleLocalizer[category.Name] ?? category.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
localizedCategoryName = localizer?[category.Name] ?? category.Name;
|
||||
}
|
||||
}
|
||||
|
||||
var categoryMenu = new MenuBuilder(localizedCategoryName);
|
||||
|
||||
foreach (var kvp in category.MenuFactories)
|
||||
{
|
||||
@@ -116,17 +206,67 @@ public class MenuManager
|
||||
var menuFactory = kvp.Value;
|
||||
var menuName = category.MenuNames.TryGetValue(menuId, out var name) ? name : menuId;
|
||||
var permission = category.MenuPermissions.TryGetValue(menuId, out var perm) ? perm : null;
|
||||
var commandName = category.MenuCommandNames.TryGetValue(menuId, out var cmd) ? cmd : null;
|
||||
|
||||
// Check permissions
|
||||
if (!string.IsNullOrEmpty(permission))
|
||||
// Check permissions with command override support
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
|
||||
// If commandName is provided, check for permission overrides
|
||||
if (!string.IsNullOrEmpty(commandName))
|
||||
{
|
||||
bool hasPermission;
|
||||
|
||||
// Check if command has overridden permissions
|
||||
if (AdminManager.CommandIsOverriden(commandName))
|
||||
{
|
||||
var overriddenPermission = AdminManager.GetPermissionOverrides(commandName);
|
||||
hasPermission = AdminManager.PlayerHasPermissions(steamId, overriddenPermission);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(permission))
|
||||
{
|
||||
// Use default permission if no override exists
|
||||
hasPermission = AdminManager.PlayerHasPermissions(steamId, permission);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No permission required
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
if (!hasPermission)
|
||||
continue;
|
||||
}
|
||||
// Fallback to standard permission check if no commandName provided
|
||||
else if (!string.IsNullOrEmpty(permission))
|
||||
{
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, permission))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get localized menu name for this player
|
||||
// If menu has its own localizer, use it; otherwise use category or main plugin localizer
|
||||
string localizedMenuName;
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
if (category.MenuLocalizers.TryGetValue(menuId, out var menuLocalizer))
|
||||
{
|
||||
// Menu has its own module localizer
|
||||
localizedMenuName = menuLocalizer[menuName] ?? menuName;
|
||||
}
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
// Use category's module localizer
|
||||
localizedMenuName = category.ModuleLocalizer[menuName] ?? menuName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use main plugin localizer
|
||||
localizedMenuName = localizer?[menuName] ?? menuName;
|
||||
}
|
||||
}
|
||||
|
||||
// Call the actual factory with player parameter
|
||||
categoryMenu.AddSubMenu(menuName, () => menuFactory(player), permission: permission);
|
||||
categoryMenu.AddSubMenu(localizedMenuName, () => menuFactory(player), permission: permission);
|
||||
}
|
||||
|
||||
return categoryMenu.WithBackButton();
|
||||
@@ -156,12 +296,12 @@ public class MenuManager
|
||||
/// </summary>
|
||||
public void InitializeDefaultCategories()
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
RegisterCategory("players", localizer?["sa_menu_players_manage"] ?? "Manage Players", "@css/generic");
|
||||
RegisterCategory("server", localizer?["sa_menu_server_manage"] ?? "Server Management", "@css/generic");
|
||||
// RegisterCategory("fun", localizer?["sa_menu_fun_commands"] ?? "Fun Commands", "@css/generic");
|
||||
RegisterCategory("admin", localizer?["sa_menu_admins_manage"] ?? "Admin Management", "@css/root");
|
||||
// Register categories with translation keys instead of translated names
|
||||
// The actual translation will happen per-player in CreateMainMenu/CreateCategoryMenu
|
||||
RegisterCategory("players", "sa_menu_players_manage", "@css/generic");
|
||||
RegisterCategory("server", "sa_menu_server_manage", "@css/generic");
|
||||
// RegisterCategory("fun", "sa_menu_fun_commands", "@css/generic");
|
||||
RegisterCategory("admin", "sa_menu_admins_manage", "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -187,4 +327,18 @@ public class MenuCategory
|
||||
public Dictionary<string, Func<CCSPlayerController, MenuBuilder>> MenuFactories { get; set; } = [];
|
||||
public Dictionary<string, string> MenuNames { get; set; } = [];
|
||||
public Dictionary<string, string> MenuPermissions { get; set; } = [];
|
||||
public Dictionary<string, string> MenuCommandNames { get; set; } = [];
|
||||
|
||||
// 🆕 NEW: Support for per-player localization in modules
|
||||
/// <summary>
|
||||
/// Optional IStringLocalizer from external module for per-player translation of category name.
|
||||
/// If null, Name is used as-is (for CS2-SimpleAdmin's built-in categories with translation keys).
|
||||
/// </summary>
|
||||
public Microsoft.Extensions.Localization.IStringLocalizer? ModuleLocalizer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores IStringLocalizer for each menu that uses module localization.
|
||||
/// Key: menuId, Value: module's localizer
|
||||
/// </summary>
|
||||
public Dictionary<string, Microsoft.Extensions.Localization.IStringLocalizer> MenuLocalizers { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -41,11 +41,12 @@ public static class PlayersMenu
|
||||
continue;
|
||||
|
||||
var enabled = admin.CanTarget(player);
|
||||
var capturedPlayer = player; // Capture in local variable to avoid closure issues
|
||||
|
||||
if (optionName != null)
|
||||
menu?.AddMenuOption(optionName, (_, _) =>
|
||||
menu?.AddMenuOption(optionName, (controller, option) =>
|
||||
{
|
||||
if (player != null) onSelectAction.Invoke(admin, player);
|
||||
if (capturedPlayer != null) onSelectAction.Invoke(admin, capturedPlayer);
|
||||
},
|
||||
!enabled);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ public record BanRecord
|
||||
|
||||
[Column("player_ip")]
|
||||
public string? PlayerIp { get; set; }
|
||||
|
||||
|
||||
[Column("created")]
|
||||
public DateTime Created { get; init; }
|
||||
|
||||
[Column("status")]
|
||||
public required string Status { get; init; }
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.8-beta-1
|
||||
1.7.8-beta-10b1
|
||||
@@ -40,7 +40,6 @@ public partial class CS2_SimpleAdmin
|
||||
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
|
||||
|
||||
// Player Management
|
||||
private static readonly HashSet<int> GodPlayers = [];
|
||||
internal static readonly HashSet<int> SilentPlayers = [];
|
||||
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
|
||||
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"sa_team_spec": "Spec",
|
||||
|
||||
"sa_slap": "Slap",
|
||||
"sa_slay": "slay",
|
||||
"sa_slay": "Slay",
|
||||
"sa_kick": "Kick",
|
||||
"sa_ban": "Ban",
|
||||
"sa_gag": "Gag",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.361" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -140,10 +140,51 @@ public interface ICS2_SimpleAdminApi
|
||||
/// </summary>
|
||||
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu category with per-player localization support for modules.
|
||||
/// 🆕 NEW: Supports per-player localization using module's IStringLocalizer!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category ID (unique identifier).</param>
|
||||
/// <param name="categoryNameKey">Translation key from module's lang files.</param>
|
||||
/// <param name="permission">Required permission to access this category.</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu in a category.
|
||||
/// </summary>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null);
|
||||
/// <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 creates the menu for a player.</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, 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>
|
||||
/// Registers a menu with per-player localization support for modules.
|
||||
/// 🆕 NEW: Supports per-player localization using module's IStringLocalizer!
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuNameKey">Translation key from module's lang files.</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).</param>
|
||||
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuNameKey, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
@@ -155,11 +196,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,564 @@ 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;
|
||||
private string _dbConnectionString = string.Empty;
|
||||
|
||||
private static ICS2_SimpleAdminApi? _sharedApi;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
/// <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
|
||||
//
|
||||
// ⚠️ LOCALIZATION OPTIONS:
|
||||
//
|
||||
// OPTION A - No translations (hard-coded text):
|
||||
_sharedApi.RegisterMenuCategory(
|
||||
"example", // Category ID (unique identifier)
|
||||
"Example Features", // Display name (hard-coded, same for all players)
|
||||
"@css/generic" // Required permission
|
||||
);
|
||||
//
|
||||
// OPTION B - With per-player translations (🆕 NEW!):
|
||||
// If your module has lang/ folder with translations, use this pattern:
|
||||
// _sharedApi.RegisterMenuCategory(
|
||||
// "example", // Category ID
|
||||
// "example_category_name", // Translation key
|
||||
// "@css/generic", // Permission
|
||||
// Localizer! // Module's localizer
|
||||
// );
|
||||
// This will translate the category name per-player based on their css_lang setting!
|
||||
|
||||
// STEP 2: Register individual menu items in the category
|
||||
// 🆕 NEW: These use MenuContext API - factory receives (admin, context) parameters
|
||||
//
|
||||
// ⚠️ LOCALIZATION OPTIONS:
|
||||
//
|
||||
// OPTION A - No translations (hard-coded text):
|
||||
|
||||
// Example 1: Simple menu with options
|
||||
_sharedApi.RegisterMenu(
|
||||
"example", // Category ID
|
||||
"simple_action", // Menu ID (unique within category)
|
||||
"Simple Actions", // Display name (hard-coded)
|
||||
CreateSimpleActionMenu, // Factory method
|
||||
"@css/generic" // Required permission
|
||||
);
|
||||
|
||||
// Example 2: Player selection menu
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"player_selection",
|
||||
"Select Player", // Display name
|
||||
CreatePlayerSelectionMenu,
|
||||
"@css/kick" // Requires kick permission
|
||||
);
|
||||
|
||||
// Example 3: Nested menu (Player → Value)
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"nested_menu",
|
||||
"Give Credits", // Display name
|
||||
CreateGiveCreditsMenu,
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
// Example 4: Menu with permission override support
|
||||
_sharedApi.RegisterMenu(
|
||||
"example",
|
||||
"test_command",
|
||||
"Test Command", // Display name
|
||||
CreateTestCommandMenu,
|
||||
"@css/root", // Default permission
|
||||
"css_test" // Command name for override checking
|
||||
);
|
||||
|
||||
// OPTION B - With per-player translations (🆕 NEW!):
|
||||
// If your module has lang/ folder, use this pattern:
|
||||
// _sharedApi.RegisterMenu(
|
||||
// "example", // Category ID
|
||||
// "menu_id", // Menu ID
|
||||
// "menu_translation_key", // Translation key (NOT translated text!)
|
||||
// CreateYourMenu, // Factory method
|
||||
// "@css/generic", // Permission
|
||||
// "css_command", // Command name (optional)
|
||||
// Localizer! // Module's localizer
|
||||
// );
|
||||
// This will translate the menu name per-player based on their css_lang!
|
||||
// See FunCommands module for real example.
|
||||
|
||||
_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()}");
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_getMyInfo")]
|
||||
commandInfo.ReplyToCommand($"Server IP: {_sharedApi?.GetServerAddress()}");
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
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}");
|
||||
|
||||
try
|
||||
{
|
||||
var playerInfo = _sharedApi?.GetPlayerInfo(caller);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,62 +366,73 @@ public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Con
|
||||
|
||||
try
|
||||
{
|
||||
_sharedApi.RegisterMenuCategory("fun", Localizer?["fun_category_name"] ?? "Fun Commands", "@css/generic");
|
||||
// 🆕 NEW: Per-player localization support for modules!
|
||||
// - This module has its own lang/ folder with translations
|
||||
// - We pass translation KEYS and the module's Localizer to the API
|
||||
// - SimpleAdmin will translate menu names per-player based on their css_lang
|
||||
// - Each player sees menus in their own language!
|
||||
_sharedApi.RegisterMenuCategory("fun", "fun_category_name", "@css/generic", Localizer!);
|
||||
|
||||
// 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
|
||||
//
|
||||
// 🆕 NEW: All menus use translation keys and module's Localizer for per-player localization!
|
||||
|
||||
if (Config.GodCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "god",
|
||||
Localizer?["fun_menu_god"] ?? "God Mode",
|
||||
CreateGodModeMenu, "@css/cheats");
|
||||
"fun_menu_god",
|
||||
CreateGodModeMenu, "@css/cheats", "css_god", Localizer!);
|
||||
|
||||
if (Config.NoclipCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "noclip",
|
||||
Localizer?["fun_menu_noclip"] ?? "No Clip",
|
||||
CreateNoClipMenu, "@css/cheats");
|
||||
"fun_menu_noclip",
|
||||
CreateNoClipMenu, "@css/cheats", "css_noclip", Localizer!);
|
||||
|
||||
if (Config.RespawnCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "respawn",
|
||||
Localizer?["fun_menu_respawn"] ?? "Respawn",
|
||||
CreateRespawnMenu, "@css/cheats");
|
||||
"fun_menu_respawn",
|
||||
CreateRespawnMenu, "@css/cheats", "css_respawn", Localizer!);
|
||||
|
||||
if (Config.GiveCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "give",
|
||||
Localizer?["fun_menu_give"] ?? "Give Weapon",
|
||||
CreateGiveWeaponMenu, "@css/cheats");
|
||||
"fun_menu_give",
|
||||
CreateGiveWeaponMenu, "@css/cheats", "css_give", Localizer!);
|
||||
|
||||
if (Config.StripCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "strip",
|
||||
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
|
||||
CreateStripWeaponsMenu, "@css/slay");
|
||||
"fun_menu_strip",
|
||||
CreateStripWeaponsMenu, "@css/slay", "css_strip", Localizer!);
|
||||
|
||||
if (Config.FreezeCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "freeze",
|
||||
Localizer?["fun_menu_freeze"] ?? "Freeze",
|
||||
CreateFreezeMenu, "@css/slay");
|
||||
"fun_menu_freeze",
|
||||
CreateFreezeMenu, "@css/slay", "css_freeze", Localizer!);
|
||||
|
||||
if (Config.HpCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "hp",
|
||||
Localizer?["fun_menu_hp"] ?? "Set HP",
|
||||
CreateSetHpMenu, "@css/slay");
|
||||
"fun_menu_hp",
|
||||
CreateSetHpMenu, "@css/slay", "css_hp", Localizer!);
|
||||
|
||||
if (Config.SpeedCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "speed",
|
||||
Localizer?["fun_menu_speed"] ?? "Set Speed",
|
||||
CreateSetSpeedMenu, "@css/slay");
|
||||
"fun_menu_speed",
|
||||
CreateSetSpeedMenu, "@css/slay", "css_speed", Localizer!);
|
||||
|
||||
if (Config.GravityCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "gravity",
|
||||
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
|
||||
CreateSetGravityMenu, "@css/slay");
|
||||
"fun_menu_gravity",
|
||||
CreateSetGravityMenu, "@css/slay", "css_gravity", Localizer!);
|
||||
|
||||
if (Config.MoneyCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "money",
|
||||
Localizer?["fun_menu_money"] ?? "Set Money",
|
||||
CreateSetMoneyMenu, "@css/slay");
|
||||
"fun_menu_money",
|
||||
CreateSetMoneyMenu, "@css/slay", "css_money", Localizer!);
|
||||
|
||||
if (Config.ResizeCommands.Count > 0)
|
||||
_sharedApi.RegisterMenu("fun", "resize",
|
||||
Localizer?["fun_menu_resize"] ?? "Resize Player",
|
||||
CreateSetResizeMenu, "@css/slay");
|
||||
"fun_menu_resize",
|
||||
CreateSetResizeMenu, "@css/slay", "css_resize", Localizer!);
|
||||
|
||||
_menusRegistered = true;
|
||||
Logger.LogInformation("Fun menus registered successfully!");
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,12 +1,62 @@
|
||||
using System.Globalization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
|
||||
namespace CS2_SimpleAdmin_FunCommands;
|
||||
|
||||
/// <summary>
|
||||
/// Menu creation methods for Fun Commands module.
|
||||
/// This file demonstrates different menu patterns using SimpleAdmin API.
|
||||
///
|
||||
/// PERMISSION OVERRIDE SUPPORT:
|
||||
/// ============================
|
||||
/// When registering menus in RegisterFunMenus(), you can pass a command name (e.g., "css_god")
|
||||
/// as the last parameter. This enables automatic permission override checking via CounterStrikeSharp's
|
||||
/// admin system.
|
||||
///
|
||||
/// How it works:
|
||||
/// 1. Server admin overrides a command's permissions (e.g., css_god requires @css/vip instead of @css/cheats)
|
||||
/// 2. SimpleAdmin's menu system automatically checks for overrides when displaying menus
|
||||
/// 3. If override exists, it uses the overridden permission; otherwise, uses the default permission
|
||||
///
|
||||
/// Example from RegisterFunMenus():
|
||||
/// _sharedApi.RegisterMenu("fun", "god",
|
||||
/// Localizer?["fun_menu_god"] ?? "God Mode",
|
||||
/// CreateGodModeMenu,
|
||||
/// "@css/cheats", // Default permission
|
||||
/// "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
|
||||
{
|
||||
@@ -19,22 +69,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);
|
||||
@@ -43,13 +92,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
|
||||
}
|
||||
@@ -63,12 +112,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));
|
||||
@@ -91,8 +140,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// </summary>
|
||||
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var weaponMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
@@ -114,11 +170,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) =>
|
||||
@@ -128,11 +183,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); });
|
||||
@@ -141,13 +195,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));
|
||||
@@ -167,8 +220,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// </summary>
|
||||
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var hpSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
@@ -177,8 +237,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
// Translate option label per-player
|
||||
string optionLabel;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
optionLabel = Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP";
|
||||
}
|
||||
|
||||
_sharedApi.AddMenuOption(hpSelectionMenu,
|
||||
Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP",
|
||||
optionLabel,
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
@@ -192,11 +259,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));
|
||||
@@ -217,8 +283,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
/// </summary>
|
||||
private object CreateSpeedSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var speedSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
@@ -231,8 +304,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
foreach (var (speed, display) in speedValues)
|
||||
{
|
||||
// Translate option label per-player
|
||||
string optionLabel;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
optionLabel = Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}";
|
||||
}
|
||||
|
||||
_sharedApi.AddMenuOption(speedSelectionMenu,
|
||||
Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}",
|
||||
optionLabel,
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
@@ -253,11 +333,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));
|
||||
@@ -273,8 +352,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
private object CreateGravitySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var gravitySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
var gravityValues = new[]
|
||||
@@ -304,11 +390,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));
|
||||
|
||||
@@ -323,8 +408,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
private object CreateMoneySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var moneySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
var moneyValues = new[] { 0, 1000, 2500, 5000, 10000, 16000 };
|
||||
@@ -346,11 +438,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));
|
||||
@@ -366,8 +457,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
||||
|
||||
private object CreateResizeSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Translate title per-player based on admin's css_lang
|
||||
string translatedTitle;
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
translatedTitle = Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}";
|
||||
}
|
||||
|
||||
var resizeSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}",
|
||||
translatedTitle,
|
||||
"fun",
|
||||
admin);
|
||||
var resizeValues = new[]
|
||||
|
||||