Refactor fun commands to external module

Commented out fun command implementations (noclip, godmode, freeze, unfreeze, resize) in funcommands.cs and removed their registration from RegisterCommands.cs. These commands are now intended to be provided by the new CS2-SimpleAdmin_FunCommands external module, improving modularity and maintainability.
This commit is contained in:
Dawid Bepierszcz
2025-10-19 03:12:58 +02:00
parent 2edacc2b3f
commit 78318102fe
69 changed files with 5943 additions and 1493 deletions

View File

@@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-SimpleAdmin_FunCommands", "CS2-SimpleAdmin_FunCommands\CS2-SimpleAdmin_FunCommands.csproj", "{72713A40-688F-401F-8211-3D28B068C791}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{72713A40-688F-401F-8211-3D28B068C791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72713A40-688F-401F-8211-3D28B068C791}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72713A40-688F-401F-8211-3D28B068C791}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72713A40-688F-401F-8211-3D28B068C791}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,237 @@
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin_FunCommands;
public partial class CS2_SimpleAdmin_FunCommands
{
private void God(CCSPlayerController? caller, CCSPlayerController player)
{
if (!caller.CanTarget(player)) return;
var callerName = caller?.PlayerName ?? "Console";
// Toggle god mode for the player (like in main plugin)
if (!GodPlayers.Add(player.Slot))
{
GodPlayers.Remove(player.Slot);
}
// Show admin activity using module's own localizer with per-player language support
var activityArgs = new object[] { "CALLER", player.PlayerName };
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
if (Localizer != null)
{
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_god_message", callerName, false,
activityArgs);
}
else
{
_sharedApi!.ShowAdminActivity("fun_admin_god_message", callerName, false, activityArgs);
}
}
// Log command using API
_sharedApi!.LogCommand(caller,
$"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
private void NoClip(CCSPlayerController? caller, CCSPlayerController player)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
var callerName = caller?.PlayerName ?? "Console";
// Toggle no-clip mode using PlayerExtensions
player.Pawn.Value?.ToggleNoclip();
// Show admin activity using module's own localizer with per-player language support
var activityArgs = new object[] { "CALLER", player.PlayerName };
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
if (Localizer != null)
{
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_noclip_message", callerName, false,
activityArgs);
}
else
{
_sharedApi!.ShowAdminActivity("fun_admin_noclip_message", callerName, false, activityArgs);
}
}
// Log command using API
_sharedApi!.LogCommand(caller,
$"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
private void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
var callerName = caller?.PlayerName ?? "Console";
// Freeze player using PlayerExtensions
player.Pawn.Value?.Freeze();
// Show admin activity using module's own localizer with per-player language support
var activityArgs = new object[] { "CALLER", player.PlayerName };
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
if (Localizer != null)
{
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_freeze_message", callerName, false,
activityArgs);
}
else
{
_sharedApi!.ShowAdminActivity("fun_admin_freeze_message", callerName, false, activityArgs);
}
}
// Auto-unfreeze after duration
if (time > 0)
{
AddTimer(time, () => player.Pawn.Value?.Unfreeze(),
CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
// Log command using API
_sharedApi!.LogCommand(caller,
$"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
}
private void Unfreeze(CCSPlayerController? caller, CCSPlayerController player)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
var callerName = caller?.PlayerName ?? "Console";
// Unfreeze player using PlayerExtensions
player.Pawn.Value?.Unfreeze();
// Show admin activity using module's own localizer with per-player language support
var activityArgs = new object[] { "CALLER", player.PlayerName };
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
if (Localizer != null)
{
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_unfreeze_message", callerName, false,
activityArgs);
}
else
{
_sharedApi!.ShowAdminActivity("fun_admin_unfreeze_message", callerName, false, activityArgs);
}
}
// Log command using API
_sharedApi!.LogCommand(caller,
$"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
/// <summary>
/// Respawns a player and teleports them to their last death position if available.
/// This demonstrates using the GetPlayerInfo API to access player data.
/// </summary>
private void Respawn(CCSPlayerController? caller, CCSPlayerController player)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
var callerName = caller?.PlayerName ?? "Console";
// Respawn the player
player.Respawn();
// Get death position from API and teleport player to it
// BEST PRACTICE: Use API to access player data like death position
if (_sharedApi != null && player.UserId.HasValue)
{
try
{
var playerInfo = _sharedApi.GetPlayerInfo(player);
// Teleport to death position if available
if (playerInfo?.DiePosition != null && player.PlayerPawn?.Value != null)
{
player.PlayerPawn.Value.Teleport(
playerInfo.DiePosition.Position,
playerInfo.DiePosition.Angle);
}
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to get player info for respawn: {ex.Message}");
}
}
// Show admin activity using module's own localizer with per-player language support
var activityArgs = new object[] { "CALLER", player.PlayerName };
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
if (Localizer != null)
{
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_respawn_message", callerName, false,
activityArgs);
}
else
{
_sharedApi!.ShowAdminActivity("fun_admin_respawn_message", callerName, false, activityArgs);
}
}
// Log command using API
_sharedApi!.LogCommand(caller,
$"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
/// <summary>
/// Resizes a player's model to the specified scale.
/// </summary>
private void Resize(CCSPlayerController? caller, CCSPlayerController player, float size)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
var callerName = caller?.PlayerName ?? "Console";
// Resize the player
var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
if (sceneNode != null)
{
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");
});
}
// Show admin activity using module's own localizer with per-player language support
var activityArgs = new object[] { "CALLER", player.PlayerName, size.ToString(CultureInfo.InvariantCulture) };
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
if (Localizer != null)
{
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_resize_message", callerName, false,
activityArgs);
}
else
{
_sharedApi!.ShowAdminActivity("fun_admin_resize_message", callerName, false, activityArgs);
}
}
// Log command using API
_sharedApi!.LogCommand(caller,
$"css_resize {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {size}");
}
}

View File

