mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-03-11 16:59:58 +00:00
Compare commits
40 Commits
bedcfd0358
...
build-1.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe73fa9917 | ||
|
|
bdada2df1e | ||
|
|
310a43fcd9 | ||
|
|
58243e813a | ||
|
|
d53446e0fe | ||
|
|
2404c1bc03 | ||
|
|
665962565e | ||
|
|
c2e8b4a898 | ||
|
|
9723a4faee | ||
|
|
4865b76262 | ||
|
|
0dded66e5d | ||
|
|
038641dbdf | ||
|
|
a03964c08a | ||
|
|
b0d8696756 | ||
|
|
21a5de6b3d | ||
|
|
718536eaef | ||
|
|
099e91b43b | ||
|
|
206c18db66 | ||
|
|
78318102fe | ||
|
|
2edacc2b3f | ||
|
|
e1e66441f2 | ||
|
|
cc54b9e879 | ||
|
|
640e618f3b | ||
|
|
23d174c4a5 | ||
|
|
b7371adf26 | ||
|
|
9154748ce6 | ||
|
|
da9830ee05 | ||
|
|
b41ac992c0 | ||
|
|
af0bda8f3a | ||
|
|
e2529cd646 | ||
|
|
9d2cd34845 | ||
|
|
5701455de0 | ||
|
|
b97426313b | ||
|
|
3ab63c05db | ||
|
|
f654d6b085 | ||
|
|
676a18d9b4 | ||
|
|
d75a092047 | ||
|
|
d34ca64970 | ||
|
|
f69f1277f8 | ||
|
|
b6c876d709 |
171
.github/workflows/build.yml
vendored
171
.github/workflows/build.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Build
|
||||
name: Build and Publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths-ignore:
|
||||
@@ -11,85 +12,117 @@ on:
|
||||
- '**/README.md'
|
||||
|
||||
env:
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
PROJECT_PATH_CS2_SIMPLEADMIN: "CS2-SimpleAdmin/CS2-SimpleAdmin.csproj"
|
||||
PROJECT_PATH_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj"
|
||||
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"
|
||||
TMP_PATH: "./tmp"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions: write-all
|
||||
outputs:
|
||||
build_version: ${{ steps.get_version.outputs.VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore CS2-SimpleAdmin
|
||||
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }}
|
||||
- name: Build CS2-SimpleAdmin
|
||||
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}
|
||||
- name: Restore CS2-SimpleAdminApi
|
||||
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }}
|
||||
- name: Build CS2-SimpleAdminApi
|
||||
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Get Version
|
||||
id: get_version
|
||||
run: echo "VERSION=$(cat CS2-SimpleAdmin/VERSION)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore & Build All Projects
|
||||
run: |
|
||||
dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }}
|
||||
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/
|
||||
|
||||
- name: Zip Main Build Output
|
||||
run: zip -r CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip ${{ env.OUTPUT_PATH }}
|
||||
|
||||
- name: Extract & Zip StatusBlocker Linux
|
||||
run: |
|
||||
mkdir -p statusblocker-linux &&
|
||||
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-linux.tar.gz -C statusblocker-linux &&
|
||||
cd statusblocker-linux &&
|
||||
zip -r ../StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip ./*
|
||||
|
||||
- name: Extract & Zip StatusBlocker Windows
|
||||
run: |
|
||||
mkdir -p statusblocker-windows &&
|
||||
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-windows.tar.gz -C statusblocker-windows &&
|
||||
cd statusblocker-windows &&
|
||||
zip -r ../StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip ./*
|
||||
|
||||
- name: Upload all artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CS2-SimpleAdmin-Build-Artifacts
|
||||
path: |
|
||||
CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip
|
||||
StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip
|
||||
StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip
|
||||
|
||||
publish:
|
||||
if: github.event_name == 'push'
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore CS2-SimpleAdmin
|
||||
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }}
|
||||
- name: Build CS2-SimpleAdmin
|
||||
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}
|
||||
- name: Restore CS2-SimpleAdminApi
|
||||
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }}
|
||||
- name: Build CS2-SimpleAdminApi
|
||||
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}
|
||||
- name: Clean files
|
||||
run: |
|
||||
rm -f \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/CounterStrikeSharp.API.dll \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/McMaster.NETCore.Plugins.dll \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.DotNet.PlatformAbstractions.dll \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.Extensions.DependencyModel.dll \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/CS2-SimpleAdminApi.* \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.* \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/CounterStrikeSharp.API.dll \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/McMaster.NETCore.Plugins.dll \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/Microsoft.DotNet.PlatformAbstractions.dll \
|
||||
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/Microsoft.Extensions.DependencyModel.dll
|
||||
- name: Combine projects
|
||||
run: |
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/plugins
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/shared
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }} ${{ env.OUTPUT_PATH }}/plugins/
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }} ${{ env.OUTPUT_PATH }}/shared/
|
||||
- name: Zip combined
|
||||
uses: thedoctor0/zip-release@0.7.6
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}-${{ env.BUILD_NUMBER }}.zip'
|
||||
path: ${{ env.OUTPUT_PATH }}
|
||||
- name: Publish combined release
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
artifacts: "${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}-${{ env.BUILD_NUMBER }}.zip"
|
||||
name: "CS2-SimpleAdmin - Build ${{ env.BUILD_NUMBER }}"
|
||||
tag: "build-${{ env.BUILD_NUMBER }}"
|
||||
body: |
|
||||
Place files in addons/counterstrikesharp
|
||||
After first launch, configure the plugins in the respective configs:
|
||||
- CS2-SimpleAdmin: addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: CS2-SimpleAdmin-Build-Artifacts
|
||||
path: .
|
||||
- name: Unzip main build artifact
|
||||
run: unzip CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip -d ./counterstrikesharp
|
||||
- name: Publish combined release
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
artifacts: |
|
||||
CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip
|
||||
StatusBlocker-linux-${{ needs.build.outputs.build_version }}.zip
|
||||
StatusBlocker-windows-${{ needs.build.outputs.build_version }}.zip
|
||||
name: "CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}"
|
||||
tag: "build-${{ needs.build.outputs.build_version }}"
|
||||
body: |
|
||||
Place the files in your server as follows:
|
||||
|
||||
- CS2-SimpleAdmin:
|
||||
Place the files inside the addons/counterstrikesharp directory.
|
||||
After the first launch, configure the plugin using the JSON config file at:
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
||||
|
||||
- StatusBlocker:
|
||||
Place the plugin files directly into the addons directory.
|
||||
This plugin is a Metamod module for the StealthModule and does not require a subfolder.
|
||||
|
||||
Remember to restart or reload your game server after installing and configuring the plugins.
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -4,5 +4,9 @@ obj/
|
||||
.git
|
||||
.vscode/
|
||||
.idea/
|
||||
Modules/CS2-SimpleAdmin_PlayTimeModule
|
||||
CS2-SimpleAdmin.sln.DotSettings.user
|
||||
Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user
|
||||
CS2-SimpleAdmin_BanSoundModule — kopia
|
||||
*.user
|
||||
CLAUDE.md
|
||||
|
||||
20
CS2-SimpleAdmin-docs/.gitignore
vendored
Normal file
20
CS2-SimpleAdmin-docs/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
41
CS2-SimpleAdmin-docs/README.md
Normal file
41
CS2-SimpleAdmin-docs/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Website
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
## Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```bash
|
||||
USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```bash
|
||||
GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
465
CS2-SimpleAdmin-docs/docs/developer/api/commands.md
Normal file
465
CS2-SimpleAdmin-docs/docs/developer/api/commands.md
Normal file
@@ -0,0 +1,465 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Commands API
|
||||
|
||||
Complete reference for command registration and management.
|
||||
|
||||
## Command Registration
|
||||
|
||||
### RegisterCommand
|
||||
|
||||
Register a new command that integrates with CS2-SimpleAdmin.
|
||||
|
||||
```csharp
|
||||
void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `name` - Command name (e.g., "css_mycommand")
|
||||
- `description` - Command description (optional)
|
||||
- `callback` - Method to call when command is executed
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterCommand("css_mycommand", "My custom command", OnMyCommand);
|
||||
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Command logic here
|
||||
}
|
||||
```
|
||||
|
||||
**Throws:**
|
||||
- `ArgumentException` - If command name is null or empty
|
||||
- `ArgumentNullException` - If callback is null
|
||||
|
||||
---
|
||||
|
||||
### UnRegisterCommand
|
||||
|
||||
Unregister a previously registered command.
|
||||
|
||||
```csharp
|
||||
void UnRegisterCommand(string commandName)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `commandName` - Name of command to unregister
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
```
|
||||
|
||||
**Best Practice:**
|
||||
Always unregister commands in your plugin's `Unload()` method:
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Target Parsing
|
||||
|
||||
### GetTarget
|
||||
|
||||
Parse player targets from command arguments.
|
||||
|
||||
```csharp
|
||||
TargetResult? GetTarget(CommandInfo command)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `command` - Command info containing arguments
|
||||
|
||||
**Returns:**
|
||||
- `TargetResult` - Contains matched players
|
||||
- `null` - If no targets found or error
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players)
|
||||
{
|
||||
// Do something with player
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Supported Target Syntax:**
|
||||
- `@all` - All players
|
||||
- `@ct` - All Counter-Terrorists
|
||||
- `@t` - All Terrorists
|
||||
- `@spec` - All spectators
|
||||
- `@alive` - All alive players
|
||||
- `@dead` - All dead players
|
||||
- `@bot` - All bots
|
||||
- `@human` - All human players
|
||||
- `@me` - Command caller
|
||||
- `#123` - Player by user ID
|
||||
- `PlayerName` - Player by name (partial match)
|
||||
|
||||
---
|
||||
|
||||
## Command Logging
|
||||
|
||||
### LogCommand (with CommandInfo)
|
||||
|
||||
Log a command execution with full command info.
|
||||
|
||||
```csharp
|
||||
void LogCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `caller` - Player who executed command (null for console)
|
||||
- `command` - Command info object
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Execute command logic
|
||||
|
||||
// Log the command
|
||||
_api!.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### LogCommand (with string)
|
||||
|
||||
Log a command execution with custom command string.
|
||||
|
||||
```csharp
|
||||
void LogCommand(CCSPlayerController? caller, string command)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `caller` - Player who executed command (null for console)
|
||||
- `command` - Command string to log
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private void DoAction(CCSPlayerController? caller, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
|
||||
// Log with custom string
|
||||
_api!.LogCommand(caller, $"css_mycommand {target.PlayerName}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Example
|
||||
|
||||
### Basic Command
|
||||
|
||||
```csharp
|
||||
private void RegisterCommands()
|
||||
{
|
||||
_api!.RegisterCommand("css_hello", "Say hello to a player", OnHelloCommand);
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnHelloCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid))
|
||||
{
|
||||
player.PrintToChat($"Hello {player.PlayerName}!");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_api?.UnRegisterCommand("css_hello");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command with Permission Check
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Get targets
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Filter for players caller can target
|
||||
var validPlayers = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
foreach (var player in validPlayers)
|
||||
{
|
||||
// Issue ban
|
||||
_api.IssuePenalty(player, caller, PenaltyType.Ban, "Banned via command", 1440);
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command with Arguments
|
||||
|
||||
```csharp
|
||||
[CommandHelper(2, "<#userid or name> <value>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Parse HP value
|
||||
if (!int.TryParse(command.GetArg(2), out int hp))
|
||||
{
|
||||
caller?.PrintToChat("Invalid HP value!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get targets
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && p.PawnIsAlive))
|
||||
{
|
||||
player.PlayerPawn?.Value?.SetHealth(hp);
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, $"css_sethp {hp}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Validate Targets
|
||||
|
||||
```csharp
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return; // No targets found
|
||||
|
||||
// Filter for valid players
|
||||
var validPlayers = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot)
|
||||
.ToList();
|
||||
|
||||
if (validPlayers.Count == 0)
|
||||
{
|
||||
caller?.PrintToChat("No valid targets found!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Check Immunity
|
||||
|
||||
```csharp
|
||||
foreach (var player in targets.Players)
|
||||
{
|
||||
// Check if caller can target this player (immunity check)
|
||||
if (!caller!.CanTarget(player))
|
||||
{
|
||||
caller.PrintToChat($"You cannot target {player.PlayerName}!");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Safe to target player
|
||||
DoAction(player);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Always Log Commands
|
||||
|
||||
```csharp
|
||||
// Log every admin command execution
|
||||
_api.LogCommand(caller, command);
|
||||
```
|
||||
|
||||
### 4. Use CommandHelper Attribute
|
||||
|
||||
```csharp
|
||||
// Specify minimum args and usage
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// CounterStrikeSharp validates args automatically
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Cleanup on Unload
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister ALL commands
|
||||
_api.UnRegisterCommand("css_command1");
|
||||
_api.UnRegisterCommand("css_command2");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Multiple Aliases
|
||||
|
||||
```csharp
|
||||
// Register same command with multiple aliases
|
||||
var aliases = new[] { "css_mycommand", "css_mycmd", "css_mc" };
|
||||
|
||||
foreach (var alias in aliases)
|
||||
{
|
||||
_api.RegisterCommand(alias, "My command", OnMyCommand);
|
||||
}
|
||||
|
||||
// Unregister all
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
foreach (var alias in aliases)
|
||||
{
|
||||
_api?.UnRegisterCommand(alias);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Command from Config
|
||||
|
||||
```csharp
|
||||
// In Config.cs
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
// In Plugin
|
||||
private void RegisterCommands()
|
||||
{
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Description", OnMyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
// Allows users to add aliases or disable by clearing list
|
||||
```
|
||||
|
||||
### Target Filtering
|
||||
|
||||
```csharp
|
||||
// Get only alive players
|
||||
var alivePlayers = targets.Players
|
||||
.Where(p => p.IsValid && p.PawnIsAlive)
|
||||
.ToList();
|
||||
|
||||
// Get only enemy team
|
||||
var enemies = targets.Players
|
||||
.Where(p => p.IsValid && p.Team != caller!.Team)
|
||||
.ToList();
|
||||
|
||||
// Get targetable players
|
||||
var targetable = targets.Players
|
||||
.Where(p => p.IsValid && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Command Registration Errors
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
_api.RegisterCommand("css_mycommand", "Description", OnMyCommand);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Logger.LogError($"Failed to register command: {ex.Message}");
|
||||
}
|
||||
```
|
||||
|
||||
### Target Parsing Errors
|
||||
|
||||
```csharp
|
||||
var targets = _api!.GetTarget(command);
|
||||
|
||||
if (targets == null)
|
||||
{
|
||||
// Target parsing failed
|
||||
// Error message already sent to caller by SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
if (targets.Players.Count == 0)
|
||||
{
|
||||
caller?.PrintToChat("No players matched your target!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### Cache Command Lists
|
||||
|
||||
```csharp
|
||||
// Don't create new list every time
|
||||
private readonly List<string> _commandAliases = new() { "css_cmd1", "css_cmd2" };
|
||||
|
||||
private void RegisterCommands()
|
||||
{
|
||||
foreach (var cmd in _commandAliases)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Description", OnCommand);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Efficient Target Filtering
|
||||
|
||||
```csharp
|
||||
// ✅ Good - single LINQ query
|
||||
var players = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot && p.PawnIsAlive && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
// ❌ Bad - multiple iterations
|
||||
var players = targets.Players.Where(p => p.IsValid).ToList();
|
||||
players = players.Where(p => !p.IsBot).ToList();
|
||||
players = players.Where(p => p.PawnIsAlive).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Menus API](menus)** - Create interactive menus
|
||||
- **[Penalties API](penalties)** - Issue penalties from commands
|
||||
- **[Utilities API](utilities)** - Helper functions for commands
|
||||
642
CS2-SimpleAdmin-docs/docs/developer/api/events.md
Normal file
642
CS2-SimpleAdmin-docs/docs/developer/api/events.md
Normal file
@@ -0,0 +1,642 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Events API
|
||||
|
||||
Complete reference for CS2-SimpleAdmin event system.
|
||||
|
||||
## Event System Overview
|
||||
|
||||
CS2-SimpleAdmin exposes events that allow modules to react to plugin actions and state changes.
|
||||
|
||||
**All events use C# event delegates and should be subscribed to in `OnAllPluginsLoaded` and unsubscribed in `Unload`.**
|
||||
|
||||
---
|
||||
|
||||
## Plugin Lifecycle Events
|
||||
|
||||
### OnSimpleAdminReady
|
||||
|
||||
Fired when CS2-SimpleAdmin is fully initialized and ready.
|
||||
|
||||
```csharp
|
||||
event Action? OnSimpleAdminReady
|
||||
```
|
||||
|
||||
**When Fired:**
|
||||
- After plugin load
|
||||
- After database initialization
|
||||
- After migrations complete
|
||||
- Menu system ready
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
// Subscribe to ready event
|
||||
_api.OnSimpleAdminReady += OnSimpleAdminReady;
|
||||
|
||||
// Also call directly for hot reload case
|
||||
OnSimpleAdminReady();
|
||||
}
|
||||
|
||||
private void OnSimpleAdminReady()
|
||||
{
|
||||
Logger.LogInformation("SimpleAdmin is ready!");
|
||||
|
||||
// Register menus (requires SimpleAdmin to be ready)
|
||||
RegisterMenus();
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
_api.OnSimpleAdminReady -= OnSimpleAdminReady;
|
||||
}
|
||||
```
|
||||
|
||||
**Best Practice:**
|
||||
Always call your handler directly after subscribing to handle hot reload:
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // ← Also call directly
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Penalty Events
|
||||
|
||||
### OnPlayerPenaltied
|
||||
|
||||
Fired when an **online player** receives a penalty.
|
||||
|
||||
```csharp
|
||||
event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `PlayerInfo player` - Player who received penalty
|
||||
2. `PlayerInfo? admin` - Admin who issued penalty (null if console)
|
||||
3. `PenaltyType penaltyType` - Type of penalty
|
||||
4. `string reason` - Penalty reason
|
||||
5. `int duration` - Duration in minutes (0 = permanent)
|
||||
6. `int? penaltyId` - Database penalty ID (null if not stored)
|
||||
7. `int? serverId` - Server ID (null in single-server mode)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
Logger.LogInformation($"{adminName} penaltied {player.PlayerName}: {type} ({duration}m) - {reason}");
|
||||
|
||||
// React to specific penalty types
|
||||
switch (type)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
// Log ban to external system
|
||||
LogBanToWebhook(player, admin, reason, duration);
|
||||
break;
|
||||
|
||||
case PenaltyType.Warn:
|
||||
// Check warning count
|
||||
if (player.Warnings >= 3)
|
||||
{
|
||||
Logger.LogWarning($"{player.PlayerName} has {player.Warnings} warnings!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### OnPlayerPenaltiedAdded
|
||||
|
||||
Fired when a penalty is added to an **offline player** by SteamID.
|
||||
|
||||
```csharp
|
||||
event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `SteamID steamId` - Target player's SteamID
|
||||
2. `PlayerInfo? admin` - Admin who issued penalty
|
||||
3. `PenaltyType penaltyType` - Type of penalty
|
||||
4. `string reason` - Penalty reason
|
||||
5. `int duration` - Duration in minutes
|
||||
6. `int? penaltyId` - Database penalty ID
|
||||
7. `int? serverId` - Server ID
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
|
||||
private void OnPlayerPenaltiedAdded(
|
||||
SteamID steamId,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
Logger.LogInformation($"Offline penalty: {adminName} -> SteamID {steamId}: {type} ({duration}m)");
|
||||
|
||||
// Log to external database or webhook
|
||||
if (type == PenaltyType.Ban)
|
||||
{
|
||||
LogOfflineBan(steamId, admin, reason, duration);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Activity Events
|
||||
|
||||
### OnAdminShowActivity
|
||||
|
||||
Fired when an admin action is displayed to players.
|
||||
|
||||
```csharp
|
||||
event Action<string, string?, bool, object>? OnAdminShowActivity
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `string messageKey` - Translation key for the message
|
||||
2. `string? callerName` - Admin name (null if console)
|
||||
3. `bool dontPublish` - If true, don't broadcast to other systems
|
||||
4. `object messageArgs` - Arguments for message formatting
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
|
||||
private void OnAdminShowActivity(
|
||||
string messageKey,
|
||||
string? callerName,
|
||||
bool dontPublish,
|
||||
object messageArgs)
|
||||
{
|
||||
if (dontPublish) return;
|
||||
|
||||
Logger.LogInformation($"Admin activity: {messageKey} by {callerName ?? "Console"}");
|
||||
|
||||
// Log to Discord, database, etc.
|
||||
LogAdminAction(messageKey, callerName, messageArgs);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### OnAdminToggleSilent
|
||||
|
||||
Fired when an admin toggles silent mode.
|
||||
|
||||
```csharp
|
||||
event Action<int, bool>? OnAdminToggleSilent
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `int slot` - Player slot of admin
|
||||
2. `bool status` - New silent status (true = silent, false = normal)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnAdminToggleSilent += OnAdminToggleSilent;
|
||||
|
||||
private void OnAdminToggleSilent(int slot, bool status)
|
||||
{
|
||||
var player = Utilities.GetPlayerFromSlot(slot);
|
||||
if (player == null) return;
|
||||
|
||||
var statusText = status ? "enabled" : "disabled";
|
||||
Logger.LogInformation($"{player.PlayerName} {statusText} silent mode");
|
||||
|
||||
// Update UI or external systems
|
||||
UpdateAdminStatus(player, status);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Ban Logging System
|
||||
|
||||
```csharp
|
||||
public class BanLogger
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
_api = api;
|
||||
|
||||
// Subscribe to both ban events
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
// Log to file
|
||||
File.AppendAllText("bans.log",
|
||||
$"[{DateTime.Now}] {player.PlayerName} ({player.SteamId}) " +
|
||||
$"banned by {admin?.PlayerName ?? "Console"} " +
|
||||
$"for {duration} minutes: {reason}\n");
|
||||
|
||||
// Send to Discord webhook
|
||||
SendDiscordNotification(
|
||||
$"🔨 **Ban Issued**\n" +
|
||||
$"Player: {player.PlayerName}\n" +
|
||||
$"Admin: {admin?.PlayerName ?? "Console"}\n" +
|
||||
$"Duration: {FormatDuration(duration)}\n" +
|
||||
$"Reason: {reason}"
|
||||
);
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltiedAdded(
|
||||
SteamID steamId,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
File.AppendAllText("bans.log",
|
||||
$"[{DateTime.Now}] Offline ban: SteamID {steamId} " +
|
||||
$"by {admin?.PlayerName ?? "Console"} " +
|
||||
$"for {duration} minutes: {reason}\n");
|
||||
}
|
||||
|
||||
private string FormatDuration(int minutes)
|
||||
{
|
||||
if (minutes == 0) return "Permanent";
|
||||
if (minutes < 60) return $"{minutes} minutes";
|
||||
if (minutes < 1440) return $"{minutes / 60} hours";
|
||||
return $"{minutes / 1440} days";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Warning Escalation System
|
||||
|
||||
```csharp
|
||||
public class WarningEscalation
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
_api = api;
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
// Only handle warnings
|
||||
if (type != PenaltyType.Warn) return;
|
||||
|
||||
Logger.LogInformation($"{player.PlayerName} now has {player.Warnings} warnings");
|
||||
|
||||
// Auto-escalate based on warning count
|
||||
if (player.Warnings >= 3)
|
||||
{
|
||||
// 3 warnings = 1 hour ban
|
||||
_api.IssuePenalty(
|
||||
GetPlayerController(player.SteamId),
|
||||
null,
|
||||
PenaltyType.Ban,
|
||||
"Automatic: 3 warnings",
|
||||
60
|
||||
);
|
||||
}
|
||||
else if (player.Warnings >= 5)
|
||||
{
|
||||
// 5 warnings = 1 day ban
|
||||
_api.IssuePenalty(
|
||||
GetPlayerController(player.SteamId),
|
||||
null,
|
||||
PenaltyType.Ban,
|
||||
"Automatic: 5 warnings",
|
||||
1440
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Admin Activity Monitor
|
||||
|
||||
```csharp
|
||||
public class AdminMonitor
|
||||
{
|
||||
private readonly Dictionary<string, int> _adminActions = new();
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
}
|
||||
|
||||
private void OnAdminShowActivity(
|
||||
string messageKey,
|
||||
string? callerName,
|
||||
bool dontPublish,
|
||||
object messageArgs)
|
||||
{
|
||||
if (callerName == null) return; // Ignore console actions
|
||||
|
||||
// Track admin actions
|
||||
if (!_adminActions.ContainsKey(callerName))
|
||||
{
|
||||
_adminActions[callerName] = 0;
|
||||
}
|
||||
|
||||
_adminActions[callerName]++;
|
||||
|
||||
// Log every 10th action
|
||||
if (_adminActions[callerName] % 10 == 0)
|
||||
{
|
||||
Logger.LogInformation(
|
||||
$"{callerName} has performed {_adminActions[callerName]} admin actions"
|
||||
);
|
||||
}
|
||||
|
||||
// Alert if admin is very active
|
||||
if (_adminActions[callerName] > 100)
|
||||
{
|
||||
Logger.LogWarning($"{callerName} has performed many actions ({_adminActions[callerName]})");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Unsubscribe
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
// Subscribe
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
_api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// ALWAYS unsubscribe
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
_api.OnAdminShowActivity -= OnAdminShowActivity;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Handle Null Admins
|
||||
|
||||
```csharp
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin, // ← Can be null!
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
// Use adminName safely
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Events for Integration
|
||||
|
||||
```csharp
|
||||
// ✅ Good - React to penalties
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
if (type == PenaltyType.Ban)
|
||||
{
|
||||
NotifyExternalSystem(player, reason);
|
||||
}
|
||||
};
|
||||
|
||||
// ❌ Bad - Wrapping penalty methods
|
||||
// Don't wrap IssuePenalty, use events instead
|
||||
```
|
||||
|
||||
### 4. Check Event Parameters
|
||||
|
||||
```csharp
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
// Check nullable parameters
|
||||
if (penaltyId.HasValue)
|
||||
{
|
||||
Logger.LogInformation($"Penalty ID: {penaltyId.Value}");
|
||||
}
|
||||
|
||||
if (serverId.HasValue)
|
||||
{
|
||||
Logger.LogInformation($"Server ID: {serverId.Value}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. OnSimpleAdminReady Pattern
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Handles both normal load and hot reload
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus();
|
||||
|
||||
// ❌ Bad - Only works on normal load
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Event-Based Statistics
|
||||
|
||||
```csharp
|
||||
public class ServerStatistics
|
||||
{
|
||||
private int _totalBans;
|
||||
private int _totalMutes;
|
||||
private int _totalWarnings;
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
_totalBans++;
|
||||
break;
|
||||
case PenaltyType.Mute:
|
||||
case PenaltyType.Gag:
|
||||
case PenaltyType.Silence:
|
||||
_totalMutes++;
|
||||
break;
|
||||
case PenaltyType.Warn:
|
||||
_totalWarnings++;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void PrintStatistics()
|
||||
{
|
||||
Logger.LogInformation($"Server Statistics:");
|
||||
Logger.LogInformation($"Total Bans: {_totalBans}");
|
||||
Logger.LogInformation($"Total Mutes: {_totalMutes}");
|
||||
Logger.LogInformation($"Total Warnings: {_totalWarnings}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Event Handling
|
||||
|
||||
```csharp
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Only handle bans
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
// Only handle permanent bans
|
||||
if (duration != 0) return;
|
||||
|
||||
// Only handle admin-issued bans
|
||||
if (admin == null) return;
|
||||
|
||||
// Process permanent admin bans
|
||||
NotifyImportantBan(player, admin, reason);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Async Operations in Events
|
||||
|
||||
```csharp
|
||||
// ⚠️ Be careful with async in event handlers
|
||||
_api.OnPlayerPenaltied += async (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Don't block the game thread
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// Long-running operation
|
||||
LogToExternalDatabase(player, type, reason);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Efficient Event Handlers
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Quick processing
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Quick logging
|
||||
Logger.LogInformation($"Ban: {player.PlayerName}");
|
||||
};
|
||||
|
||||
// ❌ Bad - Heavy processing
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Don't do expensive operations synchronously
|
||||
SendEmailNotification(player); // ← This blocks the game thread!
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Event Not Firing
|
||||
|
||||
**Check:**
|
||||
1. Did you subscribe to the event?
|
||||
2. Is `_api` not null?
|
||||
3. Are you testing the right scenario?
|
||||
4. Check server console for errors
|
||||
|
||||
### Memory Leaks
|
||||
|
||||
**Always unsubscribe:**
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unsubscribe ALL events
|
||||
_api.OnSimpleAdminReady -= OnReady;
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
// ... etc
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Penalties API](penalties)** - Issue penalties that trigger events
|
||||
- **[Commands API](commands)** - Commands that may trigger admin activity
|
||||
- **[Utilities API](utilities)** - Helper functions for event handlers
|
||||
706
CS2-SimpleAdmin-docs/docs/developer/api/menus.md
Normal file
706
CS2-SimpleAdmin-docs/docs/developer/api/menus.md
Normal file
@@ -0,0 +1,706 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Menus API
|
||||
|
||||
Complete reference for creating interactive admin menus.
|
||||
|
||||
## Menu Categories
|
||||
|
||||
### RegisterMenuCategory
|
||||
|
||||
Register a new menu category in the admin menu.
|
||||
|
||||
```csharp
|
||||
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Unique identifier for the category
|
||||
- `categoryName` - Display name shown in menu
|
||||
- `permission` - Required permission to see category (default: "@css/generic")
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenuCategory("mycategory", "My Custom Category", "@css/generic");
|
||||
```
|
||||
|
||||
**Best Practice:**
|
||||
Register categories in the `OnSimpleAdminReady` event handler:
|
||||
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += () =>
|
||||
{
|
||||
_api.RegisterMenuCategory("mycategory", Localizer?["category_name"] ?? "My Category");
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Registration
|
||||
|
||||
### RegisterMenu (Basic)
|
||||
|
||||
Register a menu within a category.
|
||||
|
||||
```csharp
|
||||
void RegisterMenu(
|
||||
string categoryId,
|
||||
string menuId,
|
||||
string menuName,
|
||||
Func<CCSPlayerController, object> menuFactory,
|
||||
string? permission = null,
|
||||
string? commandName = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Category to add menu to
|
||||
- `menuId` - Unique menu identifier
|
||||
- `menuName` - Display name in menu
|
||||
- `menuFactory` - Function that creates the menu
|
||||
- `permission` - Required permission (optional)
|
||||
- `commandName` - Command for permission override (optional, e.g., "css_god")
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenu(
|
||||
"mycategory",
|
||||
"mymenu",
|
||||
"My Menu",
|
||||
CreateMyMenu,
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
private object CreateMyMenu(CCSPlayerController player)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", player);
|
||||
// Add options...
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### RegisterMenu (with MenuContext) ⭐ RECOMMENDED
|
||||
|
||||
Register a menu with automatic context passing - **eliminates duplication!**
|
||||
|
||||
```csharp
|
||||
void RegisterMenu(
|
||||
string categoryId,
|
||||
string menuId,
|
||||
string menuName,
|
||||
Func<CCSPlayerController, MenuContext, object> menuFactory,
|
||||
string? permission = null,
|
||||
string? commandName = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Category to add menu to
|
||||
- `menuId` - Unique menu identifier
|
||||
- `menuName` - Display name in menu
|
||||
- `menuFactory` - Function that receives player AND context
|
||||
- `permission` - Required permission (optional)
|
||||
- `commandName` - Command for permission override (optional)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// ✅ NEW WAY - No duplication!
|
||||
_api.RegisterMenu(
|
||||
"fun",
|
||||
"god",
|
||||
"God Mode",
|
||||
CreateGodMenu,
|
||||
"@css/cheats",
|
||||
"css_god"
|
||||
);
|
||||
|
||||
private object CreateGodMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// context contains: CategoryId, MenuId, MenuTitle, Permission, CommandName
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // ← Automatically uses "God Mode" and "fun"
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) => ToggleGod(admin, target)
|
||||
);
|
||||
}
|
||||
|
||||
// ❌ OLD WAY - Had to repeat "God Mode" and "fun"
|
||||
private object CreateGodMenuOld(CCSPlayerController admin)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
"God Mode", // ← Repeated from RegisterMenu
|
||||
"fun", // ← Repeated from RegisterMenu
|
||||
admin,
|
||||
filter,
|
||||
action
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**MenuContext Properties:**
|
||||
```csharp
|
||||
public class MenuContext
|
||||
{
|
||||
public string CategoryId { get; } // e.g., "fun"
|
||||
public string MenuId { get; } // e.g., "god"
|
||||
public string MenuTitle { get; } // e.g., "God Mode"
|
||||
public string? Permission { get; } // e.g., "@css/cheats"
|
||||
public string? CommandName { get; } // e.g., "css_god"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### UnregisterMenu
|
||||
|
||||
Remove a menu from a category.
|
||||
|
||||
```csharp
|
||||
void UnregisterMenu(string categoryId, string menuId)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_api?.UnregisterMenu("mycategory", "mymenu");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Creation
|
||||
|
||||
### CreateMenuWithBack
|
||||
|
||||
Create a menu with automatic back button.
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category for back button navigation
|
||||
- `player` - Player viewing the menu
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", admin);
|
||||
_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
|
||||
_api.AddMenuOption(menu, "Option 2", _ => DoAction2());
|
||||
return menu;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreateMenuWithBack (with Context) ⭐ RECOMMENDED
|
||||
|
||||
Create a menu using context - **no duplication!**
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// ✅ Uses context.MenuTitle and context.CategoryId automatically
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreateMenuWithPlayers
|
||||
|
||||
Create a menu showing a list of players.
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithPlayers(
|
||||
string title,
|
||||
string categoryId,
|
||||
CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter,
|
||||
Action<CCSPlayerController, CCSPlayerController> onSelect
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category for back button
|
||||
- `admin` - Admin viewing menu
|
||||
- `filter` - Function to filter which players to show
|
||||
- `onSelect` - Action when player is selected (admin, selectedPlayer)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
"Select Player",
|
||||
"mycategory",
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
// Do something with selected player
|
||||
DoAction(admin, target);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreateMenuWithPlayers (with Context) ⭐ RECOMMENDED
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithPlayers(
|
||||
MenuContext context,
|
||||
CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter,
|
||||
Action<CCSPlayerController, CCSPlayerController> onSelect
|
||||
)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private object CreatePlayerMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // ← Automatically uses correct title and category!
|
||||
admin,
|
||||
player => player.PawnIsAlive && admin.CanTarget(player),
|
||||
(admin, target) => PerformAction(admin, target)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Options
|
||||
|
||||
### AddMenuOption
|
||||
|
||||
Add a clickable option to a menu.
|
||||
|
||||
```csharp
|
||||
void AddMenuOption(
|
||||
object menu,
|
||||
string name,
|
||||
Action<CCSPlayerController> action,
|
||||
bool disabled = false,
|
||||
string? permission = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `menu` - Menu to add option to
|
||||
- `name` - Option display text
|
||||
- `action` - Function called when selected
|
||||
- `disabled` - Whether option is disabled (default: false)
|
||||
- `permission` - Required permission to see option (optional)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api!.CreateMenuWithBack("Actions", "mycategory", admin);
|
||||
|
||||
_api.AddMenuOption(menu, "Heal Player", _ =>
|
||||
{
|
||||
target.SetHp(100);
|
||||
});
|
||||
|
||||
_api.AddMenuOption(menu, "Admin Only Option", _ =>
|
||||
{
|
||||
// Admin action
|
||||
}, false, "@css/root");
|
||||
|
||||
return menu;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### AddSubMenu
|
||||
|
||||
Add a submenu option that opens another menu.
|
||||
|
||||
```csharp
|
||||
void AddSubMenu(
|
||||
object menu,
|
||||
string name,
|
||||
Func<CCSPlayerController, object> subMenuFactory,
|
||||
bool disabled = false,
|
||||
string? permission = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `menu` - Parent menu
|
||||
- `name` - Submenu option display text
|
||||
- `subMenuFactory` - Function that creates the submenu
|
||||
- `disabled` - Whether option is disabled (default: false)
|
||||
- `permission` - Required permission (optional)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api!.CreateMenuWithBack("Main Menu", "mycategory", admin);
|
||||
|
||||
_api.AddSubMenu(menu, "Player Actions", admin =>
|
||||
{
|
||||
return CreatePlayerActionsMenu(admin);
|
||||
});
|
||||
|
||||
_api.AddSubMenu(menu, "Server Settings", admin =>
|
||||
{
|
||||
return CreateServerSettingsMenu(admin);
|
||||
}, false, "@css/root");
|
||||
|
||||
return menu;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Opening Menus
|
||||
|
||||
### OpenMenu
|
||||
|
||||
Display a menu to a player.
|
||||
|
||||
```csharp
|
||||
void OpenMenu(object menu, CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = CreateMyMenu(player);
|
||||
_api!.OpenMenu(menu, player);
|
||||
```
|
||||
|
||||
**Note:** Usually menus open automatically when selected, but this can be used for direct opening.
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Simple Player Selection Menu
|
||||
|
||||
```csharp
|
||||
private void RegisterMenus()
|
||||
{
|
||||
_api!.RegisterMenuCategory("actions", "Player Actions", "@css/generic");
|
||||
|
||||
_api.RegisterMenu(
|
||||
"actions",
|
||||
"slay",
|
||||
"Slay Player",
|
||||
CreateSlayMenu,
|
||||
"@css/slay"
|
||||
);
|
||||
}
|
||||
|
||||
private object CreateSlayMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.PawnIsAlive && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
target.PlayerPawn?.Value?.CommitSuicide(false, true);
|
||||
admin.PrintToChat($"Slayed {target.PlayerName}");
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nested Menu with Value Selection
|
||||
|
||||
```csharp
|
||||
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateHpValueMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "mycategory", admin);
|
||||
|
||||
var hpValues = new[] { 1, 10, 50, 100, 200, 500 };
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
_api.AddMenuOption(menu, $"{hp} HP", _ =>
|
||||
{
|
||||
if (target.IsValid && target.PawnIsAlive)
|
||||
{
|
||||
target.PlayerPawn?.Value?.SetHealth(hp);
|
||||
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Menu with Permissions
|
||||
|
||||
```csharp
|
||||
private object CreateAdminMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
// Everyone with menu access sees this
|
||||
_api.AddMenuOption(menu, "Basic Action", _ => DoBasicAction());
|
||||
|
||||
// Only root admins see this
|
||||
_api.AddMenuOption(menu, "Dangerous Action", _ =>
|
||||
{
|
||||
DoDangerousAction();
|
||||
}, false, "@css/root");
|
||||
|
||||
// Submenu with permission
|
||||
_api.AddSubMenu(menu, "Advanced Options", admin =>
|
||||
{
|
||||
return CreateAdvancedMenu(admin);
|
||||
}, false, "@css/root");
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dynamic Menu with Current State
|
||||
|
||||
```csharp
|
||||
private object CreateToggleMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// Show current state in option name
|
||||
bool hasGod = GodPlayers.Contains(player.Slot);
|
||||
string status = hasGod ? "✓ ON" : "✗ OFF";
|
||||
|
||||
_api.AddMenuOption(menu, $"{player.PlayerName} ({status})", _ =>
|
||||
{
|
||||
if (hasGod)
|
||||
GodPlayers.Remove(player.Slot);
|
||||
else
|
||||
GodPlayers.Add(player.Slot);
|
||||
|
||||
// Recreate menu to show updated state
|
||||
var newMenu = CreateToggleMenu(admin, context);
|
||||
_api.OpenMenu(newMenu, admin);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use MenuContext
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Uses context
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenu, "@css/generic");
|
||||
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(context, admin, filter, action);
|
||||
}
|
||||
|
||||
// ❌ Bad - Duplicates title and category
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenuOld, "@css/generic");
|
||||
|
||||
private object CreateMenuOld(CCSPlayerController admin)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers("Title", "cat", admin, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Register in OnSimpleAdminReady
|
||||
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Also call directly for hot reload
|
||||
|
||||
private void RegisterMenus()
|
||||
{
|
||||
if (_menusRegistered) return;
|
||||
|
||||
_api!.RegisterMenuCategory("category", "Category Name");
|
||||
_api.RegisterMenu("category", "menu", "Menu Name", CreateMenu);
|
||||
|
||||
_menusRegistered = true;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Always Unregister
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.UnregisterMenu("category", "menu");
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Validate Player State
|
||||
|
||||
```csharp
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && // Player exists
|
||||
!player.IsBot && // Not a bot
|
||||
player.PawnIsAlive && // Alive
|
||||
admin.CanTarget(player), // Can be targeted
|
||||
(admin, target) =>
|
||||
{
|
||||
// Extra validation before action
|
||||
if (!target.IsValid || !target.PawnIsAlive)
|
||||
return;
|
||||
|
||||
DoAction(admin, target);
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Use Translations for Menu Names
|
||||
|
||||
```csharp
|
||||
_api.RegisterMenuCategory(
|
||||
"mycategory",
|
||||
Localizer?["category_name"] ?? "Default Name",
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
_api.RegisterMenu(
|
||||
"mycategory",
|
||||
"mymenu",
|
||||
Localizer?["menu_name"] ?? "Default Menu",
|
||||
CreateMenu
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission Override
|
||||
|
||||
The `commandName` parameter allows server admins to override menu permissions via CounterStrikeSharp's admin system.
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenu(
|
||||
"fun",
|
||||
"god",
|
||||
"God Mode",
|
||||
CreateGodMenu,
|
||||
"@css/cheats", // Default permission
|
||||
"css_god" // Command name for override
|
||||
);
|
||||
```
|
||||
|
||||
**Admin config can override:**
|
||||
```json
|
||||
{
|
||||
"css_god": ["@css/vip"]
|
||||
}
|
||||
```
|
||||
|
||||
Now VIPs will see the God Mode menu instead of requiring @css/cheats!
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Player List with Actions
|
||||
|
||||
```csharp
|
||||
private object CreatePlayerListMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
foreach (var player in _api.GetValidPlayers())
|
||||
{
|
||||
if (!admin.CanTarget(player)) continue;
|
||||
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
var actionMenu = _api.CreateMenuWithBack($"Actions: {player.PlayerName}", context.CategoryId, admin);
|
||||
|
||||
_api.AddMenuOption(actionMenu, "Slay", _ => player.CommitSuicide());
|
||||
_api.AddMenuOption(actionMenu, "Kick", _ => KickPlayer(player));
|
||||
_api.AddMenuOption(actionMenu, "Ban", _ => BanPlayer(admin, player));
|
||||
|
||||
return actionMenu;
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
### Category-Based Organization
|
||||
|
||||
```csharp
|
||||
private void RegisterAllMenus()
|
||||
{
|
||||
// Player management category
|
||||
_api!.RegisterMenuCategory("players", "Player Management", "@css/generic");
|
||||
_api.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
|
||||
_api.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
|
||||
|
||||
// Server management category
|
||||
_api.RegisterMenuCategory("server", "Server Management", "@css/generic");
|
||||
_api.RegisterMenu("server", "map", "Change Map", CreateMapMenu, "@css/changemap");
|
||||
_api.RegisterMenu("server", "settings", "Settings", CreateSettingsMenu, "@css/root");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Commands API](commands)** - Command integration
|
||||
- **[Penalties API](penalties)** - Issue penalties from menus
|
||||
- **[Utilities API](utilities)** - Helper functions for menus
|
||||