mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-17 10:31:01 +00:00
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:
BIN
Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdminApi.dll
Normal file
BIN
Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdminApi.dll
Normal file
Binary file not shown.
@@ -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
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
384
Modules/CS2-SimpleAdmin_FunCommands/README.md
Normal file
384
Modules/CS2-SimpleAdmin_FunCommands/README.md
Normal 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.
|
||||
7
Modules/CS2-SimpleAdmin_FunCommands/global.json
Normal file
7
Modules/CS2-SimpleAdmin_FunCommands/global.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
602
Modules/MODULE_DEVELOPMENT.md
Normal file
602
Modules/MODULE_DEVELOPMENT.md
Normal 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
|
||||
268
Modules/TRANSLATION_EXAMPLE.md
Normal file
268
Modules/TRANSLATION_EXAMPLE.md
Normal 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!
|
||||
Reference in New Issue
Block a user