@@ -0,0 +1,492 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
using System.Globalization;
namespace CS2_SimpleAdmin_FunCommands;
/// <summary>
/// CS2-SimpleAdmin Fun Commands Module
///
/// This module serves as a REFERENCE IMPLEMENTATION for creating CS2-SimpleAdmin modules.
/// Study this code to learn best practices for:
/// - Command registration from configuration
/// - Menu creation with SimpleAdmin API
/// - Per-player translation support
/// - Proper cleanup on module unload
/// - Code organization using partial classes
///
/// File Structure:
/// - CS2-SimpleAdmin_FunCommands.cs (this file) - Plugin initialization and registration
/// - Commands.cs - Command handlers
/// - Actions.cs - Action methods (God, NoClip, Freeze, etc.)
/// - Menus.cs - Menu creation
/// - Config.cs - Configuration with command lists
/// - lang/ - Translation files (13 languages)
///
/// See README.md for detailed explanation of all patterns demonstrated here.
/// </summary>
public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Config>
{
public Config Config { get; set; }
/// <summary>
/// BEST PRACTICE: Cache expensive operations
/// Weapons enum values don't change, so we cache them on first access
/// </summary>
private static Dictionary<int, CsItem>? _weaponsCache;
private static Dictionary<int, CsItem> GetWeaponsCache()
{
if (_weaponsCache != null) return _weaponsCache;
var weaponsArray = Enum.GetValues(typeof(CsItem));
_weaponsCache = new Dictionary<int, CsItem>();
foreach (CsItem item in weaponsArray)
{
if (item == CsItem.Tablet) continue; // Skip tablet (invalid weapon)
_weaponsCache[(int)item] = item;
}
return _weaponsCache;
}
/// <summary>
/// Track players with god mode enabled
/// HashSet for O(1) lookup performance
/// </summary>
private static readonly HashSet<int> GodPlayers = [];
/// <summary>
/// Track players with modified speed
/// Dictionary for storing speed values per player
/// </summary>
private static readonly Dictionary<CCSPlayerController, float> SpeedPlayers = [];
/// <summary>
/// Track players with modified gravity
/// Dictionary for storing gravity values per player
/// </summary>
private static readonly Dictionary<CCSPlayerController, float> GravityPlayers = [];
/// <summary>
/// BEST PRACTICE: Use capability system to get SimpleAdmin API
/// This ensures your module works even if SimpleAdmin loads after your module
/// </summary>
private ICS2_SimpleAdminApi? _sharedApi;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
/// <summary>
/// BEST PRACTICE: Track menu registration state to prevent duplicate registrations
/// </summary>
private bool _menusRegistered = false;
// Plugin metadata
public override string ModuleName => "CS2-SimpleAdmin Fun Commands";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "Your Name";
public override string ModuleDescription => "Fun commands extension for CS2-SimpleAdmin";
/// <summary>
/// BEST PRACTICE: Initialize plugin after all plugins are loaded
/// This ensures SimpleAdmin API is available
/// </summary>
public override void OnAllPluginsLoaded(bool hotReload)
{
// STEP 1: Get SimpleAdmin API using capability system
_sharedApi = _pluginCapability.Get();
if (_sharedApi == null)
{
Logger.LogError("CS2-SimpleAdmin API not found - make sure CS2-SimpleAdmin is loaded!");
Unload(false);
return;
}
// STEP 2: Register commands (can be done immediately)
RegisterFunCommands();
// STEP 3: Register menus (wait for SimpleAdmin to be ready)
// BEST PRACTICE: Use event + fallback to handle both normal load and hot reload
_sharedApi.OnSimpleAdminReady += RegisterFunMenus;
RegisterFunMenus(); // Fallback for hot reload case
// STEP 4: Start timer to maintain speed and gravity modifications
StartSpeedGravityTimer();
}
public override void Unload(bool hotReload)
{
if (_sharedApi == null) return;
// Unregister commands
if (Config.NoclipCommands.Count > 0)
{
foreach (var command in Config.NoclipCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.GodCommands.Count > 0)
{
foreach (var command in Config.GodCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.FreezeCommands.Count > 0)
{
foreach (var command in Config.FreezeCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.UnfreezeCommands.Count > 0)
{
foreach (var command in Config.UnfreezeCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.RespawnCommands.Count > 0)
{
foreach (var command in Config.RespawnCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.GiveCommands.Count > 0)
{
foreach (var command in Config.GiveCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.StripCommands.Count > 0)
{
foreach (var command in Config.StripCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.HpCommands.Count > 0)
{
foreach (var command in Config.HpCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.SpeedCommands.Count > 0)
{
foreach (var command in Config.SpeedCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.GravityCommands.Count > 0)
{
foreach (var command in Config.GravityCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.MoneyCommands.Count > 0)
{
foreach (var command in Config.MoneyCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
if (Config.ResizeCommands.Count > 0)
{
foreach (var command in Config.ResizeCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
// Unregister menus
if (Config.NoclipCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "noclip");
if (Config.GodCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "god");
if (Config.RespawnCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "respawn");
if (Config.GiveCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "give");
if (Config.StripCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "strip");
if (Config.FreezeCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "freeze");
if (Config.HpCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "hp");
if (Config.SpeedCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "speed");
if (Config.GravityCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "gravity");
if (Config.MoneyCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "money");
if (Config.ResizeCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "resize");
_sharedApi.OnSimpleAdminReady -= RegisterFunMenus;
}
private void RegisterFunCommands()
{
if (_sharedApi == null) return;
if (Config.NoclipCommands.Count > 0)
{
foreach (var command in Config.NoclipCommands)
{
_sharedApi.RegisterCommand(command, "Enable noclip", OnNoclipCommand);
}
}
if (Config.GodCommands.Count > 0)
{
foreach (var command in Config.GodCommands)
{
_sharedApi.RegisterCommand(command, "Enable god mode", OnGodCommand);
}
}
if (Config.FreezeCommands.Count > 0)
{
foreach (var command in Config.FreezeCommands)
{
_sharedApi.RegisterCommand(command, "Freeze player", OnFreezeCommand);
}
}
if (Config.UnfreezeCommands.Count > 0)
{
foreach (var command in Config.UnfreezeCommands)
{
_sharedApi.RegisterCommand(command, "Unfreeze player", OnUnfreezeCommand);
}
}
if (Config.RespawnCommands.Count > 0)
{
foreach (var command in Config.RespawnCommands)
{
_sharedApi.RegisterCommand(command, "Respawn player", OnRespawnCommand);
}
}
if (Config.GiveCommands.Count > 0)
{
foreach (var command in Config.GiveCommands)
{
_sharedApi.RegisterCommand(command, "Give weapon", OnGiveWeaponCommand);
}
}
if (Config.StripCommands.Count > 0)
{
foreach (var command in Config.StripCommands)
{
_sharedApi.RegisterCommand(command, "Strip weapons", OnStripWeaponsCommand);
}
}
if (Config.HpCommands.Count > 0)
{
foreach (var command in Config.HpCommands)
{
_sharedApi.RegisterCommand(command, "Set HP", OnSetHpCommand);
}
}
if (Config.SpeedCommands.Count > 0)
{
foreach (var command in Config.SpeedCommands)
{
_sharedApi.RegisterCommand(command, "Set speed", OnSetSpeedCommand);
}
}
if (Config.GravityCommands.Count > 0)
{
foreach (var command in Config.GravityCommands)
{
_sharedApi.RegisterCommand(command, "Set gravity", OnSetGravityCommand);
}
}
if (Config.MoneyCommands.Count > 0)
{
foreach (var command in Config.MoneyCommands)
{
_sharedApi.RegisterCommand(command, "Set money", OnSetMoneyCommand);
}
}
if (Config.ResizeCommands.Count > 0)
{
foreach (var command in Config.ResizeCommands)
{
_sharedApi.RegisterCommand(command, "Resize player", OnSetResizeCommand);
}
}
}
private void RegisterFunMenus()
{
if (_sharedApi == null || _menusRegistered) return;
try
{
_sharedApi.RegisterMenuCategory("fun", Localizer?["fun_category_name"] ?? "Fun Commands", "@css/generic");
if (Config.GodCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "god",
Localizer?["fun_menu_god"] ?? "God Mode",
CreateGodModeMenu, "@css/cheats");
if (Config.NoclipCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "noclip",
Localizer?["fun_menu_noclip"] ?? "No Clip",
CreateNoClipMenu, "@css/cheats");
if (Config.RespawnCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "respawn",
Localizer?["fun_menu_respawn"] ?? "Respawn",
CreateRespawnMenu, "@css/cheats");
if (Config.GiveCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "give",
Localizer?["fun_menu_give"] ?? "Give Weapon",
CreateGiveWeaponMenu, "@css/cheats");
if (Config.StripCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "strip",
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
CreateStripWeaponsMenu, "@css/slay");
if (Config.FreezeCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "freeze",
Localizer?["fun_menu_freeze"] ?? "Freeze",
CreateFreezeMenu, "@css/slay");
if (Config.HpCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "hp",
Localizer?["fun_menu_hp"] ?? "Set HP",
CreateSetHpMenu, "@css/slay");
if (Config.SpeedCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "speed",
Localizer?["fun_menu_speed"] ?? "Set Speed",
CreateSetSpeedMenu, "@css/slay");
if (Config.GravityCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "gravity",
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
CreateSetGravityMenu, "@css/slay");
if (Config.MoneyCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "money",
Localizer?["fun_menu_money"] ?? "Set Money",
CreateSetMoneyMenu, "@css/slay");
if (Config.ResizeCommands.Count > 0)
_sharedApi.RegisterMenu("fun", "resize",
Localizer?["fun_menu_resize"] ?? "Resize Player",
CreateSetResizeMenu, "@css/slay");
_menusRegistered = true;
Logger.LogInformation("Fun menus registered successfully!");
}
catch (Exception ex)
{
Logger.LogError($"Failed to register Fun menus: {ex.Message}");
}
}
public void OnConfigParsed(Config config)
{
Config = config;
}
/// <summary>
/// Starts a repeating timer to maintain speed and gravity modifications for players.
/// This ensures that speed/gravity changes persist even after respawns or round changes.
/// </summary>
private void StartSpeedGravityTimer()
{
AddTimer(0.12f, () =>
{
// Early exit if no players have modified speed or gravity
var hasSpeedPlayers = SpeedPlayers.Count > 0;
var hasGravityPlayers = GravityPlayers.Count > 0;
if (!hasSpeedPlayers && !hasGravityPlayers)
return;
if (hasSpeedPlayers)
{
// Iterate through players with modified speed
foreach (var kvp in SpeedPlayers)
{
var player = kvp.Key;
// Early validation check - avoid accessing PlayerPawn if player is invalid
if (player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected)
{
var pawn = player.PlayerPawn?.Value;
if (pawn != null && pawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
player.SetSpeed(kvp.Value);
}
}
}
}
if (hasGravityPlayers)
{
// Iterate through players with modified gravity
foreach (var kvp in GravityPlayers)
{
var player = kvp.Key;
if (player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected)
{
var pawn = player.PlayerPawn?.Value;
if (pawn != null && pawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
player.SetGravity(kvp.Value);
}
}
}
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT);
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CS2_SimpleAdmin_FunCommands</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="CS2-SimpleAdminApi">
<HintPath>..\CS2-SimpleAdminApi.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
</ItemGroup>
<ItemGroup>
<None Update="lang\**\*.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,320 @@
using System.Globalization;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities.Constants;
namespace CS2_SimpleAdmin_FunCommands;
public partial class CS2_SimpleAdmin_FunCommands
{
// =================================
// COMMAND HANDLERS
// =================================
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
private void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _sharedApi!.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))
{
NoClip(caller, player);
}
});
}
[RequiresPermissions("@css/cheats")]
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _sharedApi!.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))
{
God(caller, player);
}
});
}
[CommandHelper(1, "<#userid or name> [duration]")]
[RequiresPermissions("@css/slay")]
private void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
{
int.TryParse(command.GetArg(2), out var time);
var targets = _sharedApi!.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);
}
});
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/slay")]
private void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _sharedApi!.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))
{
Unfreeze(caller, player);
}
});
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
private void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _sharedApi!.GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player =>
player is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
Respawn(caller, player);
}
});
}
[CommandHelper(2, "<#userid or name> <weapon>")]
[RequiresPermissions("@css/cheats")]
private void OnGiveWeaponCommand(CCSPlayerController? caller, CommandInfo command)
{
var weaponName = command.GetArg(2);
if (Enum.TryParse(weaponName, true, out CsItem weapon))
{
var targets = _sharedApi!.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))
{
player.GiveNamedItem(weapon);
LogAndShowActivity(caller, player, "fun_admin_give_message", "css_give", weapon.ToString());
}
});
}
}
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/slay")]
private void OnStripWeaponsCommand(CCSPlayerController? caller, CommandInfo command)
{
var targets = _sharedApi!.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))
{
player.RemoveWeapons();
LogAndShowActivity(caller, player, "fun_admin_strip_message", "css_strip");
}
});
}
[CommandHelper(2, "<#userid or name> <hp>")]
[RequiresPermissions("@css/slay")]
private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
{
if (int.TryParse(command.GetArg(2), out var hp))
{
var targets = _sharedApi!.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))
{
player.SetHp(hp);
LogAndShowActivity(caller, player, "fun_admin_hp_message", "css_hp", hp.ToString());
}
});
}
}
[CommandHelper(2, "<#userid or name> <speed>")]
[RequiresPermissions("@css/slay")]
private void OnSetSpeedCommand(CCSPlayerController? caller, CommandInfo command)
{
if (float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var speed))
{
var targets = _sharedApi!.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))
{
player.SetSpeed(speed);
// Track speed modification for timer
if (speed == 1f)
SpeedPlayers.Remove(player);
else
SpeedPlayers[player] = speed;
LogAndShowActivity(caller, player, "fun_admin_speed_message", "css_speed", speed.ToString(CultureInfo.InvariantCulture));
}
});
}
}
[CommandHelper(2, "<#userid or name> <gravity>")]
[RequiresPermissions("@css/slay")]
private void OnSetGravityCommand(CCSPlayerController? caller, CommandInfo command)
{
if (float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var gravity))
{
var targets = _sharedApi!.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))
{
player.SetGravity(gravity);
// Track gravity modification for timer
if (gravity == 1f)
GravityPlayers.Remove(player);
else
GravityPlayers[player] = gravity;
LogAndShowActivity(caller, player, "fun_admin_gravity_message", "css_gravity", gravity.ToString(CultureInfo.InvariantCulture));
}
});
}
}
[CommandHelper(2, "<#userid or name> <money>")]
[RequiresPermissions("@css/slay")]
private void OnSetMoneyCommand(CCSPlayerController? caller, CommandInfo command)
{
if (int.TryParse(command.GetArg(2), out var money))
{
var targets = _sharedApi!.GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player =>
player is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
player.SetMoney(money);
LogAndShowActivity(caller, player, "fun_admin_money_message", "css_money", money.ToString());
}
});
}
}
[CommandHelper(2, "<#userid or name> <size>")]
[RequiresPermissions("@css/slay")]
private void OnSetResizeCommand(CCSPlayerController? caller, CommandInfo command)
{
if (float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size))
{
var targets = _sharedApi!.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))
{
Resize(caller, player, size);
}
});
}
}
// =================================
// HELPER METHOD FOR ACTIVITIES WITH INDIVIDUAL COMMAND LOGGING
// =================================
private void LogAndShowActivity(CCSPlayerController? caller, CCSPlayerController target, string messageKey, string baseCommand, params string[] extraArgs)
{
var callerName = caller?.PlayerName ?? "Console";
// Build activity args
var args = new List<object> { "CALLER", target.PlayerName };
args.AddRange(extraArgs);
// Show admin activity using module's own localizer with per-player language support
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
// Use module's own translations with automatic per-player language support
if (Localizer != null)
{
// This will send the message in each player's configured language
_sharedApi!.ShowAdminActivityLocalized(Localizer, messageKey, callerName, false, args.ToArray());
}
else
{
// Fallback to old method if localizer is not available
_sharedApi!.ShowAdminActivity(messageKey, callerName, false, args.ToArray());
}
}
// Build and log command using API string method
var logCommand = $"{baseCommand} {(string.IsNullOrEmpty(target.PlayerName) ? target.SteamID.ToString() : target.PlayerName)}";
if (extraArgs.Length > 0)
{
logCommand += $" {string.Join(" ", extraArgs)}";
}
_sharedApi!.LogCommand(caller, logCommand);
}
}

View File

@@ -0,0 +1,21 @@
using CounterStrikeSharp.API.Core;
namespace CS2_SimpleAdmin_FunCommands;
public class Config : IBasePluginConfig
{
public int Version { get; set; } = 1;
public List<string> NoclipCommands { get; set; } = ["css_noclip"];
public List<string> GodCommands { get; set; } = ["css_god"];
public List<string> FreezeCommands { get; set; } = ["css_freeze"];
public List<string> UnfreezeCommands { get; set; } = ["css_unfreeze"];
public List<string> RespawnCommands { get; set; } = ["css_respawn"];
public List<string> GiveCommands { get; set; } = ["css_give"];
public List<string> StripCommands { get; set; } = ["css_strip"];
public List<string> HpCommands { get; set; } = ["css_hp"];
public List<string> SpeedCommands { get; set; } = ["css_speed"];
public List<string> GravityCommands { get; set; } = ["css_gravity"];
public List<string> MoneyCommands { get; set; } = ["css_money"];
public List<string> ResizeCommands { get; set; } = ["css_resize"];
}

View File

@@ -0,0 +1,69 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
namespace CS2_SimpleAdmin_FunCommands;
public partial class CS2_SimpleAdmin_FunCommands
{
[GameEventHandler]
public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return HookResult.Continue;
// Check if player has god mode (similar to main plugin)
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
// Cancel damage
@event.DmgHealth = 0;
@event.DmgArmor = 0;
// Reset health to full
if (player.PlayerPawn?.Value == null) return HookResult.Continue;
player.PlayerPawn.Value.Health = player.PlayerPawn.Value.MaxHealth;
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_iHealth");
return HookResult.Continue;
}
[GameEventHandler]
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return HookResult.Continue;
// Remove player from god mode, speed, and gravity tracking on death
GodPlayers.Remove(player.Slot);
SpeedPlayers.Remove(player);
GravityPlayers.Remove(player);
return HookResult.Continue;
}
[GameEventHandler]
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
{
// Clear all fun command modifications at round start
GodPlayers.Clear();
SpeedPlayers.Clear();
GravityPlayers.Clear();
return HookResult.Continue;
}
[GameEventHandler]
public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
{
var player = @event.Userid;
if (player == null || !player.IsValid) return HookResult.Continue;
// Clean up player from all tracking when they disconnect
GodPlayers.Remove(player.Slot);
SpeedPlayers.Remove(player);
GravityPlayers.Remove(player);
return HookResult.Continue;
}
}

View File

