Compare commits

...

40 Commits

Author SHA1 Message Date
Dawid Bepierszcz
fe73fa9917 Refactor admin and server loading timing logic
Adjusted timers for admin and server data loading to improve reliability and reduce delays. Admin data is now loaded more promptly on map changes and during command execution. ServerManager now initializes the cache after setting the server ID, and redundant admin reloads have been removed. Version bumped to 1.7.8-beta-10.
2026-01-26 01:19:25 +01:00
Dawid Bepierszcz
bdada2df1e Update VERSION 2026-01-25 14:41:33 +01:00
Dawid Bepierszcz
310a43fcd9 Update CS2-SimpleAdmin.cs 2026-01-25 14:39:01 +01:00
Dawid Bepierszcz
58243e813a Update StatusBlocker plugin binaries
Replaced StatusBlocker-v1.1.4 binaries for both Linux and Windows in the METAMOD PLUGIN directory. This may include bug fixes or improvements in the updated plugin versions.
2026-01-25 14:36:47 +01:00
Dawid Bepierszcz
d53446e0fe Bump version to 1.7.8-beta-8-recompiled
Updated the ModuleVersion string to reflect a recompiled build. No other changes were made.
2026-01-25 14:10:57 +01:00
Dawid Bepierszcz
2404c1bc03 Update dependencies and StatusBlocker plugin version
Upgraded several NuGet packages in CS2-SimpleAdmin and CS2-SimpleAdminApi projects, including CounterStrikeSharp.API, MySqlConnector, System.Linq.Async, and ZLinq. Replaced StatusBlocker v1.1.3 plugin files with v1.1.4 for both Linux and Windows in the StealthModule.
2026-01-25 14:09:12 +01:00
Dawid Bepierszcz
665962565e Update ban logic and StatusBlocker plugin version
Refactored IP ban checking logic in CacheManager for improved accuracy and maintainability. Replaced StatusBlocker plugin binaries with v1.1.3 for both Linux and Windows.

