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

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

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

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

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

|
|
||||||
|
|
||||||
## Build your localized site
|
|
||||||
|
|
||||||
Build your site for a specific locale:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build -- --locale fr
|
|
||||||
```
|
|
||||||
|
|
||||||
Or build your site to include all the locales at once:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
# Ban Commands
|
|
||||||
|
|
||||||
Commands for managing player bans.
|
|
||||||
|
|
||||||
## Ban Player
|
|
||||||
|
|
||||||
Ban a player currently on the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_ban <#userid or name> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/ban`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_ban @all 60 "Timeout for everyone"
|
|
||||||
css_ban #123 1440 "Hacking - 1 day ban"
|
|
||||||
css_ban PlayerName 0 "Permanent ban for cheating"
|
|
||||||
css_ban @ct 30 "CT team timeout"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Time in minutes (0 = permanent)
|
|
||||||
- Supports player targeting (@all, @ct, @t, #userid, name)
|
|
||||||
- Reason is optional but recommended
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Add Ban (Offline Player)
|
|
||||||
|
|
||||||
Ban a player by SteamID even if they're not online.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_addban <steamid> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/ban`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_addban STEAM_1:0:12345678 1440 "Ban evasion"
|
|
||||||
css_addban 76561198012345678 10080 "Hacking - 7 day ban"
|
|
||||||
css_addban STEAM_1:1:87654321 0 "Permanent ban"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Supported SteamID formats:**
|
|
||||||
- SteamID64: `76561198012345678`
|
|
||||||
- SteamID: `STEAM_1:0:12345678`
|
|
||||||
- SteamID3: `[U:1:12345678]`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ban IP Address
|
|
||||||
|
|
||||||
Ban an IP address.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_banip <ip> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/ban`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_banip 192.168.1.100 1440 "Ban evasion attempt"
|
|
||||||
css_banip 10.0.0.5 0 "Persistent troublemaker"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Useful for preventing ban evasion
|
|
||||||
- Can be combined with SteamID bans
|
|
||||||
- Check config for `BanType` setting (SteamID, IP, or Both)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Unban Player
|
|
||||||
|
|
||||||
Remove a ban from a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_unban <steamid or name or ip> [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/unban`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_unban 76561198012345678 "Appeal accepted"
|
|
||||||
css_unban STEAM_1:0:12345678 "Ban lifted"
|
|
||||||
css_unban 192.168.1.100 "Wrong person banned"
|
|
||||||
css_unban PlayerName "Mistake"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Works with SteamID, IP, or player name
|
|
||||||
- Unban reason is logged
|
|
||||||
- Can unban offline players
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Warn Player
|
|
||||||
|
|
||||||
Issue a warning to a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_warn <#userid or name> [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_warn #123 "Mic spam"
|
|
||||||
css_warn PlayerName "Language"
|
|
||||||
css_warn @all "Final warning"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Warnings can accumulate
|
|
||||||
- Auto-escalation to bans based on `WarnThreshold` config
|
|
||||||
- Example: 3 warnings = 1 hour ban, 4 warnings = 2 hour ban
|
|
||||||
|
|
||||||
**Warning Threshold Configuration:**
|
|
||||||
```json
|
|
||||||
"WarnThreshold": {
|
|
||||||
"3": "css_addban STEAMID64 60 \"3 warnings\"",
|
|
||||||
"4": "css_ban #USERID 120 \"4 warnings\""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Unwarn Player
|
|
||||||
|
|
||||||
Remove a warning from a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_unwarn <steamid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_unwarn 76561198012345678
|
|
||||||
css_unwarn PlayerName
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Removes the most recent warning
|
|
||||||
- Helps manage warning thresholds
|
|
||||||
- Can be used for offline players
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Permission Requirements
|
|
||||||
|
|
||||||
| Command | Required Permission | Description |
|
|
||||||
|---------|-------------------|-------------|
|
|
||||||
| `css_ban` | `@css/ban` | Ban online players |
|
|
||||||
| `css_addban` | `@css/ban` | Ban offline players by SteamID |
|
|
||||||
| `css_banip` | `@css/ban` | Ban IP addresses |
|
|
||||||
| `css_unban` | `@css/unban` | Remove bans |
|
|
||||||
| `css_warn` | `@css/kick` | Issue warnings |
|
|
||||||
| `css_unwarn` | `@css/kick` | Remove warnings |
|
|
||||||
|
|
||||||
## Ban Types
|
|
||||||
|
|
||||||
Configure ban behavior in `CS2-SimpleAdmin.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"BanType": 1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `1` - SteamID only (default)
|
|
||||||
- `2` - IP only
|
|
||||||
- `3` - Both SteamID and IP
|
|
||||||
|
|
||||||
## Time Durations
|
|
||||||
|
|
||||||
Common time values:
|
|
||||||
|
|
||||||
| Duration | Minutes | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| 1 minute | 1 | Very short timeout |
|
|
||||||
| 5 minutes | 5 | Short timeout |
|
|
||||||
| 15 minutes | 15 | Medium timeout |
|
|
||||||
| 1 hour | 60 | Standard timeout |
|
|
||||||
| 1 day | 1440 | Daily ban |
|
|
||||||
| 1 week | 10080 | Weekly ban |
|
|
||||||
| 2 weeks | 20160 | Bi-weekly ban |
|
|
||||||
| 1 month | 43200 | Monthly ban |
|
|
||||||
| Permanent | 0 | Never expires |
|
|
||||||
|
|
||||||
## Player Targeting
|
|
||||||
|
|
||||||
All ban commands support advanced targeting:
|
|
||||||
|
|
||||||
- `@all` - Target all players
|
|
||||||
- `@ct` - Target all Counter-Terrorists
|
|
||||||
- `@t` - Target all Terrorists
|
|
||||||
- `@spec` - Target all spectators
|
|
||||||
- `#123` - Target by userid
|
|
||||||
- `PlayerName` - Target by name (partial match)
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Banning
|
|
||||||
|
|
||||||
1. **Always provide a reason** - Helps with appeals and record keeping
|
|
||||||
2. **Use appropriate durations** - Don't permaban for minor offenses
|
|
||||||
3. **Check ban history** - Use `css_who` to see if player has priors
|
|
||||||
4. **Consider warnings first** - Give players a chance to improve
|
|
||||||
|
|
||||||
### Warning System
|
|
||||||
|
|
||||||
1. **Be consistent** - Use warnings for minor offenses
|
|
||||||
2. **Configure thresholds** - Set up auto-escalation in config
|
|
||||||
3. **Communicate clearly** - Let players know why they're warned
|
|
||||||
4. **Review regularly** - Check warning history with `css_warns`
|
|
||||||
|
|
||||||
### Multi-Account Detection
|
|
||||||
|
|
||||||
When `CheckMultiAccountsByIp` is enabled:
|
|
||||||
- Plugin detects multiple accounts from same IP
|
|
||||||
- Sends Discord notifications if configured
|
|
||||||
- Helps identify ban evasion
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Ban doesn't work
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Do you have `@css/ban` permission?
|
|
||||||
- Is the SteamID format correct?
|
|
||||||
- Check server console for errors
|
|
||||||
|
|
||||||
### Player rejoins after ban
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Is `MultiServerMode` enabled if using multiple servers?
|
|
||||||
- Is the database shared across servers?
|
|
||||||
- Check ban type configuration (SteamID vs IP)
|
|
||||||
|
|
||||||
### Warning threshold not working
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Is `WarnThreshold` configured correctly?
|
|
||||||
- Are the command formats correct in config?
|
|
||||||
- Check server console for execution errors
|
|
||||||
|
|
||||||
## Related Commands
|
|
||||||
|
|
||||||
- **[Communication Commands](basecomms)** - Mute, gag, silence
|
|
||||||
- **[Player Commands](playercommands)** - Kick, slay, etc.
|
|
||||||
- **[Base Commands](basecommands)** - Admin management
|
|
||||||
@@ -1,442 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 4
|
|
||||||
---
|
|
||||||
|
|
||||||
# Chat Commands
|
|
||||||
|
|
||||||
Admin chat and messaging commands.
|
|
||||||
|
|
||||||
## Admin Chat
|
|
||||||
|
|
||||||
### Admin Say (Private)
|
|
||||||
|
|
||||||
Send a message to all online admins only.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_asay <message>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Only admins see the message
|
|
||||||
- Useful for admin coordination
|
|
||||||
- Colored differently from regular chat
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_asay "Player is suspicious, keep an eye on them"
|
|
||||||
css_asay "I'm going AFK for 5 minutes"
|
|
||||||
css_asay "Need help with a situation"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Message format:**
|
|
||||||
```
|
|
||||||
[ADMIN] YourName: message
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Public Announcements
|
|
||||||
|
|
||||||
### CSS Say (Colored)
|
|
||||||
|
|
||||||
Send a colored message to all players.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_cssay <message>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Colorful formatted message
|
|
||||||
- Visible to all players
|
|
||||||
- Stands out from regular chat
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_cssay "Server will restart in 5 minutes!"
|
|
||||||
css_cssay "Welcome to our server!"
|
|
||||||
css_cssay "Rules: No cheating, be respectful"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Say (With Prefix)
|
|
||||||
|
|
||||||
Send a message to all players with admin prefix.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_say <message>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Message shows with "(ADMIN)" prefix
|
|
||||||
- Visible to all players
|
|
||||||
- Authority message format
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_say "Please be respectful in chat"
|
|
||||||
css_say "Cheating will result in permanent ban"
|
|
||||||
css_say "Type !rules for server rules"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Message format:**
|
|
||||||
```
|
|
||||||
(ADMIN) YourName: message
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Private Say (Whisper)
|
|
||||||
|
|
||||||
Send a private message to a specific player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_psay <#userid or name> <message>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Only the target player sees the message
|
|
||||||
- Useful for private warnings or help
|
|
||||||
- Doesn't clutter public chat
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_psay #123 "Please stop mic spamming"
|
|
||||||
css_psay PlayerName "You need to join a team"
|
|
||||||
css_psay @all "This is a private message to everyone"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Target receives:**
|
|
||||||
```
|
|
||||||
(ADMIN to you) AdminName: message
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Center Say
|
|
||||||
|
|
||||||
Display a message in the center of all players' screens.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_csay <message>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Large text in center of screen
|
|
||||||
- Impossible to miss
|
|
||||||
- Useful for important announcements
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_csay "ROUND STARTS IN 10 SECONDS"
|
|
||||||
css_csay "FREEZE! Don't move!"
|
|
||||||
css_csay "Server restarting NOW"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Display:**
|
|
||||||
- Shows in center of screen
|
|
||||||
- Large, bold text
|
|
||||||
- Auto-fades after a few seconds
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### HUD Say
|
|
||||||
|
|
||||||
Display a message on players' HUD (screen overlay).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_hsay <message>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Message appears on screen overlay
|
|
||||||
- Less intrusive than center say
|
|
||||||
- Stays visible longer
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_hsay "Tournament starting soon!"
|
|
||||||
css_hsay "New map voting available"
|
|
||||||
css_hsay "Visit our website: example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Announcements
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Server restart warning
|
|
||||||
css_cssay "Server will restart in 10 minutes! Save your progress!"
|
|
||||||
|
|
||||||
# Center screen countdown
|
|
||||||
css_csay "5"
|
|
||||||
# Wait...
|
|
||||||
css_csay "4"
|
|
||||||
css_csay "3"
|
|
||||||
css_csay "2"
|
|
||||||
css_csay "1"
|
|
||||||
css_csay "GO!"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Player Communication
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Private warning
|
|
||||||
css_psay PlayerName "This is your first warning for chat spam"
|
|
||||||
|
|
||||||
# Public announcement
|
|
||||||
css_say "Everyone please be quiet for the next round"
|
|
||||||
|
|
||||||
# Admin coordination
|
|
||||||
css_asay "I'm spectating the suspicious player in T spawn"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Management
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Tournament announcement
|
|
||||||
css_cssay "⚠ TOURNAMENT STARTING IN 5 MINUTES ⚠"
|
|
||||||
css_hsay "Teams, please get ready!"
|
|
||||||
css_csay "TOURNAMENT BEGINS NOW!"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Color Codes
|
|
||||||
|
|
||||||
Many chat commands support color codes:
|
|
||||||
|
|
||||||
```
|
|
||||||
{default} - Default chat color
|
|
||||||
{white} - White
|
|
||||||
{darkred} - Dark red
|
|
||||||
{green} - Green
|
|
||||||
{lightyellow} - Light yellow
|
|
||||||
{lightblue} - Light blue
|
|
||||||
{olive} - Olive
|
|
||||||
{lime} - Lime
|
|
||||||
{red} - Red
|
|
||||||
{purple} - Purple
|
|
||||||
{grey} - Grey
|
|
||||||
{yellow} - Yellow
|
|
||||||
{gold} - Gold
|
|
||||||
{silver} - Silver
|
|
||||||
{blue} - Blue
|
|
||||||
{darkblue} - Dark blue
|
|
||||||
{bluegrey} - Blue grey
|
|
||||||
{magenta} - Magenta
|
|
||||||
{lightred} - Light red
|
|
||||||
{orange} - Orange
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
css_say "{red}WARNING: {default}No camping in spawn!"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Message Targeting
|
|
||||||
|
|
||||||
Some commands support player targeting for private messages:
|
|
||||||
|
|
||||||
### Supported Targets
|
|
||||||
|
|
||||||
- `@all` - All players (private message to each)
|
|
||||||
- `@ct` - All Counter-Terrorists
|
|
||||||
- `@t` - All Terrorists
|
|
||||||
- `@spec` - All spectators
|
|
||||||
- `#123` - Specific userid
|
|
||||||
- `PlayerName` - Player by name
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
# Private message to all CTs
|
|
||||||
css_psay @ct "Defend bombsite A this round"
|
|
||||||
|
|
||||||
# Private message to all terrorists
|
|
||||||
css_psay @t "Rush B with smoke and flash"
|
|
||||||
|
|
||||||
# Message to spectators
|
|
||||||
css_psay @spec "Type !join to play"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### When to Use Each Command
|
|
||||||
|
|
||||||
**css_asay** (Admin Say):
|
|
||||||
- Admin coordination
|
|
||||||
- Discussing player behavior
|
|
||||||
- Planning admin actions
|
|
||||||
- Private admin discussions
|
|
||||||
|
|
||||||
**css_cssay** (Colored Say):
|
|
||||||
- Important server announcements
|
|
||||||
- Event notifications
|
|
||||||
- Eye-catching messages
|
|
||||||
- Server information
|
|
||||||
|
|
||||||
**css_say** (Say):
|
|
||||||
- General admin announcements
|
|
||||||
- Rule reminders
|
|
||||||
- Warnings to all players
|
|
||||||
- Admin presence
|
|
||||||
|
|
||||||
**css_psay** (Private Say):
|
|
||||||
- Private warnings
|
|
||||||
- Individual help
|
|
||||||
- Direct player communication
|
|
||||||
- Discretion needed
|
|
||||||
|
|
||||||
**css_csay** (Center Say):
|
|
||||||
- Emergency announcements
|
|
||||||
- Cannot-miss messages
|
|
||||||
- Round starts/events
|
|
||||||
- Countdowns
|
|
||||||
|
|
||||||
**css_hsay** (HUD Say):
|
|
||||||
- Persistent information
|
|
||||||
- Less urgent announcements
|
|
||||||
- Server info
|
|
||||||
- Website/Discord links
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Communication Guidelines
|
|
||||||
|
|
||||||
### Professional Communication
|
|
||||||
|
|
||||||
1. **Be clear and concise** - Don't spam long messages
|
|
||||||
2. **Use appropriate command** - Don't center-spam trivial messages
|
|
||||||
3. **Check for typos** - You represent the server
|
|
||||||
4. **Avoid excessive colors** - Can be hard to read
|
|
||||||
|
|
||||||
### Spam Prevention
|
|
||||||
|
|
||||||
1. **Don't overuse center say** - Very intrusive for players
|
|
||||||
2. **Space out announcements** - Don't flood chat
|
|
||||||
3. **Use HUD say for persistent info** - Less annoying
|
|
||||||
4. **Coordinate with other admins** - Avoid duplicate messages
|
|
||||||
|
|
||||||
### Effective Messaging
|
|
||||||
|
|
||||||
**Good Examples:**
|
|
||||||
```bash
|
|
||||||
css_cssay "🎯 New map voting system available! Type !mapvote"
|
|
||||||
css_psay PlayerName "Hey! Please enable your mic or use team chat"
|
|
||||||
css_asay "Checking player demos for possible aimbot"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Poor Examples:**
|
|
||||||
```bash
|
|
||||||
css_csay "hi" # Don't use center say for trivial messages
|
|
||||||
css_cssay "a" "b" "c" "d" # Don't spam center messages
|
|
||||||
css_say "asdfasdfasdf" # Unprofessional
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Silent Mode
|
|
||||||
|
|
||||||
Admins can use silent mode to hide their activity:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_hide # Toggle silent mode
|
|
||||||
```
|
|
||||||
|
|
||||||
When in silent mode:
|
|
||||||
- Chat messages still work
|
|
||||||
- Admin name might be hidden (depends on config)
|
|
||||||
- Useful for undercover moderation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Show Activity Type
|
|
||||||
|
|
||||||
Controls how admin actions are displayed:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"ShowActivityType": 2
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `0` - Hide all admin activity
|
|
||||||
- `1` - Anonymous ("An admin says...")
|
|
||||||
- `2` - Show name ("AdminName says...")
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Chat Restrictions
|
|
||||||
|
|
||||||
### Respecting Gags
|
|
||||||
|
|
||||||
Remember that:
|
|
||||||
- `css_asay` works even if admin is gagged (admin chat)
|
|
||||||
- Other commands respect communication penalties
|
|
||||||
- Can't use chat commands while silenced
|
|
||||||
|
|
||||||
### Permission Requirements
|
|
||||||
|
|
||||||
All chat commands require `@css/chat` permission:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_addadmin STEAMID "Name" "@css/chat" 50 0
|
|
||||||
```
|
|
||||||
|
|
||||||
Or add to a group with chat permission:
|
|
||||||
```bash
|
|
||||||
css_addgroup "#moderators" "@css/chat,@css/kick" 50
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Messages not showing
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Do you have `@css/chat` permission?
|
|
||||||
- Are you silenced/gagged?
|
|
||||||
- Check console for errors
|
|
||||||
- Verify player is connected (for css_psay)
|
|
||||||
|
|
||||||
### Colors not working
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Use correct color code syntax: `{red}`
|
|
||||||
- Some commands may not support colors
|
|
||||||
- Different chat systems handle colors differently
|
|
||||||
|
|
||||||
### Players can't see center/HUD messages
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- CS2 client-side chat settings
|
|
||||||
- Conflicting HUD plugins
|
|
||||||
- Server console for errors
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Commands
|
|
||||||
|
|
||||||
- **[Communication Commands](basecomms)** - Gag, mute, silence players
|
|
||||||
- **[Base Commands](basecommands)** - Admin management
|
|
||||||
- **[Ban Commands](basebans)** - Player punishment
|
|
||||||
@@ -1,626 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Base Commands
|
|
||||||
|
|
||||||
Core admin commands for server management and admin system.
|
|
||||||
|
|
||||||
## Player Information
|
|
||||||
|
|
||||||
### Show Penalties
|
|
||||||
|
|
||||||
View your own active penalties.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_penalties
|
|
||||||
css_mypenalties
|
|
||||||
css_comms
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** None (all players)
|
|
||||||
|
|
||||||
**Shows:**
|
|
||||||
- Active bans
|
|
||||||
- Active communication restrictions (gag, mute, silence)
|
|
||||||
- Warning count
|
|
||||||
- Duration remaining
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Hide Penalty Notifications
|
|
||||||
|
|
||||||
Hide penalty notifications when you connect to the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_hidecomms
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Toggle on/off
|
|
||||||
- Admins won't see penalty notifications on join
|
|
||||||
- Useful for admin privacy
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Admin Menu
|
|
||||||
|
|
||||||
### Open Admin Menu
|
|
||||||
|
|
||||||
Opens the main admin menu interface.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_admin
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/generic`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Player management
|
|
||||||
- Server management
|
|
||||||
- Ban/kick/mute players via menu
|
|
||||||
- Map changing
|
|
||||||
- Custom server commands
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Admin Help
|
|
||||||
|
|
||||||
Print the admin help file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_adminhelp
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/generic`
|
|
||||||
|
|
||||||
**Shows:**
|
|
||||||
- Available commands for your permission level
|
|
||||||
- Command syntax
|
|
||||||
- Permission requirements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Admin Management
|
|
||||||
|
|
||||||
### Add Admin
|
|
||||||
|
|
||||||
Add a new admin to the database.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_addadmin <steamid> <name> <flags/groups> <immunity> <duration>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/root`
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `steamid` - Player's SteamID (any format)
|
|
||||||
- `name` - Admin name (for identification)
|
|
||||||
- `flags/groups` - Permission flags or group name
|
|
||||||
- `immunity` - Immunity level (0-100, higher = more protection)
|
|
||||||
- `duration` - Duration in minutes (0 = permanent)
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
# Add permanent admin with root access
|
|
||||||
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0
|
|
||||||
|
|
||||||
# Add moderator for 30 days
|
|
||||||
css_addadmin STEAM_1:0:12345678 "ModName" "@css/kick,@css/ban" 50 43200
|
|
||||||
|
|
||||||
# Add admin using group
|
|
||||||
css_addadmin 76561198012345678 "AdminName" "#moderators" 60 0
|
|
||||||
|
|
||||||
# Add admin to all servers (-g flag)
|
|
||||||
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0 -g
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flags:**
|
|
||||||
- `-g` - Add to all servers (global admin)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Delete Admin
|
|
||||||
|
|
||||||
Remove an admin from the database.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_deladmin <steamid>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/root`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
# Remove admin from current server
|
|
||||||
css_deladmin 76561198012345678
|
|
||||||
|
|
||||||
# Remove admin from all servers (-g flag)
|
|
||||||
css_deladmin 76561198012345678 -g
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Add Admin Group
|
|
||||||
|
|
||||||
Create a new admin group.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_addgroup <group_name> <flags> <immunity>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/root`
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `group_name` - Name of the group (e.g., "#moderators")
|
|
||||||
- `flags` - Permission flags for the group
|
|
||||||
- `immunity` - Default immunity level for group members
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
# Create moderator group
|
|
||||||
css_addgroup "#moderators" "@css/kick,@css/ban,@css/chat" 50
|
|
||||||
|
|
||||||
# Create VIP group
|
|
||||||
css_addgroup "#vip" "@css/vip" 10
|
|
||||||
|
|
||||||
# Create global group (-g flag)
|
|
||||||
css_addgroup "#owner" "@css/root" 99 -g
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flags:**
|
|
||||||
- `-g` - Create group on all servers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Delete Admin Group
|
|
||||||
|
|
||||||
Remove an admin group from the database.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_delgroup <group_name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/root`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
# Delete group from current server
|
|
||||||
css_delgroup "#moderators"
|
|
||||||
|
|
||||||
# Delete group from all servers (-g flag)
|
|
||||||
css_delgroup "#moderators" -g
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Reload Admins
|
|
||||||
|
|
||||||
Reload admin permissions from the database.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_reloadadmins
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/root`
|
|
||||||
|
|
||||||
**When to use:**
|
|
||||||
- After adding/removing admins via database
|
|
||||||
- After modifying admin permissions
|
|
||||||
- After group changes
|
|
||||||
- Troubleshooting permission issues
|
|
||||||
|
|
||||||
**Note:** Admins are automatically reloaded periodically and on map change (if configured).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Player Information
|
|
||||||
|
|
||||||
### Hide in Scoreboard
|
|
||||||
|
|
||||||
Toggle admin stealth mode (hide from scoreboard).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_hide
|
|
||||||
css_stealth
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Hides you from the scoreboard
|
|
||||||
- Makes admin actions anonymous
|
|
||||||
- Useful for undercover moderation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Who is This Player
|
|
||||||
|
|
||||||
Show detailed information about a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_who <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/generic`
|
|
||||||
|
|
||||||
**Shows:**
|
|
||||||
- Player name and SteamID
|
|
||||||
- IP address (if you have `@css/showip` permission)
|
|
||||||
- Connection time
|
|
||||||
- Active penalties
|
|
||||||
- Warning count
|
|
||||||
- Ban history
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_who #123
|
|
||||||
css_who PlayerName
|
|
||||||
css_who @me
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Show Disconnected Players
|
|
||||||
|
|
||||||
Show recently disconnected players.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_disconnected
|
|
||||||
css_last
|
|
||||||
css_last10 # Show last 10 (config value)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Shows:**
|
|
||||||
- Player name
|
|
||||||
- SteamID
|
|
||||||
- Disconnect time
|
|
||||||
- Disconnect reason
|
|
||||||
|
|
||||||
**Configuration:**
|
|
||||||
```json
|
|
||||||
"DisconnectedPlayersHistoryCount": 10
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Show Warns for Player
|
|
||||||
|
|
||||||
Open warn list for a specific player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_warns <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Shows:**
|
|
||||||
- All warnings for the player
|
|
||||||
- Warning reasons
|
|
||||||
- Admins who issued warnings
|
|
||||||
- Warning timestamps
|
|
||||||
- Total warning count
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_warns #123
|
|
||||||
css_warns PlayerName
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Show Online Players
|
|
||||||
|
|
||||||
Show information about all online players.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_players
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/generic`
|
|
||||||
|
|
||||||
**Shows:**
|
|
||||||
- List of all connected players
|
|
||||||
- UserIDs
|
|
||||||
- Names
|
|
||||||
- Teams
|
|
||||||
- Connection status
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Server Management
|
|
||||||
|
|
||||||
### Kick Player
|
|
||||||
|
|
||||||
Kick a player from the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_kick <#userid or name> [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_kick #123 "AFK"
|
|
||||||
css_kick PlayerName "Rule violation"
|
|
||||||
css_kick @spec "Cleaning spectators"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Configuration:**
|
|
||||||
```json
|
|
||||||
"KickTime": 5
|
|
||||||
```
|
|
||||||
Delay in seconds before kicking (allows player to see the reason).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Change Map
|
|
||||||
|
|
||||||
Change to a different map.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_map <mapname>
|
|
||||||
css_changemap <mapname>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/changemap`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_map de_dust2
|
|
||||||
css_changemap de_mirage
|
|
||||||
```
|
|
||||||
|
|
||||||
**Configuration:**
|
|
||||||
```json
|
|
||||||
"DefaultMaps": [
|
|
||||||
"de_dust2",
|
|
||||||
"de_mirage",
|
|
||||||
"de_inferno"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Maps in this list appear in the map change menu.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Change Workshop Map
|
|
||||||
|
|
||||||
Change to a workshop map by ID or name.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_wsmap <name or id>
|
|
||||||
css_changewsmap <name or id>
|
|
||||||
css_workshop <name or id>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/changemap`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_wsmap 123456789
|
|
||||||
css_wsmap aim_map
|
|
||||||
```
|
|
||||||
|
|
||||||
**Configuration:**
|
|
||||||
```json
|
|
||||||
"WorkshopMaps": {
|
|
||||||
"aim_map": "123456789",
|
|
||||||
"surf_map": "987654321"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Maps configured here can be changed by name instead of ID.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Change CVar
|
|
||||||
|
|
||||||
Change a server console variable.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_cvar <cvar> <value>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/cvar`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_cvar sv_cheats 1
|
|
||||||
css_cvar mp_roundtime 5
|
|
||||||
css_cvar mp_maxmoney 16000
|
|
||||||
```
|
|
||||||
|
|
||||||
**Warning:** This is a powerful command. Only grant to trusted admins.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Execute RCON Command
|
|
||||||
|
|
||||||
Execute any command as the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_rcon <command>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/rcon`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_rcon status
|
|
||||||
css_rcon changelevel de_dust2
|
|
||||||
css_rcon sv_cheats 1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Warning:** Extremely powerful command. Only grant to server owners.
|
|
||||||
|
|
||||||
**Configuration:**
|
|
||||||
```json
|
|
||||||
"DisableDangerousCommands": true
|
|
||||||
```
|
|
||||||
|
|
||||||
When enabled, prevents execution of dangerous commands via css_rcon.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Restart Game
|
|
||||||
|
|
||||||
Restart the current game/round.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_rr
|
|
||||||
css_rg
|
|
||||||
css_restart
|
|
||||||
css_restartgame
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/generic`
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Restarts the current round
|
|
||||||
- Score is reset
|
|
||||||
- Players remain connected
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Permission Flags
|
|
||||||
|
|
||||||
Common permission flags used in CS2-SimpleAdmin:
|
|
||||||
|
|
||||||
| Flag | Description | Common Use |
|
|
||||||
|------|-------------|------------|
|
|
||||||
| `@css/generic` | Generic admin access | Basic admin menu, info commands |
|
|
||||||
| `@css/chat` | Chat management | Gag, mute, silence |
|
|
||||||
| `@css/kick` | Kick players | Kick, warnings, player info |
|
|
||||||
| `@css/ban` | Ban players | Ban, banip, addban |
|
|
||||||
| `@css/unban` | Unban players | Remove bans |
|
|
||||||
| `@css/permban` | Permanent bans | Issue permanent bans |
|
|
||||||
| `@css/changemap` | Change maps | Map changing |
|
|
||||||
| `@css/cvar` | Change cvars | Server variable modification |
|
|
||||||
| `@css/rcon` | Execute rcon | Full server control |
|
|
||||||
| `@css/root` | Root access | All permissions, admin management |
|
|
||||||
| `@css/slay` | Slay/respawn | Player manipulation |
|
|
||||||
| `@css/cheats` | Cheat commands | God mode, noclip, give weapons |
|
|
||||||
| `@css/showip` | View IPs | See player IP addresses |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Immunity System
|
|
||||||
|
|
||||||
Immunity prevents lower-level admins from targeting higher-level admins.
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
- Each admin has an immunity value (0-100)
|
|
||||||
- Higher immunity = more protection
|
|
||||||
- Admins can only target players with lower immunity
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
- Admin A has immunity 50
|
|
||||||
- Admin B has immunity 30
|
|
||||||
- Admin A can ban Admin B
|
|
||||||
- Admin B cannot ban Admin A
|
|
||||||
|
|
||||||
**Best Practice:**
|
|
||||||
- Owner: 99
|
|
||||||
- Senior admins: 80-90
|
|
||||||
- Moderators: 50-70
|
|
||||||
- Trial mods: 20-40
|
|
||||||
- Regular players: 0
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### Reload Admins on Map Change
|
|
||||||
|
|
||||||
```json
|
|
||||||
"ReloadAdminsEveryMapChange": false
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `true` - Reload admin permissions every map change
|
|
||||||
- `false` - Only reload when explicitly requested (better performance)
|
|
||||||
|
|
||||||
### Show Activity Type
|
|
||||||
|
|
||||||
```json
|
|
||||||
"ShowActivityType": 2
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `0` - Hide all admin activity
|
|
||||||
- `1` - Show activity anonymously ("An admin banned PlayerName")
|
|
||||||
- `2` - Show admin name ("AdminName banned PlayerName")
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Admin Management
|
|
||||||
|
|
||||||
1. **Use groups** - Easier to manage than individual permissions
|
|
||||||
2. **Set appropriate immunity** - Prevent abuse
|
|
||||||
3. **Time-limited admin** - For trial moderators
|
|
||||||
4. **Document changes** - Keep track of who has what permissions
|
|
||||||
|
|
||||||
### Permission Assignment
|
|
||||||
|
|
||||||
**Recommended hierarchy:**
|
|
||||||
```
|
|
||||||
Root (@css/root, immunity 99):
|
|
||||||
- Server owners only
|
|
||||||
|
|
||||||
Senior Admin (@css/ban,@css/kick,@css/chat,@css/changemap, immunity 80):
|
|
||||||
- Trusted long-term admins
|
|
||||||
|
|
||||||
Moderator (@css/kick,@css/chat, immunity 50):
|
|
||||||
- Regular moderators
|
|
||||||
|
|
||||||
Trial Mod (@css/kick, immunity 20):
|
|
||||||
- New moderators on probation
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
1. **Limit @css/rcon** - Only to server owner
|
|
||||||
2. **Limit @css/cvar** - Only to senior admins
|
|
||||||
3. **Monitor admin actions** - Review logs regularly
|
|
||||||
4. **Use time-limited admin** - For temporary staff
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Admin permissions not working
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
1. Is admin correctly added with `css_addadmin`?
|
|
||||||
2. Run `css_reloadadmins`
|
|
||||||
3. Check database connection
|
|
||||||
4. Verify SteamID format
|
|
||||||
|
|
||||||
### Can't target another admin
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Your immunity level vs target's immunity
|
|
||||||
- You need equal or higher immunity to target
|
|
||||||
|
|
||||||
### Commands not available
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Your permission flags
|
|
||||||
- Commands.json for disabled commands
|
|
||||||
- Server console for errors
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Commands
|
|
||||||
|
|
||||||
- **[Ban Commands](basebans)** - Player punishment
|
|
||||||
- **[Communication Commands](basecomms)** - Chat/voice management
|
|
||||||
- **[Player Commands](playercommands)** - Player manipulation
|
|
||||||
@@ -1,396 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 2
|
|
||||||
---
|
|
||||||
|
|
||||||
# Communication Commands
|
|
||||||
|
|
||||||
Commands for managing player communication (voice and text chat).
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
CS2-SimpleAdmin provides three types of communication restrictions:
|
|
||||||
|
|
||||||
- **Gag** - Blocks text chat only
|
|
||||||
- **Mute** - Blocks voice chat only
|
|
||||||
- **Silence** - Blocks both text and voice chat
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Gag Commands
|
|
||||||
|
|
||||||
### Gag Player
|
|
||||||
|
|
||||||
Prevent a player from using text chat.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_gag <#userid or name> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_gag #123 30 "Chat spam"
|
|
||||||
css_gag PlayerName 1440 "Advertising"
|
|
||||||
css_gag @all 5 "Everyone quiet for 5 minutes"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Gag (Offline Player)
|
|
||||||
|
|
||||||
Gag a player by SteamID even if they're offline.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_addgag <steamid> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_addgag 76561198012345678 60 "Chat abuse"
|
|
||||||
css_addgag STEAM_1:0:12345678 1440 "Spam"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ungag Player
|
|
||||||
|
|
||||||
Remove a gag from a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_ungag <steamid or name> [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_ungag PlayerName "Appeal accepted"
|
|
||||||
css_ungag 76561198012345678 "Mistake"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Mute Commands
|
|
||||||
|
|
||||||
### Mute Player
|
|
||||||
|
|
||||||
Prevent a player from using voice chat.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_mute <#userid or name> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_mute #123 30 "Mic spam"
|
|
||||||
css_mute PlayerName 60 "Loud music"
|
|
||||||
css_mute @t 5 "T team timeout"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Mute (Offline Player)
|
|
||||||
|
|
||||||
Mute a player by SteamID even if they're offline.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_addmute <steamid> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_addmute 76561198012345678 120 "Voice abuse"
|
|
||||||
css_addmute STEAM_1:0:12345678 1440 "Mic spam"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unmute Player
|
|
||||||
|
|
||||||
Remove a mute from a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_unmute <steamid or name> [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_unmute PlayerName "Behavior improved"
|
|
||||||
css_unmute 76561198012345678 "Time served"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Silence Commands
|
|
||||||
|
|
||||||
### Silence Player
|
|
||||||
|
|
||||||
Block both text and voice chat from a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_silence <#userid or name> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_silence #123 60 "Complete communication ban"
|
|
||||||
css_silence PlayerName 1440 "Severe abuse"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Silence (Offline Player)
|
|
||||||
|
|
||||||
Silence a player by SteamID even if they're offline.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_addsilence <steamid> [time in minutes/0 perm] [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_addsilence 76561198012345678 120 "Total communication ban"
|
|
||||||
css_addsilence STEAM_1:0:12345678 0 "Permanent silence"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unsilence Player
|
|
||||||
|
|
||||||
Remove a silence from a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_unsilence <steamid or name> [reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/chat`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_unsilence PlayerName "Punishment complete"
|
|
||||||
css_unsilence 76561198012345678 "Appeal granted"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Permission Requirements
|
|
||||||
|
|
||||||
All communication commands require the `@css/chat` permission.
|
|
||||||
|
|
||||||
| Command | Action | Offline Support |
|
|
||||||
|---------|--------|----------------|
|
|
||||||
| `css_gag` | Block text chat | No |
|
|
||||||
| `css_addgag` | Block text chat | Yes |
|
|
||||||
| `css_ungag` | Remove text block | Yes |
|
|
||||||
| `css_mute` | Block voice chat | No |
|
|
||||||
| `css_addmute` | Block voice chat | Yes |
|
|
||||||
| `css_unmute` | Remove voice block | Yes |
|
|
||||||
| `css_silence` | Block both | No |
|
|
||||||
| `css_addsilence` | Block both | Yes |
|
|
||||||
| `css_unsilence` | Remove both blocks | Yes |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Communication Penalty Types
|
|
||||||
|
|
||||||
### When to Use Each Type
|
|
||||||
|
|
||||||
**Gag (Text Only):**
|
|
||||||
- Chat spam
|
|
||||||
- Advertising in chat
|
|
||||||
- Offensive messages
|
|
||||||
- Spectator camera abuse messages
|
|
||||||
|
|
||||||
**Mute (Voice Only):**
|
|
||||||
- Mic spam
|
|
||||||
- Loud music/noise
|
|
||||||
- Voice abuse
|
|
||||||
- Excessive talking
|
|
||||||
|
|
||||||
**Silence (Both):**
|
|
||||||
- Severe abuse cases
|
|
||||||
- Players who switch between chat and voice to evade
|
|
||||||
- Complete communication bans
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### UserMessage Gag Type
|
|
||||||
|
|
||||||
In `CS2-SimpleAdmin.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"UserMessageGagChatType": false
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `false` - Standard gag implementation (default)
|
|
||||||
- `true` - Alternative gag using UserMessage system
|
|
||||||
|
|
||||||
**Note:** Try switching this if gag commands don't work as expected.
|
|
||||||
|
|
||||||
### Notify Penalties on Connect
|
|
||||||
|
|
||||||
```json
|
|
||||||
"NotifyPenaltiesToAdminOnConnect": true
|
|
||||||
```
|
|
||||||
|
|
||||||
When enabled, admins see active communication penalties when they join:
|
|
||||||
```
|
|
||||||
[CS2-SimpleAdmin] PlayerName is gagged (30 minutes remaining)
|
|
||||||
[CS2-SimpleAdmin] PlayerName is muted (1 hour remaining)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Checking Penalties
|
|
||||||
|
|
||||||
### View Own Penalties
|
|
||||||
|
|
||||||
Players can check their own communication penalties:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_penalties
|
|
||||||
css_mypenalties
|
|
||||||
css_comms
|
|
||||||
```
|
|
||||||
|
|
||||||
Shows:
|
|
||||||
- Active gags, mutes, and silences
|
|
||||||
- Duration remaining
|
|
||||||
- Reason for penalty
|
|
||||||
- Admin who issued it
|
|
||||||
|
|
||||||
### Admin View of Penalties
|
|
||||||
|
|
||||||
Use the admin menu or player info command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_who <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
Shows complete penalty history including communication restrictions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Time Durations
|
|
||||||
|
|
||||||
Common duration values:
|
|
||||||
|
|
||||||
| Duration | Minutes | Use Case |
|
|
||||||
|----------|---------|----------|
|
|
||||||
| 1 minute | 1 | Quick warning |
|
|
||||||
| 5 minutes | 5 | Minor spam |
|
|
||||||
| 15 minutes | 15 | Standard timeout |
|
|
||||||
| 30 minutes | 30 | Repeated offense |
|
|
||||||
| 1 hour | 60 | Moderate abuse |
|
|
||||||
| 6 hours | 360 | Serious abuse |
|
|
||||||
| 1 day | 1440 | Severe abuse |
|
|
||||||
| 1 week | 10080 | Extreme cases |
|
|
||||||
| Permanent | 0 | Reserved for worst cases |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Player Targeting
|
|
||||||
|
|
||||||
All communication commands support advanced targeting:
|
|
||||||
|
|
||||||
- `@all` - Target all players
|
|
||||||
- `@ct` - Target all Counter-Terrorists
|
|
||||||
- `@t` - Target all Terrorists
|
|
||||||
- `@spec` - Target all spectators
|
|
||||||
- `#123` - Target by userid
|
|
||||||
- `PlayerName` - Target by name
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_gag @all 1 "Quiet for one minute"
|
|
||||||
css_mute @t 5 "T team voice timeout"
|
|
||||||
css_silence @ct 10 "CT team complete silence"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Communication Management
|
|
||||||
|
|
||||||
1. **Start with warnings** - Not all chat issues need immediate gag
|
|
||||||
2. **Use appropriate durations** - Match severity to punishment
|
|
||||||
3. **Provide reasons** - Helps players understand what they did wrong
|
|
||||||
4. **Consider silence carefully** - Complete communication ban is harsh
|
|
||||||
|
|
||||||
### Gag vs Mute vs Silence
|
|
||||||
|
|
||||||
**Progressive Approach:**
|
|
||||||
1. Verbal warning
|
|
||||||
2. Gag or mute (specific to offense)
|
|
||||||
3. Longer gag/mute for repeat offense
|
|
||||||
4. Silence for continued abuse
|
|
||||||
5. Temporary ban for extreme cases
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
1. **Always provide reasons** - Required for appeals
|
|
||||||
2. **Be specific** - "Mic spam" not just "abuse"
|
|
||||||
3. **Keep records** - Use admin logs for repeat offenders
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Discord Integration
|
|
||||||
|
|
||||||
Communication penalties can send Discord notifications when configured:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"DiscordPenaltyGagSettings": [...],
|
|
||||||
"DiscordPenaltyMuteSettings": [...],
|
|
||||||
"DiscordPenaltySilenceSettings": [...]
|
|
||||||
```
|
|
||||||
|
|
||||||
Notifications include:
|
|
||||||
- Player name and SteamID
|
|
||||||
- Penalty type and duration
|
|
||||||
- Reason provided
|
|
||||||
- Admin who issued it
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Gag doesn't work
|
|
||||||
|
|
||||||
**Try:**
|
|
||||||
1. Switch `UserMessageGagChatType` in config
|
|
||||||
2. Ensure player is actually gagged (check with `css_who`)
|
|
||||||
3. Check for conflicting plugins
|
|
||||||
|
|
||||||
### Mute doesn't block voice
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Is sv_talk_enemy_dead configured correctly?
|
|
||||||
- Are there voice management plugins conflicting?
|
|
||||||
- Check server console for errors
|
|
||||||
|
|
||||||
### Penalties not persistent across maps
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- Penalties should persist automatically
|
|
||||||
- Check database connection
|
|
||||||
- Verify MultiServerMode if using multiple servers
|
|
||||||
|
|
||||||
### Player can't see their penalties
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Command aliases in Commands.json
|
|
||||||
- Ensure `css_penalties` is enabled
|
|
||||||
- Check player chat permissions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Commands
|
|
||||||
|
|
||||||
- **[Ban Commands](basebans)** - For more serious offenses
|
|
||||||
- **[Player Commands](playercommands)** - Kick, team switch
|
|
||||||
- **[Base Commands](basecommands)** - Admin management
|
|
||||||
@@ -1,436 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 6
|
|
||||||
---
|
|
||||||
|
|
||||||
# Vote Commands
|
|
||||||
|
|
||||||
Commands for creating polls and votes on your server.
|
|
||||||
|
|
||||||
## Create Vote
|
|
||||||
|
|
||||||
Create a custom poll for players to vote on.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote <question> [option1] [option2] [option3] ...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/generic`
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `question` - The question to ask players
|
|
||||||
- `option1, option2, ...` - Vote options (at least 2 required)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Simple Yes/No Vote
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Should we change map?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Player sees:**
|
|
||||||
```
|
|
||||||
Vote: Should we change map?
|
|
||||||
1. Yes
|
|
||||||
2. No
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Multiple Options
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Which map should we play next?" "de_dust2" "de_mirage" "de_inferno" "de_nuke"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Player sees:**
|
|
||||||
```
|
|
||||||
Vote: Which map should we play next?
|
|
||||||
1. de_dust2
|
|
||||||
2. de_mirage
|
|
||||||
3. de_inferno
|
|
||||||
4. de_nuke
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Rule Vote
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Should we allow AWPs?" "Yes" "No" "Only one per team"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Activity Vote
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "What should we do?" "Surf" "Deathrun" "Competitive" "Fun Round"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How Voting Works
|
|
||||||
|
|
||||||
### Player Participation
|
|
||||||
|
|
||||||
Players vote by:
|
|
||||||
1. Opening their chat
|
|
||||||
2. Typing the number of their choice
|
|
||||||
3. Or using vote menu (if available)
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```
|
|
||||||
Player: 1 (votes for option 1)
|
|
||||||
Player: 2 (votes for option 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vote Duration
|
|
||||||
|
|
||||||
- Default vote time: ~30 seconds
|
|
||||||
- Vote timer shows on screen
|
|
||||||
- Results shown when vote ends
|
|
||||||
|
|
||||||
### Vote Results
|
|
||||||
|
|
||||||
After voting ends, results are displayed:
|
|
||||||
```
|
|
||||||
Vote Results:
|
|
||||||
1. Yes - 12 votes (60%)
|
|
||||||
2. No - 8 votes (40%)
|
|
||||||
|
|
||||||
Winner: Yes
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
### Map Voting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rule Changes
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Enable friendly fire?" "Yes" "No"
|
|
||||||
css_vote "Restart round?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Player Punishment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Ban PlayerName for cheating?" "Yes" "No"
|
|
||||||
css_vote "Kick AFK player?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fun Rounds
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Fun round type?" "Knife only" "Deagle only" "Zeus only" "Normal"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Server Settings
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_vote "Round time?" "2 minutes" "3 minutes" "5 minutes"
|
|
||||||
css_vote "Max players?" "10v10" "5v5" "7v7"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Question Clarity
|
|
||||||
|
|
||||||
**Good Questions:**
|
|
||||||
- Clear and concise
|
|
||||||
- Specific
|
|
||||||
- Easy to understand
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
✅ css_vote "Change to de_dust2?" "Yes" "No"
|
|
||||||
❌ css_vote "Map?" "Yes" "No" # Unclear what map
|
|
||||||
|
|
||||||
✅ css_vote "Restart this round?" "Yes" "No"
|
|
||||||
❌ css_vote "Restart?" "Yes" "No" # Restart what?
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option Limits
|
|
||||||
|
|
||||||
**Recommendations:**
|
|
||||||
- 2-5 options ideal
|
|
||||||
- Too many options confuse players
|
|
||||||
- Keep options brief
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
✅ css_vote "Next map?" "dust2" "mirage" "inferno"
|
|
||||||
❌ css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno" "de_nuke" "de_vertigo" "de_ancient" "de_anubis"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Timing
|
|
||||||
|
|
||||||
**When to use votes:**
|
|
||||||
- End of round
|
|
||||||
- Between maps
|
|
||||||
- During downtime
|
|
||||||
- Not during active gameplay
|
|
||||||
|
|
||||||
**When NOT to use votes:**
|
|
||||||
- Mid-round
|
|
||||||
- During clutch situations
|
|
||||||
- Too frequently
|
|
||||||
|
|
||||||
### Vote Spam Prevention
|
|
||||||
|
|
||||||
Don't spam votes:
|
|
||||||
```bash
|
|
||||||
❌ Multiple votes in quick succession
|
|
||||||
❌ Overlapping votes
|
|
||||||
❌ Votes every round
|
|
||||||
```
|
|
||||||
|
|
||||||
Wait for current vote to finish before starting another.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vote Types
|
|
||||||
|
|
||||||
### Administrative Votes
|
|
||||||
|
|
||||||
**Map change:**
|
|
||||||
```bash
|
|
||||||
css_vote "Change map now?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Server restart:**
|
|
||||||
```bash
|
|
||||||
css_vote "Restart server?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rule enforcement:**
|
|
||||||
```bash
|
|
||||||
css_vote "Kick PlayerName?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gameplay Votes
|
|
||||||
|
|
||||||
**Weapon restrictions:**
|
|
||||||
```bash
|
|
||||||
css_vote "Disable AWP?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Team scramble:**
|
|
||||||
```bash
|
|
||||||
css_vote "Scramble teams?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Round rules:**
|
|
||||||
```bash
|
|
||||||
css_vote "Knife round first?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Votes
|
|
||||||
|
|
||||||
**Tournament:**
|
|
||||||
```bash
|
|
||||||
css_vote "Start tournament?" "Yes, start now" "Wait 5 minutes" "No, cancel"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Custom game mode:**
|
|
||||||
```bash
|
|
||||||
css_vote "Game mode?" "Hide and Seek" "Gungame" "Surf" "Normal"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
### Technical Limits
|
|
||||||
|
|
||||||
- Maximum ~10 options (depends on menu system)
|
|
||||||
- One vote at a time
|
|
||||||
- Requires active players to participate
|
|
||||||
|
|
||||||
### Permission Required
|
|
||||||
|
|
||||||
Only admins with `@css/generic` permission can start votes.
|
|
||||||
|
|
||||||
To grant permission:
|
|
||||||
```bash
|
|
||||||
css_addadmin STEAMID "Name" "@css/generic" 50 0
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vote Results Handling
|
|
||||||
|
|
||||||
### Manual Enforcement
|
|
||||||
|
|
||||||
Votes don't automatically execute actions. Admins must:
|
|
||||||
|
|
||||||
1. **See the results**
|
|
||||||
2. **Manually execute the winning option**
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
# Start vote
|
|
||||||
css_vote "Change to de_dust2?" "Yes" "No"
|
|
||||||
|
|
||||||
# If "Yes" wins, manually change map
|
|
||||||
css_map de_dust2
|
|
||||||
```
|
|
||||||
|
|
||||||
### Why Manual?
|
|
||||||
|
|
||||||
- Prevents abuse
|
|
||||||
- Allows admin oversight
|
|
||||||
- Gives control over execution
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### Combining with Commands
|
|
||||||
|
|
||||||
Use votes to decide, then execute:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Vote on map
|
|
||||||
css_vote "Next map?" "dust2" "mirage" "inferno"
|
|
||||||
# If dust2 wins:
|
|
||||||
css_map de_dust2
|
|
||||||
|
|
||||||
# Vote on player kick
|
|
||||||
css_vote "Kick PlayerName?" "Yes" "No"
|
|
||||||
# If Yes wins:
|
|
||||||
css_kick PlayerName "Voted to be kicked"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sequential Votes
|
|
||||||
|
|
||||||
Run multiple votes for complex decisions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# First vote: Mode
|
|
||||||
css_vote "Game mode?" "Competitive" "Casual"
|
|
||||||
|
|
||||||
# If Competitive wins, second vote:
|
|
||||||
css_vote "Round time?" "2 min" "3 min" "5 min"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Check if vote commands are enabled in:
|
|
||||||
```
|
|
||||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Commands": {
|
|
||||||
"css_vote": {
|
|
||||||
"Aliases": [
|
|
||||||
"css_vote"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To disable votes, remove all aliases:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Commands": {
|
|
||||||
"css_vote": {
|
|
||||||
"Aliases": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Vote doesn't start
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Do you have `@css/generic` permission?
|
|
||||||
- Is command enabled in Commands.json?
|
|
||||||
- Are there at least 2 options?
|
|
||||||
|
|
||||||
### Players can't vote
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Vote menu is showing
|
|
||||||
- Players know how to vote (type number in chat)
|
|
||||||
- Vote hasn't already ended
|
|
||||||
|
|
||||||
### Vote results not showing
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Wait for vote to complete
|
|
||||||
- Check server console
|
|
||||||
- Ensure voting system is working
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Permission Requirements
|
|
||||||
|
|
||||||
| Command | Permission | Description |
|
|
||||||
|---------|------------|-------------|
|
|
||||||
| `css_vote` | `@css/generic` | Create votes/polls |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
### Effective Polling
|
|
||||||
|
|
||||||
1. **Ask clear questions** - No ambiguity
|
|
||||||
2. **Limit options** - 2-4 is ideal
|
|
||||||
3. **Time it right** - Between rounds
|
|
||||||
4. **Follow through** - Execute winning option
|
|
||||||
5. **Don't overuse** - Votes lose impact if spammed
|
|
||||||
|
|
||||||
### Community Engagement
|
|
||||||
|
|
||||||
Use votes to:
|
|
||||||
- Involve community in decisions
|
|
||||||
- Gauge player preferences
|
|
||||||
- Create democratic server atmosphere
|
|
||||||
- Get feedback on changes
|
|
||||||
|
|
||||||
### Example Scenarios
|
|
||||||
|
|
||||||
**New map test:**
|
|
||||||
```bash
|
|
||||||
css_vote "Try new map cs_office?" "Yes" "No"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Event planning:**
|
|
||||||
```bash
|
|
||||||
css_vote "Tournament this weekend?" "Saturday" "Sunday" "No thanks"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rule feedback:**
|
|
||||||
```bash
|
|
||||||
css_vote "Keep no-AWP rule?" "Yes" "No" "Only limit to 2"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Commands
|
|
||||||
|
|
||||||
- **[Base Commands](basecommands)** - Server management
|
|
||||||
- **[Chat Commands](basechat)** - Announcements
|
|
||||||
- **[Player Commands](playercommands)** - Player actions
|
|
||||||
@@ -1,516 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 5
|
|
||||||
---
|
|
||||||
|
|
||||||
# Player Commands
|
|
||||||
|
|
||||||
Commands for managing and manipulating players on your server.
|
|
||||||
|
|
||||||
:::note
|
|
||||||
Many of these commands are included in the base plugin. For extended fun commands (god mode, noclip, freeze, etc.), see the [Fun Commands Module](../../modules/funcommands).
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Player Management
|
|
||||||
|
|
||||||
### Slay Player
|
|
||||||
|
|
||||||
Kill a player instantly.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_slay <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/slay`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_slay #123
|
|
||||||
css_slay PlayerName
|
|
||||||
css_slay @ct # Slay all CTs
|
|
||||||
css_slay @t # Slay all terrorists
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use cases:**
|
|
||||||
- Punishment for rule breaking
|
|
||||||
- Ending rounds quickly
|
|
||||||
- Removing camping players
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Slap Player
|
|
||||||
|
|
||||||
Slap a player, dealing damage and pushing them.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_slap <#userid or name> [damage]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/slay`
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `damage` - HP damage to deal (default: 0)
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_slap #123 # Slap with no damage
|
|
||||||
css_slap PlayerName 10 # Slap for 10 HP damage
|
|
||||||
css_slap @all 5 # Slap everyone for 5 damage
|
|
||||||
```
|
|
||||||
|
|
||||||
**Effects:**
|
|
||||||
- Player is pushed in a random direction
|
|
||||||
- Optional damage dealt
|
|
||||||
- Makes slap sound
|
|
||||||
|
|
||||||
**Use cases:**
|
|
||||||
- Funny punishment
|
|
||||||
- Getting player attention
|
|
||||||
- Moving AFK players
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Player Attributes
|
|
||||||
|
|
||||||
### Set Player Health
|
|
||||||
|
|
||||||
Set a player's health points.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_hp <#userid or name> <health>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/slay`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_hp #123 100 # Set to full health
|
|
||||||
css_hp PlayerName 1 # Set to 1 HP
|
|
||||||
css_hp @ct 200 # Give all CTs 200 HP
|
|
||||||
```
|
|
||||||
|
|
||||||
**Valid range:** 1 - 999+
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Set Player Speed
|
|
||||||
|
|
||||||
Modify a player's movement speed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_speed <#userid or name> <speed>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/slay`
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `speed` - Speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed)
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_speed #123 1.5 # 150% speed
|
|
||||||
css_speed PlayerName 0.5 # 50% speed (slow motion)
|
|
||||||
css_speed @all 2.0 # Double speed for everyone
|
|
||||||
css_speed #123 1.0 # Reset to normal speed
|
|
||||||
```
|
|
||||||
|
|
||||||
**Common values:**
|
|
||||||
- `0.5` - Slow motion
|
|
||||||
- `1.0` - Normal (default)
|
|
||||||
- `1.5` - Fast
|
|
||||||
- `2.0` - Very fast
|
|
||||||
- `3.0` - Extremely fast
|
|
||||||
|
|
||||||
**Note:** Speed persists across respawns until reset.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Set Player Gravity
|
|
||||||
|
|
||||||
Modify a player's gravity.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_gravity <#userid or name> <gravity>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/slay`
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `gravity` - Gravity multiplier (1.0 = normal, 0.5 = moon jump, 2.0 = heavy)
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_gravity #123 0.5 # Moon jump
|
|
||||||
css_gravity PlayerName 2.0 # Heavy gravity
|
|
||||||
css_gravity @all 0.1 # Super jump for everyone
|
|
||||||
css_gravity #123 1.0 # Reset to normal
|
|
||||||
```
|
|
||||||
|
|
||||||
**Common values:**
|
|
||||||
- `0.1` - Super high jumps
|
|
||||||
- `0.5` - Moon gravity
|
|
||||||
- `1.0` - Normal (default)
|
|
||||||
- `2.0` - Heavy/fast falling
|
|
||||||
|
|
||||||
**Note:** Gravity persists across respawns until reset.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Set Player Money
|
|
||||||
|
|
||||||
Set a player's money amount.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_money <#userid or name> <amount>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/slay`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_money #123 16000 # Max money
|
|
||||||
css_money PlayerName 0 # Remove all money
|
|
||||||
css_money @ct 10000 # Give all CTs $10,000
|
|
||||||
```
|
|
||||||
|
|
||||||
**Valid range:** 0 - 65535 (CS2 engine limit)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Team Management
|
|
||||||
|
|
||||||
### Switch Player Team
|
|
||||||
|
|
||||||
Move a player to a different team.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_team <#userid or name> [ct/t/spec] [-k]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `ct` - Counter-Terrorist team
|
|
||||||
- `t` - Terrorist team
|
|
||||||
- `spec` - Spectators
|
|
||||||
- `-k` - Kill player during switch (optional)
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_team #123 ct # Move to CT
|
|
||||||
css_team PlayerName t # Move to T
|
|
||||||
css_team @spec t # Move all spectators to T
|
|
||||||
css_team #123 ct -k # Move to CT and kill
|
|
||||||
```
|
|
||||||
|
|
||||||
**Configuration:**
|
|
||||||
```json
|
|
||||||
"TeamSwitchType": 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Determines team switch behavior.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Rename Player
|
|
||||||
|
|
||||||
Temporarily rename a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_rename <#userid or name> <new name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_rename #123 "NewName"
|
|
||||||
css_rename PlayerName "RenamedPlayer"
|
|
||||||
css_rename @all "Everyone"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Rename is temporary (resets on reconnect)
|
|
||||||
- For permanent rename, use `css_prename`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Permanent Rename
|
|
||||||
|
|
||||||
Permanently force a player's name.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_prename <#userid or name> <new name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/ban`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_prename #123 "EnforcedName"
|
|
||||||
css_prename PlayerName "NewIdentity"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Name is enforced even after reconnect
|
|
||||||
- Stored in database
|
|
||||||
- Player cannot change it
|
|
||||||
- Useful for offensive names
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Weapon Management
|
|
||||||
|
|
||||||
### Give Weapon
|
|
||||||
|
|
||||||
Give a weapon to a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_give <#userid or name> <weapon>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/cheats`
|
|
||||||
|
|
||||||
**Weapon names:**
|
|
||||||
- Rifles: `ak47`, `m4a1`, `m4a1_silencer`, `aug`, `sg556`, `awp`
|
|
||||||
- SMGs: `mp5sd`, `mp7`, `mp9`, `p90`, `ump45`
|
|
||||||
- Heavy: `nova`, `xm1014`, `mag7`, `m249`, `negev`
|
|
||||||
- Pistols: `deagle`, `elite`, `fiveseven`, `glock`, `hkp2000`, `p250`, `tec9`, `usp_silencer`
|
|
||||||
- Grenades: `flashbang`, `hegrenade`, `smokegrenade`, `molotov`, `incgrenade`, `decoy`
|
|
||||||
- Equipment: `kevlar`, `assaultsuit`, `defuser`, `knife`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_give #123 awp
|
|
||||||
css_give PlayerName ak47
|
|
||||||
css_give @ct m4a1
|
|
||||||
css_give @all deagle
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Strip Weapons
|
|
||||||
|
|
||||||
Remove all weapons from a player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_strip <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/slay`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_strip #123
|
|
||||||
css_strip PlayerName
|
|
||||||
css_strip @t # Disarm all terrorists
|
|
||||||
```
|
|
||||||
|
|
||||||
**Effects:**
|
|
||||||
- Removes all weapons
|
|
||||||
- Leaves player with knife only
|
|
||||||
- Removes grenades and equipment
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Teleportation
|
|
||||||
|
|
||||||
### Teleport to Player
|
|
||||||
|
|
||||||
Teleport yourself to another player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_tp <#userid or name>
|
|
||||||
css_tpto <#userid or name>
|
|
||||||
css_goto <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_tp #123
|
|
||||||
css_goto PlayerName
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use cases:**
|
|
||||||
- Checking player behavior
|
|
||||||
- Admin help
|
|
||||||
- Spectating suspicious players
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Teleport Player to You
|
|
||||||
|
|
||||||
Bring a player to your location.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_bring <#userid or name>
|
|
||||||
css_tphere <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/kick`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_bring #123
|
|
||||||
css_tphere PlayerName
|
|
||||||
css_bring @all # Bring everyone to you
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use cases:**
|
|
||||||
- Moving stuck players
|
|
||||||
- Gathering players
|
|
||||||
- Admin events
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Respawn Player
|
|
||||||
|
|
||||||
Respawn a dead player.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_respawn <#userid or name>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permission:** `@css/cheats`
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
```bash
|
|
||||||
css_respawn #123
|
|
||||||
css_respawn PlayerName
|
|
||||||
css_respawn @ct # Respawn all dead CTs
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Player spawns at spawn point
|
|
||||||
- Equipped with default weapons
|
|
||||||
- Can break competitive balance
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Player Targeting
|
|
||||||
|
|
||||||
All player commands support advanced targeting:
|
|
||||||
|
|
||||||
### Target Syntax
|
|
||||||
|
|
||||||
- `@all` - All players
|
|
||||||
- `@ct` - All Counter-Terrorists
|
|
||||||
- `@t` - All Terrorists
|
|
||||||
- `@spec` - All spectators
|
|
||||||
- `@alive` - All alive players
|
|
||||||
- `@dead` - All dead players
|
|
||||||
- `@bot` - All bots
|
|
||||||
- `@human` - All human players
|
|
||||||
- `@me` - Yourself
|
|
||||||
- `#123` - Specific user ID
|
|
||||||
- `PlayerName` - By name (partial match supported)
|
|
||||||
|
|
||||||
### Multiple Targets
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_slay @ct # Kills all CTs
|
|
||||||
css_hp @all 200 # Give everyone 200 HP
|
|
||||||
css_speed @t 2.0 # Make all Ts fast
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Permission Requirements
|
|
||||||
|
|
||||||
| Command | Required Permission | Description |
|
|
||||||
|---------|-------------------|-------------|
|
|
||||||
| `css_slay` | `@css/slay` | Kill players |
|
|
||||||
| `css_slap` | `@css/slay` | Slap players |
|
|
||||||
| `css_hp` | `@css/slay` | Set health |
|
|
||||||
| `css_speed` | `@css/slay` | Modify speed |
|
|
||||||
| `css_gravity` | `@css/slay` | Modify gravity |
|
|
||||||
| `css_money` | `@css/slay` | Set money |
|
|
||||||
| `css_team` | `@css/kick` | Change team |
|
|
||||||
| `css_rename` | `@css/kick` | Temporary rename |
|
|
||||||
| `css_prename` | `@css/ban` | Permanent rename |
|
|
||||||
| `css_give` | `@css/cheats` | Give weapons |
|
|
||||||
| `css_strip` | `@css/slay` | Remove weapons |
|
|
||||||
| `css_tp` | `@css/kick` | Teleport to player |
|
|
||||||
| `css_bring` | `@css/kick` | Bring player |
|
|
||||||
| `css_respawn` | `@css/cheats` | Respawn players |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Punishment Commands
|
|
||||||
|
|
||||||
**Slay:**
|
|
||||||
- Use for rule violations
|
|
||||||
- Better than kick for minor issues
|
|
||||||
- Allows player to stay and learn
|
|
||||||
|
|
||||||
**Slap:**
|
|
||||||
- Lighter punishment
|
|
||||||
- Good for warnings
|
|
||||||
- Can be funny/entertaining
|
|
||||||
|
|
||||||
### Gameplay Modification
|
|
||||||
|
|
||||||
**HP/Speed/Gravity:**
|
|
||||||
- Use for events/fun rounds
|
|
||||||
- Don't abuse during competitive play
|
|
||||||
- Reset to normal after use
|
|
||||||
|
|
||||||
**Respawn:**
|
|
||||||
- Very disruptive to gameplay
|
|
||||||
- Use sparingly
|
|
||||||
- Good for fixing bugs/mistakes
|
|
||||||
|
|
||||||
### Team Management
|
|
||||||
|
|
||||||
**Team switching:**
|
|
||||||
- Balance teams fairly
|
|
||||||
- Don't abuse for winning
|
|
||||||
- Use `-k` flag for competitive integrity
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Team Switch Behavior
|
|
||||||
|
|
||||||
```json
|
|
||||||
"TeamSwitchType": 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Controls how team switching works.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Speed/Gravity not persisting
|
|
||||||
|
|
||||||
**Solution:** These are maintained by a timer. If they reset:
|
|
||||||
- Check server console for errors
|
|
||||||
- Ensure plugin is loaded correctly
|
|
||||||
- Try reapplying the modification
|
|
||||||
|
|
||||||
### Can't teleport
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Target player is connected
|
|
||||||
- You have correct permissions
|
|
||||||
- Both players are valid
|
|
||||||
|
|
||||||
### Give weapon not working
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Weapon name is correct
|
|
||||||
- Player is alive
|
|
||||||
- Player has inventory space
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Commands
|
|
||||||
|
|
||||||
- **[Fun Commands Module](../../modules/funcommands)** - Extended fun commands (freeze, god mode, noclip)
|
|
||||||
- **[Ban Commands](basebans)** - Punishment commands
|
|
||||||
- **[Base Commands](basecommands)** - Server management
|
|
||||||
@@ -1,397 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
|
|
||||||
Learn how to configure CS2-SimpleAdmin to suit your server's needs.
|
|
||||||
|
|
||||||
## Configuration File Location
|
|
||||||
|
|
||||||
The main configuration file is located at:
|
|
||||||
```
|
|
||||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Structure
|
|
||||||
|
|
||||||
The configuration file is divided into several sections:
|
|
||||||
|
|
||||||
### Database Configuration
|
|
||||||
|
|
||||||
Configure your database connection:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"DatabaseConfig": {
|
|
||||||
"DatabaseType": "SQLite",
|
|
||||||
"SqliteFilePath": "cs2-simpleadmin.sqlite",
|
|
||||||
"DatabaseHost": "",
|
|
||||||
"DatabasePort": 3306,
|
|
||||||
"DatabaseUser": "",
|
|
||||||
"DatabasePassword": "",
|
|
||||||
"DatabaseName": "",
|
|
||||||
"DatabaseSSlMode": "preferred"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Database Types:**
|
|
||||||
- `SQLite` - Local database file (good for single server)
|
|
||||||
- `MySQL` - MySQL/MariaDB server (required for multi-server setups)
|
|
||||||
|
|
||||||
**MySQL Example:**
|
|
||||||
```json
|
|
||||||
"DatabaseConfig": {
|
|
||||||
"DatabaseType": "MySQL",
|
|
||||||
"DatabaseHost": "localhost",
|
|
||||||
"DatabasePort": 3306,
|
|
||||||
"DatabaseUser": "cs2admin",
|
|
||||||
"DatabasePassword": "your_password",
|
|
||||||
"DatabaseName": "cs2_simpleadmin",
|
|
||||||
"DatabaseSSlMode": "preferred"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Other Settings
|
|
||||||
|
|
||||||
General plugin settings:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"OtherSettings": {
|
|
||||||
"ShowActivityType": 2,
|
|
||||||
"TeamSwitchType": 1,
|
|
||||||
"KickTime": 5,
|
|
||||||
"BanType": 1,
|
|
||||||
"TimeMode": 1,
|
|
||||||
"DisableDangerousCommands": true,
|
|
||||||
"MaxBanDuration": 10080,
|
|
||||||
"MaxMuteDuration": 10080,
|
|
||||||
"ExpireOldIpBans": 0,
|
|
||||||
"ReloadAdminsEveryMapChange": false,
|
|
||||||
"DisconnectedPlayersHistoryCount": 10,
|
|
||||||
"NotifyPenaltiesToAdminOnConnect": true,
|
|
||||||
"ShowBanMenuIfNoTime": true,
|
|
||||||
"UserMessageGagChatType": false,
|
|
||||||
"CheckMultiAccountsByIp": true,
|
|
||||||
"AdditionalCommandsToLog": [],
|
|
||||||
"IgnoredIps": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Settings Explained:**
|
|
||||||
|
|
||||||
| Setting | Description | Default |
|
|
||||||
|---------|-------------|---------|
|
|
||||||
| `ShowActivityType` | How to display admin actions (0=hide, 1=anonymous, 2=show name) | 2 |
|
|
||||||
| `TeamSwitchType` | Team switch behavior | 1 |
|
|
||||||
| `KickTime` | Delay before kicking player (seconds) | 5 |
|
|
||||||
| `BanType` | Ban type (1=SteamID, 2=IP, 3=Both) | 1 |
|
|
||||||
| `TimeMode` | Time display mode | 1 |
|
|
||||||
| `DisableDangerousCommands` | Disable potentially dangerous commands | true |
|
|
||||||
| `MaxBanDuration` | Maximum ban duration in minutes (0=unlimited) | 10080 |
|
|
||||||
| `MaxMuteDuration` | Maximum mute duration in minutes (0=unlimited) | 10080 |
|
|
||||||
| `ExpireOldIpBans` | Auto-expire IP bans after X days (0=disabled) | 0 |
|
|
||||||
| `ReloadAdminsEveryMapChange` | Reload admin permissions on map change | false |
|
|
||||||
| `DisconnectedPlayersHistoryCount` | Number of disconnected players to track | 10 |
|
|
||||||
| `NotifyPenaltiesToAdminOnConnect` | Show penalties to admins when they connect | true |
|
|
||||||
| `ShowBanMenuIfNoTime` | Show ban menu even without time parameter | true |
|
|
||||||
| `UserMessageGagChatType` | Use UserMessage for gag (alternative chat blocking) | false |
|
|
||||||
| `CheckMultiAccountsByIp` | Detect multiple accounts from same IP | true |
|
|
||||||
| `AdditionalCommandsToLog` | Array of additional commands to log | [] |
|
|
||||||
| `IgnoredIps` | IPs to ignore in multi-account detection | [] |
|
|
||||||
|
|
||||||
### Metrics and Updates
|
|
||||||
|
|
||||||
```json
|
|
||||||
"EnableMetrics": true,
|
|
||||||
"EnableUpdateCheck": true
|
|
||||||
```
|
|
||||||
|
|
||||||
- `EnableMetrics` - Send anonymous usage statistics
|
|
||||||
- `EnableUpdateCheck` - Check for plugin updates on load
|
|
||||||
|
|
||||||
### Timezone
|
|
||||||
|
|
||||||
Set your server's timezone for accurate timestamps:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"Timezone": "UTC"
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [list of timezones](#timezone-list) below.
|
|
||||||
|
|
||||||
### Warning Thresholds
|
|
||||||
|
|
||||||
Configure automatic actions when players reach warning thresholds:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"WarnThreshold": {
|
|
||||||
"998": "css_addban STEAMID64 60 \"3/4 Warn\"",
|
|
||||||
"999": "css_ban #USERID 120 \"4/4 Warn\""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:** Automatically ban a player for 60 minutes when they receive their 3rd warning.
|
|
||||||
|
|
||||||
### Multi-Server Mode
|
|
||||||
|
|
||||||
Enable if you're running multiple servers with a shared database:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"MultiServerMode": true
|
|
||||||
```
|
|
||||||
|
|
||||||
When enabled:
|
|
||||||
- Bans are shared across all servers
|
|
||||||
- Admin permissions can be global or server-specific
|
|
||||||
- Player data is synchronized
|
|
||||||
|
|
||||||
### Discord Integration
|
|
||||||
|
|
||||||
Send notifications to Discord webhooks:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"Discord": {
|
|
||||||
"DiscordLogWebhook": "https://discord.com/api/webhooks/...",
|
|
||||||
"DiscordPenaltyBanSettings": [...],
|
|
||||||
"DiscordPenaltyMuteSettings": [...],
|
|
||||||
"DiscordPenaltyGagSettings": [...],
|
|
||||||
"DiscordPenaltySilenceSettings": [...],
|
|
||||||
"DiscordPenaltyWarnSettings": [...],
|
|
||||||
"DiscordAssociatedAccountsSettings": [...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Webhook Settings:**
|
|
||||||
Each penalty type can have its own webhook configuration:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"DiscordPenaltyBanSettings": [
|
|
||||||
{
|
|
||||||
"name": "Color",
|
|
||||||
"value": "#FF0000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Webhook",
|
|
||||||
"value": "https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ThumbnailUrl",
|
|
||||||
"value": "https://example.com/ban-icon.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ImageUrl",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Footer",
|
|
||||||
"value": "CS2-SimpleAdmin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Time",
|
|
||||||
"value": "{relative}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Available Placeholders:**
|
|
||||||
- `{relative}` - Relative timestamp
|
|
||||||
- `{fixed}` - Fixed timestamp
|
|
||||||
|
|
||||||
### Map Configuration
|
|
||||||
|
|
||||||
Configure default maps and workshop maps:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"DefaultMaps": [
|
|
||||||
"de_dust2",
|
|
||||||
"de_mirage",
|
|
||||||
"de_inferno"
|
|
||||||
],
|
|
||||||
"WorkshopMaps": {
|
|
||||||
"aim_map": "123456789",
|
|
||||||
"surf_map": "987654321"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Server Commands
|
|
||||||
|
|
||||||
Add custom commands to the admin menu:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"CustomServerCommands": [
|
|
||||||
{
|
|
||||||
"Flag": "@css/root",
|
|
||||||
"DisplayName": "Reload Admins",
|
|
||||||
"Command": "css_reloadadmins"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Flag": "@css/cheats",
|
|
||||||
"DisplayName": "Enable sv_cheats",
|
|
||||||
"Command": "sv_cheats 1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Menu Configuration
|
|
||||||
|
|
||||||
Configure menu appearance and options:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"MenuConfig": {
|
|
||||||
"MenuType": "selectable",
|
|
||||||
"Durations": [
|
|
||||||
{ "name": "1 minute", "duration": 1 },
|
|
||||||
{ "name": "5 minutes", "duration": 5 },
|
|
||||||
{ "name": "15 minutes", "duration": 15 },
|
|
||||||
{ "name": "1 hour", "duration": 60 },
|
|
||||||
{ "name": "1 day", "duration": 1440 },
|
|
||||||
{ "name": "7 days", "duration": 10080 },
|
|
||||||
{ "name": "14 days", "duration": 20160 },
|
|
||||||
{ "name": "30 days", "duration": 43200 },
|
|
||||||
{ "name": "Permanent", "duration": 0 }
|
|
||||||
],
|
|
||||||
"BanReasons": [
|
|
||||||
"Hacking",
|
|
||||||
"Voice Abuse",
|
|
||||||
"Chat Abuse",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
],
|
|
||||||
"KickReasons": [
|
|
||||||
"Voice Abuse",
|
|
||||||
"Chat Abuse",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
],
|
|
||||||
"WarnReasons": [
|
|
||||||
"Voice Abuse",
|
|
||||||
"Chat Abuse",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
],
|
|
||||||
"MuteReasons": [
|
|
||||||
"Advertising",
|
|
||||||
"Spamming",
|
|
||||||
"Spectator camera abuse",
|
|
||||||
"Hate",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
],
|
|
||||||
"AdminFlags": [
|
|
||||||
{ "name": "Generic", "flag": "@css/generic" },
|
|
||||||
{ "name": "Chat", "flag": "@css/chat" },
|
|
||||||
{ "name": "Change Map", "flag": "@css/changemap" },
|
|
||||||
{ "name": "Slay", "flag": "@css/slay" },
|
|
||||||
{ "name": "Kick", "flag": "@css/kick" },
|
|
||||||
{ "name": "Ban", "flag": "@css/ban" },
|
|
||||||
{ "name": "Perm Ban", "flag": "@css/permban" },
|
|
||||||
{ "name": "Unban", "flag": "@css/unban" },
|
|
||||||
{ "name": "Show IP", "flag": "@css/showip" },
|
|
||||||
{ "name": "Cvar", "flag": "@css/cvar" },
|
|
||||||
{ "name": "Rcon", "flag": "@css/rcon" },
|
|
||||||
{ "name": "Root (all flags)", "flag": "@css/root" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Timezone List
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click to expand timezone list</summary>
|
|
||||||
|
|
||||||
```
|
|
||||||
UTC
|
|
||||||
America/New_York
|
|
||||||
America/Chicago
|
|
||||||
America/Denver
|
|
||||||
America/Los_Angeles
|
|
||||||
Europe/London
|
|
||||||
Europe/Paris
|
|
||||||
Europe/Berlin
|
|
||||||
Europe/Warsaw
|
|
||||||
Europe/Moscow
|
|
||||||
Asia/Tokyo
|
|
||||||
Asia/Shanghai
|
|
||||||
Asia/Dubai
|
|
||||||
Australia/Sydney
|
|
||||||
Pacific/Auckland
|
|
||||||
... (and many more)
|
|
||||||
```
|
|
||||||
|
|
||||||
For a complete list, see the info.txt file in the documentation folder.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Commands Configuration
|
|
||||||
|
|
||||||
You can customize command aliases in:
|
|
||||||
```
|
|
||||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Commands": {
|
|
||||||
"css_ban": {
|
|
||||||
"Aliases": [
|
|
||||||
"css_ban",
|
|
||||||
"css_ban2"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This allows you to:
|
|
||||||
- **Disable commands** - Remove all aliases from the array
|
|
||||||
- **Add aliases** - Add multiple command variations
|
|
||||||
- **Rename commands** - Change the command name while keeping functionality
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
1. **Use MySQL in production** - SQLite is not suitable for multi-server setups
|
|
||||||
2. **Set MaxBanDuration** - Prevent accidental permanent bans
|
|
||||||
3. **Enable DisableDangerousCommands** - Protect against accidental server crashes
|
|
||||||
4. **Use strong database passwords** - If using MySQL
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
1. **Set ReloadAdminsEveryMapChange to false** - Unless you frequently modify admin permissions
|
|
||||||
2. **Limit DisconnectedPlayersHistoryCount** - Reduce memory usage
|
|
||||||
3. **Use database indices** - Migrations create these automatically
|
|
||||||
|
|
||||||
### Multi-Server Setup
|
|
||||||
|
|
||||||
1. **Enable MultiServerMode** - Share data across servers
|
|
||||||
2. **Use MySQL** - Required for multi-server
|
|
||||||
3. **Configure server IDs** - Each server gets a unique ID automatically
|
|
||||||
4. **Test penalties** - Ensure bans work across all servers
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Changes not taking effect
|
|
||||||
|
|
||||||
**Solution:** Reload the plugin or restart the server:
|
|
||||||
```
|
|
||||||
css_plugins reload CS2-SimpleAdmin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Discord webhooks not working
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- Verify webhook URL is correct
|
|
||||||
- Check that the webhook is not deleted in Discord
|
|
||||||
- Ensure server has internet access
|
|
||||||
|
|
||||||
### TimeMode issues
|
|
||||||
|
|
||||||
**Solution:** Set your timezone correctly in the configuration
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- **[Learn admin commands](commands/basebans)** - Browse available commands
|
|
||||||
- **[Set up admins](#)** - Add your admin team
|
|
||||||
- **[Configure modules](../modules/intro)** - Extend functionality
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 2
|
|
||||||
---
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
This guide will help you install CS2-SimpleAdmin on your Counter-Strike 2 server.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before installing CS2-SimpleAdmin, ensure you have the following dependencies installed:
|
|
||||||
|
|
||||||
### Required Dependencies
|
|
||||||
|
|
||||||
1. **[CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/)** (v1.0.340+)
|
|
||||||
- The core framework for CS2 server plugins
|
|
||||||
|
|
||||||
2. **[AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)**
|
|
||||||
- Required by PlayerSettings
|
|
||||||
|
|
||||||
3. **[PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)**
|
|
||||||
- Required by MenuManager
|
|
||||||
|
|
||||||
4. **[MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)**
|
|
||||||
- Provides the menu system
|
|
||||||
|
|
||||||
### Database Requirements
|
|
||||||
|
|
||||||
You'll need either:
|
|
||||||
- **MySQL** server (recommended for production)
|
|
||||||
- **SQLite** (built-in, good for testing)
|
|
||||||
|
|
||||||
## Installation Steps
|
|
||||||
|
|
||||||
### 1. Download the Plugin
|
|
||||||
|
|
||||||
Download the latest release from the [GitHub Releases page](https://github.com/daffyyyy/CS2-SimpleAdmin/releases).
|
|
||||||
|
|
||||||
You can either:
|
|
||||||
- Download the pre-built release ZIP file
|
|
||||||
- Clone the repository and build from source
|
|
||||||
|
|
||||||
### 2. Extract Files
|
|
||||||
|
|
||||||
Extract the downloaded files to your server's CounterStrikeSharp directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
game/csgo/addons/counterstrikesharp/plugins/CS2-SimpleAdmin/
|
|
||||||
```
|
|
||||||
|
|
||||||
Your directory structure should look like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
csgo/
|
|
||||||
└── addons/
|
|
||||||
└── counterstrikesharp/
|
|
||||||
├── plugins/
|
|
||||||
│ └── CS2-SimpleAdmin/
|
|
||||||
│ ├── CS2-SimpleAdmin.dll
|
|
||||||
│ ├── lang/
|
|
||||||
│ └── ... (other files)
|
|
||||||
└── shared/
|
|
||||||
└── CS2-SimpleAdminApi/
|
|
||||||
└── CS2-SimpleAdminApi.dll
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. First Launch
|
|
||||||
|
|
||||||
Start your server. On the first launch, CS2-SimpleAdmin will:
|
|
||||||
|
|
||||||
1. Create a configuration file at:
|
|
||||||
```
|
|
||||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Create a database (if using SQLite):
|
|
||||||
```
|
|
||||||
addons/counterstrikesharp/plugins/CS2-SimpleAdmin/cs2-simpleadmin.sqlite
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Apply database migrations automatically
|
|
||||||
|
|
||||||
### 4. Configure the Plugin
|
|
||||||
|
|
||||||
Edit the generated configuration file to match your server setup.
|
|
||||||
|
|
||||||
See the [Configuration Guide](configuration) for detailed information.
|
|
||||||
|
|
||||||
### 5. Restart Your Server
|
|
||||||
|
|
||||||
After editing the configuration, restart your server or reload the plugin:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
css_plugins reload CS2-SimpleAdmin
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building from Source
|
|
||||||
|
|
||||||
If you want to build CS2-SimpleAdmin from source:
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- .NET 8.0 SDK
|
|
||||||
- Git
|
|
||||||
|
|
||||||
### Build Steps
|
|
||||||
|
|
||||||
1. **Clone the repository:**
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/daffyyyy/CS2-SimpleAdmin.git
|
|
||||||
cd CS2-SimpleAdmin
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Restore dependencies:**
|
|
||||||
```bash
|
|
||||||
dotnet restore CS2-SimpleAdmin.sln
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Build the solution:**
|
|
||||||
```bash
|
|
||||||
dotnet build CS2-SimpleAdmin.sln -c Release
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Build output location:**
|
|
||||||
```
|
|
||||||
CS2-SimpleAdmin/bin/Release/net8.0/
|
|
||||||
CS2-SimpleAdminApi/bin/Release/net8.0/
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Copy to server:**
|
|
||||||
- Copy `CS2-SimpleAdmin.dll` and its dependencies to `plugins/CS2-SimpleAdmin/`
|
|
||||||
- Copy `CS2-SimpleAdminApi.dll` to `shared/CS2-SimpleAdminApi/`
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
To verify the installation was successful:
|
|
||||||
|
|
||||||
1. **Check server console** for the plugin load message:
|
|
||||||
```
|
|
||||||
[CS2-SimpleAdmin] Plugin loaded successfully
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Run an admin command** in-game:
|
|
||||||
```
|
|
||||||
css_admin
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Check the logs** at:
|
|
||||||
```
|
|
||||||
addons/counterstrikesharp/logs/CS2-SimpleAdmin*.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Plugin doesn't load
|
|
||||||
|
|
||||||
**Solution:** Ensure all required dependencies are installed:
|
|
||||||
- CounterStrikeSharp (latest version)
|
|
||||||
- AnyBaseLibCS2
|
|
||||||
- PlayerSettings
|
|
||||||
- MenuManagerCS2
|
|
||||||
|
|
||||||
### Database connection errors
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- For MySQL: Verify database credentials in the config file
|
|
||||||
- For SQLite: Ensure the plugin has write permissions in its directory
|
|
||||||
|
|
||||||
### Commands not working
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- Check that you have admin permissions configured
|
|
||||||
- Verify the commands are enabled in `Commands.json`
|
|
||||||
- Check server console for error messages
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- **[Configure your plugin](configuration)** - Set up database, permissions, and features
|
|
||||||
- **[Learn the commands](commands/basebans)** - Browse available admin commands
|
|
||||||
- **[Add admins](#)** - Set up your admin team
|
|
||||||
|
|
||||||
## Need Help?
|
|
||||||
|
|
||||||
If you encounter issues:
|
|
||||||
|
|
||||||
1. Check the [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues) for similar problems
|
|
||||||
2. Review server logs for error messages
|
|
||||||
3. Ask for help on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
# Introduction
|
|
||||||
|
|
||||||
Welcome to **CS2-SimpleAdmin** - a comprehensive administration plugin for Counter-Strike 2 servers built with C# (.NET 8.0) for CounterStrikeSharp.
|
|
||||||
|
|
||||||
## What is CS2-SimpleAdmin?
|
|
||||||
|
|
||||||
CS2-SimpleAdmin is a powerful server administration tool that provides comprehensive features for managing your Counter-Strike 2 server. It offers:
|
|
||||||
|
|
||||||
- **Player Management** - Ban, kick, mute, gag, and warn players
|
|
||||||
- **Admin System** - Flexible permission system with flags and groups
|
|
||||||
- **Multi-Server Support** - Manage multiple servers with a shared database
|
|
||||||
- **Discord Integration** - Send notifications to Discord webhooks
|
|
||||||
- **Menu System** - Easy-to-use admin menus
|
|
||||||
- **Extensive Commands** - Over 50 admin commands
|
|
||||||
- **Module Support** - Extend functionality with custom modules
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### 🛡️ Comprehensive Penalty System
|
|
||||||
- **Bans** - Ban players by SteamID or IP address
|
|
||||||
- **Mutes** - Mute voice communication
|
|
||||||
- **Gags** - Gag text chat
|
|
||||||
- **Silence** - Block both voice and text
|
|
||||||
- **Warnings** - Progressive warning system with auto-escalation
|
|
||||||
|
|
||||||
### 👥 Flexible Admin System
|
|
||||||
- Permission-based access control using flags
|
|
||||||
- Admin groups for easy management
|
|
||||||
- Immunity levels to prevent abuse
|
|
||||||
- Server-specific or global admin assignments
|
|
||||||
|
|
||||||
### 🗄️ Database Support
|
|
||||||
- **MySQL** - For production environments
|
|
||||||
- **SQLite** - For quick setup and testing
|
|
||||||
- Automatic migration system
|
|
||||||
- Multi-server mode with shared data
|
|
||||||
|
|
||||||
### 🎮 User-Friendly Interface
|
|
||||||
- Interactive admin menus
|
|
||||||
- In-game admin panel
|
|
||||||
- Player selection menus
|
|
||||||
- Duration and reason selection
|
|
||||||
|
|
||||||
### 🔧 Extensibility
|
|
||||||
- Public API for module development
|
|
||||||
- Event system for custom integrations
|
|
||||||
- Command registration system
|
|
||||||
- Menu builder API
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
Before installing CS2-SimpleAdmin, make sure you have:
|
|
||||||
|
|
||||||
- [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/) (v1.0.340+)
|
|
||||||
- [AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)
|
|
||||||
- [PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)
|
|
||||||
- [MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)
|
|
||||||
- MySQL database (or use SQLite for testing)
|
|
||||||
|
|
||||||
## Quick Links
|
|
||||||
|
|
||||||
- **[Installation Guide](installation)** - Get started with CS2-SimpleAdmin
|
|
||||||
- **[Configuration](configuration)** - Learn how to configure the plugin
|
|
||||||
- **[Commands](commands/basebans)** - Browse all available commands
|
|
||||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code and releases
|
|
||||||
|
|
||||||
## Community & Support
|
|
||||||
|
|
||||||
Need help or want to contribute?
|
|
||||||
|
|
||||||
- **Issues** - Report bugs on [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
|
||||||
- **Discussions** - Ask questions on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
|
||||||
- **Pull Requests** - Contribute improvements
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
CS2-SimpleAdmin is open-source software. Check the [GitHub repository](https://github.com/daffyyyy/CS2-SimpleAdmin) for license details.
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
// `@type` JSDoc annotations allow editor autocompletion and type checking
|
|
||||||
// (when paired with `@ts-check`).
|
|
||||||
// There are various equivalent ways to declare your Docusaurus config.
|
|
||||||
// See: https://docusaurus.io/docs/api/docusaurus-config
|
|
||||||
|
|
||||||
import {themes as prismThemes} from 'prism-react-renderer';
|
|
||||||
|
|
||||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
|
||||||
|
|
||||||
/** @type {import('@docusaurus/types').Config} */
|
|
||||||
const config = {
|
|
||||||
title: 'CS2-SimpleAdmin',
|
|
||||||
tagline: 'Comprehensive administration plugin for Counter-Strike 2 servers',
|
|
||||||
favicon: 'img/favicon.ico',
|
|
||||||
|
|
||||||
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
|
||||||
future: {
|
|
||||||
v4: true, // Improve compatibility with the upcoming Docusaurus v4
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set the production url of your site here
|
|
||||||
url: 'https://cs2-simpleadmin.daffyy.dev',
|
|
||||||
// Set the /<baseUrl>/ pathname under which your site is served
|
|
||||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
|
||||||
baseUrl: '/',
|
|
||||||
|
|
||||||
// GitHub pages deployment config.
|
|
||||||
// If you aren't using GitHub pages, you don't need these.
|
|
||||||
organizationName: 'daffyyyy', // Usually your GitHub org/user name.
|
|
||||||
projectName: 'CS2-SimpleAdmin', // Usually your repo name.
|
|
||||||
|
|
||||||
onBrokenLinks: 'throw',
|
|
||||||
|
|
||||||
// Even if you don't use internationalization, you can use this field to set
|
|
||||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
|
||||||
// may want to replace "en" with "zh-Hans".
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en'],
|
|
||||||
},
|
|
||||||
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'classic',
|
|
||||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
|
||||||
({
|
|
||||||
docs: {
|
|
||||||
sidebarPath: './sidebars.js',
|
|
||||||
// Please change this to your repo.
|
|
||||||
// Remove this to remove the "edit this page" links.
|
|
||||||
// editUrl:
|
|
||||||
// '',
|
|
||||||
},
|
|
||||||
blog: false, // Disable blog
|
|
||||||
theme: {
|
|
||||||
customCss: './src/css/custom.css',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
themeConfig:
|
|
||||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
|
||||||
({
|
|
||||||
// Replace with your project's social card
|
|
||||||
image: 'img/docusaurus-social-card.jpg',
|
|
||||||
metadata: [
|
|
||||||
{name: 'keywords', content: 'CS2, Counter-Strike 2, admin plugin, server management, bans, mutes, CounterStrikeSharp'},
|
|
||||||
{name: 'description', content: 'Comprehensive administration plugin for Counter-Strike 2 servers with ban management, multi-server support, and extensible API'},
|
|
||||||
{name: 'author', content: 'daffyyyy'},
|
|
||||||
{property: 'og:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
|
|
||||||
{property: 'og:description', content: 'Comprehensive administration plugin for CS2 servers. Manage bans, mutes, warnings, and permissions with multi-server support.'},
|
|
||||||
{property: 'og:type', content: 'website'},
|
|
||||||
{property: 'og:url', content: 'https://cs2-simpleadmin.daffyy.dev'},
|
|
||||||
{property: 'og:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
|
|
||||||
{name: 'twitter:card', content: 'summary_large_image'},
|
|
||||||
{name: 'twitter:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
|
|
||||||
{name: 'twitter:description', content: 'Comprehensive administration plugin for CS2 servers with ban management and multi-server support.'},
|
|
||||||
{name: 'twitter:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
|
|
||||||
],
|
|
||||||
colorMode: {
|
|
||||||
respectPrefersColorScheme: true,
|
|
||||||
},
|
|
||||||
navbar: {
|
|
||||||
title: 'CS2-SimpleAdmin',
|
|
||||||
logo: {
|
|
||||||
alt: 'CS2-SimpleAdmin Logo',
|
|
||||||
src: 'img/logo.svg',
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
type: 'docSidebar',
|
|
||||||
sidebarId: 'userSidebar',
|
|
||||||
position: 'left',
|
|
||||||
label: 'User Guide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'docSidebar',
|
|
||||||
sidebarId: 'modulesSidebar',
|
|
||||||
position: 'left',
|
|
||||||
label: 'Modules',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'docSidebar',
|
|
||||||
sidebarId: 'developerSidebar',
|
|
||||||
position: 'left',
|
|
||||||
label: 'Developer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
|
|
||||||
label: 'GitHub',
|
|
||||||
position: 'right',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
style: 'dark',
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
title: 'Documentation',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'User Guide',
|
|
||||||
to: '/docs/user/intro',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Modules',
|
|
||||||
to: '/docs/modules/intro',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Developer',
|
|
||||||
to: '/docs/developer/intro',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Community',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'GitHub Issues',
|
|
||||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/issues',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'GitHub Discussions',
|
|
||||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/discussions',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'More',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'GitHub',
|
|
||||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Releases',
|
|
||||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/releases',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} CS2-SimpleAdmin. Built with Docusaurus.`,
|
|
||||||
},
|
|
||||||
prism: {
|
|
||||||
theme: prismThemes.github,
|
|
||||||
darkTheme: prismThemes.dracula,
|
|
||||||
additionalLanguages: ['csharp', 'json', 'bash'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
17981
CS2-SimpleAdmin-docs/package-lock.json
generated
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cs-2-simple-admin-docs",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"docusaurus": "docusaurus",
|
|
||||||
"start": "docusaurus start",
|
|
||||||
"build": "docusaurus build",
|
|
||||||
"swizzle": "docusaurus swizzle",
|
|
||||||
"deploy": "docusaurus deploy",
|
|
||||||
"clear": "docusaurus clear",
|
|
||||||
"serve": "docusaurus serve",
|
|
||||||
"write-translations": "docusaurus write-translations",
|
|
||||||
"write-heading-ids": "docusaurus write-heading-ids"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@docusaurus/core": "3.9.2",
|
|
||||||
"@docusaurus/preset-classic": "3.9.2",
|
|
||||||
"@mdx-js/react": "^3.0.0",
|
|
||||||
"clsx": "^2.0.0",
|
|
||||||
"prism-react-renderer": "^2.3.0",
|
|
||||||
"react": "^19.0.0",
|
|
||||||
"react-dom": "^19.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@docusaurus/module-type-aliases": "3.9.2",
|
|
||||||
"@docusaurus/types": "3.9.2"
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.5%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 3 chrome version",
|
|
||||||
"last 3 firefox version",
|
|
||||||
"last 5 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creating a sidebar enables you to:
|
|
||||||
- create an ordered group of docs
|
|
||||||
- render a sidebar for each doc of that group
|
|
||||||
- provide next/previous navigation
|
|
||||||
|
|
||||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
|
||||||
|
|
||||||
Create as many sidebars as you want.
|
|
||||||
|
|
||||||
@type {import('@docusaurus/plugin-content-docs').SidebarsConfig}
|
|
||||||
*/
|
|
||||||
const sidebars = {
|
|
||||||
userSidebar: [
|
|
||||||
'user/intro',
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Getting Started',
|
|
||||||
items: [
|
|
||||||
'user/installation',
|
|
||||||
'user/configuration',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Commands',
|
|
||||||
items: [
|
|
||||||
'user/commands/basebans',
|
|
||||||
'user/commands/basecomms',
|
|
||||||
'user/commands/basecommands',
|
|
||||||
'user/commands/basechat',
|
|
||||||
'user/commands/playercommands',
|
|
||||||
'user/commands/basevotes',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
modulesSidebar: [
|
|
||||||
'modules/intro',
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Official Modules',
|
|
||||||
items: [
|
|
||||||
'modules/funcommands',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'modules/development',
|
|
||||||
],
|
|
||||||
|
|
||||||
developerSidebar: [
|
|
||||||
'developer/intro',
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'API Reference',
|
|
||||||
items: [
|
|
||||||
'developer/api/overview',
|
|
||||||
'developer/api/commands',
|
|
||||||
'developer/api/menus',
|
|
||||||
'developer/api/penalties',
|
|
||||||
'developer/api/events',
|
|
||||||
'developer/api/utilities',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Module Development',
|
|
||||||
items: [
|
|
||||||
'developer/module/getting-started',
|
|
||||||
'developer/module/best-practices',
|
|
||||||
'developer/module/examples',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'developer/architecture',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sidebars;
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import clsx from 'clsx';
|
|
||||||
import Heading from '@theme/Heading';
|
|
||||||
import styles from './styles.module.css';
|
|
||||||
|
|
||||||
const FeatureList = [
|
|
||||||
{
|
|
||||||
title: 'Comprehensive Admin Tools',
|
|
||||||
img: require('@site/static/img/index_1.png').default,
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
Full suite of admin commands for managing players, bans, mutes, warnings,
|
|
||||||
and server settings. Everything you need to moderate your CS2 server.
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Multi-Server Support',
|
|
||||||
img: require('@site/static/img/index_2.png').default,
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
Manage multiple servers with synchronized admin permissions and penalties.
|
|
||||||
Share bans, mutes, and admin groups across your entire server network.
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Extensible API',
|
|
||||||
img: require('@site/static/img/index_3.png').default,
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
Build custom modules using the public API. Create your own commands,
|
|
||||||
menus, and integrate with CS2-SimpleAdmin's permission and penalty systems.
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function Feature({img, title, description}) {
|
|
||||||
return (
|
|
||||||
<div className={clsx('col col--4')}>
|
|
||||||
<div className="text--center">
|
|
||||||
<img src={img} className={styles.featureSvg} alt={title} />
|
|
||||||
</div>
|
|
||||||
<div className="text--center padding-horiz--md">
|
|
||||||
<Heading as="h3">{title}</Heading>
|
|
||||||
<p>{description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomepageFeatures() {
|
|
||||||
return (
|
|
||||||
<section className={styles.features}>
|
|
||||||
<div className="container">
|
|
||||||
<div className="row">
|
|
||||||
{FeatureList.map((props, idx) => (
|
|
||||||
<Feature key={idx} {...props} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
.features {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 2rem 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featureSvg {
|
|
||||||
height: 200px;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/**
|
|
||||||
* Any CSS included here will be global. The classic template
|
|
||||||
* bundles Infima by default. Infima is a CSS framework designed to
|
|
||||||
* work well for content-centric websites.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* You can override the default Infima variables here. */
|
|
||||||
:root {
|
|
||||||
--ifm-color-primary: #ff8c00;
|
|
||||||
--ifm-color-primary-dark: #e67e00;
|
|
||||||
--ifm-color-primary-darker: #d97700;
|
|
||||||
--ifm-color-primary-darkest: #b36200;
|
|
||||||
--ifm-color-primary-light: #ff9a1a;
|
|
||||||
--ifm-color-primary-lighter: #ffa328;
|
|
||||||
--ifm-color-primary-lightest: #ffb54d;
|
|
||||||
--ifm-code-font-size: 95%;
|
|
||||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
|
||||||
[data-theme='dark'] {
|
|
||||||
--ifm-color-primary: #ff9500;
|
|
||||||
--ifm-color-primary-dark: #e68600;
|
|
||||||
--ifm-color-primary-darker: #cc7700;
|
|
||||||
--ifm-color-primary-darkest: #b36200;
|
|
||||||
--ifm-color-primary-light: #ffa31a;
|
|
||||||
--ifm-color-primary-lighter: #ffad33;
|
|
||||||
--ifm-color-primary-lightest: #ffc266;
|
|
||||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import clsx from 'clsx';
|
|
||||||
import Link from '@docusaurus/Link';
|
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
||||||
import Layout from '@theme/Layout';
|
|
||||||
import HomepageFeatures from '@site/src/components/HomepageFeatures';
|
|
||||||
|
|
||||||
import Heading from '@theme/Heading';
|
|
||||||
import styles from './index.module.css';
|
|
||||||
|
|
||||||
function HomepageHeader() {
|
|
||||||
const {siteConfig} = useDocusaurusContext();
|
|
||||||
return (
|
|
||||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
|
||||||
<iframe
|
|
||||||
className={styles.videoBackground}
|
|
||||||
src="https://www.youtube.com/embed/4qEdIXLdxMo?autoplay=1&mute=1&loop=1&playlist=4qEdIXLdxMo&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1"
|
|
||||||
title="Background Video"
|
|
||||||
frameBorder="0"
|
|
||||||
allow="autoplay; encrypted-media"
|
|
||||||
allowFullScreen
|
|
||||||
/>
|
|
||||||
<div className={styles.videoOverlay}></div>
|
|
||||||
<div className="container">
|
|
||||||
<Heading as="h1" className="hero__title">
|
|
||||||
{siteConfig.title}
|
|
||||||
</Heading>
|
|
||||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
|
||||||
<div className={styles.buttons}>
|
|
||||||
<Link
|
|
||||||
className="button button--secondary button--lg"
|
|
||||||
to="/docs/user/intro">
|
|
||||||
Get Started
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
const {siteConfig} = useDocusaurusContext();
|
|
||||||
return (
|
|
||||||
<Layout
|
|
||||||
title={`${siteConfig.title} - Admin Plugin for CS2`}
|
|
||||||
description="CS2-SimpleAdmin is a comprehensive administration plugin for Counter-Strike 2 servers. Manage bans, mutes, warnings, and permissions with multi-server support and extensible API.">
|
|
||||||
<HomepageHeader />
|
|
||||||
<main>
|
|
||||||
<HomepageFeatures />
|
|
||||||
</main>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
|
||||||
* and scoped locally.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.heroBanner {
|
|
||||||
padding: 8rem 0;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 600px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.videoBackground {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 100vw;
|
|
||||||
height: 56.25vw; /* 16:9 Aspect Ratio */
|
|
||||||
min-height: 100vh;
|
|
||||||
min-width: 177.77vh; /* 16:9 Aspect Ratio */
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.videoOverlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroBanner :global(.container) {
|
|
||||||
position: relative;
|
|
||||||
z-index: 999 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroBanner :global(.hero__title) {
|
|
||||||
color: white !important;
|
|
||||||
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9);
|
|
||||||
font-size: 3.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroBanner :global(.hero__subtitle) {
|
|
||||||
color: white !important;
|
|
||||||
text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.9);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroBanner .buttons {
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 996px) {
|
|
||||||
.heroBanner {
|
|
||||||
padding: 4rem 2rem;
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.videoBackground {
|
|
||||||
width: 200vw;
|
|
||||||
height: 112.5vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroBanner :global(.hero__title) {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroBanner :global(.hero__subtitle) {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons :global(.button) {
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Markdown page example
|
|
||||||
---
|
|
||||||
|
|
||||||
# Markdown page example
|
|
||||||
|
|
||||||
You don't need React to write simple standalone pages.
|
|
||||||
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
@@ -1,31 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.8.34309.116
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS2-SimpleAdmin", "CS2-SimpleAdmin\CS2-SimpleAdmin.csproj", "{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS2-SimpleAdminApi", "CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj", "{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {86114444-059F-4DB8-9A55-E6D1BB76238D}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
BIN
CS2-SimpleAdmin/3rd_party/MenuManagerApi.dll
vendored
@@ -1,446 +0,0 @@
|
|||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Core.Commands;
|
|
||||||
using CounterStrikeSharp.API.Core.Translations;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
|
||||||
using CS2_SimpleAdmin.Managers;
|
|
||||||
using CS2_SimpleAdmin.Menus;
|
|
||||||
using CS2_SimpleAdminApi;
|
|
||||||
using Microsoft.Extensions.Localization;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin.Api;
|
|
||||||
|
|
||||||
public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
|
||||||
{
|
|
||||||
public event Action? OnSimpleAdminReady;
|
|
||||||
public void OnSimpleAdminReadyEvent() => OnSimpleAdminReady?.Invoke();
|
|
||||||
|
|
||||||
public PlayerInfo GetPlayerInfo(CCSPlayerController player)
|
|
||||||
{
|
|
||||||
return !player.UserId.HasValue
|
|
||||||
? throw new KeyNotFoundException("Player with specific UserId not found")
|
|
||||||
: CS2_SimpleAdmin.PlayersInfo[player.SteamID];
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
|
|
||||||
public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
|
|
||||||
public int? GetServerId() => CS2_SimpleAdmin.ServerId;
|
|
||||||
|
|
||||||
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(
|
|
||||||
CCSPlayerController player)
|
|
||||||
{
|
|
||||||
return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied;
|
|
||||||
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded;
|
|
||||||
public event Action<string, string?, bool, object>? OnAdminShowActivity;
|
|
||||||
public event Action<int, bool>? OnAdminToggleSilent;
|
|
||||||
|
|
||||||
public void OnPlayerPenaltiedEvent(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
|
|
||||||
int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration,
|
|
||||||
penaltyId, CS2_SimpleAdmin.ServerId);
|
|
||||||
|
|
||||||
public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
|
|
||||||
int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration,
|
|
||||||
penaltyId, CS2_SimpleAdmin.ServerId);
|
|
||||||
|
|
||||||
public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false,
|
|
||||||
params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
|
|
||||||
|
|
||||||
public void OnAdminToggleSilentEvent(int slot, bool status) => OnAdminToggleSilent?.Invoke(slot, status);
|
|
||||||
|
|
||||||
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType,
|
|
||||||
string reason, int duration = -1)
|
|
||||||
{
|
|
||||||
switch (penaltyType)
|
|
||||||
{
|
|
||||||
case PenaltyType.Ban:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Kick:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Gag:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Mute:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Silence:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Warn:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason,
|
|
||||||
int duration = -1)
|
|
||||||
{
|
|
||||||
switch (penaltyType)
|
|
||||||
{
|
|
||||||
case PenaltyType.Ban:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.AddBan(admin, steamid, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Gag:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.AddGag(admin, steamid, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Mute:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.AddMute(admin, steamid, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Silence:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.AddSilence(admin, steamid, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PenaltyType.Warn:
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.AddWarn(admin, steamid, duration, reason);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LogCommand(CCSPlayerController? caller, string command)
|
|
||||||
{
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LogCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAdminSilent(CCSPlayerController player)
|
|
||||||
{
|
|
||||||
return CS2_SimpleAdmin.SilentPlayers.Contains(player.Slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashSet<int> ListSilentAdminsSlots()
|
|
||||||
{
|
|
||||||
return CS2_SimpleAdmin.SilentPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
throw new ArgumentException("Command name cannot be null or empty.", nameof(name));
|
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(callback);
|
|
||||||
|
|
||||||
var definition = new CommandDefinition(name, description ?? "No description", callback);
|
|
||||||
if (!RegisterCommands._commandDefinitions.TryGetValue(name, out var list))
|
|
||||||
{
|
|
||||||
list = new List<CommandDefinition>();
|
|
||||||
RegisterCommands._commandDefinitions[name] = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnRegisterCommand(string commandName)
|
|
||||||
{
|
|
||||||
var definitions = RegisterCommands._commandDefinitions[commandName];
|
|
||||||
if (definitions.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var definition in definitions)
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.RemoveCommand(commandName, definition.Callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TargetResult? GetTarget(CommandInfo command)
|
|
||||||
{
|
|
||||||
return CS2_SimpleAdmin.GetTarget(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false,
|
|
||||||
params object[] messageArgs)
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(messageKey, callerName, dontPublish, messageArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null,
|
|
||||||
bool dontPublish = false)
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivityTranslated(translatedMessage, callerName, dontPublish);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowAdminActivityLocalized(object moduleLocalizer, string messageKey, string? callerName = null,
|
|
||||||
bool dontPublish = false, params object[] messageArgs)
|
|
||||||
{
|
|
||||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
|
||||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
|
||||||
|
|
||||||
Helper.ShowAdminActivityLocalized(localizer, messageKey, callerName, dontPublish, messageArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer)
|
|
||||||
{
|
|
||||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
|
||||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
|
||||||
|
|
||||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryNameKey, permission, localizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
|
||||||
Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null)
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
|
|
||||||
return;
|
|
||||||
|
|
||||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
|
||||||
{
|
|
||||||
if (menuFactory(player) is not MenuBuilder menuBuilder)
|
|
||||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
|
||||||
|
|
||||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
|
||||||
menuBuilder.WithBackAction(p =>
|
|
||||||
{
|
|
||||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return menuBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
|
||||||
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null)
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
|
|
||||||
return;
|
|
||||||
|
|
||||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
|
||||||
{
|
|
||||||
var context = new MenuContext(categoryId, menuId, menuName, permission, commandName);
|
|
||||||
|
|
||||||
if (menuFactory(player, context) is not MenuBuilder menuBuilder)
|
|
||||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
|
||||||
|
|
||||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
|
||||||
menuBuilder.WithBackAction(p =>
|
|
||||||
{
|
|
||||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return menuBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterMenu(string categoryId, string menuId, string menuNameKey,
|
|
||||||
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer)
|
|
||||||
{
|
|
||||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
|
||||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
|
||||||
|
|
||||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuNameKey, BuilderFactory, permission, commandName, localizer);
|
|
||||||
return;
|
|
||||||
|
|
||||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
|
||||||
{
|
|
||||||
var context = new MenuContext(categoryId, menuId, menuNameKey, permission, commandName);
|
|
||||||
|
|
||||||
if (menuFactory(player, context) is not MenuBuilder menuBuilder)
|
|
||||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
|
||||||
|
|
||||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
|
||||||
menuBuilder.WithBackAction(p =>
|
|
||||||
{
|
|
||||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return menuBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void UnregisterMenu(string categoryId, string menuId)
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.UnregisterMenu(categoryId, menuId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)
|
|
||||||
{
|
|
||||||
var builder = new MenuBuilder(title);
|
|
||||||
builder.WithBackAction(p =>
|
|
||||||
{
|
|
||||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
|
||||||
{
|
|
||||||
// Get translated title if module has localizer
|
|
||||||
string title = context.MenuTitle;
|
|
||||||
|
|
||||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
|
||||||
{
|
|
||||||
// Check if this specific menu has a localizer
|
|
||||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
|
||||||
{
|
|
||||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
|
||||||
{
|
|
||||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fallback to category localizer
|
|
||||||
else if (category.ModuleLocalizer != null)
|
|
||||||
{
|
|
||||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
|
||||||
{
|
|
||||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateMenuWithBack(title, context.CategoryId, player);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CCSPlayerController> GetValidPlayers()
|
|
||||||
{
|
|
||||||
return Helper.GetValidPlayers();
|
|
||||||
}
|
|
||||||
|
|
||||||
public object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin,
|
|
||||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
|
||||||
{
|
|
||||||
var menu = (MenuBuilder)CreateMenuWithBack(title, categoryId, admin);
|
|
||||||
var players = Helper.GetValidPlayers().Where(filter);
|
|
||||||
|
|
||||||
foreach (var player in players)
|
|
||||||
{
|
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
|
||||||
menu.AddOption(playerName, _ =>
|
|
||||||
{
|
|
||||||
if (player.IsValid)
|
|
||||||
{
|
|
||||||
onSelect(admin, player);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
|
|
||||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
|
||||||
{
|
|
||||||
// Get translated title if module has localizer
|
|
||||||
string title = context.MenuTitle;
|
|
||||||
|
|
||||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
|
||||||
{
|
|
||||||
// Check if this specific menu has a localizer
|
|
||||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
|
||||||
{
|
|
||||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
|
||||||
{
|
|
||||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fallback to category localizer
|
|
||||||
else if (category.ModuleLocalizer != null)
|
|
||||||
{
|
|
||||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
|
||||||
{
|
|
||||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateMenuWithPlayers(title, context.CategoryId, admin, filter, onSelect);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
|
||||||
string? permission = null)
|
|
||||||
{
|
|
||||||
if (menu is not MenuBuilder menuBuilder)
|
|
||||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
|
||||||
|
|
||||||
menuBuilder.AddOption(name, action, disabled, permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory,
|
|
||||||
bool disabled = false, string? permission = null)
|
|
||||||
{
|
|
||||||
if (menu is not MenuBuilder menuBuilder)
|
|
||||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
|
||||||
|
|
||||||
menuBuilder.AddSubMenu(name, player =>
|
|
||||||
{
|
|
||||||
var subMenu = subMenuFactory(player);
|
|
||||||
if (subMenu is not MenuBuilder builder)
|
|
||||||
throw new InvalidOperationException("SubMenu factory must return MenuBuilder");
|
|
||||||
return builder;
|
|
||||||
}, disabled, permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenMenu(object menu, CCSPlayerController player)
|
|
||||||
{
|
|
||||||
if (menu is not MenuBuilder menuBuilder)
|
|
||||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
|
||||||
|
|
||||||
menuBuilder.OpenMenu(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
using CounterStrikeSharp.API;
|
|
||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Core.Attributes;
|
|
||||||
using CounterStrikeSharp.API.Core.Capabilities;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
|
||||||
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
|
||||||
using CS2_SimpleAdmin.Database;
|
|
||||||
using CS2_SimpleAdmin.Managers;
|
|
||||||
using CS2_SimpleAdmin.Menus;
|
|
||||||
using CS2_SimpleAdminApi;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MySqlConnector;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
[MinimumApiVersion(300)]
|
|
||||||
public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdminConfig>
|
|
||||||
{
|
|
||||||
internal static CS2_SimpleAdmin Instance { get; private set; } = new();
|
|
||||||
|
|
||||||
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
|
||||||
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
|
||||||
public override string ModuleAuthor => "daffyy";
|
|
||||||
public override string ModuleVersion => "1.7.8-beta-6";
|
|
||||||
|
|
||||||
public override void Load(bool hotReload)
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
if (hotReload)
|
|
||||||
{
|
|
||||||
ServerLoaded = false;
|
|
||||||
_serverLoading = false;
|
|
||||||
|
|
||||||
CacheManager?.Dispose();
|
|
||||||
CacheManager = new CacheManager();
|
|
||||||
|
|
||||||
// OnGameServerSteamAPIActivated();
|
|
||||||
OnMapStart(string.Empty);
|
|
||||||
|
|
||||||
AddTimer(6.0f, () =>
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
PlayersInfo.Clear();
|
|
||||||
CachedPlayers.Clear();
|
|
||||||
BotPlayers.Clear();
|
|
||||||
|
|
||||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
|
|
||||||
{
|
|
||||||
if (!player.IsBot)
|
|
||||||
PlayerManager.LoadPlayerData(player, true);
|
|
||||||
else
|
|
||||||
BotPlayers.Add(player);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
PlayersTimer?.Kill();
|
|
||||||
PlayersTimer = null;
|
|
||||||
}
|
|
||||||
_cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>(GameData.GetSignature("CBasePlayerController_SetPawn"));
|
|
||||||
|
|
||||||
SimpleAdminApi = new Api.CS2_SimpleAdminApi();
|
|
||||||
Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi);
|
|
||||||
|
|
||||||
PlayersTimer?.Kill();
|
|
||||||
PlayersTimer = null;
|
|
||||||
PlayerManager.CheckPlayersTimer();
|
|
||||||
|
|
||||||
Menus.MenuManager.Instance.InitializeDefaultCategories();
|
|
||||||
BasicMenu.Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnAllPluginsLoaded(bool hotReload)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MenuApi = MenuCapability.Get();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError("Unable to load required plugins ... \n{exception}", ex.Message);
|
|
||||||
Unload(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddTimer(6.0f, () => ReloadAdmins(null));
|
|
||||||
RegisterEvents();
|
|
||||||
AddTimer(0.5f, RegisterCommands.InitializeCommands);
|
|
||||||
|
|
||||||
if (!CoreConfig.UnlockConCommands)
|
|
||||||
{
|
|
||||||
_logger?.LogError(
|
|
||||||
$"⚠️ Warning: 'UnlockConCommands' is disabled in core.json. " +
|
|
||||||
$"Players will not be automatically banned when kicked and will be able " +
|
|
||||||
$"to rejoin the server for 60 seconds. " +
|
|
||||||
$"To enable instant banning, set 'UnlockConCommands': true"
|
|
||||||
);
|
|
||||||
_logger?.LogError(
|
|
||||||
$"⚠️ Warning: 'UnlockConCommands' is disabled in core.json. " +
|
|
||||||
$"Players will not be automatically banned when kicked and will be able " +
|
|
||||||
$"to rejoin the server for 60 seconds. " +
|
|
||||||
$"To enable instant banning, set 'UnlockConCommands': true"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnConfigParsed(CS2_SimpleAdminConfig config)
|
|
||||||
{
|
|
||||||
if (System.Diagnostics.Debugger.IsAttached)
|
|
||||||
Environment.FailFast(":(!");
|
|
||||||
|
|
||||||
Helper.UpdateConfig(config);
|
|
||||||
|
|
||||||
_logger = Logger;
|
|
||||||
Config = config;
|
|
||||||
|
|
||||||
bool missing = false;
|
|
||||||
var cssPath = Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp");
|
|
||||||
var pluginsPath = Path.Combine(cssPath, "plugins");
|
|
||||||
var sharedPath = Path.Combine(cssPath, "shared");
|
|
||||||
|
|
||||||
foreach (var plugin in _requiredPlugins)
|
|
||||||
{
|
|
||||||
var pluginDirPath = Path.Combine(pluginsPath, plugin);
|
|
||||||
var pluginDllPath = Path.Combine(pluginDirPath, $"{plugin}.dll");
|
|
||||||
|
|
||||||
if (!Directory.Exists(pluginDirPath))
|
|
||||||
{
|
|
||||||
_logger?.LogError(
|
|
||||||
$"❌ Plugin directory '{plugin}' missing at: {pluginDirPath}"
|
|
||||||
);
|
|
||||||
missing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(pluginDllPath))
|
|
||||||
{
|
|
||||||
_logger?.LogError(
|
|
||||||
$"❌ Plugin DLL '{plugin}.dll' missing at: {pluginDllPath}"
|
|
||||||
);
|
|
||||||
missing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var shared in _requiredShared)
|
|
||||||
{
|
|
||||||
var sharedDirPath = Path.Combine(sharedPath, shared);
|
|
||||||
var sharedDllPath = Path.Combine(sharedDirPath, $"{shared}.dll");
|
|
||||||
|
|
||||||
if (!Directory.Exists(sharedDirPath))
|
|
||||||
{
|
|
||||||
_logger?.LogError(
|
|
||||||
$"❌ Shared library directory '{shared}' missing at: {sharedDirPath}"
|
|
||||||
);
|
|
||||||
missing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(sharedDllPath))
|
|
||||||
{
|
|
||||||
_logger?.LogError(
|
|
||||||
$"❌ Shared library DLL '{shared}.dll' missing at: {sharedDllPath}"
|
|
||||||
);
|
|
||||||
missing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (missing)
|
|
||||||
Server.ExecuteCommand($"css_plugins unload {ModuleName}");
|
|
||||||
|
|
||||||
Instance = this;
|
|
||||||
|
|
||||||
if (Config.DatabaseConfig.DatabaseType.Contains("mysql", StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseHost) ||
|
|
||||||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseName) ||
|
|
||||||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseUser))
|
|
||||||
{
|
|
||||||
throw new Exception("[CS2-SimpleAdmin] You need to setup MySQL credentials in config!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = new MySqlConnectionStringBuilder()
|
|
||||||
{
|
|
||||||
Server = config.DatabaseConfig.DatabaseHost,
|
|
||||||
Database = config.DatabaseConfig.DatabaseName,
|
|
||||||
UserID = config.DatabaseConfig.DatabaseUser,
|
|
||||||
Password = config.DatabaseConfig.DatabasePassword,
|
|
||||||
Port = (uint)config.DatabaseConfig.DatabasePort,
|
|
||||||
SslMode = Enum.TryParse(config.DatabaseConfig.DatabaseSSlMode, true, out MySqlSslMode sslMode)
|
|
||||||
? sslMode
|
|
||||||
: MySqlSslMode.Preferred,
|
|
||||||
Pooling = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
DbConnectionString = builder.ConnectionString;
|
|
||||||
DatabaseProvider = new MySqlDatabaseProvider(DbConnectionString);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.SqliteFilePath))
|
|
||||||
{
|
|
||||||
throw new Exception("[CS2-SimpleAdmin] You need to specify SQLite file path in config!");
|
|
||||||
}
|
|
||||||
|
|
||||||
DatabaseProvider = new SqliteDatabaseProvider(ModuleDirectory + "/" + config.DatabaseConfig.SqliteFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var (success, exception) = Task.Run(() => DatabaseProvider.CheckConnectionAsync()).GetAwaiter().GetResult();
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
if (exception != null)
|
|
||||||
Logger.LogError("Problem with database connection! \n{exception}", exception);
|
|
||||||
|
|
||||||
Unload(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(() => DatabaseProvider.DatabaseMigrationAsync());
|
|
||||||
|
|
||||||
if (!Directory.Exists(ModuleDirectory + "/data"))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(ModuleDirectory + "/data");
|
|
||||||
}
|
|
||||||
|
|
||||||
_localizer = Localizer;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Config.Discord.DiscordLogWebhook))
|
|
||||||
DiscordWebhookClientLog = new DiscordManager(Config.Discord.DiscordLogWebhook);
|
|
||||||
|
|
||||||
if (Config.EnableUpdateCheck)
|
|
||||||
Task.Run(async () => await PluginInfo.CheckVersion(ModuleVersion, Logger));
|
|
||||||
|
|
||||||
PermissionManager = new PermissionManager(DatabaseProvider);
|
|
||||||
BanManager = new BanManager(DatabaseProvider);
|
|
||||||
MuteManager = new MuteManager(DatabaseProvider);
|
|
||||||
WarnManager = new WarnManager(DatabaseProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static TargetResult? GetTarget(CommandInfo command, int argument = 1)
|
|
||||||
{
|
|
||||||
var matches = command.GetArgTargetResult(argument);
|
|
||||||
|
|
||||||
if (!matches.Any())
|
|
||||||
{
|
|
||||||
command.ReplyToCommand($"Target {command.GetArg(argument)} not found.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.GetArg(argument).StartsWith('@'))
|
|
||||||
return matches;
|
|
||||||
|
|
||||||
if (matches.Count() == 1)
|
|
||||||
return matches;
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(argument)}\".");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Unload(bool hotReload)
|
|
||||||
{
|
|
||||||
CacheManager?.Dispose();
|
|
||||||
CacheManager = null;
|
|
||||||
PlayersTimer?.Kill();
|
|
||||||
PlayersTimer = null;
|
|
||||||
UnregisterEvents();
|
|
||||||
|
|
||||||
if (hotReload)
|
|
||||||
PlayersInfo.Clear();
|
|
||||||
else
|
|
||||||
Server.ExecuteCommand($"css_plugins unload {ModuleDirectory}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<RootNamespace>CS2_SimpleAdmin</RootNamespace>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
|
||||||
<DebugType>none</DebugType>
|
|
||||||
<DebugSymbols>false</DebugSymbols>
|
|
||||||
<PublishTrimmed>true</PublishTrimmed>
|
|
||||||
<DebuggerSupport>false</DebuggerSupport>
|
|
||||||
<!-- <GenerateDependencyFile>false</GenerateDependencyFile>-->
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346">
|
|
||||||
<PrivateAssets>none</PrivateAssets>
|
|
||||||
<ExcludeAssets>runtime</ExcludeAssets>
|
|
||||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
|
||||||
<PackageReference Include="MySqlConnector" Version="2.5.0-beta.1" />
|
|
||||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
|
||||||
<PackageReference Include="System.Linq.Async" Version="7.0.0-preview.1.g24680b5469" />
|
|
||||||
<PackageReference Include="ZLinq" Version="1.5.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
|
|
||||||
<None Update="Database\Migrations\Mysql\001_CreateTables.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\002_CreateFlagsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\003_ChangeColumnsPosition.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\004_MoveOldFlagsToFlagsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\005_CreateUnbansTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\006_ServerGroupsFeature.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\007_ServerGroupsGlobal.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\008_OnlineTimeInPenalties.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\009_BanAllUsedIpAddress.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\010_CreateWarnsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\011_AddRconColumnToServersTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\012_AddUpdatedAtColumnToSaBansTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\013_AddNameColumnToSaPlayerIpsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\014_AddIndexesToMutesTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\015_steamidToBigInt.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Mysql\016_OptimizeTablesAndIndexes.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\001_CreateTables.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\002_CreateFlagsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\003_ChangeColumnsPosition.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\004_MoveOldFlagsToFlagsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\005_CreateUnbansTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\006_ServerGroupsFeature.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\007_ServerGroupsGlobal.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\008_OnlineTimeInPenalties.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\009_BanAllUsedIpAddress.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\010_CreateWarnsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\011_AddRconColumnToServersTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\012_AddUpdatedAtColumnToSaBansTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\013_AddNameColumnToSaPlayerIpsTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\014_AddIndexesToMutesTable.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\015_steamidToBigInt.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Database\Migrations\Sqlite\016_OptimizeTablesAndIndexes.sql">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="Database\Migrations\*.sql" CopyToOutputDirectory="PreserveNewest" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="admin_help.txt" CopyToOutputDirectory="PreserveNewest" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="MenuManagerApi">
|
|
||||||
<HintPath>3rd_party\MenuManagerApi.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Text.Json;
|
|
||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Core.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
public static class RegisterCommands
|
|
||||||
{
|
|
||||||
internal static readonly Dictionary<string, IList<CommandDefinition>> _commandDefinitions =
|
|
||||||
new(StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
|
|
||||||
private delegate void CommandCallback(CCSPlayerController? caller, CommandInfo.CommandCallback callback);
|
|
||||||
|
|
||||||
private static readonly string CommandsPath = Path.Combine(CS2_SimpleAdmin.ConfigDirectory, "Commands.json");
|
|
||||||
private static readonly List<CommandMapping> CommandMappings =
|
|
||||||
[
|
|
||||||
new("css_ban", CS2_SimpleAdmin.Instance.OnBanCommand),
|
|
||||||
new("css_addban", CS2_SimpleAdmin.Instance.OnAddBanCommand),
|
|
||||||
new("css_banip", CS2_SimpleAdmin.Instance.OnBanIpCommand),
|
|
||||||
new("css_unban", CS2_SimpleAdmin.Instance.OnUnbanCommand),
|
|
||||||
new("css_warn", CS2_SimpleAdmin.Instance.OnWarnCommand),
|
|
||||||
new("css_unwarn", CS2_SimpleAdmin.Instance.OnUnwarnCommand),
|
|
||||||
|
|
||||||
new("css_asay", CS2_SimpleAdmin.Instance.OnAdminToAdminSayCommand),
|
|
||||||
new("css_cssay", CS2_SimpleAdmin.Instance.OnAdminCustomSayCommand),
|
|
||||||
new("css_say", CS2_SimpleAdmin.Instance.OnAdminSayCommand),
|
|
||||||
new("css_psay", CS2_SimpleAdmin.Instance.OnAdminPrivateSayCommand),
|
|
||||||
new("css_csay", CS2_SimpleAdmin.Instance.OnAdminCenterSayCommand),
|
|
||||||
new("css_hsay", CS2_SimpleAdmin.Instance.OnAdminHudSayCommand),
|
|
||||||
|
|
||||||
new("css_penalties", CS2_SimpleAdmin.Instance.OnPenaltiesCommand),
|
|
||||||
new("css_admin", CS2_SimpleAdmin.Instance.OnAdminCommand),
|
|
||||||
new("css_adminhelp", CS2_SimpleAdmin.Instance.OnAdminHelpCommand),
|
|
||||||
new("css_addadmin", CS2_SimpleAdmin.Instance.OnAddAdminCommand),
|
|
||||||
new("css_deladmin", CS2_SimpleAdmin.Instance.OnDelAdminCommand),
|
|
||||||
new("css_addgroup", CS2_SimpleAdmin.Instance.OnAddGroup),
|
|
||||||
new("css_delgroup", CS2_SimpleAdmin.Instance.OnDelGroupCommand),
|
|
||||||
new("css_reloadadmins", CS2_SimpleAdmin.Instance.OnRelAdminCommand),
|
|
||||||
new("css_reloadbans", CS2_SimpleAdmin.Instance.OnRelBans),
|
|
||||||
new("css_hide", CS2_SimpleAdmin.Instance.OnHideCommand),
|
|
||||||
new("css_hidecomms", CS2_SimpleAdmin.Instance.OnHideCommsCommand),
|
|
||||||
new("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand),
|
|
||||||
new("css_disconnected", CS2_SimpleAdmin.Instance.OnDisconnectedCommand),
|
|
||||||
new("css_warns", CS2_SimpleAdmin.Instance.OnWarnsCommand),
|
|
||||||
new("css_players", CS2_SimpleAdmin.Instance.OnPlayersCommand),
|
|
||||||
new("css_kick", CS2_SimpleAdmin.Instance.OnKickCommand),
|
|
||||||
new("css_map", CS2_SimpleAdmin.Instance.OnMapCommand),
|
|
||||||
new("css_wsmap", CS2_SimpleAdmin.Instance.OnWorkshopMapCommand),
|
|
||||||
new("css_cvar", CS2_SimpleAdmin.Instance.OnCvarCommand),
|
|
||||||
new("css_rcon", CS2_SimpleAdmin.Instance.OnRconCommand),
|
|
||||||
new("css_rr", CS2_SimpleAdmin.Instance.OnRestartCommand),
|
|
||||||
|
|
||||||
new("css_gag", CS2_SimpleAdmin.Instance.OnGagCommand),
|
|
||||||
new("css_addgag", CS2_SimpleAdmin.Instance.OnAddGagCommand),
|
|
||||||
new("css_ungag", CS2_SimpleAdmin.Instance.OnUngagCommand),
|
|
||||||
new("css_mute", CS2_SimpleAdmin.Instance.OnMuteCommand),
|
|
||||||
new("css_addmute", CS2_SimpleAdmin.Instance.OnAddMuteCommand),
|
|
||||||
new("css_unmute", CS2_SimpleAdmin.Instance.OnUnmuteCommand),
|
|
||||||
new("css_silence", CS2_SimpleAdmin.Instance.OnSilenceCommand),
|
|
||||||
new("css_addsilence", CS2_SimpleAdmin.Instance.OnAddSilenceCommand),
|
|
||||||
new("css_unsilence", CS2_SimpleAdmin.Instance.OnUnsilenceCommand),
|
|
||||||
|
|
||||||
new("css_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
|
|
||||||
|
|
||||||
new("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
|
|
||||||
new("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
|
|
||||||
new("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
|
|
||||||
new("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
|
|
||||||
new("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
|
|
||||||
new("css_tp", CS2_SimpleAdmin.Instance.OnGotoCommand),
|
|
||||||
new("css_bring", CS2_SimpleAdmin.Instance.OnBringCommand),
|
|
||||||
new("css_pluginsmanager", CS2_SimpleAdmin.Instance.OnPluginManagerCommand),
|
|
||||||
new("css_adminvoice", CS2_SimpleAdmin.Instance.OnAdminVoiceCommand)
|
|
||||||
];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes command registration.
|
|
||||||
/// If the commands config file does not exist, creates it and then recurses to register commands.
|
|
||||||
/// Otherwise, directly registers commands from the configuration.
|
|
||||||
/// </summary>
|
|
||||||
public static void InitializeCommands()
|
|
||||||
{
|
|
||||||
if (!File.Exists(CommandsPath))
|
|
||||||
{
|
|
||||||
CreateConfig();
|
|
||||||
InitializeCommands();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates the default commands configuration JSON file with built-in commands and aliases.
|
|
||||||
/// </summary>
|
|
||||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
|
||||||
private static void CreateConfig()
|
|
||||||
{
|
|
||||||
var commands = new CommandsConfig
|
|
||||||
{
|
|
||||||
Commands = new Dictionary<string, Command>
|
|
||||||
{
|
|
||||||
{ "css_ban", new Command { Aliases = ["css_ban"] } },
|
|
||||||
{ "css_addban", new Command { Aliases = ["css_addban"] } },
|
|
||||||
{ "css_banip", new Command { Aliases = ["css_banip"] } },
|
|
||||||
{ "css_unban", new Command { Aliases = ["css_unban"] } },
|
|
||||||
{ "css_warn", new Command { Aliases = ["css_warn"] } },
|
|
||||||
{ "css_unwarn", new Command { Aliases = ["css_unwarn"] } },
|
|
||||||
{ "css_asay", new Command { Aliases = ["css_asay"] } },
|
|
||||||
{ "css_cssay", new Command { Aliases = ["css_cssay"] } },
|
|
||||||
{ "css_say", new Command { Aliases = ["css_say"] } },
|
|
||||||
{ "css_psay", new Command { Aliases = ["css_psay"] } },
|
|
||||||
{ "css_csay", new Command { Aliases = ["css_csay"] } },
|
|
||||||
{ "css_hsay", new Command { Aliases = ["css_hsay"] } },
|
|
||||||
{ "css_penalties", new Command { Aliases = ["css_penalties", "css_mypenalties", "css_comms"] } },
|
|
||||||
{ "css_admin", new Command { Aliases = ["css_admin"] } },
|
|
||||||
{ "css_adminhelp", new Command { Aliases = ["css_adminhelp"] } },
|
|
||||||
{ "css_addadmin", new Command { Aliases = ["css_addadmin"] } },
|
|
||||||
{ "css_deladmin", new Command { Aliases = ["css_deladmin"] } },
|
|
||||||
{ "css_addgroup", new Command { Aliases = ["css_addgroup"] } },
|
|
||||||
{ "css_delgroup", new Command { Aliases = ["css_delgroup"] } },
|
|
||||||
{ "css_reloadadmins", new Command { Aliases = ["css_reloadadmins"] } },
|
|
||||||
{ "css_reloadbans", new Command { Aliases = ["css_reloadbans"] } },
|
|
||||||
{ "css_hide", new Command { Aliases = ["css_hide", "css_stealth"] } },
|
|
||||||
{ "css_hidecomms", new Command { Aliases = ["css_hidecomms"] } },
|
|
||||||
{ "css_who", new Command { Aliases = ["css_who"] } },
|
|
||||||
{ "css_disconnected", new Command { Aliases = ["css_disconnected", "css_last"] } },
|
|
||||||
{ "css_warns", new Command { Aliases = ["css_warns"] } },
|
|
||||||
{ "css_players", new Command { Aliases = ["css_players"] } },
|
|
||||||
{ "css_kick", new Command { Aliases = ["css_kick"] } },
|
|
||||||
{ "css_map", new Command { Aliases = ["css_map", "css_changemap"] } },
|
|
||||||
{ "css_wsmap", new Command { Aliases = ["css_wsmap", "css_changewsmap", "css_workshop"] } },
|
|
||||||
{ "css_cvar", new Command { Aliases = ["css_cvar"] } },
|
|
||||||
{ "css_rcon", new Command { Aliases = ["css_rcon"] } },
|
|
||||||
{ "css_rr", new Command { Aliases = ["css_rr", "css_rg", "css_restart", "css_restartgame"] } },
|
|
||||||
{ "css_gag", new Command { Aliases = ["css_gag"] } },
|
|
||||||
{ "css_addgag", new Command { Aliases = ["css_addgag"] } },
|
|
||||||
{ "css_ungag", new Command { Aliases = ["css_ungag"] } },
|
|
||||||
{ "css_mute", new Command { Aliases = ["css_mute"] } },
|
|
||||||
{ "css_addmute", new Command { Aliases = ["css_addmute"] } },
|
|
||||||
{ "css_unmute", new Command { Aliases = ["css_unmute"] } },
|
|
||||||
{ "css_silence", new Command { Aliases = ["css_silence"] } },
|
|
||||||
{ "css_addsilence", new Command { Aliases = ["css_addsilence"] } },
|
|
||||||
{ "css_unsilence", new Command { Aliases = ["css_unsilence"] } },
|
|
||||||
{ "css_vote", new Command { Aliases = ["css_vote"] } },
|
|
||||||
{ "css_slay", new Command { Aliases = ["css_slay"] } },
|
|
||||||
{ "css_slap", new Command { Aliases = ["css_slap"] } },
|
|
||||||
{ "css_team", new Command { Aliases = ["css_team"] } },
|
|
||||||
{ "css_rename", new Command { Aliases = ["css_rename"] } },
|
|
||||||
{ "css_prename", new Command { Aliases = ["css_prename"] } },
|
|
||||||
{ "css_resize", new Command { Aliases = ["css_resize", "css_size"] } },
|
|
||||||
{ "css_tp", new Command { Aliases = ["css_tp", "css_tpto", "css_goto"] } },
|
|
||||||
{ "css_bring", new Command { Aliases = ["css_bring", "css_tphere"] } },
|
|
||||||
{ "css_pluginsmanager", new Command { Aliases = ["css_pluginsmanager", "css_pluginmanager"] } },
|
|
||||||
{ "css_adminvoice", new Command { Aliases = ["css_adminvoice", "css_listenall"] } }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var options = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true,
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
||||||
};
|
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(commands, options);
|
|
||||||
File.WriteAllText(CommandsPath, json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads the command configuration JSON file and registers all commands and their aliases with their callbacks.
|
|
||||||
/// Also registers any custom commands previously stored.
|
|
||||||
/// </summary>
|
|
||||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
|
||||||
private static void Register()
|
|
||||||
{
|
|
||||||
var json = File.ReadAllText(CommandsPath);
|
|
||||||
var commandsConfig = JsonSerializer.Deserialize<CommandsConfig>(json,
|
|
||||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
||||||
|
|
||||||
if (commandsConfig?.Commands != null)
|
|
||||||
{
|
|
||||||
foreach (var command in commandsConfig.Commands)
|
|
||||||
{
|
|
||||||
if (command.Value.Aliases == null) continue;
|
|
||||||
|
|
||||||
CS2_SimpleAdmin._logger?.LogInformation(
|
|
||||||
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
|
|
||||||
|
|
||||||
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
|
|
||||||
if (mapping == null || command.Value.Aliases.Length == 0) continue;
|
|
||||||
|
|
||||||
foreach (var alias in command.Value.Aliases)
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (name, definitions) in _commandDefinitions)
|
|
||||||
{
|
|
||||||
foreach (var definition in definitions)
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin._logger?.LogInformation($"Registering custom command: `{name}`");
|
|
||||||
CS2_SimpleAdmin.Instance.AddCommand(name, definition.Description, definition.Callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the JSON configuration structure for commands.
|
|
||||||
/// </summary>
|
|
||||||
private class CommandsConfig
|
|
||||||
{
|
|
||||||
public Dictionary<string, Command>? Commands { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a command definition containing a list of aliases.
|
|
||||||
/// </summary>
|
|
||||||
private class Command
|
|
||||||
{
|
|
||||||
public string[]? Aliases { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maps a command key to its respective command callback handler.
|
|
||||||
/// </summary>
|
|
||||||
private class CommandMapping(string commandKey, CommandInfo.CommandCallback callback)
|
|
||||||
{
|
|
||||||
public string CommandKey { get; } = commandKey;
|
|
||||||
public CommandInfo.CommandCallback Callback { get; } = callback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,580 +0,0 @@
|
|||||||
using CounterStrikeSharp.API;
|
|
||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Modules.Admin;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
|
||||||
using CounterStrikeSharp.API.ValveConstants.Protobuf;
|
|
||||||
using CS2_SimpleAdmin.Managers;
|
|
||||||
using CS2_SimpleAdmin.Menus;
|
|
||||||
using CS2_SimpleAdminApi;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
public partial class CS2_SimpleAdmin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the 'ban' command, allowing admins to ban one or more valid connected players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the ban command, or null for console.</param>
|
|
||||||
/// <param name="command">The command information including arguments.</param>
|
|
||||||
[RequiresPermissions("@css/ban")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
if (command.ArgCount < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
|
|
||||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
|
||||||
{
|
|
||||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player,
|
|
||||||
ManagePlayersMenu.BanMenu);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ban(caller, player, time, reason, callerName, BanManager, command);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Core logic to ban a specific player, scheduling database updates, notifications, and kicks.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the ban, or null for console.</param>
|
|
||||||
/// <param name="player">The player to be banned.</param>
|
|
||||||
/// <param name="time">Ban duration in minutes; 0 means permanent.</param>
|
|
||||||
/// <param name="reason">Reason for the ban.</param>
|
|
||||||
/// <param name="callerName">Optional caller name string. If null, defaults to player name or console.</param>
|
|
||||||
/// <param name="banManager">Optional BanManager to handle ban persistence.</param>
|
|
||||||
/// <param name="command">Optional command info object for logging.</param>
|
|
||||||
/// <param name="silent">If true, suppresses command logging.</param>
|
|
||||||
internal void Ban(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, BanManager? banManager = null, CommandInfo? command = null, bool silent = false)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
if (!CheckValidBan(caller, time)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
|
||||||
? caller.PlayerName
|
|
||||||
: (_localizer?["sa_console"] ?? "Console");
|
|
||||||
|
|
||||||
// Get player and admin information
|
|
||||||
var playerInfo = PlayersInfo[player.SteamID];
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Asynchronously handle banning logic
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine message keys and arguments based on ban time
|
|
||||||
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
|
|
||||||
? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm",
|
|
||||||
[reason, "CALLER"],
|
|
||||||
["CALLER", player.PlayerName, reason])
|
|
||||||
: ("sa_player_ban_message_time", "sa_admin_ban_message_time",
|
|
||||||
new object[] { reason, time, "CALLER" },
|
|
||||||
new object[] { "CALLER", player.PlayerName, reason, time });
|
|
||||||
|
|
||||||
// Display center message to the player
|
|
||||||
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
|
|
||||||
|
|
||||||
// Display admin activity message if necessary
|
|
||||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule a kick timer
|
|
||||||
if (player.UserId.HasValue)
|
|
||||||
{
|
|
||||||
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED, Config.OtherSettings.KickTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute ban command if necessary
|
|
||||||
if (UnlockedCommands)
|
|
||||||
{
|
|
||||||
Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!silent)
|
|
||||||
{
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_ban {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
|
|
||||||
else
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a ban for a player by their SteamID, including offline bans.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the ban command.</param>
|
|
||||||
/// <param name="steamid">SteamID of the player to ban.</param>
|
|
||||||
/// <param name="time">Ban duration in minutes (0 means permanent).</param>
|
|
||||||
/// <param name="reason">Reason for banning.</param>
|
|
||||||
/// <param name="banManager">Optional ban manager for database operations.</param>
|
|
||||||
internal void AddBan(CCSPlayerController? caller, SteamID steamid, int time, string reason, BanManager? banManager = null)
|
|
||||||
{
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
|
||||||
? caller.PlayerName
|
|
||||||
: (_localizer?["sa_console"] ?? "Console");
|
|
||||||
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player))
|
|
||||||
return;
|
|
||||||
Ban(caller, player, time, reason, callerName, silent: true);
|
|
||||||
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(steamid))
|
|
||||||
return;
|
|
||||||
// Asynchronous ban operation if player is not online or not found
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await BanManager.AddBanBySteamid(steamid.SteamId64, adminInfo, reason, time);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Ban, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Ban, _localizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles banning a player by specifying their SteamID via command.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the command, or null if console.</param>
|
|
||||||
/// <param name="command">Command information including arguments (SteamID, time, reason).</param>
|
|
||||||
[RequiresPermissions("@css/ban")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
|
|
||||||
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
|
|
||||||
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand("Invalid SteamID64.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var steamid = steamId.SteamId64;
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
|
||||||
if (!CheckValidBan(caller, time)) return;
|
|
||||||
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue
|
|
||||||
? PlayersInfo[caller.SteamID]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Ban(caller, player, time, reason, callerName, silent: true);
|
|
||||||
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous ban operation if player is not online or not found
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Ban, _localizer);
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
if (UnlockedCommands)
|
|
||||||
Server.ExecuteCommand($"banid 1 {steamId.SteamId3}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles banning a player by their IP address, supporting offline banning if player is not online.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the ban command.</param>
|
|
||||||
/// <param name="command">The command containing the IP, time, and reason arguments.</param>
|
|
||||||
[RequiresPermissions("@css/ban")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<ip> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnBanIpCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
|
|
||||||
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
|
|
||||||
var ipAddress = command.GetArg(1);
|
|
||||||
|
|
||||||
if (!Helper.IsValidIp(ipAddress))
|
|
||||||
{
|
|
||||||
command.ReplyToCommand($"Invalid IP address.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
|
||||||
|
|
||||||
if (!CheckValidBan(caller, time)) return;
|
|
||||||
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue
|
|
||||||
? PlayersInfo[caller.SteamID]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
var players = Helper.GetPlayerFromIp(ipAddress);
|
|
||||||
if (players.Count >= 1)
|
|
||||||
{
|
|
||||||
foreach (var player in players)
|
|
||||||
{
|
|
||||||
if (player == null || !player.IsValid) continue;
|
|
||||||
if (!caller.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Ban(caller, player, time, reason, callerName, silent: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Asynchronous ban operation if player is not online or not found
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await BanManager.AddBanByIp(ipAddress, adminInfo, reason, time);
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Player with ip {ipAddress} is not online. Ban has been added offline.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether the ban duration is valid based on the caller's permissions and configured limits.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the ban command.</param>
|
|
||||||
/// <param name="duration">Requested ban duration in minutes.</param>
|
|
||||||
/// <returns>True if ban duration is valid; otherwise, false.</returns>
|
|
||||||
private bool CheckValidBan(CCSPlayerController? caller, int duration)
|
|
||||||
{
|
|
||||||
if (caller == null) return true;
|
|
||||||
|
|
||||||
var canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
|
|
||||||
|
|
||||||
if (duration <= 0 && !canPermBan)
|
|
||||||
{
|
|
||||||
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration <= Config.OtherSettings.MaxBanDuration || canPermBan) return true;
|
|
||||||
|
|
||||||
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_max_duration_exceeded", Config.OtherSettings.MaxBanDuration]}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles unbanning players by pattern (steamid, name, or IP).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the unban command.</param>
|
|
||||||
/// <param name="command">Command containing target pattern and optional reason.</param>
|
|
||||||
[RequiresPermissions("@css/unban")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnUnbanCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
|
||||||
if (command.GetArg(1).Length <= 1)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand($"Too short pattern to search.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pattern = command.GetArg(1);
|
|
||||||
var reason = command.ArgCount >= 2
|
|
||||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
Task.Run(async () => await BanManager.UnbanPlayer(pattern, callerSteamId, reason));
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
command.ReplyToCommand($"Unbanned player with pattern {pattern}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles warning players, supporting multiple targets and warning durations.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the warn command.</param>
|
|
||||||
/// <param name="command">The command containing target, time, and reason parameters.</param>
|
|
||||||
[RequiresPermissions("@css/kick")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnWarnCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null)
|
|
||||||
return;
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
if (command.ArgCount < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList();
|
|
||||||
|
|
||||||
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
if (caller!.CanTarget(player))
|
|
||||||
{
|
|
||||||
Warn(caller, player, time, reason, callerName, command);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Issues a warning penalty to a specific player with optional duration and reason.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the warning.</param>
|
|
||||||
/// <param name="player">The player to warn.</param>
|
|
||||||
/// <param name="time">Duration of the warning in minutes.</param>
|
|
||||||
/// <param name="reason">Reason for the warning.</param>
|
|
||||||
/// <param name="callerName">Optional display name of the caller.</param>
|
|
||||||
/// <param name="command">Optional command info for logging.</param>
|
|
||||||
internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
if (!CheckValidBan(caller, time)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
|
||||||
? caller.PlayerName
|
|
||||||
: (_localizer?["sa_console"] ?? "Console");
|
|
||||||
|
|
||||||
// Freeze player pawn if alive
|
|
||||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
|
||||||
{
|
|
||||||
player.PlayerPawn?.Value?.Freeze();
|
|
||||||
AddTimer(5.0f, () => player.PlayerPawn?.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get player and admin information
|
|
||||||
var playerInfo = PlayersInfo[player.SteamID];
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Asynchronously handle warning logic
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await WarnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for warn thresholds and execute punish command if applicable
|
|
||||||
var totalWarns = await WarnManager.GetPlayerWarnsCount(player.SteamID);
|
|
||||||
if (Config.WarnThreshold.Count > 0)
|
|
||||||
{
|
|
||||||
string? punishCommand = null;
|
|
||||||
var lastKey = Config.WarnThreshold.Keys.Max();
|
|
||||||
|
|
||||||
if (totalWarns >= lastKey)
|
|
||||||
punishCommand = Config.WarnThreshold[lastKey];
|
|
||||||
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
|
|
||||||
punishCommand = value;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(punishCommand))
|
|
||||||
{
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
Server.ExecuteCommand(punishCommand.Replace("USERID", playerInfo.UserId.ToString()).Replace("STEAMID64", playerInfo.SteamId?.ToString()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine message keys and arguments based on warning time
|
|
||||||
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
|
|
||||||
? ("sa_player_warn_message_perm", "sa_admin_warn_message_perm",
|
|
||||||
new object[] { reason, "CALLER" },
|
|
||||||
new object[] { "CALLER", player.PlayerName, reason })
|
|
||||||
: ("sa_player_warn_message_time", "sa_admin_warn_message_time",
|
|
||||||
[reason, time, "CALLER"],
|
|
||||||
["CALLER", player.PlayerName, reason, time]);
|
|
||||||
|
|
||||||
// Display center message to the playser
|
|
||||||
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
|
|
||||||
|
|
||||||
// Display admin activity message if necessary
|
|
||||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the warning command
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_warn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
|
|
||||||
else
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
// Send Discord notification for the warning
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a warning to a player by their SteamID, including support for offline players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the warn command.</param>
|
|
||||||
/// <param name="steamid">SteamID of the player to warn.</param>
|
|
||||||
/// <param name="time">Warning duration in minutes.</param>
|
|
||||||
/// <param name="reason">Reason for the warning.</param>
|
|
||||||
/// <param name="warnManager">Optional warn manager instance.</param>
|
|
||||||
internal void AddWarn(CCSPlayerController? caller, SteamID steamid, int time, string reason, WarnManager? warnManager = null)
|
|
||||||
{
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
|
||||||
? caller.PlayerName
|
|
||||||
: (_localizer?["sa_console"] ?? "Console");
|
|
||||||
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Warn(caller, player, time, reason, callerName);
|
|
||||||
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(steamid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous ban operation if player is not online or not found
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await WarnManager.AddWarnBySteamid(steamid.SteamId64, adminInfo, reason, time);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Warn, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for warn thresholds and execute punish command if applicable
|
|
||||||
var totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
|
|
||||||
if (Config.WarnThreshold.Count > 0)
|
|
||||||
{
|
|
||||||
string? punishCommand = null;
|
|
||||||
var lastKey = Config.WarnThreshold.Keys.Max();
|
|
||||||
|
|
||||||
if (totalWarns >= lastKey)
|
|
||||||
punishCommand = Config.WarnThreshold[lastKey];
|
|
||||||
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
|
|
||||||
punishCommand = value;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(punishCommand))
|
|
||||||
{
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
Server.ExecuteCommand(punishCommand.Replace("STEAMID64", steamid.SteamId64.ToString()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Warn, _localizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles removing a warning (unwarn) by a pattern string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the unwarn command.</param>
|
|
||||||
/// <param name="command">The command containing target pattern.</param>
|
|
||||||
[RequiresPermissions("@css/kick")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnUnwarnCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
if (command.GetArg(1).Length <= 1)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand($"Too short pattern to search.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pattern = command.GetArg(1);
|
|
||||||
Task.Run(async () => await WarnManager.UnwarnPlayer(pattern));
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
command.ReplyToCommand($"Unwarned player with pattern {pattern}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Core.Translations;
|
|
||||||
using CounterStrikeSharp.API.Modules.Admin;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Memory;
|
|
||||||
using CounterStrikeSharp.API.Modules.Utils;
|
|
||||||
using System.Text;
|
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
public partial class CS2_SimpleAdmin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a chat message only to admins that have chat permission.
|
|
||||||
/// The message is encoded properly to handle UTF-8 characters.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin player sending the message, or null for console.</param>
|
|
||||||
/// <param name="command">The command input containing the message.</param>
|
|
||||||
[CommandHelper(1, "<message>")]
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
public void OnAdminToAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
|
|
||||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
|
||||||
|
|
||||||
foreach (var player in Helper.GetValidPlayers()
|
|
||||||
.Where(p => AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
|
|
||||||
{
|
|
||||||
if (_localizer != null)
|
|
||||||
player.PrintToChat(_localizer["sa_adminchat_template_admin",
|
|
||||||
caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName,
|
|
||||||
utf8String]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a custom chat message to all players with color tags processed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin or console sending the message.</param>
|
|
||||||
/// <param name="command">The command input containing the message.</param>
|
|
||||||
[CommandHelper(1, "<message>")]
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
public void OnAdminCustomSayCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return;
|
|
||||||
|
|
||||||
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
|
|
||||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
foreach (var player in Helper.GetValidPlayers())
|
|
||||||
{
|
|
||||||
player.PrintToChat(utf8String.ReplaceColorTags());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a chat message to all players with localization prefix and color tags handled.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin or console sending the message.</param>
|
|
||||||
/// <param name="command">The command input containing the message.</param>
|
|
||||||
[CommandHelper(1, "<message>")]
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
public void OnAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return;
|
|
||||||
|
|
||||||
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
|
|
||||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
foreach (var player in Helper.GetValidPlayers())
|
|
||||||
{
|
|
||||||
player.SendLocalizedMessage(_localizer,
|
|
||||||
"sa_adminsay_prefix",
|
|
||||||
utf8String.ReplaceColorTags());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a private chat message from the caller to the specified target player(s).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin or console sending the private message.</param>
|
|
||||||
/// <param name="command">The command input containing target and message.</param>
|
|
||||||
[CommandHelper(2, "<#userid or name> <message>")]
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
public void OnAdminPrivateSayCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
//Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
var range = command.GetArg(0).Length + command.GetArg(1).Length + 2;
|
|
||||||
var message = command.GetCommandString[range..];
|
|
||||||
|
|
||||||
var utf8BytesString = Encoding.UTF8.GetBytes(message);
|
|
||||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
player.PrintToChat($"({callerName}) {utf8String}".ReplaceColorTags());
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($" Private message sent!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcasts a center-screen message to all players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin or console sending the message.</param>
|
|
||||||
/// <param name="command">The command input containing the message.</param>
|
|
||||||
[CommandHelper(1, "<message>")]
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
public void OnAdminCenterSayCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
|
|
||||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
Helper.PrintToCenterAll(utf8String.ReplaceColorTags());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a HUD alert message to all players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin or console sending the message.</param>
|
|
||||||
/// <param name="command">The command input containing the message.</param>
|
|
||||||
[CommandHelper(1, "<message>")]
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
public void OnAdminHudSayCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
|
|
||||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
VirtualFunctions.ClientPrintAll(
|
|
||||||
HudDestination.Alert,
|
|
||||||
utf8String.ReplaceColorTags(),
|
|
||||||
0, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,973 +0,0 @@
|
|||||||
using CounterStrikeSharp.API;
|
|
||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Modules.Admin;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
|
||||||
using CS2_SimpleAdmin.Managers;
|
|
||||||
using CS2_SimpleAdmin.Menus;
|
|
||||||
using CS2_SimpleAdminApi;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
public partial class CS2_SimpleAdmin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Processes the 'gag' command, applying a muted penalty to target players with optional time and reason.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the gag command or null for console.</param>
|
|
||||||
/// <param name="command">The command input containing targets, time, and reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnGagCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
if (!caller!.CanTarget(player)) return;
|
|
||||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
|
||||||
{
|
|
||||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player,
|
|
||||||
ManagePlayersMenu.GagMenu);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gag(caller, player, time, reason, callerName, command);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies the gag penalty logic to an individual player, performing permission checks, notification, and logging.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the gag.</param>
|
|
||||||
/// <param name="player">The player to gag.</param>
|
|
||||||
/// <param name="time">Duration of the gag in minutes, 0 is permanent.</param>
|
|
||||||
/// <param name="reason">Reason for the gag.</param>
|
|
||||||
/// <param name="callerName">Optional caller name for notifications.</param>
|
|
||||||
/// <param name="command">Optional command info for logging.</param>
|
|
||||||
/// <param name="silent">If true, suppresses logging notifications.</param>
|
|
||||||
internal void Gag(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
if (!CheckValidMute(caller, time)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Get player and admin information
|
|
||||||
var playerInfo = PlayersInfo[player.SteamID];
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Asynchronously handle gag logic
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add penalty to the player's penalty manager
|
|
||||||
PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Gag, Time.ActualDateTime().AddMinutes(time), time);
|
|
||||||
|
|
||||||
// Determine message keys and arguments based on gag time (permanent or timed)
|
|
||||||
var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0
|
|
||||||
? ("sa_player_gag_message_perm", "sa_admin_gag_message_perm",
|
|
||||||
[reason, "CALLER"],
|
|
||||||
["CALLER", player.PlayerName, reason])
|
|
||||||
: ("sa_player_gag_message_time", "sa_admin_gag_message_time",
|
|
||||||
new object[] { reason, time, "CALLER" },
|
|
||||||
new object[] { "CALLER", player.PlayerName, reason, time });
|
|
||||||
|
|
||||||
// Display center message to the gagged player
|
|
||||||
Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs);
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the player's total gags count
|
|
||||||
PlayersInfo[player.SteamID].TotalGags++;
|
|
||||||
|
|
||||||
// Log the gag command and send Discord notification
|
|
||||||
if (!silent)
|
|
||||||
{
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_gag {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
|
|
||||||
else
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Gag, _localizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a gag penalty to a player identified by SteamID, supporting offline players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the command or null for console.</param>
|
|
||||||
/// <param name="steamid">SteamID of the target player.</param>
|
|
||||||
/// <param name="time">Duration in minutes (0 for permanent).</param>
|
|
||||||
/// <param name="reason">Reason for the gag.</param>
|
|
||||||
internal void AddGag(CCSPlayerController? caller, SteamID steamid, int time, string reason)
|
|
||||||
{
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
|
||||||
? caller.PlayerName
|
|
||||||
: (_localizer?["sa_console"] ?? "Console");
|
|
||||||
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Gag(caller, player, time, reason, callerName, silent: true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(steamid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous ban operation if player is not online or not found
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 3);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Gag, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Gag, _localizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the 'addgag' command, which adds a gag penalty to a player specified by SteamID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the command or null for console.</param>
|
|
||||||
/// <param name="command">Command input that includes SteamID, optional time, and reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnAddGagCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
// Set caller name
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Validate command arguments
|
|
||||||
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
|
|
||||||
|
|
||||||
// Validate and extract SteamID
|
|
||||||
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand("Invalid SteamID64.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var steamid = steamId.SteamId64;
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
|
||||||
if (!CheckValidMute(caller, time)) return;
|
|
||||||
|
|
||||||
// Get player and admin info
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Attempt to match player based on SteamID
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
// Check if caller can target the player
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
|
|
||||||
// Perform the gag for an online player
|
|
||||||
Gag(caller, player, time, reason, callerName, silent: true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous gag operation for offline players
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Gag, _localizer);
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Gag has been added offline.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the gag command and respond to the command
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles removing a gag penalty ('ungag') of a player, either by SteamID or pattern match.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the ungag command or null for console.</param>
|
|
||||||
/// <param name="command">Command input containing SteamID or player name and optional reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnUngagCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
|
||||||
var pattern = command.GetArg(1);
|
|
||||||
var reason = command.ArgCount >= 2
|
|
||||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
if (pattern.Length <= 1)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand($"Too short pattern to search.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
// Check if pattern is a valid SteamID64
|
|
||||||
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
|
|
||||||
{
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Gag);
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Ungaged player {player.PlayerName}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a valid SteamID64, check by player name
|
|
||||||
var nameMatches = Helper.GetPlayerFromName(pattern);
|
|
||||||
var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null;
|
|
||||||
|
|
||||||
if (namePlayer != null && namePlayer.IsValid)
|
|
||||||
{
|
|
||||||
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Gag);
|
|
||||||
|
|
||||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalGags > 0)
|
|
||||||
PlayersInfo[namePlayer.SteamID].TotalGags--;
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Ungaged player {namePlayer.PlayerName}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(pattern, callerSteamId, reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Ungaged offline player with pattern {pattern}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes the 'mute' command, applying a voice mute penalty to target players with optional time and reason.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the mute command or null for console.</param>
|
|
||||||
/// <param name="command">The command input containing targets, time, and reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnMuteCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
if (!caller!.CanTarget(player)) return;
|
|
||||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
|
||||||
{
|
|
||||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player,
|
|
||||||
ManagePlayersMenu.MuteMenu);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mute(caller, player, time, reason, callerName, command);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies the mute penalty logic to an individual player, handling permissions, notifications, logging, and countdown timers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the mute.</param>
|
|
||||||
/// <param name="player">The player to mute.</param>
|
|
||||||
/// <param name="time">Duration in minutes, 0 indicates permanent mute.</param>
|
|
||||||
/// <param name="reason">Reason for the mute.</param>
|
|
||||||
/// <param name="callerName">Optional caller name for notification messages.</param>
|
|
||||||
/// <param name="command">Optional command info for logging.</param>
|
|
||||||
/// <param name="silent">If true, suppresses some logging.</param>
|
|
||||||
internal void Mute(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
if (!CheckValidMute(caller, time)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Get player and admin information
|
|
||||||
var playerInfo = PlayersInfo[player.SteamID];
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Set player's voice flags to muted
|
|
||||||
player.VoiceFlags = VoiceFlags.Muted;
|
|
||||||
|
|
||||||
// Asynchronously handle mute logic
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add penalty to the player's penalty manager
|
|
||||||
PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Mute, Time.ActualDateTime().AddMinutes(time), time);
|
|
||||||
|
|
||||||
// Determine message keys and arguments based on mute time (permanent or timed)
|
|
||||||
var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0
|
|
||||||
? ("sa_player_mute_message_perm", "sa_admin_mute_message_perm",
|
|
||||||
[reason, "CALLER"],
|
|
||||||
["CALLER", player.PlayerName, reason])
|
|
||||||
: ("sa_player_mute_message_time", "sa_admin_mute_message_time",
|
|
||||||
new object[] { reason, time, "CALLER" },
|
|
||||||
new object[] { "CALLER", player.PlayerName, reason, time });
|
|
||||||
|
|
||||||
// Display center message to the muted player
|
|
||||||
Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs);
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the player's total mutes count
|
|
||||||
PlayersInfo[player.SteamID].TotalMutes++;
|
|
||||||
|
|
||||||
// Log the mute command and send Discord notification
|
|
||||||
if (!silent)
|
|
||||||
{
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_mute {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
|
|
||||||
else
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Mute, _localizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the 'addmute' command that adds a mute penalty to a player specified by SteamID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player issuing the command or null for console.</param>
|
|
||||||
/// <param name="command">Command input includes SteamID, optional time, and reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnAddMuteCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
// Set caller name
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Validate command arguments
|
|
||||||
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
|
|
||||||
|
|
||||||
// Validate and extract SteamID
|
|
||||||
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand("Invalid SteamID64.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var steamid = steamId.SteamId64;
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
|
||||||
if (!CheckValidMute(caller, time)) return;
|
|
||||||
|
|
||||||
// Get player and admin info
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Attempt to match player based on SteamID
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
// Check if caller can target the player
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
|
|
||||||
// Perform the mute for an online player
|
|
||||||
Mute(caller, player, time, reason, callerName, silent: true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous mute operation for offline players
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Mute, _localizer);
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Mute has been added offline.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the mute command and respond to the command
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously adds a mute penalty to a player by Steam ID. Handles both online and offline players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin/player issuing the mute.</param>
|
|
||||||
/// <param name="steamid">The Steam ID of the player to mute.</param>
|
|
||||||
/// <param name="time">Duration of the mute in minutes.</param>
|
|
||||||
/// <param name="reason">Reason for the mute.</param>
|
|
||||||
/// <param name="muteManager">Optional mute manager instance for handling database ops.</param>
|
|
||||||
internal void AddMute(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
|
|
||||||
{
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
|
||||||
? caller.PlayerName
|
|
||||||
: (_localizer?["sa_console"] ?? "Console");
|
|
||||||
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Mute(caller, player, time, reason, callerName, silent: true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(steamid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous ban operation if player is not online or not found
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 1);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Mute, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Mute, _localizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the unmute command - removes mute penalty from player identified by SteamID or name.
|
|
||||||
/// Can target both online and offline players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin/player issuing the unmute.</param>
|
|
||||||
/// <param name="command">The command arguments including target identifier and optional reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnUnmuteCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
|
||||||
var pattern = command.GetArg(1);
|
|
||||||
var reason = command.ArgCount >= 2
|
|
||||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
if (pattern.Length <= 1)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand("Too short pattern to search.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
// Check if pattern is a valid SteamID64
|
|
||||||
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
|
|
||||||
{
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Mute);
|
|
||||||
player.VoiceFlags = VoiceFlags.Normal;
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Unmuted player {player.PlayerName}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a valid SteamID64, check by player name
|
|
||||||
var nameMatches = Helper.GetPlayerFromName(pattern);
|
|
||||||
var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null;
|
|
||||||
|
|
||||||
if (namePlayer != null && namePlayer.IsValid)
|
|
||||||
{
|
|
||||||
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Mute);
|
|
||||||
namePlayer.VoiceFlags = VoiceFlags.Normal;
|
|
||||||
|
|
||||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalMutes > 0)
|
|
||||||
PlayersInfo[namePlayer.SteamID].TotalMutes--;
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Unmuted player {namePlayer.PlayerName}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(pattern, callerSteamId, reason, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Unmuted offline player with pattern {pattern}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Issue a 'silence' penalty to a player - disables voice communication.
|
|
||||||
/// Handles online and offline players, with duration and reason specified.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin/player issuing the silence.</param>
|
|
||||||
/// <param name="command">Command containing target, duration, and optional reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnSilenceCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
if (!caller!.CanTarget(player)) return;
|
|
||||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
|
||||||
{
|
|
||||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player,
|
|
||||||
ManagePlayersMenu.SilenceMenu);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Silence(caller, player, time, reason, callerName, command);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies silence logical processing for a player - updates database and notifies.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Admin/player applying the silence.</param>
|
|
||||||
/// <param name="player">Target player.</param>
|
|
||||||
/// <param name="time">Duration of silence.</param>
|
|
||||||
/// <param name="reason">Reason for silence.</param>
|
|
||||||
/// <param name="callerName">Optional name of silent admin or console.</param>
|
|
||||||
/// <param name="command">Optional command details for logging.</param>
|
|
||||||
/// <param name="silent">If true, suppresses logging notifications.</param>
|
|
||||||
internal void Silence(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
if (!CheckValidMute(caller, time)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Get player and admin information
|
|
||||||
var playerInfo = PlayersInfo[player.SteamID];
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Asynchronously handle silence logic
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time,
|
|
||||||
penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add penalty to the player's penalty manager
|
|
||||||
PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Silence, Time.ActualDateTime().AddMinutes(time), time);
|
|
||||||
player.VoiceFlags = VoiceFlags.Muted;
|
|
||||||
|
|
||||||
// Determine message keys and arguments based on silence time (permanent or timed)
|
|
||||||
var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0
|
|
||||||
? ("sa_player_silence_message_perm", "sa_admin_silence_message_perm",
|
|
||||||
[reason, "CALLER"],
|
|
||||||
["CALLER", player.PlayerName, reason])
|
|
||||||
: ("sa_player_silence_message_time", "sa_admin_silence_message_time",
|
|
||||||
new object[] { reason, time, "CALLER" },
|
|
||||||
new object[] { "CALLER", player.PlayerName, reason, time });
|
|
||||||
|
|
||||||
// Display center message to the silenced player
|
|
||||||
Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs);
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the player's total silences count
|
|
||||||
PlayersInfo[player.SteamID].TotalSilences++;
|
|
||||||
|
|
||||||
// Log the silence command and send Discord notification
|
|
||||||
if (!silent)
|
|
||||||
{
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_silence {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
|
|
||||||
else
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Silence, _localizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the 'AddSilence' command, applying a silence penalty to a player specified by SteamID,
|
|
||||||
/// with support for offline player penalties.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player/admin issuing the command.</param>
|
|
||||||
/// <param name="command">The command input containing SteamID, optional time, and reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnAddSilenceCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
// Set caller name
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Validate command arguments
|
|
||||||
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
|
|
||||||
|
|
||||||
// Validate and extract SteamID
|
|
||||||
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand("Invalid SteamID64.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var steamid = steamId.SteamId64;
|
|
||||||
var reason = command.ArgCount >= 3
|
|
||||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
|
||||||
if (!CheckValidMute(caller, time)) return;
|
|
||||||
|
|
||||||
// Get player and admin info
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
// Attempt to match player based on SteamID
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
// Check if caller can target the player
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
|
|
||||||
// Perform the silence for an online player
|
|
||||||
Silence(caller, player, time, reason, callerName, silent: true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous silence operation for offline players
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason,
|
|
||||||
time, penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Silence, _localizer);
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Silence has been added offline.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the silence command and respond to the command
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a silence penalty to a player by Steam ID. Manages both online and offline player cases.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Admin/player initiating the silence.</param>
|
|
||||||
/// <param name="steamid">Steam ID of player.</param>
|
|
||||||
/// <param name="time">Duration of silence.</param>
|
|
||||||
/// <param name="reason">Reason for the penalty.</param>
|
|
||||||
/// <param name="muteManager">Optional mute manager for DB operations.</param>
|
|
||||||
internal void AddSilence(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
|
|
||||||
{
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
|
||||||
? caller.PlayerName
|
|
||||||
: (_localizer?["sa_console"] ?? "Console");
|
|
||||||
|
|
||||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
|
||||||
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Silence(caller, player, time, reason, callerName, silent: true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(steamid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Asynchronous ban operation if player is not online or not found
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 2);
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Silence, reason,
|
|
||||||
time, penaltyId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Silence, _localizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the silence penalty from a player, either by SteamID, name, or offline pattern.
|
|
||||||
/// Resets voice settings and updates notices accordingly.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Admin/player issuing the unsilence.</param>
|
|
||||||
/// <param name="command">Command arguments with target pattern and optional reason.</param>
|
|
||||||
[RequiresPermissions("@css/chat")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<steamid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnUnsilenceCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (DatabaseProvider == null) return;
|
|
||||||
|
|
||||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
|
||||||
var pattern = command.GetArg(1);
|
|
||||||
var reason = command.ArgCount >= 2
|
|
||||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
|
||||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
|
||||||
|
|
||||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
|
||||||
|
|
||||||
if (pattern.Length <= 1)
|
|
||||||
{
|
|
||||||
command.ReplyToCommand("Too short pattern to search.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
// Check if pattern is a valid SteamID64
|
|
||||||
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
|
|
||||||
{
|
|
||||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
|
||||||
|
|
||||||
if (player != null && player.IsValid)
|
|
||||||
{
|
|
||||||
PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Silence);
|
|
||||||
|
|
||||||
// Reset voice flags to normal
|
|
||||||
player.VoiceFlags = VoiceFlags.Normal;
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason, 2); // Unmute by type 2 (silence)
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Unsilenced player {player.PlayerName}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a valid SteamID64, check by player name
|
|
||||||
var nameMatches = Helper.GetPlayerFromName(pattern);
|
|
||||||
var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null;
|
|
||||||
|
|
||||||
if (namePlayer != null && namePlayer.IsValid)
|
|
||||||
{
|
|
||||||
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Silence);
|
|
||||||
|
|
||||||
// Reset voice flags to normal
|
|
||||||
namePlayer.VoiceFlags = VoiceFlags.Normal;
|
|
||||||
|
|
||||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalSilences > 0)
|
|
||||||
PlayersInfo[namePlayer.SteamID].TotalSilences--;
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason, 2); // Unmute by type 2 (silence)
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Unsilenced player {namePlayer.PlayerName}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MuteManager.UnmutePlayer(pattern, callerSteamId, reason, 2); // Unmute by type 2 (silence)
|
|
||||||
});
|
|
||||||
|
|
||||||
command.ReplyToCommand($"Unsilenced offline player with pattern {pattern}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates mute penalty duration based on admin privileges and configured max duration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Admin/player issuing the mute.</param>
|
|
||||||
/// <param name="duration">Requested duration in minutes.</param>
|
|
||||||
/// <returns>True if mute penalty duration is allowed; false otherwise.</returns>
|
|
||||||
private bool CheckValidMute(CCSPlayerController? caller, int duration)
|
|
||||||
{
|
|
||||||
if (caller == null) return true;
|
|
||||||
|
|
||||||
var canPermMute = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permmute");
|
|
||||||
|
|
||||||
if (duration <= 0 && !canPermMute)
|
|
||||||
{
|
|
||||||
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration <= Config.OtherSettings.MaxMuteDuration || canPermMute) return true;
|
|
||||||
|
|
||||||
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_max_duration_exceeded", Config.OtherSettings.MaxMuteDuration]}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Core.Translations;
|
|
||||||
using CounterStrikeSharp.API.Modules.Admin;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Menu;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
public partial class CS2_SimpleAdmin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the vote command, creates voting menu for players, and collects answers.
|
|
||||||
/// Displays results after timeout and resets voting state.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player/admin who initiated the vote, or null for console.</param>
|
|
||||||
/// <param name="command">Command object containing question and options.</param>
|
|
||||||
[RequiresPermissions("@css/generic")]
|
|
||||||
[CommandHelper(minArgs: 2, usage: "<question> [... options ...]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnVoteCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
if (command.ArgCount < 2 || _localizer == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
VoteAnswers.Clear();
|
|
||||||
|
|
||||||
var question = command.GetArg(1);
|
|
||||||
var answersCount = command.ArgCount;
|
|
||||||
|
|
||||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
for (var i = 2; i <= answersCount - 1; i++)
|
|
||||||
{
|
|
||||||
VoteAnswers.Add(command.GetArg(i), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var player in Helper.GetValidPlayers())
|
|
||||||
{
|
|
||||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
|
||||||
{
|
|
||||||
IMenu? voteMenu = Helper.CreateMenu(_localizer["sa_admin_vote_menu_title", question]);
|
|
||||||
if (voteMenu == null)
|
|
||||||
return;
|
|
||||||
//ChatMenu voteMenu = new(_localizer!["sa_admin_vote_menu_title", question]);
|
|
||||||
|
|
||||||
for (var i = 2; i <= answersCount - 1; i++)
|
|
||||||
{
|
|
||||||
voteMenu.AddMenuOption(command.GetArg(i), Helper.HandleVotes);
|
|
||||||
}
|
|
||||||
|
|
||||||
voteMenu.PostSelectAction = PostSelectAction.Close;
|
|
||||||
|
|
||||||
Helper.PrintToCenterAll(_localizer["sa_admin_vote_message", caller == null ? _localizer["sa_console"] : caller.PlayerName, question]);
|
|
||||||
|
|
||||||
player.SendLocalizedMessage(_localizer,
|
|
||||||
"sa_admin_vote_message",
|
|
||||||
caller == null ? _localizer["sa_console"] : caller.PlayerName,
|
|
||||||
question);
|
|
||||||
|
|
||||||
voteMenu.Open(player);
|
|
||||||
|
|
||||||
//MenuManager.OpenChatMenu(player, voteMenu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VoteInProgress = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VoteInProgress)
|
|
||||||
{
|
|
||||||
AddTimer(30, () =>
|
|
||||||
{
|
|
||||||
foreach (var player in Helper.GetValidPlayers())
|
|
||||||
{
|
|
||||||
if (_localizer != null)
|
|
||||||
player.SendLocalizedMessage(_localizer,
|
|
||||||
"sa_admin_vote_message_results",
|
|
||||||
question);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (key, value) in VoteAnswers)
|
|
||||||
{
|
|
||||||
foreach (var player in Helper.GetValidPlayers())
|
|
||||||
{
|
|
||||||
if (_localizer != null)
|
|
||||||
player.SendLocalizedMessage(_localizer,
|
|
||||||
"sa_admin_vote_message_results_answer",
|
|
||||||
key,
|
|
||||||
value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VoteAnswers.Clear();
|
|
||||||
VoteInProgress = false;
|
|
||||||
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,307 +0,0 @@
|
|||||||
// using System.Globalization;
|
|
||||||
// using CounterStrikeSharp.API;
|
|
||||||
// using CounterStrikeSharp.API.Core;
|
|
||||||
// using CounterStrikeSharp.API.Modules.Admin;
|
|
||||||
// using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
//
|
|
||||||
// namespace CS2_SimpleAdmin;
|
|
||||||
//
|
|
||||||
// public partial class CS2_SimpleAdmin
|
|
||||||
// {
|
|
||||||
// /// <summary>
|
|
||||||
// /// Enables or disables no-clip mode for specified player(s).
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player issuing the command.</param>
|
|
||||||
// /// <param name="command">The command input containing targets.</param>
|
|
||||||
// [CommandHelper(1, "<#userid or name>")]
|
|
||||||
// [RequiresPermissions("@css/cheats")]
|
|
||||||
// public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
// {
|
|
||||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
//
|
|
||||||
// var targets = GetTarget(command);
|
|
||||||
// if (targets == null) return;
|
|
||||||
// var playersToTarget = targets.Players.Where(player =>
|
|
||||||
// player.IsValid &&
|
|
||||||
// player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
|
||||||
//
|
|
||||||
// playersToTarget.ForEach(player =>
|
|
||||||
// {
|
|
||||||
// if (caller!.CanTarget(player))
|
|
||||||
// {
|
|
||||||
// NoClip(caller, player, callerName);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// Helper.LogCommand(caller, command);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Toggles no-clip mode for a player and shows admin activity messages.
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player/admin toggling no-clip.</param>
|
|
||||||
// /// <param name="player">The target player whose no-clip state changes.</param>
|
|
||||||
// /// <param name="callerName">Optional caller name for messages.</param>
|
|
||||||
// /// <param name="command">Optional command info for logging.</param>
|
|
||||||
// internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
|
||||||
// {
|
|
||||||
// if (!player.IsValid) return;
|
|
||||||
// if (!caller.CanTarget(player)) return;
|
|
||||||
//
|
|
||||||
// // Set default caller name if not provided
|
|
||||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
|
||||||
//
|
|
||||||
// // Toggle no-clip mode for the player
|
|
||||||
// player.Pawn.Value?.ToggleNoclip();
|
|
||||||
//
|
|
||||||
// // Determine message keys and arguments for the no-clip notification
|
|
||||||
// var (activityMessageKey, adminActivityArgs) =
|
|
||||||
// ("sa_admin_noclip_message",
|
|
||||||
// new object[] { "CALLER", player.PlayerName });
|
|
||||||
//
|
|
||||||
// // Display admin activity message to other players
|
|
||||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
// {
|
|
||||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Log the command
|
|
||||||
// if (command == null)
|
|
||||||
// Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Enables or disables god mode for specified player(s).
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player issuing the command.</param>
|
|
||||||
// /// <param name="command">The command input containing targets.</param>
|
|
||||||
//
|
|
||||||
// [RequiresPermissions("@css/cheats")]
|
|
||||||
// [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
// public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
// {
|
|
||||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
// var targets = GetTarget(command);
|
|
||||||
// if (targets == null) return;
|
|
||||||
//
|
|
||||||
// var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
|
||||||
//
|
|
||||||
// playersToTarget.ForEach(player =>
|
|
||||||
// {
|
|
||||||
// if (player.Connected != PlayerConnectedState.PlayerConnected)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// if (caller!.CanTarget(player))
|
|
||||||
// {
|
|
||||||
// God(caller, player, command);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// Helper.LogCommand(caller, command);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Toggles god mode for a player and notifies admins.
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player/admin toggling god mode.</param>
|
|
||||||
// /// <param name="player">The target player whose god mode changes.</param>
|
|
||||||
// /// <param name="command">Optional command info for logging.</param>
|
|
||||||
// internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
|
|
||||||
// {
|
|
||||||
// if (!caller.CanTarget(player)) return;
|
|
||||||
//
|
|
||||||
// // Set default caller name if not provided
|
|
||||||
// var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
|
||||||
//
|
|
||||||
// // Toggle god mode for the player
|
|
||||||
// if (!GodPlayers.Add(player.Slot))
|
|
||||||
// {
|
|
||||||
// GodPlayers.Remove(player.Slot);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Log the command
|
|
||||||
// if (command == null)
|
|
||||||
// Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
|
||||||
//
|
|
||||||
// // Determine message key and arguments for the god mode notification
|
|
||||||
// var (activityMessageKey, adminActivityArgs) =
|
|
||||||
// ("sa_admin_god_message",
|
|
||||||
// new object[] { "CALLER", player.PlayerName });
|
|
||||||
//
|
|
||||||
// // Display admin activity message to other players
|
|
||||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
// {
|
|
||||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Freezes target player(s) for an optional specified duration.
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player issuing the freeze command.</param>
|
|
||||||
// /// <param name="command">The command input containing targets and duration.</param>
|
|
||||||
// [CommandHelper(1, "<#userid or name> [duration]")]
|
|
||||||
// [RequiresPermissions("@css/slay")]
|
|
||||||
// public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
// {
|
|
||||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
// int.TryParse(command.GetArg(2), out var time);
|
|
||||||
//
|
|
||||||
// var targets = GetTarget(command);
|
|
||||||
// if (targets == null) return;
|
|
||||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
|
||||||
//
|
|
||||||
// playersToTarget.ForEach(player =>
|
|
||||||
// {
|
|
||||||
// if (caller!.CanTarget(player))
|
|
||||||
// {
|
|
||||||
// Freeze(caller, player, time, callerName, command);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// Helper.LogCommand(caller, command);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Resizes the target player(s) models to a specified scale.
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player issuing the resize command.</param>
|
|
||||||
// /// <param name="command">The command input containing targets and scale factor.</param>
|
|
||||||
// [CommandHelper(1, "<#userid or name> [size]")]
|
|
||||||
// [RequiresPermissions("@css/slay")]
|
|
||||||
// public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
// {
|
|
||||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
// float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
|
|
||||||
//
|
|
||||||
// var targets = GetTarget(command);
|
|
||||||
// if (targets == null) return;
|
|
||||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
|
||||||
//
|
|
||||||
// playersToTarget.ForEach(player =>
|
|
||||||
// {
|
|
||||||
// if (!caller!.CanTarget(player)) return;
|
|
||||||
//
|
|
||||||
// var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
|
|
||||||
// if (sceneNode == null) return;
|
|
||||||
//
|
|
||||||
// sceneNode.GetSkeletonInstance().Scale = size;
|
|
||||||
// player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
|
|
||||||
//
|
|
||||||
// Server.NextWorldUpdate(() =>
|
|
||||||
// {
|
|
||||||
// Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// var (activityMessageKey, adminActivityArgs) =
|
|
||||||
// ("sa_admin_resize_message",
|
|
||||||
// new object[] { "CALLER", player.PlayerName });
|
|
||||||
//
|
|
||||||
// // Display admin activity message to other players
|
|
||||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
// {
|
|
||||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// Helper.LogCommand(caller, command);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Freezes a single player and optionally schedules automatic unfreeze after a duration.
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player/admin freezing the player.</param>
|
|
||||||
// /// <param name="player">The player to freeze.</param>
|
|
||||||
// /// <param name="time">Duration of freeze in seconds.</param>
|
|
||||||
// /// <param name="callerName">Optional name for notifications.</param>
|
|
||||||
// /// <param name="command">Optional command info for logging.</param>
|
|
||||||
// internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
|
|
||||||
// {
|
|
||||||
// if (!player.IsValid) return;
|
|
||||||
// if (!caller.CanTarget(player)) return;
|
|
||||||
//
|
|
||||||
// // Set default caller name if not provided
|
|
||||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
|
||||||
//
|
|
||||||
// // Freeze player pawn
|
|
||||||
// player.Pawn.Value?.Freeze();
|
|
||||||
//
|
|
||||||
// // Determine message keys and arguments for the freeze notification
|
|
||||||
// var (activityMessageKey, adminActivityArgs) =
|
|
||||||
// ("sa_admin_freeze_message",
|
|
||||||
// new object[] { "CALLER", player.PlayerName });
|
|
||||||
//
|
|
||||||
// // Display admin activity message to other players
|
|
||||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
// {
|
|
||||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Schedule unfreeze for the player if time is specified
|
|
||||||
// if (time > 0)
|
|
||||||
// {
|
|
||||||
// Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Log the command and send Discord notification
|
|
||||||
// if (command == null)
|
|
||||||
// Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Unfreezes target player(s).
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player issuing the unfreeze command.</param>
|
|
||||||
// /// <param name="command">The command input with targets.</param>
|
|
||||||
// [CommandHelper(1, "<#userid or name>")]
|
|
||||||
// [RequiresPermissions("@css/slay")]
|
|
||||||
// public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
// {
|
|
||||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
//
|
|
||||||
// var targets = GetTarget(command);
|
|
||||||
// if (targets == null) return;
|
|
||||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
|
||||||
//
|
|
||||||
// playersToTarget.ForEach(player =>
|
|
||||||
// {
|
|
||||||
// Unfreeze(caller, player, callerName, command);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// Helper.LogCommand(caller, command);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// <summary>
|
|
||||||
// /// Unfreezes a single player and notifies admins.
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="caller">The player/admin unfreezing the player.</param>
|
|
||||||
// /// <param name="player">The player to unfreeze.</param>
|
|
||||||
// /// <param name="callerName">Optional name for notifications.</param>
|
|
||||||
// /// <param name="command">Optional command info for logging.</param>
|
|
||||||
// internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
|
||||||
// {
|
|
||||||
// if (!player.IsValid) return;
|
|
||||||
// if (!caller.CanTarget(player)) return;
|
|
||||||
//
|
|
||||||
// // Set default caller name if not provided
|
|
||||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
|
||||||
//
|
|
||||||
// // Unfreeze player pawn
|
|
||||||
// player.Pawn.Value?.Unfreeze();
|
|
||||||
//
|
|
||||||
// // Determine message keys and arguments for the unfreeze notification
|
|
||||||
// var (activityMessageKey, adminActivityArgs) =
|
|
||||||
// ("sa_admin_unfreeze_message",
|
|
||||||
// new object[] { "CALLER", player.PlayerName });
|
|
||||||
//
|
|
||||||
// // Display admin activity message to other players
|
|
||||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
// {
|
|
||||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Log the command and send Discord notification
|
|
||||||
// if (command == null)
|
|
||||||
// Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,625 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using CounterStrikeSharp.API;
|
|
||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Modules.Admin;
|
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
|
||||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
|
||||||
using CounterStrikeSharp.API.Modules.Memory;
|
|
||||||
using CounterStrikeSharp.API.Modules.Utils;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
public partial class CS2_SimpleAdmin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Executes the 'slay' command, forcing the targeted players to commit suicide.
|
|
||||||
/// Checks player validity and permissions.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Player or console issuing the command.</param>
|
|
||||||
/// <param name="command">Command details, including targets.</param>
|
|
||||||
[RequiresPermissions("@css/slay")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
|
|
||||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
Slay(caller, player, callerName, command);
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Performs the actual slay action on a player, with notification and logging.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Admin or console issuing the slay.</param>
|
|
||||||
/// <param name="player">Target player to slay.</param>
|
|
||||||
/// <param name="callerName">Optional name to display as the slayer.</param>
|
|
||||||
/// <param name="command">Optional command info for logging.</param>
|
|
||||||
internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
|
||||||
{
|
|
||||||
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return;
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
|
||||||
|
|
||||||
// Make the player commit suicide
|
|
||||||
player.CommitSuicide(false, true);
|
|
||||||
player.EmitSound("Player.Death");
|
|
||||||
|
|
||||||
// Determine message keys and arguments for the slay notification
|
|
||||||
var (activityMessageKey, adminActivityArgs) =
|
|
||||||
("sa_admin_slay_message",
|
|
||||||
new object[] { "CALLER", player.PlayerName });
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the command and send Discord notification
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies damage as a slap effect to the targeted players.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player/admin executing the slap command.</param>
|
|
||||||
/// <param name="command">The command including targets and optional damage value.</param>
|
|
||||||
[RequiresPermissions("@css/slay")]
|
|
||||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [damage]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnSlapCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
var damage = 0;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
|
|
||||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
|
||||||
|
|
||||||
if (command.ArgCount >= 2)
|
|
||||||
{
|
|
||||||
int.TryParse(command.GetArg(2), out damage);
|
|
||||||
}
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (caller!.CanTarget(player))
|
|
||||||
{
|
|
||||||
Slap(caller, player, damage, command);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies slap damage to a specific player with notifications and logging.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player/admin applying the slap effect.</param>
|
|
||||||
/// <param name="player">The target player to slap.</param>
|
|
||||||
/// <param name="damage">The damage amount to apply.</param>
|
|
||||||
/// <param name="command">Optional command info for logging.</param>
|
|
||||||
internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, CommandInfo? command = null)
|
|
||||||
{
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
|
||||||
|
|
||||||
// Apply slap damage to the player
|
|
||||||
player.Pawn.Value?.Slap(damage);
|
|
||||||
player.EmitSound("Player.DamageFall");
|
|
||||||
|
|
||||||
// Log the command
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}");
|
|
||||||
|
|
||||||
// Determine message key and arguments for the slap notification
|
|
||||||
var (activityMessageKey, adminActivityArgs) =
|
|
||||||
("sa_admin_slap_message",
|
|
||||||
new object[] { "CALLER", player.PlayerName });
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
|
||||||
|
|
||||||
if (_localizer != null)
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Changes the team of targeted players with optional kill on switch.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player/admin issuing the command.</param>
|
|
||||||
/// <param name="command">The command containing targets, team info, and optional kill flag.</param>
|
|
||||||
[RequiresPermissions("@css/kick")]
|
|
||||||
[CommandHelper(minArgs: 2, usage: "<#userid or name> [<ct/tt/spec>] [-k]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
|
||||||
public void OnTeamCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
var teamName = command.GetArg(2).ToLower();
|
|
||||||
string _teamName;
|
|
||||||
var teamNum = CsTeam.Spectator;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
switch (teamName)
|
|
||||||
{
|
|
||||||
case "ct":
|
|
||||||
case "counterterrorist":
|
|
||||||
teamNum = CsTeam.CounterTerrorist;
|
|
||||||
_teamName = "CT";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "t":
|
|
||||||
case "tt":
|
|
||||||
case "terrorist":
|
|
||||||
teamNum = CsTeam.Terrorist;
|
|
||||||
_teamName = "TT";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "swap":
|
|
||||||
_teamName = "SWAP";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
teamNum = CsTeam.Spectator;
|
|
||||||
_teamName = "SPEC";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var kill = command.GetArg(3).ToLower().Equals("-k");
|
|
||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
ChangeTeam(caller, player, _teamName, teamNum, kill, command);
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Changes the team of a player with various conditions and logs the operation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The player/admin issuing the change.</param>
|
|
||||||
/// <param name="player">The target player.</param>
|
|
||||||
/// <param name="teamName">Team name string.</param>
|
|
||||||
/// <param name="teamNum">Team enumeration value.</param>
|
|
||||||
/// <param name="kill">If true, kills player on team change.</param>
|
|
||||||
/// <param name="command">Optional command info for logging.</param>
|
|
||||||
internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null)
|
|
||||||
{
|
|
||||||
// Check if the player is valid and connected
|
|
||||||
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ensure the caller can target the player
|
|
||||||
if (!caller.CanTarget(player)) return;
|
|
||||||
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
|
||||||
|
|
||||||
// Change team based on the provided teamName and conditions
|
|
||||||
if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
|
||||||
player.SwitchTeam(teamNum);
|
|
||||||
else
|
|
||||||
player.ChangeTeam(teamNum);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (player.TeamNum != (byte)CsTeam.Spectator)
|
|
||||||
{
|
|
||||||
var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist;
|
|
||||||
teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT";
|
|
||||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
|
||||||
player.SwitchTeam(_teamNum);
|
|
||||||
else
|
|
||||||
player.ChangeTeam(_teamNum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the command
|
|
||||||
if (command == null)
|
|
||||||
Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}");
|
|
||||||
|
|
||||||
// Determine message key and arguments for the team change notification
|
|
||||||
var activityMessageKey = "sa_admin_team_message";
|
|
||||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, teamName };
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
|
||||||
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Renames targeted players to a new name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin issuing the rename command.</param>
|
|
||||||
/// <param name="command">The command including targets and new name.</param>
|
|
||||||
[CommandHelper(1, "<#userid or name> <new name>")]
|
|
||||||
[RequiresPermissions("@css/kick")]
|
|
||||||
public void OnRenameCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Get the new name from the command arguments
|
|
||||||
var newName = command.GetArg(2);
|
|
||||||
|
|
||||||
// Check if the new name is valid
|
|
||||||
if (string.IsNullOrEmpty(newName))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Retrieve the targets based on the command
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
|
|
||||||
// Filter out valid players from the targets
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
// Log the command
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
// Process each player to rename
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
// Check if the player is connected and can be targeted
|
|
||||||
if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Determine message key and arguments for the rename notification
|
|
||||||
var activityMessageKey = "sa_admin_rename_message";
|
|
||||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
|
||||||
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
|
|
||||||
// Rename the player
|
|
||||||
player.Rename(newName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Renames permamently targeted players to a new name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">The admin issuing the pre-rename command.</param>
|
|
||||||
/// <param name="command">The command containing targets and new alias.</param>
|
|
||||||
[CommandHelper(1, "<#userid or name> <new name>")]
|
|
||||||
[RequiresPermissions("@css/ban")]
|
|
||||||
public void OnPrenameCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
// Set default caller name if not provided
|
|
||||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
|
||||||
|
|
||||||
// Get the new name from the command arguments
|
|
||||||
var newName = command.GetArg(2);
|
|
||||||
|
|
||||||
// Retrieve the targets based on the command
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null) return;
|
|
||||||
|
|
||||||
// Filter out valid players from the targets
|
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
|
||||||
|
|
||||||
// Log the command
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
// Process each player to rename
|
|
||||||
playersToTarget.ForEach(player =>
|
|
||||||
{
|
|
||||||
// Check if the player is connected and can be targeted
|
|
||||||
if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Determine message key and arguments for the rename notification
|
|
||||||
var activityMessageKey = "sa_admin_rename_message";
|
|
||||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
|
|
||||||
|
|
||||||
// Display admin activity message to other players
|
|
||||||
if (caller != null && !SilentPlayers.Contains(caller.Slot))
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if the new name is valid and update the renamed players list
|
|
||||||
if (!string.IsNullOrEmpty(newName))
|
|
||||||
{
|
|
||||||
RenamedPlayers[player.SteamID] = newName;
|
|
||||||
player.Rename(newName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RenamedPlayers.Remove(player.SteamID);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Teleports targeted player(s) to another player's location.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Admin issuing teleport command.</param>
|
|
||||||
/// <param name="command">Command containing teleport targets and destination.</param>
|
|
||||||
[CommandHelper(1, "<#userid or name> [#userid or name]")]
|
|
||||||
[RequiresPermissions("@css/kick")]
|
|
||||||
public void OnGotoCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
IEnumerable<CCSPlayerController> playersToTeleport;
|
|
||||||
CCSPlayerController? destinationPlayer;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
|
|
||||||
if (command.ArgCount < 3)
|
|
||||||
{
|
|
||||||
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (targets == null || targets.Count() != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
destinationPlayer = targets.Players.FirstOrDefault(p =>
|
|
||||||
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
|
||||||
|
|
||||||
if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
playersToTeleport = [caller];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var destination = GetTarget(command, 2);
|
|
||||||
if (targets == null || destination == null || destination.Count() != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
destinationPlayer = destination.Players.FirstOrDefault(p =>
|
|
||||||
p is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
|
||||||
|
|
||||||
if (destinationPlayer == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
playersToTeleport = targets.Players
|
|
||||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (!playersToTeleport.Any())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log command
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
foreach (var player in playersToTeleport)
|
|
||||||
{
|
|
||||||
if (player.PlayerPawn?.Value == null || destinationPlayer?.PlayerPawn?.Value == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
player.TeleportPlayer(destinationPlayer);
|
|
||||||
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
|
|
||||||
AddTimer(4, () =>
|
|
||||||
{
|
|
||||||
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
|
||||||
{
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
|
||||||
{
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity("sa_admin_tp_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Brings targeted player(s) to the caller or specified destination player's location.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="caller">Player issuing the bring command.</param>
|
|
||||||
/// <param name="command">Command containing the destination and targets.</param>
|
|
||||||
[CommandHelper(1, "<#destination or name> [#userid or name...]")]
|
|
||||||
[RequiresPermissions("@css/kick")]
|
|
||||||
public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
{
|
|
||||||
IEnumerable<CCSPlayerController> playersToTeleport;
|
|
||||||
CCSPlayerController? destinationPlayer;
|
|
||||||
|
|
||||||
if (command.ArgCount < 3)
|
|
||||||
{
|
|
||||||
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var targets = GetTarget(command);
|
|
||||||
if (targets == null || !targets.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
destinationPlayer = caller;
|
|
||||||
|
|
||||||
playersToTeleport = targets.Players
|
|
||||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var destination = GetTarget(command);
|
|
||||||
if (destination == null || destination.Count() != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
destinationPlayer = destination.Players.FirstOrDefault(p =>
|
|
||||||
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
|
||||||
|
|
||||||
if (destinationPlayer == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Rest args = targets to teleport
|
|
||||||
var targets = GetTarget(command, 2);
|
|
||||||
if (targets == null || !targets.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
playersToTeleport = targets.Players
|
|
||||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destinationPlayer == null || !playersToTeleport.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Log command
|
|
||||||
Helper.LogCommand(caller, command);
|
|
||||||
|
|
||||||
foreach (var player in playersToTeleport)
|
|
||||||
{
|
|
||||||
if (player.PlayerPawn?.Value == null || destinationPlayer.PlayerPawn?.Value == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Teleport
|
|
||||||
player.TeleportPlayer(destinationPlayer);
|
|
||||||
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
|
|
||||||
AddTimer(4, () =>
|
|
||||||
{
|
|
||||||
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
|
||||||
{
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
|
||||||
{
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
|
||||||
{
|
|
||||||
Helper.ShowAdminActivity("sa_admin_bring_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [CommandHelper(1, "<#userid or name> [#userid or name]")]
|
|
||||||
// [RequiresPermissions("@css/kick")]
|
|
||||||
// public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
|
|
||||||
// {
|
|
||||||
// // Check if the caller is valid and has a live pawn
|
|
||||||
// if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// // Get the target players
|
|
||||||
// var targets = GetTarget(command);
|
|
||||||
// if (targets == null || targets.Count() > 1) return;
|
|
||||||
//
|
|
||||||
// var playersToTarget = targets.Players
|
|
||||||
// .Where(player => player is { IsValid: true, IsHLTV: false })
|
|
||||||
// .ToList();
|
|
||||||
//
|
|
||||||
// // Log the command
|
|
||||||
// Helper.LogCommand(caller, command);
|
|
||||||
//
|
|
||||||
// // Process each player to teleport
|
|
||||||
// foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).Where(caller.CanTarget))
|
|
||||||
// {
|
|
||||||
// if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null)
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// // Teleport the player to the caller and toggle noclip
|
|
||||||
// player.TeleportPlayer(caller);
|
|
||||||
// // caller.PlayerPawn.Value.ToggleNoclip();
|
|
||||||
//
|
|
||||||
// caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
// caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
//
|
|
||||||
// Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
// Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
//
|
|
||||||
// player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
// player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
|
||||||
//
|
|
||||||
// Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
// Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
//
|
|
||||||
// // Set a timer to toggle collision back after 4 seconds
|
|
||||||
// AddTimer(4, () =>
|
|
||||||
// {
|
|
||||||
// if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
// caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
//
|
|
||||||
// Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
// Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
//
|
|
||||||
// player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
// player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
|
||||||
//
|
|
||||||
// Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
|
||||||
// Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // Prepare message key and arguments for the bring notification
|
|
||||||
// var activityMessageKey = "sa_admin_bring_message";
|
|
||||||
// var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
|
|
||||||
//
|
|
||||||
// // Show admin activity
|
|
||||||
// if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
|
||||||
// {
|
|
||||||
// Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, false, adminActivityArgs);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
|
||||||
|
|
||||||
public class DurationItem
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("duration")]
|
|
||||||
public int Duration { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AdminFlag
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("flag")]
|
|
||||||
public required string Flag { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DiscordPenaltySetting
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("value")]
|
|
||||||
public string? Value { get; set; } = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Discord
|
|
||||||
{
|
|
||||||
[JsonPropertyName("DiscordLogWebhook")]
|
|
||||||
public string DiscordLogWebhook { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonPropertyName("DiscordPenaltyBanSettings")]
|
|
||||||
public DiscordPenaltySetting[] DiscordPenaltyBanSettings { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "Color", Value = "" },
|
|
||||||
new() { Name = "Webhook", Value = "" },
|
|
||||||
new() { Name = "ThumbnailUrl", Value = "" },
|
|
||||||
new() { Name = "ImageUrl", Value = "" },
|
|
||||||
new() { Name = "Footer", Value = "" },
|
|
||||||
new() { Name = "Time", Value = "{relative}" },
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("DiscordPenaltyMuteSettings")]
|
|
||||||
public DiscordPenaltySetting[] DiscordPenaltyMuteSettings { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "Color", Value = "" },
|
|
||||||
new() { Name = "Webhook", Value = "" },
|
|
||||||
new() { Name = "ThumbnailUrl", Value = "" },
|
|
||||||
new() { Name = "ImageUrl", Value = "" },
|
|
||||||
new() { Name = "Footer", Value = "" },
|
|
||||||
new() { Name = "Time", Value = "{relative}" },
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("DiscordPenaltyGagSettings")]
|
|
||||||
public DiscordPenaltySetting[] DiscordPenaltyGagSettings { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "Color", Value = "" },
|
|
||||||
new() { Name = "Webhook", Value = "" },
|
|
||||||
new() { Name = "ThumbnailUrl", Value = "" },
|
|
||||||
new() { Name = "ImageUrl", Value = "" },
|
|
||||||
new() { Name = "Footer", Value = "" },
|
|
||||||
new() { Name = "Time", Value = "{relative}" },
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("DiscordPenaltySilenceSettings")]
|
|
||||||
public DiscordPenaltySetting[] DiscordPenaltySilenceSettings { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "Color", Value = "" },
|
|
||||||
new() { Name = "Webhook", Value = "" },
|
|
||||||
new() { Name = "ThumbnailUrl", Value = "" },
|
|
||||||
new() { Name = "ImageUrl", Value = "" },
|
|
||||||
new() { Name = "Footer", Value = "" },
|
|
||||||
new() { Name = "Time", Value = "{relative}" },
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("DiscordPenaltyWarnSettings")]
|
|
||||||
public DiscordPenaltySetting[] DiscordPenaltyWarnSettings { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "Color", Value = "" },
|
|
||||||
new() { Name = "Webhook", Value = "" },
|
|
||||||
new() { Name = "ThumbnailUrl", Value = "" },
|
|
||||||
new() { Name = "ImageUrl", Value = "" },
|
|
||||||
new() { Name = "Footer", Value = "" },
|
|
||||||
new() { Name = "Time", Value = "{relative}" },
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("DiscordAssociatedAccountsSettings")]
|
|
||||||
public DiscordPenaltySetting[] DiscordAssociatedAccountsSettings { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "Color", Value = "" },
|
|
||||||
new() { Name = "Webhook", Value = "" },
|
|
||||||
new() { Name = "ThumbnailUrl", Value = "" },
|
|
||||||
new() { Name = "ImageUrl", Value = "" },
|
|
||||||
new() { Name = "Footer", Value = "" },
|
|
||||||
new() { Name = "Time", Value = "{relative}" },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CustomServerCommandData
|
|
||||||
{
|
|
||||||
[JsonPropertyName("Flag")]
|
|
||||||
public string Flag { get; set; } = "@css/generic";
|
|
||||||
|
|
||||||
[JsonPropertyName("DisplayName")]
|
|
||||||
public string DisplayName { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonPropertyName("Command")]
|
|
||||||
public string Command { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonPropertyName("ExecuteOnClient")]
|
|
||||||
public bool ExecuteOnClient { get; set; } = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MenuConfig
|
|
||||||
{
|
|
||||||
[JsonPropertyName("MenuType")] public string MenuType { get; set; } = "selectable";
|
|
||||||
|
|
||||||
[JsonPropertyName("Durations")]
|
|
||||||
public DurationItem[] Durations { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "1 minute", Duration = 1 },
|
|
||||||
new() { Name = "5 minutes", Duration = 5 },
|
|
||||||
new() { Name = "15 minutes", Duration = 15 },
|
|
||||||
new() { Name = "1 hour", Duration = 60 },
|
|
||||||
new() { Name = "1 day", Duration = 60 * 24 },
|
|
||||||
new() { Name = "7 days", Duration = 60 * 24 * 7 },
|
|
||||||
new() { Name = "14 days", Duration = 60 * 24 * 14 },
|
|
||||||
new() { Name = "30 days", Duration = 60 * 24 * 30 },
|
|
||||||
new() { Name = "Permanent", Duration = 0 }
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("BanReasons")]
|
|
||||||
public List<string> BanReasons { get; set; } =
|
|
||||||
[
|
|
||||||
"Hacking",
|
|
||||||
"Voice Abuse",
|
|
||||||
"Chat Abuse",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("KickReasons")]
|
|
||||||
public List<string> KickReasons { get; set; } =
|
|
||||||
[
|
|
||||||
"Voice Abuse",
|
|
||||||
"Chat Abuse",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("WarnReasons")]
|
|
||||||
public List<string> WarnReasons { get; set; } =
|
|
||||||
[
|
|
||||||
"Voice Abuse",
|
|
||||||
"Chat Abuse",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("MuteReasons")]
|
|
||||||
public List<string> MuteReasons { get; set; } =
|
|
||||||
[
|
|
||||||
"Advertising",
|
|
||||||
"Spamming",
|
|
||||||
"Spectator camera abuse",
|
|
||||||
"Hate",
|
|
||||||
"Admin disrespect",
|
|
||||||
"Other"
|
|
||||||
];
|
|
||||||
|
|
||||||
[JsonPropertyName("AdminFlags")]
|
|
||||||
public AdminFlag[] AdminFlags { get; set; } =
|
|
||||||
[
|
|
||||||
new() { Name = "Generic", Flag = "@css/generic" },
|
|
||||||
new() { Name = "Chat", Flag = "@css/chat" },
|
|
||||||
new() { Name = "Change Map", Flag = "@css/changemap" },
|
|
||||||
new() { Name = "Slay", Flag = "@css/slay" },
|
|
||||||
new() { Name = "Kick", Flag = "@css/kick" },
|
|
||||||
new() { Name = "Ban", Flag = "@css/ban" },
|
|
||||||
new() { Name = "Perm Ban", Flag = "@css/permban" },
|
|
||||||
new() { Name = "Unban", Flag = "@css/unban" },
|
|
||||||
new() { Name = "Show IP", Flag = "@css/showip" },
|
|
||||||
new() { Name = "Cvar", Flag = "@css/cvar" },
|
|
||||||
new() { Name = "Rcon", Flag = "@css/rcon" },
|
|
||||||
new() { Name = "Root (all flags)", Flag = "@css/root" }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OtherSettings
|
|
||||||
{
|
|
||||||
[JsonPropertyName("ShowActivityType")]
|
|
||||||
public int ShowActivityType { get; set; } = 2;
|
|
||||||
|
|
||||||
[JsonPropertyName("TeamSwitchType")]
|
|
||||||
public int TeamSwitchType { get; set; } = 1;
|
|
||||||
|
|
||||||
[JsonPropertyName("KickTime")]
|
|
||||||
public int KickTime { get; set; } = 5;
|
|
||||||
|
|
||||||
[JsonPropertyName("BanType")]
|
|
||||||
public int BanType { get; set; } = 1;
|
|
||||||
|
|
||||||
[JsonPropertyName("TimeMode")]
|
|
||||||
public int TimeMode { get; set; } = 1;
|
|
||||||
|
|
||||||
[JsonPropertyName("DisableDangerousCommands")]
|
|
||||||
public bool DisableDangerousCommands { get; set; } = true;
|
|
||||||
|
|
||||||
[JsonPropertyName("MaxBanDuration")]
|
|
||||||
public int MaxBanDuration { get; set; } = 60 * 24 * 7;
|
|
||||||
|
|
||||||
[JsonPropertyName("MaxMuteDuration")]
|
|
||||||
public int MaxMuteDuration { get; set; } = 60 * 24 * 7;
|
|
||||||
|
|
||||||
[JsonPropertyName("ExpireOldIpBans")]
|
|
||||||
public int ExpireOldIpBans { get; set; } = 0;
|
|
||||||
|
|
||||||
[JsonPropertyName("ReloadAdminsEveryMapChange")]
|
|
||||||
public bool ReloadAdminsEveryMapChange { get; set; } = false;
|
|
||||||
|
|
||||||
[JsonPropertyName("DisconnectedPlayersHistoryCount")]
|
|
||||||
public int DisconnectedPlayersHistoryCount { get; set; } = 10;
|
|
||||||
|
|
||||||
[JsonPropertyName("NotifyPenaltiesToAdminOnConnect")]
|
|
||||||
public bool NotifyPenaltiesToAdminOnConnect { get; set; } = true;
|
|
||||||
|
|
||||||
[JsonPropertyName("ShowBanMenuIfNoTime")]
|
|
||||||
public bool ShowBanMenuIfNoTime { get; set; } = true;
|
|
||||||
|
|
||||||
[JsonPropertyName("UserMessageGagChatType")]
|
|
||||||
public bool UserMessageGagChatType { get; set; } = false;
|
|
||||||
|
|
||||||
[JsonPropertyName("CheckMultiAccountsByIp")]
|
|
||||||
public bool CheckMultiAccountsByIp { get; set; } = true;
|
|
||||||
|
|
||||||
[JsonPropertyName("AdditionalCommandsToLog")]
|
|
||||||
public List<string> AdditionalCommandsToLog { get; set; } = new();
|
|
||||||
[JsonPropertyName("IgnoredIps")]
|
|
||||||
public List<string> IgnoredIps { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CS2_SimpleAdminConfig : BasePluginConfig
|
|
||||||
{
|
|
||||||
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 25;
|
|
||||||
|
|
||||||
[JsonPropertyName("DatabaseConfig")]
|
|
||||||
public DatabaseConfig DatabaseConfig { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonPropertyName("OtherSettings")]
|
|
||||||
public OtherSettings OtherSettings { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonPropertyName("EnableMetrics")]
|
|
||||||
public bool EnableMetrics { get; set; } = true;
|
|
||||||
|
|
||||||
[JsonPropertyName("EnableUpdateCheck")]
|
|
||||||
public bool EnableUpdateCheck { get; set; } = true;
|
|
||||||
|
|
||||||
[JsonPropertyName("Timezone")]
|
|
||||||
public string Timezone { get; set; } = "UTC";
|
|
||||||
|
|
||||||
[JsonPropertyName("WarnThreshold")]
|
|
||||||
public Dictionary<int, string> WarnThreshold { get; set; } = new()
|
|
||||||
{
|
|
||||||
{ 998, "css_addban STEAMID64 60 \"3/4 Warn\"" },
|
|
||||||
{ 999, "css_ban #USERID 120 \"4/4 Warn\"" },
|
|
||||||
};
|
|
||||||
|
|
||||||
[JsonPropertyName("MultiServerMode")]
|
|
||||||
public bool MultiServerMode { get; set; } = true;
|
|
||||||
|
|
||||||
[JsonPropertyName("Discord")]
|
|
||||||
public Discord Discord { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonPropertyName("DefaultMaps")]
|
|
||||||
public List<string> DefaultMaps { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonPropertyName("WorkshopMaps")]
|
|
||||||
public Dictionary<string, long?> WorkshopMaps { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonPropertyName("CustomServerCommands")]
|
|
||||||
public List<CustomServerCommandData> CustomServerCommands { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonPropertyName("MenuConfig")]
|
|
||||||
public MenuConfig MenuConfigs { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class DatabaseConfig
|
|
||||||
{
|
|
||||||
[JsonPropertyName("DatabaseType")]
|
|
||||||
public string DatabaseType { get; set; } = "SQLite";
|
|
||||||
|
|
||||||
[JsonPropertyName("SqliteFilePath")]
|
|
||||||
public string SqliteFilePath { get; set; } = "cs2-simpleadmin.sqlite";
|
|
||||||
|
|
||||||
[JsonPropertyName("DatabaseHost")]
|
|
||||||
public string DatabaseHost { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonPropertyName("DatabasePort")]
|
|
||||||
public int DatabasePort { get; set; } = 3306;
|
|
||||||
|
|
||||||
[JsonPropertyName("DatabaseUser")]
|
|
||||||
public string DatabaseUser { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonPropertyName("DatabasePassword")]
|
|
||||||
public string DatabasePassword { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonPropertyName("DatabaseName")]
|
|
||||||
public string DatabaseName { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonPropertyName("DatabaseSSlMode")]
|
|
||||||
public string DatabaseSSlMode { get; set; } = "preferred";
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DatabaseType
|
|
||||||
{
|
|
||||||
MySQL,
|
|
||||||
SQLite
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MySqlConnector;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin.Database;
|
|
||||||
|
|
||||||
public class Database(string dbConnectionString)
|
|
||||||
{
|
|
||||||
public MySqlConnection GetConnection()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var connection = new MySqlConnection(dbConnectionString);
|
|
||||||
connection.Open();
|
|
||||||
|
|
||||||
// using var cmd = connection.CreateCommand();
|
|
||||||
// cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
|
||||||
// cmd.ExecuteNonQueryAsync();
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin._logger?.LogCritical($"Unable to connect to database: {ex.Message}");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<MySqlConnection> GetConnectionAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var connection = new MySqlConnection(dbConnectionString);
|
|
||||||
await connection.OpenAsync();
|
|
||||||
|
|
||||||
// await using var cmd = connection.CreateCommand();
|
|
||||||
// cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
|
||||||
// await cmd.ExecuteNonQueryAsync();
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin._logger?.LogCritical($"Unable to connect to database: {ex.Message}");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// public async Task DatabaseMigration()
|
|
||||||
// {
|
|
||||||
// Migration migrator = new(this);
|
|
||||||
// await migrator.ExecuteMigrationsAsync();
|
|
||||||
// }
|
|
||||||
|
|
||||||
public bool CheckDatabaseConnection(out string? exception)
|
|
||||||
{
|
|
||||||
using var connection = GetConnection();
|
|
||||||
exception = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return connection.Ping();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
exception = ex.Message;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using System.Data.Common;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin.Database;
|
|
||||||
|
|
||||||
public interface IDatabaseProvider
|
|
||||||
{
|
|
||||||
Task<DbConnection> CreateConnectionAsync();
|
|
||||||
Task<(bool Success, string? Exception)> CheckConnectionAsync();
|
|
||||||
Task DatabaseMigrationAsync();
|
|
||||||
|
|
||||||
// CacheManager
|
|
||||||
string GetBanSelectQuery(bool multiServer);
|
|
||||||
string GetIpHistoryQuery();
|
|
||||||
string GetBanUpdateQuery(bool multiServer);
|
|
||||||
|
|
||||||
// PlayerManager
|
|
||||||
string GetUpsertPlayerIpQuery();
|
|
||||||
|
|
||||||
// PermissionManager
|
|
||||||
string GetAdminsQuery();
|
|
||||||
string GetDeleteAdminQuery(bool globalDelete);
|
|
||||||
string GetAddAdminQuery();
|
|
||||||
string GetAddAdminFlagsQuery();
|
|
||||||
string GetUpdateAdminGroupQuery();
|
|
||||||
string GetGroupsQuery();
|
|
||||||
string GetGroupIdByNameQuery();
|
|
||||||
string GetAddGroupQuery();
|
|
||||||
string GetAddGroupFlagsQuery();
|
|
||||||
string GetAddGroupServerQuery();
|
|
||||||
string GetDeleteGroupQuery();
|
|
||||||
string GetDeleteOldAdminsQuery();
|
|
||||||
|
|
||||||
// BanManager
|
|
||||||
string GetAddBanQuery();
|
|
||||||
string GetAddBanBySteamIdQuery();
|
|
||||||
string GetAddBanByIpQuery();
|
|
||||||
string GetUnbanRetrieveBansQuery(bool multiServer);
|
|
||||||
string GetUnbanAdminIdQuery();
|
|
||||||
string GetInsertUnbanQuery(bool includeReason);
|
|
||||||
string GetUpdateBanStatusQuery();
|
|
||||||
string GetExpireBansQuery(bool multiServer);
|
|
||||||
string GetExpireIpBansQuery(bool multiServer);
|
|
||||||
|
|
||||||
// MuteManager
|
|
||||||
string GetAddMuteQuery(bool includePlayerName);
|
|
||||||
string GetIsMutedQuery(bool multiServer, int timeMode);
|
|
||||||
string GetMuteStatsQuery(bool multiServer);
|
|
||||||
string GetUpdateMutePassedQuery(bool multiServer);
|
|
||||||
string GetCheckExpiredMutesQuery(bool multiServer);
|
|
||||||
string GetRetrieveMutesQuery(bool multiServer);
|
|
||||||
string GetUnmuteAdminIdQuery();
|
|
||||||
string GetInsertUnmuteQuery(bool includeReason);
|
|
||||||
string GetUpdateMuteStatusQuery();
|
|
||||||
string GetExpireMutesQuery(bool multiServer, int timeMode);
|
|
||||||
|
|
||||||
// WarnManager
|
|
||||||
string GetAddWarnQuery(bool includePlayerName);
|
|
||||||
string GetPlayerWarnsQuery(bool multiServer, bool active);
|
|
||||||
string GetPlayerWarnsCountQuery(bool multiServer, bool active);
|
|
||||||
string GetUnwarnByIdQuery(bool multiServer);
|
|
||||||
string GetUnwarnLastQuery(bool multiServer);
|
|
||||||
string GetExpireWarnsQuery(bool multiServer);
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
using System.Data.Common;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin.Database;
|
|
||||||
|
|
||||||
public class Migration(string migrationsPath)
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Executes all migration scripts found in the configured migrations path that have not been applied yet.
|
|
||||||
/// Creates a migration tracking table if it does not exist.
|
|
||||||
/// Applies migration scripts in filename order and logs successes or failures.
|
|
||||||
/// </summary>
|
|
||||||
public async Task ExecuteMigrationsAsync()
|
|
||||||
{
|
|
||||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
|
||||||
var files = Directory.GetFiles(migrationsPath, "*.sql").OrderBy(f => f).ToList();
|
|
||||||
if (files.Count == 0) return;
|
|
||||||
|
|
||||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
|
||||||
await using (var cmd = connection.CreateCommand())
|
|
||||||
{
|
|
||||||
if (migrationsPath.Contains("sqlite", StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
cmd.CommandText = """
|
|
||||||
CREATE TABLE IF NOT EXISTS sa_migrations (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
version TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cmd.CommandText = """
|
|
||||||
CREATE TABLE IF NOT EXISTS sa_migrations (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
version VARCHAR(128) NOT NULL
|
|
||||||
);
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastAppliedVersion = await GetLastAppliedVersionAsync(connection);
|
|
||||||
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
var version = Path.GetFileNameWithoutExtension(file);
|
|
||||||
if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var sqlScript = await File.ReadAllTextAsync(file);
|
|
||||||
|
|
||||||
await using (var cmdMigration = connection.CreateCommand())
|
|
||||||
{
|
|
||||||
cmdMigration.CommandText = sqlScript;
|
|
||||||
await cmdMigration.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
await UpdateLastAppliedVersionAsync(connection, version);
|
|
||||||
|
|
||||||
CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin._logger?.LogError(ex, $"Error applying migration \"{version}\".");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves the version string of the last applied migration from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="connection">The open database connection.</param>
|
|
||||||
/// <returns>The version string of the last applied migration, or empty string if none.</returns>
|
|
||||||
private static async Task<string> GetLastAppliedVersionAsync(DbConnection connection)
|
|
||||||
{
|
|
||||||
await using var cmd = connection.CreateCommand();
|
|
||||||
cmd.CommandText = "SELECT version FROM sa_migrations ORDER BY id DESC LIMIT 1;";
|
|
||||||
var result = await cmd.ExecuteScalarAsync();
|
|
||||||
return result?.ToString() ?? string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inserts a record tracking the successful application of a migration version.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="connection">The open database connection.</param>
|
|
||||||
/// <param name="version">The version string of the migration applied.</param>
|
|
||||||
private static async Task UpdateLastAppliedVersionAsync(DbConnection connection, string version)
|
|
||||||
{
|
|
||||||
await using var cmd = connection.CreateCommand();
|
|
||||||
cmd.CommandText = "INSERT INTO sa_migrations (version) VALUES (@Version);";
|
|
||||||
|
|
||||||
var param = cmd.CreateParameter();
|
|
||||||
param.ParameterName = "@Version";
|
|
||||||
param.Value = version;
|
|
||||||
cmd.Parameters.Add(param);
|
|
||||||
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_bans` (
|
|
||||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`player_name` VARCHAR(128),
|
|
||||||
`player_steamid` VARCHAR(64),
|
|
||||||
`player_ip` VARCHAR(128),
|
|
||||||
`admin_steamid` VARCHAR(64) NOT NULL,
|
|
||||||
`admin_name` VARCHAR(128) NOT NULL,
|
|
||||||
`reason` VARCHAR(255) NOT NULL,
|
|
||||||
`duration` INT NOT NULL,
|
|
||||||
`ends` TIMESTAMP NULL,
|
|
||||||
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`server_id` INT NULL,
|
|
||||||
`status` ENUM('ACTIVE', 'UNBANNED', 'EXPIRED', '') NOT NULL DEFAULT 'ACTIVE'
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_mutes` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`player_name` varchar(128) NULL,
|
|
||||||
`player_steamid` varchar(64) NOT NULL,
|
|
||||||
`admin_steamid` varchar(64) NOT NULL,
|
|
||||||
`admin_name` varchar(128) NOT NULL,
|
|
||||||
`reason` varchar(255) NOT NULL,
|
|
||||||
`duration` int(11) NOT NULL,
|
|
||||||
`ends` timestamp NULL,
|
|
||||||
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`type` enum('GAG','MUTE','SILENCE','') NOT NULL DEFAULT 'GAG',
|
|
||||||
`server_id` INT NULL,
|
|
||||||
`status` enum('ACTIVE','UNMUTED','EXPIRED','') NOT NULL DEFAULT 'ACTIVE',
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_admins` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`player_name` varchar(128) NOT NULL,
|
|
||||||
`player_steamid` varchar(64) NOT NULL,
|
|
||||||
`flags` TEXT NULL,
|
|
||||||
`immunity` int(11) NOT NULL DEFAULT 0,
|
|
||||||
`server_id` INT NULL,
|
|
||||||
`ends` timestamp NULL,
|
|
||||||
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_servers` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`hostname` varchar(128) NOT NULL,
|
|
||||||
`address` varchar(64) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `address` (`address`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_admins_flags` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`admin_id` int(11) NOT NULL,
|
|
||||||
`flag` varchar(64) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
FOREIGN KEY (`admin_id`) REFERENCES `sa_admins` (`id`) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
ALTER TABLE `sa_admins` CHANGE `flags` `flags` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE `sa_bans` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;
|
|
||||||
ALTER TABLE `sa_mutes` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;
|
|
||||||
ALTER TABLE `sa_admins` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;
|
|
||||||
ALTER TABLE `sa_servers` CHANGE `hostname` `hostname` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
INSERT INTO sa_admins_flags (admin_id, flag)
|
|
||||||
SELECT
|
|
||||||
min_admins.admin_id,
|
|
||||||
TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(sa_admins.flags, ',', numbers.n), ',', -1)) AS flag
|
|
||||||
FROM (
|
|
||||||
SELECT MIN(id) AS admin_id, player_steamid, server_id
|
|
||||||
FROM sa_admins
|
|
||||||
WHERE player_steamid != 'Console'
|
|
||||||
GROUP BY player_steamid, server_id
|
|
||||||
) AS min_admins
|
|
||||||
JOIN sa_admins ON min_admins.player_steamid = sa_admins.player_steamid
|
|
||||||
JOIN (
|
|
||||||
SELECT 1 AS n UNION ALL
|
|
||||||
SELECT 2 UNION ALL
|
|
||||||
SELECT 3 UNION ALL
|
|
||||||
SELECT 4 UNION ALL
|
|
||||||
SELECT 5 UNION ALL
|
|
||||||
SELECT 6 UNION ALL
|
|
||||||
SELECT 7 UNION ALL
|
|
||||||
SELECT 8 UNION ALL
|
|
||||||
SELECT 9 UNION ALL
|
|
||||||
SELECT 10 UNION ALL
|
|
||||||
SELECT 11 UNION ALL
|
|
||||||
SELECT 12 UNION ALL
|
|
||||||
SELECT 13 UNION ALL
|
|
||||||
SELECT 14 UNION ALL
|
|
||||||
SELECT 15 UNION ALL
|
|
||||||
SELECT 16 UNION ALL
|
|
||||||
SELECT 17 UNION ALL
|
|
||||||
SELECT 18 UNION ALL
|
|
||||||
SELECT 19 UNION ALL
|
|
||||||
SELECT 20
|
|
||||||
) AS numbers
|
|
||||||
ON CHAR_LENGTH(sa_admins.flags) - CHAR_LENGTH(REPLACE(sa_admins.flags, ',', '')) >= numbers.n - 1
|
|
||||||
AND (min_admins.server_id = sa_admins.server_id OR (min_admins.server_id IS NULL AND sa_admins.server_id IS NULL))
|
|
||||||
WHERE sa_admins.id IS NOT NULL;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_unbans` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`ban_id` int(11) NOT NULL,
|
|
||||||
`admin_id` int(11) NOT NULL DEFAULT 0,
|
|
||||||
`reason` varchar(255) NOT NULL DEFAULT 'Unknown',
|
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_unmutes` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`mute_id` int(11) NOT NULL,
|
|
||||||
`admin_id` int(11) NOT NULL DEFAULT 0,
|
|
||||||
`reason` varchar(255) NOT NULL DEFAULT 'Unknown',
|
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
INSERT IGNORE INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
|
|
||||||
VALUES (0, 'Console', 'Console', '', '0', NULL, NULL, NOW());
|
|
||||||
|
|
||||||
UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1;
|
|
||||||
|
|
||||||
ALTER TABLE `sa_bans` ADD `unban_id` INT NULL AFTER `server_id`;
|
|
||||||
ALTER TABLE `sa_mutes` ADD `unmute_id` INT NULL AFTER `server_id`;
|
|
||||||
ALTER TABLE `sa_bans` ADD FOREIGN KEY (`unban_id`) REFERENCES `sa_unbans`(`id`) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE `sa_mutes` ADD FOREIGN KEY (`unmute_id`) REFERENCES `sa_unmutes`(`id`) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE `sa_unbans` ADD FOREIGN KEY (`admin_id`) REFERENCES `sa_admins`(`id`) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE `sa_unmutes` ADD FOREIGN KEY (`admin_id`) REFERENCES `sa_admins`(`id`) ON DELETE CASCADE;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_groups` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`name` varchar(255) NOT NULL,
|
|
||||||
`immunity` int(11) NOT NULL DEFAULT 0,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_groups_flags` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`group_id` int(11) NOT NULL,
|
|
||||||
`flag` varchar(64) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`group_id` int(11) NOT NULL,
|
|
||||||
`server_id` int(11) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
ALTER TABLE `sa_admins` ADD `group_id` INT NULL AFTER `created`;
|
|
||||||
ALTER TABLE `sa_groups_flags` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE `sa_groups_servers` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE `sa_admins` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE SET NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `sa_groups_servers` CHANGE `server_id` `server_id` INT(11) NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `sa_mutes` ADD `passed` INT NULL AFTER `duration`;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
|
||||||
`steamid` bigint(20) NOT NULL,
|
|
||||||
`address` varchar(64) NOT NULL,
|
|
||||||
`used_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`steamid`, `address`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_warns` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`player_name` varchar(128) DEFAULT NULL,
|
|
||||||
`player_steamid` varchar(64) NOT NULL,
|
|
||||||
`admin_steamid` varchar(64) NOT NULL,
|
|
||||||
`admin_name` varchar(128) NOT NULL,
|
|
||||||
`reason` varchar(255) NOT NULL,
|
|
||||||
`duration` int(11) NOT NULL,
|
|
||||||
`ends` timestamp NOT NULL,
|
|
||||||
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`server_id` int(11) DEFAULT NULL,
|
|
||||||
`status` enum('ACTIVE','EXPIRED','') NOT NULL DEFAULT 'ACTIVE',
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `sa_servers` ADD `rcon_password` varchar(128) NULL AFTER `hostname`;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `status`;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
DELETE FROM sa_players_ips WHERE INET_ATON(address) IS NULL AND address IS NOT NULL;
|
|
||||||
UPDATE `sa_players_ips` SET `address` = INET_ATON(address);
|
|
||||||
ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL;
|
|
||||||
ALTER TABLE `sa_players_ips` ADD INDEX (used_at DESC);
|
|
||||||
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE sa_mutes ADD INDEX (player_steamid, status, ends);
|
|
||||||
ALTER TABLE sa_mutes ADD INDEX(player_steamid, status, server_id, duration);
|
|
||||||
ALTER TABLE sa_mutes ADD INDEX(player_steamid, type);
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
ALTER TABLE `sa_bans` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
|
|
||||||
UPDATE `sa_bans`
|
|
||||||
SET admin_steamid = '0'
|
|
||||||
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
|
|
||||||
ALTER TABLE `sa_bans` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
|
|
||||||
|
|
||||||
ALTER TABLE `sa_mutes` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
|
|
||||||
UPDATE `sa_mutes`
|
|
||||||
SET admin_steamid = '0'
|
|
||||||
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
|
|
||||||
ALTER TABLE `sa_mutes` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
|
|
||||||
|
|
||||||
ALTER TABLE `sa_warns` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
|
|
||||||
UPDATE `sa_warns`
|
|
||||||
SET admin_steamid = '0'
|
|
||||||
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
|
|
||||||
ALTER TABLE `sa_warns` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
|
|
||||||
|
|
||||||
UPDATE `sa_admins`
|
|
||||||
SET player_steamid = '0'
|
|
||||||
WHERE player_steamid NOT REGEXP '^[0-9]+$';
|
|
||||||
ALTER TABLE `sa_admins` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
|
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
-- -- Migration 016: Optimize tables and indexes
|
|
||||||
-- -- Add proper indexes for all tables to improve query performance
|
|
||||||
|
|
||||||
-- -- Optimize sa_players_ips table indexes
|
|
||||||
-- -- Add index on used_at for efficient date-based queries
|
|
||||||
-- ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
|
||||||
|
|
||||||
-- -- Optimize sa_bans table indexes
|
|
||||||
-- -- Add composite indexes for common query patterns
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
|
||||||
|
|
||||||
-- -- Optimize sa_admins table indexes
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
|
||||||
|
|
||||||
-- -- Optimize sa_mutes table indexes (in addition to migration 014)
|
|
||||||
-- -- Add index for expire queries
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
|
||||||
|
|
||||||
-- -- Optimize sa_warns table indexes (if exists)
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
|
||||||
|
|
||||||
-- -- Add index on sa_servers for faster lookups
|
|
||||||
-- CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_bans` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`player_name` VARCHAR(128),
|
|
||||||
`player_steamid` VARCHAR(64),
|
|
||||||
`player_ip` VARCHAR(128),
|
|
||||||
`admin_steamid` VARCHAR(64) NOT NULL,
|
|
||||||
`admin_name` VARCHAR(128) NOT NULL,
|
|
||||||
`reason` VARCHAR(255) NOT NULL,
|
|
||||||
`duration` INTEGER NOT NULL,
|
|
||||||
`ends` TIMESTAMP NULL,
|
|
||||||
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`server_id` INTEGER NULL,
|
|
||||||
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_mutes` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`player_name` VARCHAR(128) NULL,
|
|
||||||
`player_steamid` VARCHAR(64) NOT NULL,
|
|
||||||
`admin_steamid` VARCHAR(64) NOT NULL,
|
|
||||||
`admin_name` VARCHAR(128) NOT NULL,
|
|
||||||
`reason` VARCHAR(255) NOT NULL,
|
|
||||||
`duration` INTEGER NOT NULL,
|
|
||||||
`ends` TIMESTAMP NULL,
|
|
||||||
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`type` TEXT NOT NULL DEFAULT 'GAG',
|
|
||||||
`server_id` INTEGER NULL,
|
|
||||||
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_admins` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`player_name` VARCHAR(128) NOT NULL,
|
|
||||||
`player_steamid` VARCHAR(64) NOT NULL,
|
|
||||||
`flags` TEXT NULL,
|
|
||||||
`immunity` INTEGER NOT NULL DEFAULT 0,
|
|
||||||
`server_id` INTEGER NULL,
|
|
||||||
`ends` TIMESTAMP NULL,
|
|
||||||
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_servers` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`hostname` VARCHAR(128) NOT NULL,
|
|
||||||
`address` VARCHAR(64) NOT NULL,
|
|
||||||
UNIQUE (`address`)
|
|
||||||
);
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_admins_flags` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`admin_id` INTEGER NOT NULL,
|
|
||||||
`flag` VARCHAR(64) NOT NULL,
|
|
||||||
FOREIGN KEY (`admin_id`) REFERENCES `sa_admins` (`id`) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
INSERT INTO sa_admins_flags (admin_id, flag)
|
|
||||||
WITH RECURSIVE
|
|
||||||
min_admins AS (
|
|
||||||
SELECT MIN(id) AS admin_id, player_steamid, server_id
|
|
||||||
FROM sa_admins
|
|
||||||
WHERE player_steamid != 'Console'
|
|
||||||
GROUP BY player_steamid, server_id
|
|
||||||
),
|
|
||||||
split_flags AS (
|
|
||||||
SELECT
|
|
||||||
ma.admin_id,
|
|
||||||
sa.flags,
|
|
||||||
1 AS pos,
|
|
||||||
CASE
|
|
||||||
WHEN INSTR(sa.flags || ',', ',') = 0 THEN sa.flags
|
|
||||||
ELSE SUBSTR(sa.flags, 1, INSTR(sa.flags || ',', ',') - 1)
|
|
||||||
END AS flag,
|
|
||||||
CASE
|
|
||||||
WHEN INSTR(sa.flags || ',', ',') = 0 THEN ''
|
|
||||||
ELSE SUBSTR(sa.flags, INSTR(sa.flags || ',', ',') + 1)
|
|
||||||
END AS remaining
|
|
||||||
FROM min_admins ma
|
|
||||||
JOIN sa_admins sa ON ma.player_steamid = sa.player_steamid
|
|
||||||
AND (ma.server_id = sa.server_id OR (ma.server_id IS NULL AND sa.server_id IS NULL))
|
|
||||||
WHERE sa.flags IS NOT NULL AND sa.flags != ''
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
admin_id,
|
|
||||||
flags,
|
|
||||||
pos + 1,
|
|
||||||
CASE
|
|
||||||
WHEN INSTR(remaining || ',', ',') = 0 THEN remaining
|
|
||||||
ELSE SUBSTR(remaining, 1, INSTR(remaining || ',', ',') - 1)
|
|
||||||
END AS flag,
|
|
||||||
CASE
|
|
||||||
WHEN INSTR(remaining || ',', ',') = 0 THEN ''
|
|
||||||
ELSE SUBSTR(remaining, INSTR(remaining || ',', ',') + 1)
|
|
||||||
END AS remaining
|
|
||||||
FROM split_flags
|
|
||||||
WHERE remaining != ''
|
|
||||||
)
|
|
||||||
SELECT admin_id, TRIM(flag)
|
|
||||||
FROM split_flags
|
|
||||||
WHERE flag IS NOT NULL AND TRIM(flag) != '';
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_unbans` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`ban_id` INTEGER NOT NULL,
|
|
||||||
`admin_id` INTEGER NOT NULL DEFAULT 0,
|
|
||||||
`reason` VARCHAR(255) NOT NULL DEFAULT 'Unknown',
|
|
||||||
`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_unmutes` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`mute_id` INTEGER NOT NULL,
|
|
||||||
`admin_id` INTEGER NOT NULL DEFAULT 0,
|
|
||||||
`reason` VARCHAR(255) NOT NULL DEFAULT 'Unknown',
|
|
||||||
`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
|
|
||||||
VALUES (0, 'Console', 'Console', '', '0', NULL, NULL, CURRENT_TIMESTAMP);
|
|
||||||
|
|
||||||
UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1;
|
|
||||||
|
|
||||||
ALTER TABLE `sa_bans` ADD `unban_id` INTEGER NULL;
|
|
||||||
ALTER TABLE `sa_mutes` ADD `unmute_id` INTEGER NULL;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_groups` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`name` VARCHAR(255) NOT NULL,
|
|
||||||
`immunity` INTEGER NOT NULL DEFAULT 0
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_groups_flags` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`group_id` INTEGER NOT NULL,
|
|
||||||
`flag` VARCHAR(64) NOT NULL,
|
|
||||||
FOREIGN KEY (`group_id`) REFERENCES `sa_groups` (`id`) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
`group_id` INTEGER NOT NULL,
|
|
||||||
`server_id` INTEGER NULL,
|
|
||||||
FOREIGN KEY (`group_id`) REFERENCES `sa_groups` (`id`) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE `sa_admins` ADD `group_id` INTEGER NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `sa_mutes` ADD `passed` INTEGER NULL;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
|
||||||
`steamid` INTEGER NOT NULL,
|
|
||||||
`address` INTEGER NOT NULL,
|
|
||||||
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`steamid`, `address`)
|
|
||||||
);
|
|
||||||