@@ -0,0 +1,391 @@
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
namespace CS2_SimpleAdmin_FunCommands;
/// <summary>
/// Menu creation methods for Fun Commands module.
/// This file demonstrates different menu patterns using SimpleAdmin API.
/// </summary>
public partial class CS2_SimpleAdmin_FunCommands
{
// =================================
// SIMPLE PLAYER SELECTION MENUS
// =================================
// Pattern: Direct player selection with immediate action
// Use CreateMenuWithPlayers when you just need to select a player and execute an action
/// <summary>
/// Creates a simple player selection menu for god mode.
/// PATTERN: CreateMenuWithPlayers with method reference
/// </summary>
private object CreateGodModeMenu(CCSPlayerController admin)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_god"] ?? "God Mode", // Menu title from translation
"fun", // Category ID (for back button navigation)
admin, // Admin opening the menu
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player), // Filter: only alive, targetable players
God); // Action to execute (method reference)
}
private object CreateNoClipMenu(CCSPlayerController admin)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_noclip"] ?? "No Clip",
"fun",
admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
NoClip);
}
/// <summary>
/// Creates a player selection menu for respawn command.
/// PATTERN: CreateMenuWithPlayers with method reference
/// </summary>
private object CreateRespawnMenu(CCSPlayerController admin)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_respawn"] ?? "Respawn", // Menu title from translation
"fun", // Category ID
admin, // Admin
admin.CanTarget, // Filter: only targetable players (no LifeState check - can respawn dead players)
Respawn); // Use the Respawn method which includes death position teleport
}
// =================================
// NESTED MENUS - PLAYER → VALUE SELECTION
// =================================
// Pattern: First select player, then select a value/option for that player
// Use CreateMenuWithBack + AddSubMenu for multi-level menus
/// <summary>
/// Creates a nested menu: Player selection → Weapon selection.
/// PATTERN: CreateMenuWithBack + foreach + AddSubMenu
/// </summary>
private object CreateGiveWeaponMenu(CCSPlayerController admin)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_give"] ?? "Give Weapon",
"fun",
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
// AddSubMenu automatically adds a "Back" button to the submenu
// The lambda receives 'p' but we use captured 'player' variable (closure)
_sharedApi.AddSubMenu(menu, playerName, p => CreateWeaponSelectionMenu(admin, player));
}
return menu;
}
/// <summary>
/// Creates weapon selection submenu for a specific player.
/// PATTERN: CreateMenuWithBack + foreach + AddMenuOption
/// </summary>
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var weaponMenu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}",
"fun",
admin);
// Loop through cached weapons (performance optimization)
foreach (var weapon in GetWeaponsCache())
{
// AddMenuOption for each selectable option
// IMPORTANT: Always validate target.IsValid before executing action
_sharedApi.AddMenuOption(weaponMenu, weapon.Value.ToString(), _ =>
{
if (target.IsValid) // Player might disconnect before selection
{
target.GiveNamedItem(weapon.Value);
LogAndShowActivity(admin, target, "fun_admin_give_message", $"css_give", weapon.Value.ToString());
}
});
}
return weaponMenu;
}
private object CreateStripWeaponsMenu(CCSPlayerController admin)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
"fun",
admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
(adminPlayer, targetPlayer) =>
{
targetPlayer.RemoveWeapons();
LogAndShowActivity(adminPlayer, targetPlayer, "fun_admin_strip_message", "css_strip");
});
}
private object CreateFreezeMenu(CCSPlayerController admin)
{
return _sharedApi!.CreateMenuWithPlayers(
Localizer?["fun_menu_freeze"] ?? "Freeze",
"fun",
admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
(adminPlayer, targetPlayer) => { Freeze(adminPlayer, targetPlayer, -1); });
}
/// <summary>
/// Creates a nested menu for setting player HP with predefined values.
/// PATTERN: Same as Give Weapon (player selection → value selection)
/// This is a common pattern you'll use frequently!
/// </summary>
private object CreateSetHpMenu(CCSPlayerController admin)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_hp"] ?? "Set HP",
"fun",
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
_sharedApi.AddSubMenu(menu, playerName, p => CreateHpSelectionMenu(admin, player));
}
return menu;
}
/// <summary>
/// Creates HP value selection submenu.
/// TIP: Use arrays for predefined values - easy to modify and maintain
/// </summary>
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var hpSelectionMenu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}",
"fun",
admin);
// Predefined HP values - easy to customize
var hpValues = new[] { 1, 10, 25, 50, 100, 200, 500, 999 };
foreach (var hp in hpValues)
{
_sharedApi.AddMenuOption(hpSelectionMenu,
Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP",
_ =>
{
if (target.IsValid)
{
target.SetHp(hp);
LogAndShowActivity(admin, target, "fun_admin_hp_message", "css_hp", hp.ToString());
}
});
}
return hpSelectionMenu;
}
private object CreateSetSpeedMenu(CCSPlayerController admin)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_speed"] ?? "Set Speed",
"fun",
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
_sharedApi.AddSubMenu(menu, playerName, p => CreateSpeedSelectionMenu(admin, player));
}
return menu;
}
/// <summary>
/// Creates speed value selection submenu.
/// TIP: Use tuples (value, display) when you need different internal value vs display text
/// Example: (0.5f, "0.5") - float value for code, string for display
/// </summary>
private object CreateSpeedSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var speedSelectionMenu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}",
"fun",
admin);
// Tuple pattern: (actualValue, displayText)
// Useful when display text differs from actual value
var speedValues = new[]
{
(0.1f, "0.1"), (0.25f, "0.25"), (0.5f, "0.5"), (0.75f, "0.75"), (1f, "1"), (2f, "2"), (3f, "3"), (4f, "4")
};
foreach (var (speed, display) in speedValues)
{
_sharedApi.AddMenuOption(speedSelectionMenu,
Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}",
_ =>
{
if (target.IsValid)
{
target.SetSpeed(speed);
// Track speed modification for timer
if (speed == 1f)
SpeedPlayers.Remove(target);
else
SpeedPlayers[target] = speed;
LogAndShowActivity(admin, target, "fun_admin_speed_message", "css_speed", speed.ToString(CultureInfo.InvariantCulture));
}
});
}
return speedSelectionMenu;
}
private object CreateSetGravityMenu(CCSPlayerController admin)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
"fun",
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
_sharedApi.AddSubMenu(menu, playerName, p => CreateGravitySelectionMenu(admin, player));
}
return menu;
}
private object CreateGravitySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var gravitySelectionMenu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}",
"fun",
admin);
var gravityValues = new[]
{ (0.1f, "0.1"), (0.25f, "0.25"), (0.5f, "0.5"), (0.75f, "0.75"), (1f, "1"), (2f, "2") };
foreach (var (gravity, display) in gravityValues)
{
_sharedApi.AddMenuOption(gravitySelectionMenu,
Localizer?["fun_menu_gravity_value", display] ?? $"Gravity {display}",
_ =>
{
if (target.IsValid)
{
target.SetGravity(Convert.ToSingle(gravity, CultureInfo.InvariantCulture));
// Track gravity modification for timer
if (gravity == 1f)
GravityPlayers.Remove(target);
else
GravityPlayers[target] = gravity;
LogAndShowActivity(admin, target, "fun_admin_gravity_message", "css_gravity", gravity.ToString(CultureInfo.InvariantCulture));
}
});
}
return gravitySelectionMenu;
}
private object CreateSetMoneyMenu(CCSPlayerController admin)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_money"] ?? "Set Money",
"fun",
admin);
var players = _sharedApi.GetValidPlayers().Where(p => admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
_sharedApi.AddSubMenu(menu, playerName, p => CreateMoneySelectionMenu(admin, player));
}
return menu;
}
private object CreateMoneySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var moneySelectionMenu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}",
"fun",
admin);
var moneyValues = new[] { 0, 1000, 2500, 5000, 10000, 16000 };
foreach (var money in moneyValues)
{
_sharedApi.AddMenuOption(moneySelectionMenu,
Localizer?["fun_menu_money_value", money] ?? $"${money}",
_ =>
{
if (target.IsValid)
{
target.SetMoney(money);
LogAndShowActivity(admin, target, "fun_admin_money_message", "css_money", money.ToString());
}
});
}
return moneySelectionMenu;
}
private object CreateSetResizeMenu(CCSPlayerController admin)
{
var menu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_resize"] ?? "Resize Player",
"fun",
admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
_sharedApi.AddSubMenu(menu, playerName, p => CreateResizeSelectionMenu(admin, player));
}
return menu;
}
private object CreateResizeSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var resizeSelectionMenu = _sharedApi!.CreateMenuWithBack(
Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}",
"fun",
admin);
var resizeValues = new[]
{ (0.5f, "0.5"), (0.75f, "0.75"), (1f, "1"), (1.25f, "1.25"), (1.5f, "1.5"), (2f, "2"), (3f, "3") };
foreach (var (resize, display) in resizeValues)
{
_sharedApi.AddMenuOption(resizeSelectionMenu,
Localizer?["fun_menu_resize_value", display] ?? $"Size {display}",
_ =>
{
if (target.IsValid)
{
Resize(admin, target, resize);
}
});
}
return resizeSelectionMenu;
}
}

View File

