mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-18 02:41:55 +00:00
Introduces a new documentation site for CS2-SimpleAdmin using Docusaurus, including developer API references, tutorials, user guides, and module documentation. Removes the CleanModule example and updates FunCommands and ExampleModule. Also updates main plugin and API files to support new documentation and module structure.
17 KiB
17 KiB
sidebar_position
| 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 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
dotnet new classlib -n YourModuleName -f net8.0
cd YourModuleName
2. Add References
Edit your .csproj file:
<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
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:
// 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
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
- Use command lists - Allow users to add aliases or disable features
- Provide defaults - Sensible default values
- Version your config - Track config changes
- Document settings - Clear property names
Registering Commands
Basic Command Registration
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:
public override void Unload(bool hotReload)
{
if (_api == null) return;
foreach (var cmd in Config.MyCommands)
{
_api.UnRegisterCommand(cmd);
}
}
Creating Menus
Register Menu Category
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)
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
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
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:
{
"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
// 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- Englishlang/pl.json- Polishlang/ru.json- Russianlang/de.json- German- etc.
Working with API
Issue Penalties
// 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
// 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
// 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
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
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
if (_api == null)
{
Logger.LogError("API not available!");
return;
}
2. Validate Player State
if (!player.IsValid || !player.PawnIsAlive)
{
return;
}
3. Check Target Permissions
if (!caller.CanTarget(target))
{
// caller can't target this player (immunity)
return;
}
4. Log Commands
_api.LogCommand(caller, command);
// or
_api.LogCommand(caller, $"css_mycommand {player.PlayerName}");
5. Use Per-Player Translations
// Each player sees message in their language!
_api.ShowAdminActivityLocalized(
Localizer,
"translation_key",
callerName,
false,
args
);
6. Clean Up Resources
public override void Unload(bool hotReload)
{
// Unregister commands
// Unregister menus
// Unsubscribe events
// Dispose resources
}
Common Patterns
Player Targeting Helper
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!)
// ✅ 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
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
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:
Logger.LogInformation("Debug: ...");
Logger.LogWarning("Warning: ...");
Logger.LogError("Error: ...");
Example: Complete Mini-Module
Here's a complete working example:
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 - Complete reference
- Read API Documentation - Full API reference
- Check Examples - More code examples
Resources
- CS2-SimpleAdmin GitHub - Source code
- CounterStrikeSharp Docs - CSS documentation
- Module Development Guide - Detailed guide
Need Help?
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Examples: Study official modules for reference