EOL, no more new features
2025-12-13 16:54:33 +01:00
Dawid Bepierszcz
c2e8b4a898 Refactor player cache and ban checks, update version
Improves player cache handling and ban status checks for race condition safety. Removes unused GodPlayers logic and related event handler. Refactors event handlers for disconnect and team changes, and fixes warn reason field naming. Updates version to 1.7.8-beta-7.
2025-12-02 17:37:14 +01:00
Dawid Bepierszcz
9723a4faee :(
:(
2025-11-13 01:40:50 +01:00
Dawid Bepierszcz
4865b76262 Improve ban cache sync and update dependencies
Enhanced CacheManager to use database time for ban updates, improving multi-server consistency and handling of status changes. Increased PlayerManager's semaphore limit and improved player/bans refresh logic to ensure status changes are detected even when the server is empty. Updated author and version metadata, commented out duplicate event registration, and bumped CounterStrikeSharp.API dependency to 1.0.346.
2025-11-13 01:33:38 +01:00
Dawid Bepierszcz
0dded66e5d Fix closure issues in menus and update dependencies
Captured player and duration variables in menu callbacks to prevent closure-related bugs. Updated package versions in project files and bumped plugin version to 1.7.8-beta-5. Improved player validation and message localization logic.
2025-11-06 02:24:43 +01:00
Dawid Bepierszcz
038641dbdf Comment out MySQL index migration and remove Sqlite optimization
Commented out all index creation statements in the MySQL migration 016 for table and index optimization. Removed the corresponding Sqlite migration 016 entirely. Also replaced TRUNCATE TABLE with DELETE FROM in Sqlite migration 013 for sa_players_ips to improve compatibility.
2025-10-30 18:17:47 +01:00
Dawid Bepierszcz
a03964c08a Add per-player menu localization and refactor menus
Introduces per-player localization for menu categories and items using translation keys and IStringLocalizer, allowing modules and the main plugin to display menu names in the player's language. Refactors menu registration and builder logic to use translation keys, updates API and documentation, and adds database provider upsert query abstraction for player IPs. Also updates version to 1.7.8-beta-4 and corrects a translation string typo.
2025-10-30 01:41:08 +01:00
Dawid Bepierszcz
b0d8696756 Add CS2-SimpleAdmin documentation site
Introduces a new documentation site for CS2-SimpleAdmin using Docusaurus, including developer API references, tutorials, user guides, and module documentation. Removes the CleanModule example and updates FunCommands and ExampleModule. Also updates main plugin and API files to support new documentation and module structure.
2025-10-20 01:27:01 +02:00
Dawid Bepierszcz
21a5de6b3d Update StatusBlocker plugin to v1.1.0
Replaced StatusBlocker v1.0.9 binaries with v1.1.0 for both Linux and Windows in the METAMOD PLUGIN directory.
2025-10-19 18:01:41 +02:00
Dawid Bepierszcz
718536eaef Update player sound effects and StatusBlocker plugin
Changed sound effects for player suicide and slap commands in playercommands.cs. Replaced StatusBlocker v1.0.7 plugin files with v1.0.9 for both Linux and Windows in the StealthModule.
2025-10-19 16:53:17 +02:00
Dawid Bepierszcz
099e91b43b Update FunCommands module project path in build workflow
Corrected the PROJECT_PATH_FUNCOMMANDSMODULE environment variable to point to the new location of the CS2-SimpleAdmin_FunCommands.csproj file within the build workflow configuration.
2025-10-19 16:08:45 +02:00
Dawid Bepierszcz
206c18db66 Add permission override support for menus
Menus can now specify a command name for permission override checking, allowing server admins to control menu visibility via CounterStrikeSharp's admin system. Updated API, MenuManager, and FunCommands module to support this feature. Also updated slap command to emit a sound, fixed SQL migration for IP address type, and bumped version to 1.7.8-beta-2.
2025-10-19 16:06:03 +02:00
Dawid Bepierszcz
78318102fe Refactor fun commands to external module
Commented out fun command implementations (noclip, godmode, freeze, unfreeze, resize) in funcommands.cs and removed their registration from RegisterCommands.cs. These commands are now intended to be provided by the new CS2-SimpleAdmin_FunCommands external module, improving modularity and maintainability.
2025-10-19 03:12:58 +02:00
Dawid Bepierszcz
2edacc2b3f Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2025-10-03 12:10:52 +02:00
Dawid Bepierszcz
e1e66441f2 Remove custom ClearBuildFiles target and cleanup csproj
Commented out the GenerateDependencyFile property and removed the ClearBuildFiles target from the project file. Also reformatted and cleaned up the ItemGroup for migration scripts, improving maintainability.
2025-10-03 12:10:50 +02:00
Dawid Bepierszcz
cc54b9e879 Update README.md 2025-10-03 02:35:45 +02:00
Dawid Bepierszcz
640e618f3b Update README.md 2025-10-03 02:34:03 +02:00
Dawid Bepierszcz
23d174c4a5 Update build.yml 2025-10-03 02:26:07 +02:00
Dawid Bepierszcz
b7371adf26 Update build.yml 2025-10-03 02:23:07 +02:00
Dawid Bepierszcz
9154748ce6 Update build.yml 2025-10-03 02:19:05 +02:00
Dawid Bepierszcz
da9830ee05 Bump version to 1.7.7-alpha-10 and update release workflow
Updated the version to 1.7.7-alpha-10 in both the code and VERSION file. Fixed artifact naming and release name in the GitHub Actions workflow. Also, modified Helper.cs to return DateTime.UtcNow in ActualDateTime().
2025-10-03 02:16:49 +02:00
Dawid Bepierszcz
b41ac992c0 Update README.md 2025-10-03 02:13:51 +02:00
Dawid Bepierszcz
af0bda8f3a Update README.md 2025-10-03 02:04:10 +02:00
Dawid Bepierszcz
e2529cd646 Update build.yml 2025-10-03 01:44:23 +02:00
Dawid Bepierszcz
9d2cd34845 Update build.yml 2025-10-03 01:41:48 +02:00
Dawid Bepierszcz
5701455de0 Refactor database layer and add module/plugin improvements
Reworked the database layer to support both MySQL and SQLite via new provider classes and migration scripts for each backend. Updated the build workflow to support building and packaging additional modules, including StealthModule and BanSoundModule, and improved artifact handling. Refactored command registration to allow dynamic registration/unregistration and improved API event handling. Updated dependencies, project structure, and configuration checks for better reliability and extensibility. Added new language files, updated versioning, and removed obsolete files.

**⚠️ Warning: SQLite support is currently experimental.
Using this version requires reconfiguration of your database settings!
Plugin now uses UTC time. Please adjust your configurations accordingly!
**
2025-10-03 01:37:03 +02:00
Dawid Bepierszcz
b97426313b 1.7.7-alpha
- Fixed steamid only bans
- Added missing multiservermode
- Better caching
2025-05-25 01:00:14 +02:00
Dawid Bepierszcz
3ab63c05db 1.7.7-alpha-small-optimizations
- Clear bans cache on plugin reload
- Changed ip history to int
2025-05-23 03:28:10 +02:00
Dawid Bepierszcz
f654d6b085 1.7.7-alpha-connection-fix
- Fixed lag when player connecting
- Added command to force refresh ban cache (css_reloadbans)
2025-05-21 13:11:01 +02:00
Dawid Bepierszcz
676a18d9b4 1.7.7-alpha-associated-accounts (missing migration) 2025-05-21 03:13:05 +02:00
Dawid Bepierszcz
d75a092047 1.7.7-alpha-associated-accounts
- Added possibility to view player associated account (by ip)
2025-05-21 03:08:02 +02:00
Dawid Bepierszcz
d34ca64970 1.7.7-alpha-fix
- Added missing migration
- Added missing ignoredips
2025-05-21 00:38:54 +02:00
Dawid Bepierszcz
f69f1277f8 1.7.7-alpha
- Fixed (@all,@team,@lifestate) spam with discord webhook
- Currently `small` actions for ban optimizations
2025-05-20 23:51:59 +02:00
Dawid Bepierszcz
b6c876d709 1.7.6a
- Changed PawnIsAlive to LifeState
- Changed AdminCache - now it only removes flags added in the database - not all assigned to player
- Added config variable `IgnoredIps` to ignore ip check on connect, useful when proxying
2025-03-25 11:39:00 +01:00
209 changed files with 44876 additions and 4147 deletions

View File

@@ -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
View File

@@ -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
View 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*

View 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.

View 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

View 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

View 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