@@ -0,0 +1,271 @@
using System.Drawing;
using System.Globalization;
using System.Numerics;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.UserMessages;
namespace CS2_SimpleAdmin_FunCommands;
public static class PlayerExtensions
{
/// <summary>
/// Slaps the player pawn by applying optional damage and adding a random velocity knockback.
/// </summary>
/// <param name="pawn">The player pawn to slap.</param>
/// <param name="damage">The amount of damage to apply (default is 0).</param>
public static void Slap(this CBasePlayerPawn pawn, int damage = 0)
{
PerformSlap(pawn, damage);
}
/// <summary>
/// Determines if the player controller can target another player controller, respecting admin permissions and immunity.
/// </summary>
/// <param name="controller">The player controller who wants to target.</param>
/// <param name="target">The player controller being targeted.</param>
/// <returns>True if targeting is allowed, false otherwise.</returns>
public static bool CanTarget(this CCSPlayerController? controller, CCSPlayerController? target)
{
if (controller is null || target is null) return true;
if (target.IsBot) return true;
return AdminManager.CanPlayerTarget(controller, target) ||
AdminManager.CanPlayerTarget(new SteamID(controller.SteamID),
new SteamID(target.SteamID)) ||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(target);
}
/// <summary>
/// Checks if the controller can target a player by SteamID, considering targeting permissions and immunities.
/// </summary>
/// <param name="controller">The attacker player controller.</param>
/// <param name="steamId">The SteamID of the target player.</param>
/// <returns>True if targeting is permitted, false otherwise.</returns>
public static bool CanTarget(this CCSPlayerController? controller, SteamID steamId)
{
if (controller is null) return true;
return AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), steamId) ||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(steamId);
}
/// <summary>
/// Sets the movement speed modifier of the player controller.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="speed">The speed modifier value.</param>
public static void SetSpeed(this CCSPlayerController? controller, float speed)
{
var playerPawnValue = controller?.PlayerPawn.Value;
if (playerPawnValue == null) return;
playerPawnValue.VelocityModifier = speed;
}
/// <summary>
/// Sets the gravity scale for the player controller.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="gravity">The gravity scale.</param>
public static void SetGravity(this CCSPlayerController? controller, float gravity)
{
var playerPawnValue = controller?.PlayerPawn.Value;
if (playerPawnValue == null) return;
playerPawnValue.ActualGravityScale = gravity;
}
/// <summary>
/// Sets the player's in-game money amount.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="money">The amount of money to set.</param>
public static void SetMoney(this CCSPlayerController? controller, int money)
{
var moneyServices = controller?.InGameMoneyServices;
if (moneyServices == null) return;
moneyServices.Account = money;
if (controller != null) Utilities.SetStateChanged(controller, "CCSPlayerController", "m_pInGameMoneyServices");
}
/// <summary>
/// Sets the player's health points.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="health">The health value, default is 100.</param>
public static void SetHp(this CCSPlayerController? controller, int health = 100)
{
if (controller == null) return;
if (health <= 0 || controller.PlayerPawn.Value == null || controller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return;
controller.PlayerPawn.Value.Health = health;
if (health > 100)
{
controller.PlayerPawn.Value.MaxHealth = health;
}
Utilities.SetStateChanged(controller.PlayerPawn.Value, "CBaseEntity", "m_iHealth");
}
/// <summary>
/// Buries the player pawn by moving it down by a depth offset.
/// </summary>
/// <param name="pawn">The player pawn to bury.</param>
/// <param name="depth">The depth offset (default 10 units).</param>
public static void Bury(this CBasePlayerPawn pawn, float depth = 10f)
{
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
pawn.AbsOrigin!.Z - depth);
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
pawn.Teleport(newPos, newRotation, newVelocity);
}
/// <summary>
/// Unburies the player pawn by moving it up by a depth offset.
/// </summary>
/// <param name="pawn">The player pawn to unbury.</param>
/// <param name="depth">The depth offset (default 15 units).</param>
public static void Unbury(this CBasePlayerPawn pawn, float depth = 15f)
{
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
pawn.AbsOrigin!.Z + depth);
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
pawn.Teleport(newPos, newRotation, newVelocity);
}
/// <summary>
/// Freezes the player pawn, disabling movement.
/// </summary>
/// <param name="pawn">The player pawn to freeze.</param>
public static void Freeze(this CBasePlayerPawn pawn)
{
pawn.MoveType = MoveType_t.MOVETYPE_INVALID;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 11); // invalid
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
/// <summary>
/// Unfreezes the player pawn, enabling movement.
/// </summary>
/// <param name="pawn">The player pawn to unfreeze.</param>
public static void Unfreeze(this CBasePlayerPawn pawn)
{
pawn.MoveType = MoveType_t.MOVETYPE_WALK;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
/// <summary>
/// Changes the player's color tint to specified RGBA values.
/// </summary>
/// <param name="pawn">The pawn to colorize.</param>
/// <param name="r">Red component (0-255).</param>
/// <param name="g">Green component (0-255).</param>
/// <param name="b">Blue component (0-255).</param>
/// <param name="a">Alpha (transparency) component (0-255).</param>
public static void Colorize(this CBasePlayerPawn pawn, int r = 255, int g = 255, int b = 255, int a = 255)
{
pawn.Render = Color.FromArgb(a, r, g, b);
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
}
/// <summary>
/// Toggles noclip mode for the player pawn.
/// </summary>
/// <param name="pawn">The player pawn.</param>
public static void ToggleNoclip(this CBasePlayerPawn pawn)
{
if (pawn.MoveType == MoveType_t.MOVETYPE_NOCLIP)
{
pawn.MoveType = MoveType_t.MOVETYPE_WALK;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
else
{
pawn.MoveType = MoveType_t.MOVETYPE_NOCLIP;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 8); // noclip
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
}
/// <summary>
/// Teleports a player controller to the position, rotation, and velocity of another player controller.
/// </summary>
/// <param name="controller">The controller to teleport.</param>
/// <param name="target">The target controller whose position to copy.</param>
public static void TeleportPlayer(this CCSPlayerController? controller, CCSPlayerController? target)
{
if (controller?.PlayerPawn.Value == null && target?.PlayerPawn.Value == null)
return;
if (
controller?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null } &&
target?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null }
)
{
controller.PlayerPawn.Value.Teleport(
target.PlayerPawn.Value.AbsOrigin,
target.PlayerPawn.Value.AbsRotation,
target.PlayerPawn.Value.AbsVelocity
);
}
}
/// <summary>
/// Applies a slap effect to the given player pawn, optionally inflicting damage and adding velocity knockback.
/// </summary>
/// <param name="pawn">The player pawn to slap.</param>
/// <param name="damage">The amount of damage to deal (default is 0).</param>
private static void PerformSlap(CBasePlayerPawn pawn, int damage = 0)
{
if (pawn.LifeState != (int)LifeState_t.LIFE_ALIVE)
return;
var controller = pawn.Controller.Value?.As<CCSPlayerController>();
/* Teleport in a random direction - thank you, Mani!*/
/* Thank you AM & al!*/
var random = new Random();
var vel = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
vel.X += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
vel.Y += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
vel.Z += random.Next(200) + 100;
pawn.AbsVelocity.X = vel.X;
pawn.AbsVelocity.Y = vel.Y;
pawn.AbsVelocity.Z = vel.Z;
if (controller != null && controller.IsValid)
{
var shakeMessage = UserMessage.FromPartialName("Shake");
shakeMessage.SetFloat("duration", 1);
shakeMessage.SetFloat("amplitude", 10);
shakeMessage.SetFloat("frequency", 1f);
shakeMessage.SetInt("command", 0);
shakeMessage.Recipients.Add(controller);
shakeMessage.Send();
}
if (damage <= 0)
return;
pawn.Health -= damage;
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
if (pawn.Health <= 0)
pawn.CommitSuicide(true, true);
}
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "أوامر المرح",
"fun_admin_give_message": "{lightred}{0}{default} أعطى {lightred}{1}{default} {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} أخذ جميع أسلحة اللاعب {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} غيّر عدد نقاط الحياة لـ {lightred}{1}{default}!",
"fun_admin_speed_message": "{lightred}{0}{default} غيّر السرعة لـ {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} غيّر الجاذبية لـ {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} غيّر المال لـ {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} غيّر وضع الله لـ {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} فعّل/ألغى نمط اللا تصادم لـ {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} جمد {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} أذاب {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} أحيى {lightred}{1}{default}!",
"fun_menu_god": "وضع الله",
"fun_menu_noclip": "اللا تصادم",
"fun_menu_respawn": "إحياء",
"fun_menu_give": "إعطاء سلاح",
"fun_menu_give_player": "إعطاء سلاح: {0}",
"fun_menu_strip": "نزع الأسلحة",
"fun_menu_freeze": "تجميد",
"fun_menu_hp": "ضبط الحياة",
"fun_menu_hp_player": "ضبط الحياة: {0}",
"fun_menu_speed": "ضبط السرعة",
"fun_menu_speed_player": "ضبط السرعة: {0}",
"fun_menu_gravity": "ضبط الجاذبية",
"fun_menu_gravity_player": "ضبط الجاذبية: {0}",
"fun_menu_money": "ضبط المال",
"fun_menu_money_player": "ضبط المال: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "السرعة {0}",
"fun_menu_gravity_value": "الجاذبية {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} غيّر حجم {lightred}{1}{default} إلى {lightred}{2}{default}!",
"fun_menu_resize": "تغيير الحجم",
"fun_menu_resize_player": "تغيير الحجم: {0}",
"fun_menu_resize_value": "الحجم {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Spaß-Befehle",
"fun_admin_give_message": "{lightred}{0}{default} hat {lightred}{1}{default} ein {lightred}{2}{default} gegeben!",
"fun_admin_strip_message": "{lightred}{0}{default} hat alle Waffen von Spieler {lightred}{1}{default} entfernt!",
"fun_admin_hp_message": "{lightred}{0}{default} hat die Lebenspunkte von {lightred}{1}{default} geändert!",
"fun_admin_speed_message": "{lightred}{0}{default} hat die Geschwindigkeit von {lightred}{1}{default} geändert!",
"fun_admin_gravity_message": "{lightred}{0}{default} hat die Schwerkraft von {lightred}{1}{default} geändert!",
"fun_admin_money_message": "{lightred}{0}{default} hat das Geld von {lightred}{1}{default} geändert!",
"fun_admin_god_message": "{lightred}{0}{default} hat den Gottmodus von {lightred}{1}{default} geändert!",
"fun_admin_noclip_message": "{lightred}{0}{default} hat den Noclip-Modus für {lightred}{1}{default} aktiviert/deaktiviert!",
"fun_admin_freeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} eingefroren!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} aufgetaut!",
"fun_admin_respawn_message": "{lightred}{0}{default} hat {lightred}{1}{default} wiederbelebt!",
"fun_menu_god": "Gottmodus",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Wiederbeleben",
"fun_menu_give": "Waffe Geben",
"fun_menu_give_player": "Waffe Geben: {0}",
"fun_menu_strip": "Waffen Entfernen",
"fun_menu_freeze": "Einfrieren",
"fun_menu_hp": "HP Festlegen",
"fun_menu_hp_player": "HP Festlegen: {0}",
"fun_menu_speed": "Geschwindigkeit Festlegen",
"fun_menu_speed_player": "Geschwindigkeit Festlegen: {0}",
"fun_menu_gravity": "Schwerkraft Festlegen",
"fun_menu_gravity_player": "Schwerkraft Festlegen: {0}",
"fun_menu_money": "Geld Festlegen",
"fun_menu_money_player": "Geld Festlegen: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Geschwindigkeit {0}",
"fun_menu_gravity_value": "Schwerkraft {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} hat die Größe von {lightred}{1}{default} auf {lightred}{2}{default} geändert!",
"fun_menu_resize": "Größe Ändern",
"fun_menu_resize_player": "Größe Ändern: {0}",
"fun_menu_resize_value": "Größe {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Fun Commands",
"fun_admin_give_message": "{lightred}{0}{default} gave {lightred}{1}{default} a {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} took all of player {lightred}{1}{default} weapons!",
"fun_admin_hp_message": "{lightred}{0}{default} changed {lightred}{1}{default} hp amount!",
"fun_admin_speed_message": "{lightred}{0}{default} changed speed for {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} changed gravity for {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} changed money for {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} toggled noclip for {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} froze {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} unfroze {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} respawned {lightred}{1}{default}!",
"fun_menu_god": "God Mode",
"fun_menu_noclip": "No Clip",
"fun_menu_respawn": "Respawn",
"fun_menu_give": "Give Weapon",
"fun_menu_give_player": "Give Weapon: {0}",
"fun_menu_strip": "Strip Weapons",
"fun_menu_freeze": "Freeze",
"fun_menu_hp": "Set HP",
"fun_menu_hp_player": "Set HP: {0}",
"fun_menu_speed": "Set Speed",
"fun_menu_speed_player": "Set Speed: {0}",
"fun_menu_gravity": "Set Gravity",
"fun_menu_gravity_player": "Set Gravity: {0}",
"fun_menu_money": "Set Money",
"fun_menu_money_player": "Set Money: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Speed {0}",
"fun_menu_gravity_value": "Gravity {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} resized {lightred}{1}{default} to {lightred}{2}{default}!",
"fun_menu_resize": "Resize Player",
"fun_menu_resize_player": "Resize: {0}",
"fun_menu_resize_value": "Size {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Comandos Divertidos",
"fun_admin_give_message": "{lightred}{0}{default} dio {lightred}{1}{default} un {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} quitó todas las armas del jugador {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} cambió la cantidad de HP de {lightred}{1}{default}!",
"fun_admin_speed_message": "{lightred}{0}{default} cambió la velocidad de {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} cambió la gravedad de {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} cambió el dinero de {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} cambió el modo dios de {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} alternó noclip para {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} congeló a {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} descongeló a {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} reapareció a {lightred}{1}{default}!",
"fun_menu_god": "Modo Dios",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Reaparecer",
"fun_menu_give": "Dar Arma",
"fun_menu_give_player": "Dar Arma: {0}",
"fun_menu_strip": "Quitar Armas",
"fun_menu_freeze": "Congelar",
"fun_menu_hp": "Establecer HP",
"fun_menu_hp_player": "Establecer HP: {0}",
"fun_menu_speed": "Establecer Velocidad",
"fun_menu_speed_player": "Establecer Velocidad: {0}",
"fun_menu_gravity": "Establecer Gravedad",
"fun_menu_gravity_player": "Establecer Gravedad: {0}",
"fun_menu_money": "Establecer Dinero",
"fun_menu_money_player": "Establecer Dinero: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Velocidad {0}",
"fun_menu_gravity_value": "Gravedad {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} cambió el tamaño de {lightred}{1}{default} a {lightred}{2}{default}!",
"fun_menu_resize": "Cambiar Tamaño",
"fun_menu_resize_player": "Cambiar Tamaño: {0}",
"fun_menu_resize_value": "Tamaño {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "دستورات سرگرمی",
"fun_admin_give_message": "{lightred}{0}{default} {lightred}{2}{default} را به {lightred}{1}{default} داد!",
"fun_admin_strip_message": "{lightred}{0}{default} تمام سلاح‌های بازیکن {lightred}{1}{default} را گرفت!",
"fun_admin_hp_message": "{lightred}{0}{default} مقدار سلامت {lightred}{1}{default} را تغییر داد!",
"fun_admin_speed_message": "{lightred}{0}{default} سرعت {lightred}{1}{default} را تغییر داد!",
"fun_admin_gravity_message": "{lightred}{0}{default} جاذبه {lightred}{1}{default} را تغییر داد!",
"fun_admin_money_message": "{lightred}{0}{default} پول {lightred}{1}{default} را تغییر داد!",
"fun_admin_god_message": "{lightred}{0}{default} حالت خدا را برای {lightred}{1}{default} تغییر داد!",
"fun_admin_noclip_message": "{lightred}{0}{default} ناپدیدی را برای {lightred}{1}{default} فعال/غیرفعال کرد!",
"fun_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default} را یخ‌زده کرد!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default} را از حالت یخ خارج کرد!",
"fun_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default} را دوباره زنده کرد!",
"fun_menu_god": "حالت خدا",
"fun_menu_noclip": "ناپدیدی",
"fun_menu_respawn": "احیا",
"fun_menu_give": "دادن سلاح",
"fun_menu_give_player": "دادن سلاح: {0}",
"fun_menu_strip": "گرفتن سلاح‌ها",
"fun_menu_freeze": "یخ زدن",
"fun_menu_hp": "تنظیم سلامت",
"fun_menu_hp_player": "تنظیم سلامت: {0}",
"fun_menu_speed": "تنظیم سرعت",
"fun_menu_speed_player": "تنظیم سرعت: {0}",
"fun_menu_gravity": "تنظیم جاذبه",
"fun_menu_gravity_player": "تنظیم جاذبه: {0}",
"fun_menu_money": "تنظیم پول",
"fun_menu_money_player": "تنظیم پول: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "سرعت {0}",
"fun_menu_gravity_value": "جاذبه {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} اندازه {lightred}{1}{default} را به {lightred}{2}{default} تغییر داد!",
"fun_menu_resize": "تغییر اندازه",
"fun_menu_resize_player": "تغییر اندازه: {0}",
"fun_menu_resize_value": "اندازه {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Commandes Amusantes",
"fun_admin_give_message": "{lightred}{0}{default} a donné {lightred}{2}{default} à {lightred}{1}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} a retiré toutes les armes de {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} a modifié la quantité de HP de {lightred}{1}{default}!",
"fun_admin_speed_message": "{lightred}{0}{default} a modifié la vitesse de {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} a modifié la gravité de {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} a modifié l'argent de {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} a modifié le mode dieu de {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} a activé/désactivé le noclip pour {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} a gelé {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} a dégivré {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} a réapparu {lightred}{1}{default}!",
"fun_menu_god": "Mode Dieu",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Réapparition",
"fun_menu_give": "Donner Arme",
"fun_menu_give_player": "Donner Arme: {0}",
"fun_menu_strip": "Retirer Armes",
"fun_menu_freeze": "Geler",
"fun_menu_hp": "Définir HP",
"fun_menu_hp_player": "Définir HP: {0}",
"fun_menu_speed": "Définir Vitesse",
"fun_menu_speed_player": "Définir Vitesse: {0}",
"fun_menu_gravity": "Définir Gravité",
"fun_menu_gravity_player": "Définir Gravité: {0}",
"fun_menu_money": "Définir Argent",
"fun_menu_money_player": "Définir Argent: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Vitesse {0}",
"fun_menu_gravity_value": "Gravité {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} a redimensionné {lightred}{1}{default} à {lightred}{2}{default}!",
"fun_menu_resize": "Redimensionner",
"fun_menu_resize_player": "Redimensionner: {0}",
"fun_menu_resize_value": "Taille {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Izklaidējošas Komandas",
"fun_admin_give_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} noņēma visus {lightred}{1}{default} ieročus!",
"fun_admin_hp_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} HP daudzumu!",
"fun_admin_speed_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} ātrumu!",
"fun_admin_gravity_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} gravitāciju!",
"fun_admin_money_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} naudu!",
"fun_admin_god_message": "{lightred}{0}{default} mainīja dieva režīmu priekš {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} aktivizēja/deaktivizēja noclip priekš {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} sasaldēja {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} atkausēja {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} atdzīvināja {lightred}{1}{default}!",
"fun_menu_god": "Dieva Režīms",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Atdzīvināt",
"fun_menu_give": "Dot Ieroci",
"fun_menu_give_player": "Dot Ieroci: {0}",
"fun_menu_strip": "Noņemt Ieročus",
"fun_menu_freeze": "Sasaldēt",
"fun_menu_hp": "Uzstādīt HP",
"fun_menu_hp_player": "Uzstādīt HP: {0}",
"fun_menu_speed": "Uzstādīt Ātrumu",
"fun_menu_speed_player": "Uzstādīt Ātrumu: {0}",
"fun_menu_gravity": "Uzstādīt Gravitāciju",
"fun_menu_gravity_player": "Uzstādīt Gravitāciju: {0}",
"fun_menu_money": "Uzstādīt Naudu",
"fun_menu_money_player": "Uzstādīt Naudu: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Ātrums {0}",
"fun_menu_gravity_value": "Gravitācija {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} izmēru uz {lightred}{2}{default}!",
"fun_menu_resize": "Mainīt Izmēru",
"fun_menu_resize_player": "Mainīt Izmēru: {0}",
"fun_menu_resize_value": "Izmērs {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Komendy Rozrywkowe",
"fun_admin_give_message": "{lightred}{0}{default} dał {lightred}{1}{default} przedmiot {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} zabrał wszystkie bronie {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} zmienił ilość hp dla {lightred}{1}{default}!",
"fun_admin_speed_message": "{lightred}{0}{default} zmienił prędkość dla {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} zmienił grawitację dla {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} zmienił pieniądze dla {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} zmienił tryb Boga dla {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} ustawił latanie dla {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} zamroził {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} odmroził {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} odrodził {lightred}{1}{default}!",
"fun_menu_god": "Tryb Boga",
"fun_menu_noclip": "Latanie",
"fun_menu_respawn": "Odrodzenie",
"fun_menu_give": "Daj Broń",
"fun_menu_give_player": "Daj Broń: {0}",
"fun_menu_strip": "Zabierz Bronie",
"fun_menu_freeze": "Zamrożenie",
"fun_menu_hp": "Ustaw HP",
"fun_menu_hp_player": "Ustaw HP: {0}",
"fun_menu_speed": "Ustaw Prędkość",
"fun_menu_speed_player": "Ustaw Prędkość: {0}",
"fun_menu_gravity": "Ustaw Grawitację",
"fun_menu_gravity_player": "Ustaw Grawitację: {0}",
"fun_menu_money": "Ustaw Pieniądze",
"fun_menu_money_player": "Ustaw Pieniądze: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Prędkość {0}",
"fun_menu_gravity_value": "Grawitacja {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} zmienił rozmiar {lightred}{1}{default} na {lightred}{2}{default}!",
"fun_menu_resize": "Zmień Rozmiar",
"fun_menu_resize_player": "Zmień Rozmiar: {0}",
"fun_menu_resize_value": "Rozmiar {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Comandos Divertidos",
"fun_admin_give_message": "{lightred}{0}{default} deu {lightred}{1}{default} um(a) {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} tirou todas as armas de {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} mudou a quantidade de HP de {lightred}{1}{default}!",
"fun_admin_speed_message": "{lightred}{0}{default} mudou a velocidade de {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} ativou/desativou o noclip para {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} congelou {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} descongelou {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} reanimou {lightred}{1}{default}!",
"fun_menu_god": "Modo Deus",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Reanimar",
"fun_menu_give": "Dar Arma",
"fun_menu_give_player": "Dar Arma: {0}",
"fun_menu_strip": "Remover Armas",
"fun_menu_freeze": "Congelar",
"fun_menu_hp": "Definir HP",
"fun_menu_hp_player": "Definir HP: {0}",
"fun_menu_speed": "Definir Velocidade",
"fun_menu_speed_player": "Definir Velocidade: {0}",
"fun_menu_gravity": "Definir Gravidade",
"fun_menu_gravity_player": "Definir Gravidade: {0}",
"fun_menu_money": "Definir Dinheiro",
"fun_menu_money_player": "Definir Dinheiro: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Velocidade {0}",
"fun_menu_gravity_value": "Gravidade {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} redimensionou {lightred}{1}{default} para {lightred}{2}{default}!",
"fun_menu_resize": "Redimensionar",
"fun_menu_resize_player": "Redimensionar: {0}",
"fun_menu_resize_value": "Tamanho {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Comandos Divertidos",
"fun_admin_give_message": "{lightred}{0}{default} deu {lightred}{1}{default} um(a) {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} tirou todas as armas de {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} mudou a quantidade de HP de {lightred}{1}{default}!",
"fun_admin_speed_message": "{lightred}{0}{default} mudou a velocidade de {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} ativou/desativou o noclip para {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} congelou {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} descongelou {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} reanimou {lightred}{1}{default}!",
"fun_menu_god": "Modo Deus",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Reanimar",
"fun_menu_give": "Dar Arma",
"fun_menu_give_player": "Dar Arma: {0}",
"fun_menu_strip": "Remover Armas",
"fun_menu_freeze": "Congelar",
"fun_menu_hp": "Definir HP",
"fun_menu_hp_player": "Definir HP: {0}",
"fun_menu_speed": "Definir Velocidade",
"fun_menu_speed_player": "Definir Velocidade: {0}",
"fun_menu_gravity": "Definir Gravidade",
"fun_menu_gravity_player": "Definir Gravidade: {0}",
"fun_menu_money": "Definir Dinheiro",
"fun_menu_money_player": "Definir Dinheiro: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Velocidade {0}",
"fun_menu_gravity_value": "Gravidade {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} redimensionou {lightred}{1}{default} para {lightred}{2}{default}!",
"fun_menu_resize": "Redimensionar",
"fun_menu_resize_player": "Redimensionar: {0}",
"fun_menu_resize_value": "Tamanho {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Развлекательные Команды",
"fun_admin_give_message": "{lightred}{0}{default} дал {lightred}{1}{default} {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} забрал все оружие у {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} изменил количество HP у {lightred}{1}{default}!",
"fun_admin_speed_message": "{lightred}{0}{default} изменил скорость {lightred}{1}{default}!",
"fun_admin_gravity_message": "{lightred}{0}{default} изменил гравитацию для {lightred}{1}{default}!",
"fun_admin_money_message": "{lightred}{0}{default} изменил количество денег у {lightred}{1}{default}!",
"fun_admin_god_message": "{lightred}{0}{default} изменил режим бога для {lightred}{1}{default}!",
"fun_admin_noclip_message": "{lightred}{0}{default} включил/выключил noclip для {lightred}{1}{default}!",
"fun_admin_freeze_message": "{lightred}{0}{default} заморозил {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} разморозил {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} возродил {lightred}{1}{default}!",
"fun_menu_god": "Режим Бога",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Возрождение",
"fun_menu_give": "Выдать Оружие",
"fun_menu_give_player": "Выдать Оружие: {0}",
"fun_menu_strip": "Забрать Оружие",
"fun_menu_freeze": "Заморозить",
"fun_menu_hp": "Установить HP",
"fun_menu_hp_player": "Установить HP: {0}",
"fun_menu_speed": "Установить Скорость",
"fun_menu_speed_player": "Установить Скорость: {0}",
"fun_menu_gravity": "Установить Гравитацию",
"fun_menu_gravity_player": "Установить Гравитацию: {0}",
"fun_menu_money": "Установить Деньги",
"fun_menu_money_player": "Установить Деньги: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Скорость {0}",
"fun_menu_gravity_value": "Гравитация {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} изменил размер {lightred}{1}{default} на {lightred}{2}{default}!",
"fun_menu_resize": "Изменить Размер",
"fun_menu_resize_player": "Изменить Размер: {0}",
"fun_menu_resize_value": "Размер {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "Eğlence Komutları",
"fun_admin_give_message": "{lightred}{0}{default} {lightred}{1}{default}'e {lightred}{2}{default} verdi!",
"fun_admin_strip_message": "{lightred}{0}{default} {lightred}{1}{default}'in tüm silahlarını aldı!",
"fun_admin_hp_message": "{lightred}{0}{default} {lightred}{1}{default}'in HP miktarını değiştirdi!",
"fun_admin_speed_message": "{lightred}{0}{default} {lightred}{1}{default}'in hızını değiştirdi!",
"fun_admin_gravity_message": "{lightred}{0}{default} {lightred}{1}{default}'in yer çekimini değiştirdi!",
"fun_admin_money_message": "{lightred}{0}{default} {lightred}{1}{default}'in parasını değiştirdi!",
"fun_admin_god_message": "{lightred}{0}{default} {lightred}{1}{default}'in tanrı modunu değiştirdi!",
"fun_admin_noclip_message": "{lightred}{0}{default} {lightred}{1}{default} için noclip'i açtı/kapatı!",
"fun_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default}'i dondurdu!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default}'in dondurmasını çözdü!",
"fun_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default}'i yeniden doğurdu!",
"fun_menu_god": "Tanrı Modu",
"fun_menu_noclip": "Noclip",
"fun_menu_respawn": "Yeniden Doğma",
"fun_menu_give": "Silah Ver",
"fun_menu_give_player": "Silah Ver: {0}",
"fun_menu_strip": "Silahları Al",
"fun_menu_freeze": "Dondur",
"fun_menu_hp": "HP Ayarla",
"fun_menu_hp_player": "HP Ayarla: {0}",
"fun_menu_speed": "Hız Ayarla",
"fun_menu_speed_player": "Hız Ayarla: {0}",
"fun_menu_gravity": "Yer Çekimi Ayarla",
"fun_menu_gravity_player": "Yer Çekimi Ayarla: {0}",
"fun_menu_money": "Para Ayarla",
"fun_menu_money_player": "Para Ayarla: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "Hız {0}",
"fun_menu_gravity_value": "Yer Çekimi {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} {lightred}{1}{default}'in boyutunu {lightred}{2}{default} olarak değiştirdi!",
"fun_menu_resize": "Boyut Değiştir",
"fun_menu_resize_player": "Boyut Değiştir: {0}",
"fun_menu_resize_value": "Boyut {0}"
}

View File

@@ -0,0 +1,40 @@
{
"fun_category_name": "趣味命令",
"fun_admin_give_message": "{lightred}{0}{default} 给了 {lightred}{1}{default} {lightred}{2}{default}!",
"fun_admin_strip_message": "{lightred}{0}{default} 移除了 {lightred}{1}{default} 的所有武器!",
"fun_admin_hp_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的生命值!",
"fun_admin_speed_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的速度!",
"fun_admin_gravity_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的重力!",
"fun_admin_money_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的金钱!",
"fun_admin_god_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的上帝模式!",
"fun_admin_noclip_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的穿墙模式!",
"fun_admin_freeze_message": "{lightred}{0}{default} 冻结了 {lightred}{1}{default}!",
"fun_admin_unfreeze_message": "{lightred}{0}{default} 解冻了 {lightred}{1}{default}!",
"fun_admin_respawn_message": "{lightred}{0}{default} 复活了 {lightred}{1}{default}!",
"fun_menu_god": "上帝模式",
"fun_menu_noclip": "穿墙模式",
"fun_menu_respawn": "复活",
"fun_menu_give": "给予武器",
"fun_menu_give_player": "给予武器: {0}",
"fun_menu_strip": "移除武器",
"fun_menu_freeze": "冻结",
"fun_menu_hp": "设置生命值",
"fun_menu_hp_player": "设置生命值: {0}",
"fun_menu_speed": "设置速度",
"fun_menu_speed_player": "设置速度: {0}",
"fun_menu_gravity": "设置重力",
"fun_menu_gravity_player": "设置重力: {0}",
"fun_menu_money": "设置金钱",
"fun_menu_money_player": "设置金钱: {0}",
"fun_menu_hp_value": "{0} HP",
"fun_menu_speed_value": "速度 {0}",
"fun_menu_gravity_value": "重力 {0}",
"fun_menu_money_value": "${0}",
"fun_admin_resize_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 的大小改为 {lightred}{2}{default}!",
"fun_menu_resize": "调整大小",
"fun_menu_resize_player": "调整大小: {0}",
"fun_menu_resize_value": "大小 {0}"
}

View File

@@ -0,0 +1,384 @@
# CS2-SimpleAdmin Fun Commands Module
This module serves as a **reference implementation** for creating CS2-SimpleAdmin modules. It demonstrates best practices for menu creation, command registration, translation support, and API usage.
## 📚 What This Module Teaches
This module is designed to be educational and shows you how to:
1.**Register commands dynamically** from configuration
2.**Create menu categories** and menu items
3.**Use per-player translations** with `ShowAdminActivityLocalized`
4.**Handle player targeting** and validation
5.**Implement proper cleanup** on module unload
6.**Structure code** using partial classes for organization
7.**Cache data** for performance (weapons cache)
8.**Use configuration** to enable/disable features
## 🎯 Features
This module provides fun admin commands:
- **God Mode** (`css_god`) - Toggle god mode for players
- **No Clip** (`css_noclip`) - Enable no-clip mode
- **Freeze/Unfreeze** (`css_freeze`, `css_unfreeze`) - Freeze players in place
- **Respawn** (`css_respawn`) - Respawn dead players
- **Give Weapon** (`css_give`) - Give weapons to players
- **Strip Weapons** (`css_strip`) - Remove all player weapons
- **Set HP** (`css_hp`) - Set player health
- **Set Speed** (`css_speed`) - Modify player movement speed
- **Set Gravity** (`css_gravity`) - Change player gravity
- **Set Money** (`css_money`) - Set player money
## 📁 File Structure
```
CS2-SimpleAdmin_FunCommands/
├── CS2-SimpleAdmin_FunCommands.cs # Main plugin file - initialization, registration
├── Commands.cs # Command handlers
├── Actions.cs # Action methods (God, NoClip, Freeze, etc.)
├── Menus.cs # Menu creation using SimpleAdmin API
├── Config.cs # Configuration with command lists
└── lang/ # Translation files (13 languages)
├── en.json
├── pl.json
├── ru.json
└── ... (10 more languages)
```
## 🔍 Code Organization Explained
### 1. Main Plugin File (`CS2-SimpleAdmin_FunCommands.cs`)
**Key Concepts Demonstrated:**
```csharp
public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Config>
{
// ✅ BEST PRACTICE: Use capability system to get API
private ICS2_SimpleAdminApi? _sharedApi;
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
// ✅ BEST PRACTICE: Cache expensive data
private static Dictionary<int, CsItem>? _weaponsCache;
// ✅ BEST PRACTICE: Track menu registration state
private bool _menusRegistered = false;
public override void OnAllPluginsLoaded(bool hotReload)
{
// Get the API
_sharedApi = _pluginCapability.Get();
// Register commands
RegisterFunCommands();
// ✅ BEST PRACTICE: Wait for SimpleAdmin to be ready before registering menus
_sharedApi.OnSimpleAdminReady += RegisterFunMenus;
RegisterFunMenus(); // Fallback for hot reload
}
}
```
**Why partial classes?**
- Separates concerns (commands, actions, menus)
- Makes code easier to navigate
- Each file has a specific purpose
### 2. Configuration (`Config.cs`)
**Key Concept:** Command lists for flexibility
```csharp
public class Config : IBasePluginConfig
{
// ✅ BEST PRACTICE: Allow multiple command aliases
public List<string> NoclipCommands { get; set; } = ["css_noclip"];
public List<string> GodCommands { get; set; } = ["css_god"];
// ... more command lists
}
```
**Benefits:**
- Users can disable features by emptying the list
- Users can add command aliases (e.g., `["css_god", "css_godmode"]`)
- Menus only register if commands exist
### 3. Commands (`Commands.cs`)
**Key Concepts Demonstrated:**
```csharp
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
private void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
{
// ✅ BEST PRACTICE: Use API to get targets (handles target syntax)
var targets = _sharedApi!.GetTarget(command);
if (targets == null) return;
// ✅ BEST PRACTICE: Filter for alive players
var playersToTarget = targets.Players.Where(player =>
player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
// ✅ BEST PRACTICE: Check targeting permissions
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
God(caller, player);
}
});
// ✅ BEST PRACTICE: Always log commands
_sharedApi.LogCommand(caller, command);
}
```
### 4. Actions (`Actions.cs`)
**Key Concepts Demonstrated:**
```csharp
private void God(CCSPlayerController? caller, CCSPlayerController player)
{
// Perform the action
if (!GodPlayers.Add(player.Slot))
{
GodPlayers.Remove(player.Slot);
}
// ✅ BEST PRACTICE: Use per-player language support
var activityArgs = new object[] { "CALLER", player.PlayerName };
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
{
if (Localizer != null)
{
// Each player sees message in their configured language!
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_god_message", callerName, false, activityArgs);
}
}
// ✅ BEST PRACTICE: Log the action
_sharedApi!.LogCommand(caller, $"css_god {player.PlayerName}");
}
```
### 5. Menus (`Menus.cs`)
**Key Concepts Demonstrated:**
#### Simple Player Selection Menu
```csharp
private object CreateGodModeMenu(CCSPlayerController admin)
{
// ✅ BEST PRACTICE: Use CreateMenuWithPlayers for simple player selection
return _sharedApi!.CreateMenuWithPlayers("God Mode", "fun", admin,
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
God); // Direct method reference
}
```
#### Nested Menu with Value Selection
```csharp
private object CreateSetHpMenu(CCSPlayerController admin)
{
// ✅ BEST PRACTICE: Use CreateMenuWithBack for menus with back button
var menu = _sharedApi!.CreateMenuWithBack("Set HP", "fun", admin);
var players = _sharedApi.GetValidPlayers().Where(p =>
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
// ✅ BEST PRACTICE: AddSubMenu automatically adds back button to submenu
_sharedApi.AddSubMenu(menu, playerName, p => CreateHpSelectionMenu(admin, player));
}
return menu;
}
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var hpMenu = _sharedApi!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "fun", admin);
var hpValues = new[] { 1, 10, 25, 50, 100, 200, 500, 999 };
foreach (var hp in hpValues)
{
// ✅ BEST PRACTICE: AddMenuOption for simple actions
_sharedApi.AddMenuOption(hpMenu, $"{hp} HP", _ =>
{
// ✅ BEST PRACTICE: Always validate before executing
if (target.IsValid)
{
target.SetHp(hp);
LogAndShowActivity(admin, target, "fun_admin_hp_message", "css_hp", hp.ToString());
}
});
}
return hpMenu;
}
```
### 6. Translations
**Key Concept:** Module-specific translations
```json
// lang/en.json
{
"fun_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!",
"fun_admin_hp_message": "{lightred}{0}{default} changed {lightred}{1}{default} hp amount!"
}
```
**Why module translations?**
- Your module is independent from SimpleAdmin
- You can update translations without affecting main plugin
- Each player sees messages in their language automatically
## 🛠️ How to Use This as a Template
### Step 1: Copy the Module
```bash
cp -r CS2-SimpleAdmin_FunCommands YourModuleName
```
### Step 2: Rename Files
- Rename `.csproj` file
- Rename all `.cs` files to match your module name
- Update namespace in all files
### Step 3: Update References
- Change `namespace CS2_SimpleAdmin_FunCommands` to `namespace YourModuleName`
- Update plugin metadata (name, version, author, description)
### Step 4: Modify Config
```csharp
public class Config : IBasePluginConfig
{
public int Version { get; set; } = 1;
// Add your own command lists
public List<string> YourCommands { get; set; } = ["css_yourcommand"];
}
```
### Step 5: Add Your Commands
Look at `Commands.cs` for examples of command handlers
### Step 6: Add Your Menus
Look at `Menus.cs` for examples of menu creation
### Step 7: Add Translations
Create language files in `lang/{language}.json` (e.g., `lang/en.json`, `lang/pl.json`)
## 📖 Learning Path
If you're new to module development, study files in this order:
1. **Config.cs** - Understand configuration structure
2. **CS2-SimpleAdmin_FunCommands.cs** - See initialization and API acquisition
3. **Commands.cs** - Learn command registration and handling
4. **Actions.cs** - Understand action methods and translations
5. **Menus.cs** - Study menu creation patterns
## 🎓 Best Practices Demonstrated
### ✅ Command Registration
```csharp
// Dynamic registration based on config
if (Config.GodCommands.Count > 0)
{
foreach (var command in Config.GodCommands)
{
_sharedApi.RegisterCommand(command, "Enable god mode", OnGodCommand);
}
}
```
### ✅ Target Validation
```csharp
// Always check if player can be targeted
if (!caller.CanTarget(player)) return;
// Always validate player state
if (!player.IsValid) return;
```
### ✅ Translation Usage
```csharp
// Use module's localizer for per-player language support
if (Localizer != null)
{
_sharedApi.ShowAdminActivityLocalized(Localizer, "translation_key", callerName, false, args);
}
```
### ✅ Cleanup on Unload
```csharp
public override void Unload(bool hotReload)
{
// Unregister all commands
if (Config.GodCommands.Count > 0)
{
foreach (var command in Config.GodCommands)
{
_sharedApi.UnRegisterCommand(command);
}
}
// Unregister all menus
if (Config.GodCommands.Count > 0)
_sharedApi.UnregisterMenu("fun", "god");
// Remove event handlers
_sharedApi.OnSimpleAdminReady -= RegisterFunMenus;
}
```
### ✅ Data Caching
```csharp
// Cache expensive operations
private static Dictionary<int, CsItem> GetWeaponsCache()
{
if (_weaponsCache != null) return _weaponsCache;
var weaponsArray = Enum.GetValues(typeof(CsItem));
_weaponsCache = new Dictionary<int, CsItem>();
// ... populate cache
return _weaponsCache;
}
```
## 🔗 Related Documentation
- **[MODULE_DEVELOPMENT.md](../MODULE_DEVELOPMENT.md)** - Complete API reference
- **[TRANSLATION_EXAMPLE.md](../TRANSLATION_EXAMPLE.md)** - Translation usage guide
- **[CS2-SimpleAdminApi](../../CS2-SimpleAdminApi/)** - API interface definitions
## 💡 Tips
1. **Start Simple** - Begin with one command and one menu, then expand
2. **Test Thoroughly** - Test with multiple players, different permissions, and languages
3. **Handle Errors** - Always validate player state before actions
4. **Log Everything** - Use `_sharedApi.LogCommand()` for all admin actions
5. **Use Partial Classes** - Split your code into logical files
6. **Comment Your Code** - Especially if others will use it as reference
## 🐛 Common Mistakes to Avoid
-**Don't** forget to check `IsValid` before accessing player properties
-**Don't** skip permission checks (`CanTarget`, `RequiresPermissions`)
-**Don't** forget to unregister commands/menus in `Unload()`
-**Don't** use SimpleAdmin's translations - create your own
-**Don't** hardcode command names - use config lists instead
## 🤝 Contributing
If you improve this module or find better patterns, please contribute back so others can learn!
## 📄 License
This module is provided as-is for educational purposes. Feel free to use it as a template for your own modules.

View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}

View File

@@ -0,0 +1,602 @@
# Module Development Guide - CS2-SimpleAdmin
> **🎓 New to module development?** Start with the [Fun Commands Module](./CS2-SimpleAdmin_FunCommands/) - it's a fully documented reference implementation showing all best practices!
This guide explains how to create modules for CS2-SimpleAdmin with custom commands, menus, and translations.
## 📖 Table of Contents
1. [Quick Start](#quick-start)
2. [Learning Resources](#learning-resources)
3. [API Methods Reference](#api-methods)
4. [Menu Patterns](#menu-patterns)
5. [Best Practices](#best-practices)
6. [Common Patterns](#common-patterns)
7. [Troubleshooting](#troubleshooting)
## 🚀 Quick Start
### Step 1: Study the Example Module
The **[CS2-SimpleAdmin_FunCommands](./CS2-SimpleAdmin_FunCommands/)** module is your best learning resource. It demonstrates:
✅ Command registration from config
✅ Dynamic menu creation
✅ Per-player translation support
✅ Proper cleanup on unload
✅ Code organization with partial classes
✅ All menu patterns you'll need
**Start here:** Read [`CS2-SimpleAdmin_FunCommands/README.md`](./CS2-SimpleAdmin_FunCommands/README.md)
### Step 2: Create Your Module Structure
```
YourModule/
├── YourModule.csproj # Project file
├── YourModule.cs # Main plugin class
├── Commands.cs # Command handlers (optional)
├── Menus.cs # Menu creation (optional)
├── Config.cs # Configuration
└── lang/ # Translations
├── en.json
├── pl.json
└── ru.json
```
### Step 3: Minimal Working Example
```csharp
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CS2_SimpleAdminApi;
public class MyModule : 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");
return;
}
// Register menus after API is ready
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Fallback for hot reload
}
private void RegisterMenus()
{
if (_api == null) return;
// 1. Register a new category
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
// 2. Register menu items in the category
_api.RegisterMenu("mymodule", "action1", "Action 1", CreateAction1Menu, "@css/generic");
_api.RegisterMenu("mymodule", "action2", "Action 2", CreateAction2Menu, "@css/kick");
}
private object CreateAction1Menu(CCSPlayerController admin)
{
// Create a menu with automatic back button
var menu = _api!.CreateMenuWithBack("Action 1 Menu", "mymodule", admin);
// Add menu options
_api.AddMenuOption(menu, "Option 1", player =>
{
player.PrintToChat("You selected Option 1");
});
_api.AddMenuOption(menu, "Option 2", player =>
{
player.PrintToChat("You selected Option 2");
});
return menu;
}
private object CreateAction2Menu(CCSPlayerController admin)
{
// Use the built-in player selection menu
return _api!.CreateMenuWithPlayers("Select Player", "mymodule", admin,
player => player.IsValid && admin.CanTarget(player),
(adminPlayer, targetPlayer) =>
{
adminPlayer.PrintToChat($"You selected {targetPlayer.PlayerName}");
});
}
public override void Unload(bool hotReload)
{
if (_api == null) return;
// Clean up registered menus
_api.UnregisterMenu("mymodule", "action1");
_api.UnregisterMenu("mymodule", "action2");
_api.OnSimpleAdminReady -= RegisterMenus;
}
}
```
## 📚 Learning Resources
### For Beginners
1. **Start Here:** [`CS2-SimpleAdmin_FunCommands/README.md`](./CS2-SimpleAdmin_FunCommands/README.md)
- Explains every file and pattern
- Shows code organization
- Demonstrates all menu types
2. **Read the Code:** Study these files in order:
- `Config.cs` - Simple configuration
- `CS2-SimpleAdmin_FunCommands.cs` - Plugin initialization
- `Commands.cs` - Command registration
- `Menus.cs` - Menu creation patterns
3. **Translations:** [`TRANSLATION_EXAMPLE.md`](./TRANSLATION_EXAMPLE.md)
- How to use module translations
- Per-player language support
- Best practices
### Key Concepts
| Concept | What It Does | Example File |
|---------|-------------|--------------|
| **API Capability** | Get access to SimpleAdmin API | `CS2-SimpleAdmin_FunCommands.cs:37` |
| **Command Registration** | Register console commands | `Commands.cs:15-34` |
| **Menu Registration** | Add menus to admin menu | `CS2-SimpleAdmin_FunCommands.cs:130-141` |
| **Translations** | Per-player language support | `Actions.cs:20-31` |
| **Cleanup** | Unregister on plugin unload | `CS2-SimpleAdmin_FunCommands.cs:63-97` |
## 🎯 Menu Patterns
The FunCommands module demonstrates **3 essential menu patterns** you'll use in every module:
### Pattern 1: Simple Player Selection
**When to use:** Select a player and immediately execute an action
```csharp
// Example: God Mode menu
private object CreateGodModeMenu(CCSPlayerController admin)
{
return _api.CreateMenuWithPlayers(
"God Mode", // Title
"yourmodule", // Category ID
admin, // Admin
player => player.IsValid && admin.CanTarget(player), // Filter
(adminPlayer, target) => // Action
{
// Execute action immediately
ToggleGodMode(target);
});
}
```
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:21-29`
### Pattern 2: Nested Menu (Player → Value)
**When to use:** Select a player, then select a value/option for that player
```csharp
// Example: Set HP menu (player selection)
private object CreateSetHpMenu(CCSPlayerController admin)
{
var menu = _api.CreateMenuWithBack("Set HP", "yourmodule", admin);
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
foreach (var player in players)
{
// AddSubMenu automatically adds back button to submenu
_api.AddSubMenu(menu, player.PlayerName,
p => CreateHpValueMenu(admin, player));
}
return menu;
}
// Example: Set HP menu (value selection)
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api.CreateMenuWithBack($"HP for {target.PlayerName}", "yourmodule", admin);
var values = new[] { 50, 100, 200, 500 };
foreach (var hp in values)
{
_api.AddMenuOption(menu, $"{hp} HP", _ =>
{
if (target.IsValid) // Always validate!
{
target.PlayerPawn.Value.Health = hp;
}
});
}
return menu;
}
```
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:134-173`
### Pattern 3: Nested Menu with Complex Data
**When to use:** Need to display more complex options (like weapons with icons, items with descriptions)
```csharp
// Example: Give Weapon menu
private object CreateGiveWeaponMenu(CCSPlayerController admin)
{
var menu = _api.CreateMenuWithBack("Give Weapon", "yourmodule", admin);
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
foreach (var player in players)
{
_api.AddSubMenu(menu, player.PlayerName,
p => CreateWeaponSelectionMenu(admin, player));
}
return menu;
}
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var menu = _api.CreateMenuWithBack($"Weapons for {target.PlayerName}", "yourmodule", admin);
// Use cached data for performance
foreach (var weapon in GetWeaponsCache())
{
_api.AddMenuOption(menu, weapon.Value.ToString(), _ =>
{
if (target.IsValid)
{
target.GiveNamedItem(weapon.Value);
}
});
}
return menu;
}
```
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:67-109`
## 📋 API Methods Reference
### 1. Category Management
#### `RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")`
Registers a new menu category that appears in the main admin menu.
**Parameters:**
- `categoryId` - Unique identifier for the category (e.g., "fun", "vip", "economy")
- `categoryName` - Display name shown in menu (e.g., "Fun Commands")
- `permission` - Required permission to see the category (default: "@css/generic")
**Example:**
```csharp
_api.RegisterMenuCategory("vip", "VIP Features", "@css/vip");
```
### 2. Menu Registration
#### `RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null)`
Registers a menu item within a category.
**Parameters:**
- `categoryId` - The category to add this menu to
- `menuId` - Unique identifier for the menu
- `menuName` - Display name in the category menu
- `menuFactory` - Function that creates the menu when selected (receives admin player)
- `permission` - Optional permission required to see this menu item
**Example:**
```csharp
_api.RegisterMenu("fun", "godmode", "God Mode", CreateGodModeMenu, "@css/cheats");
```
#### `UnregisterMenu(string categoryId, string menuId)`
Removes a menu item from a category.
**Example:**
```csharp
_api.UnregisterMenu("fun", "godmode");
```
### 3. Menu Creation
#### `CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)`
Creates a menu with an automatic "Back" button that returns to the category menu.
**Parameters:**
- `title` - Menu title
- `categoryId` - Category this menu belongs to (for back navigation)
- `player` - The admin player viewing the menu
**Returns:** `object` (MenuBuilder instance)
**Example:**
```csharp
var menu = _api.CreateMenuWithBack("Weapon Selection", "fun", admin);
```
#### `CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)`
Creates a menu with a list of players, filtered and with automatic back button.
**Parameters:**
- `title` - Menu title
- `categoryId` - Category for back navigation
- `admin` - The admin player viewing the menu
- `filter` - Function to filter which players appear in the menu
- `onSelect` - Action to execute when a player is selected (receives admin and target)
**Returns:** `object` (MenuBuilder instance)
**Example:**
```csharp
return _api.CreateMenuWithPlayers("Select Player to Kick", "admin", admin,
player => player.IsValid && admin.CanTarget(player),
(adminPlayer, targetPlayer) =>
{
// Kick the selected player
Server.ExecuteCommand($"css_kick {targetPlayer.UserId}");
});
```
### 4. Menu Manipulation
#### `AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)`
Adds a clickable option to a menu.
**Parameters:**
- `menu` - The menu object (from CreateMenuWithBack)
- `name` - Display name of the option
- `action` - Action to execute when clicked (receives the player who clicked)
- `disabled` - Whether the option is disabled (grayed out)
- `permission` - Optional permission required to see this option
**Example:**
```csharp
_api.AddMenuOption(menu, "Give AK-47", player =>
{
player.GiveNamedItem("weapon_ak47");
}, permission: "@css/cheats");
```
#### `AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory, bool disabled = false, string? permission = null)`
Adds a submenu option that opens another menu. **Automatically adds a back button to the submenu.**
**Parameters:**
- `menu` - The parent menu
- `name` - Display name of the submenu option
- `subMenuFactory` - Function that creates the submenu (receives the player)
- `disabled` - Whether the option is disabled
- `permission` - Optional permission required
**Example:**
```csharp
_api.AddSubMenu(menu, "Weapon Category", player =>
{
var weaponMenu = _api.CreateMenuWithBack("Weapons", "fun", player);
_api.AddMenuOption(weaponMenu, "AK-47", p => p.GiveNamedItem("weapon_ak47"));
_api.AddMenuOption(weaponMenu, "AWP", p => p.GiveNamedItem("weapon_awp"));
return weaponMenu;
});
```
#### `OpenMenu(object menu, CCSPlayerController player)`
Opens a menu for a specific player.
**Example:**
```csharp
var menu = _api.CreateMenuWithBack("Custom Menu", "fun", player);
_api.AddMenuOption(menu, "Test", p => p.PrintToChat("Test!"));
_api.OpenMenu(menu, player);
```
## Advanced Examples
### Nested Menus with Player Selection
```csharp
private object CreateGiveWeaponMenu(CCSPlayerController admin)
{
var menu = _api.CreateMenuWithBack("Give Weapon", "fun", admin);
var players = _api.GetValidPlayers()
.Where(p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
foreach (var player in players)
{
// Add submenu for each player - automatic back button will be added
_api.AddSubMenu(menu, player.PlayerName, p => CreateWeaponSelectionMenu(admin, player));
}
return menu;
}
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var weaponMenu = _api.CreateMenuWithBack($"Weapons for {target.PlayerName}", "fun", admin);
var weapons = new[] { "weapon_ak47", "weapon_m4a1", "weapon_awp", "weapon_deagle" };
foreach (var weapon in weapons)
{
_api.AddMenuOption(weaponMenu, weapon, _ =>
{
if (target.IsValid)
{
target.GiveNamedItem(weapon);
admin.PrintToChat($"Gave {weapon} to {target.PlayerName}");
}
});
}
return weaponMenu;
}
```
### Dynamic Menu with Value Selection
```csharp
private object CreateSetHpMenu(CCSPlayerController admin)
{
var menu = _api.CreateMenuWithBack("Set HP", "admin", admin);
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
foreach (var player in players)
{
_api.AddSubMenu(menu, player.PlayerName, p => CreateHpValueMenu(admin, player));
}
return menu;
}
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
{
var hpMenu = _api.CreateMenuWithBack($"HP for {target.PlayerName}", "admin", admin);
var hpValues = new[] { 1, 50, 100, 200, 500, 1000 };
foreach (var hp in hpValues)
{
_api.AddMenuOption(hpMenu, $"{hp} HP", _ =>
{
if (target.IsValid && target.PlayerPawn?.Value != null)
{
target.PlayerPawn.Value.Health = hp;
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
}
});
}
return hpMenu;
}
```
### Permission-Based Menu Options
```csharp
private object CreateAdminToolsMenu(CCSPlayerController admin)
{
var menu = _api.CreateMenuWithBack("Admin Tools", "admin", admin);
// Only root admins can see this
_api.AddMenuOption(menu, "Dangerous Action", player =>
{
player.PrintToChat("Executing dangerous action...");
}, permission: "@css/root");
// All admins can see this
_api.AddMenuOption(menu, "Safe Action", player =>
{
player.PrintToChat("Executing safe action...");
}, permission: "@css/generic");
return menu;
}
```
## Best Practices
1. **Always check for API availability**
```csharp
if (_api == null) return;
```
2. **Validate player state before actions**
```csharp
if (target.IsValid && target.PlayerPawn?.Value != null)
{
// Safe to perform action
}
```
3. **Use descriptive category and menu IDs**
- Good: `"economy"`, `"vip_features"`, `"fun_commands"`
- Bad: `"cat1"`, `"menu"`, `"test"`
4. **Clean up on unload**
```csharp
public override void Unload(bool hotReload)
{
_api?.UnregisterMenu("mymodule", "mymenu");
_api.OnSimpleAdminReady -= RegisterMenus;
}
```
5. **Use appropriate permissions**
- `@css/generic` - All admins
- `@css/ban` - Admins who can ban
- `@css/kick` - Admins who can kick
- `@css/root` - Root admins only
- Custom permissions from your module
6. **Handle hot reload**
```csharp
_api.OnSimpleAdminReady += RegisterMenus;
RegisterMenus(); // Fallback for hot reload case
```
## Automatic Back Button
The menu system **automatically adds a "Back" button** to all submenus created with:
- `CreateMenuWithBack()` - Returns to the category menu
- `AddSubMenu()` - Returns to the parent menu
You don't need to manually add back buttons - the system handles navigation automatically!
## Getting Valid Players
Use the API helper method to get valid, connected players:
```csharp
var players = _api.GetValidPlayers();
// With filtering
var alivePlayers = _api.GetValidPlayers()
.Where(p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE);
var targetablePlayers = _api.GetValidPlayers()
.Where(p => admin.CanTarget(p));
```
## Complete Module Example
See the `CS2-SimpleAdmin_FunCommands` module in the `Modules/` directory for a complete, production-ready example of:
- Category registration
- Multiple menu types
- Nested menus with automatic back buttons
- Player selection menus
- Permission-based access
- Proper cleanup on unload
## Troubleshooting
**Q: My category doesn't appear in the admin menu**
- Ensure you're calling `RegisterMenuCategory()` after the API is ready
- Check that the player has the required permission
- Verify the category has at least one menu registered with `RegisterMenu()`
**Q: Back button doesn't work**
- Make sure you're using `CreateMenuWithBack()` instead of creating menus manually
- The `categoryId` parameter must match the category you registered
- Use `AddSubMenu()` for nested menus - it handles back navigation automatically
**Q: Menu appears but is empty**
- Check that you're adding options with `AddMenuOption()` or `AddSubMenu()`
- Verify permission checks aren't filtering out all options
- Ensure player validation in filters isn't too restrictive
**Q: API is null in OnAllPluginsLoaded**
- Wait for the `OnSimpleAdminReady` event instead of immediate registration
- Make sure CS2-SimpleAdmin is loaded before your module

View File

@@ -0,0 +1,268 @@
# Module Translation Guide
> **🎓 New to translations?** This guide shows you how to add multi-language support to your module!
## Why Use Module Translations?
When you use SimpleAdmin API's translation system, **each player automatically sees messages in their preferred language**!
**Example:**
- 🇬🇧 **Player with English:** "Admin gave PlayerName a weapon!"
- 🇵🇱 **Player with Polish:** "Admin dał PlayerName broń!"
- 🇷🇺 **Player with Russian:** "Admin дал PlayerName оружие!"
**All from ONE line of code!**
## Quick Start
### Step 1: Create Your Translation Files
Create a `lang` folder in your module with translation files for each language:
```
YourModule/
└── lang/
├── en.json
├── pl.json
└── ru.json
```
**Example: `lang/en.json`**
```json
{
"yourmod_admin_action": "{lightred}{0}{default} performed action on {lightred}{1}{default}!"
}
```
**Example: `lang/pl.json`**
```json
{
"yourmod_admin_action": "{lightred}{0}{default} wykonał akcję na {lightred}{1}{default}!"
}
```
### Step 2: Use in Your Code
**✅ RECOMMENDED METHOD:** `ShowAdminActivityLocalized`
```csharp
// Show activity with per-player language support
var args = new object[] { "CALLER", targetPlayer.PlayerName };
if (admin == null || !_api.IsAdminSilent(admin))
{
if (Localizer != null)
{
// Each player sees this in their language!
_api.ShowAdminActivityLocalized(
Localizer, // Your module's localizer
"yourmod_admin_action", // Translation key
admin.PlayerName, // Caller name
false, // dontPublish
args); // Message arguments
}
}
```
That's it! SimpleAdmin handles the rest.
## Complete Example
```csharp
using CounterStrikeSharp.API.Core;
using CS2_SimpleAdminApi;
public partial class MyModule : BasePlugin
{
private ICS2_SimpleAdminApi? _api;
private void GiveWeaponToPlayer(CCSPlayerController admin, CCSPlayerController target, string weaponName)
{
// Give the weapon
target.GiveNamedItem(weaponName);
var callerName = admin.PlayerName;
// Show activity using module's localizer - each player sees it in their language!
if (admin == null || !_api!.IsAdminSilent(admin))
{
var args = new object[] { "CALLER", target.PlayerName, weaponName };
if (Localizer != null)
{
_api!.ShowAdminActivityLocalized(Localizer, "yourmod_admin_give_message", callerName, false, args);
}
}
// Log the command
_api!.LogCommand(admin, $"css_give {target.PlayerName} {weaponName}");
}
}
```
## 🔑 Important: The "CALLER" Placeholder
**Always use `"CALLER"` as the first argument** in your translation messages!
The API automatically replaces `"CALLER"` based on the server's `ShowActivityType` configuration:
| ShowActivityType | What Players See |
|-----------------|-----------------|
| `1` | Non-admins see "Admin", admins see real name |
| `2+` | Everyone sees real admin name |
**Example:**
```json
{
"yourmod_message": "{0} did something to {1}"
This will be replaced with "Admin" or admin's name
}
```
```csharp
var args = new object[] { "CALLER", targetPlayer.PlayerName };
// ↑ API replaces this automatically
```
## 💡 Pro Tips
### Tip 1: Use a Helper Method
Create a reusable helper to reduce code duplication:
```csharp
/// <summary>
/// Helper method to show activity and log command
/// Copy this to your module!
/// </summary>
private void LogAndShowActivity(
CCSPlayerController? caller,
CCSPlayerController target,
string translationKey,
string baseCommand,
params string[] extraArgs)
{
var callerName = caller?.PlayerName ?? "Console";
// Build args: "CALLER" + target name + any extra args
var args = new List<object> { "CALLER", target.PlayerName };
args.AddRange(extraArgs);
// Show activity with per-player language support
if (caller == null || !_api.IsAdminSilent(caller))
{
if (Localizer != null)
{
_api.ShowAdminActivityLocalized(
Localizer,
translationKey,
callerName,
false,
args.ToArray());
}
}
// Build and log command
var logCommand = $"{baseCommand} {target.PlayerName}";
if (extraArgs.Length > 0)
{
logCommand += $" {string.Join(" ", extraArgs)}";
}
_api.LogCommand(caller, logCommand);
}
```
**Usage:**
```csharp
// Simple action
LogAndShowActivity(admin, target, "yourmod_kick_message", "css_kick");
// Action with parameters
LogAndShowActivity(admin, target, "yourmod_hp_message", "css_hp", "100");
```
### Tip 2: Translation Key Naming Convention
Use a consistent prefix for your module:
```json
{
"yourmod_admin_action1": "...",
"yourmod_admin_action2": "...",
"yourmod_error_notarget": "..."
}
```
This prevents conflicts with other modules and makes it clear which module owns the translation.
### Tip 3: Color Formatting
Use CounterStrikeSharp color tags in your translations:
```json
{
"yourmod_message": "{lightred}{0}{default} gave {green}{1}{default} a {yellow}{2}{default}!"
}
```
**Available colors:**
- `{default}`, `{white}`, `{darkred}`, `{green}`, `{lightyellow}`
- `{lightblue}`, `{olive}`, `{lime}`, `{red}`, `{purple}`
- `{grey}`, `{yellow}`, `{gold}`, `{silver}`, `{blue}`
- `{darkblue}`, `{bluegrey}`, `{magenta}`, `{lightred}`, `{orange}`
## 📖 Real Example: Fun Commands Module
The **[CS2-SimpleAdmin_FunCommands](./CS2-SimpleAdmin_FunCommands/)** module is a perfect reference:
**Translation files:** `Modules/CS2-SimpleAdmin_FunCommands/lang/`
- Has 13 languages (en, pl, ru, de, fr, es, etc.)
- Shows proper key naming (`fun_admin_*`)
- Demonstrates color usage
**Code examples:** `Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Actions.cs`
- Lines 20-31: God mode with translations
- Lines 48-59: NoClip with translations
- Lines 76-86: Freeze with translations
**Helper method:** `Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Commands.cs:274-306`
## ❌ Common Mistakes
### Mistake 1: Forgetting "CALLER"
```csharp
// ❌ WRONG
var args = new object[] { admin.PlayerName, target.PlayerName };
// ✅ CORRECT
var args = new object[] { "CALLER", target.PlayerName };
```
### Mistake 2: Using SimpleAdmin's Translations
```csharp
// ❌ WRONG - Uses SimpleAdmin's keys
_api.ShowAdminActivity("sa_admin_kick", ...)
// ✅ CORRECT - Uses YOUR module's keys
_api.ShowAdminActivityLocalized(Localizer, "yourmod_kick", ...)
```
### Mistake 3: Not Checking Localizer
```csharp
// ❌ WRONG - Will crash if Localizer is null
_api.ShowAdminActivityLocalized(Localizer, "key", ...)
// ✅ CORRECT - Check first
if (Localizer != null)
{
_api.ShowAdminActivityLocalized(Localizer, "key", ...)
}
```
## 🔗 See Also
- **[MODULE_DEVELOPMENT.md](./MODULE_DEVELOPMENT.md)** - Complete module development guide
- **[CS2-SimpleAdmin_FunCommands/README.md](./CS2-SimpleAdmin_FunCommands/README.md)** - Reference implementation
- **[CounterStrikeSharp Localization](https://docs.cssharp.dev/guides/localization/)** - Official CSS localization docs
---
**Need help?** Study the FunCommands module - it demonstrates all these patterns correctly!