Compare commits

..

3 Commits

Author SHA1 Message Date
Dawid Bepierszcz
3a57371be9 Updated StatusBlocker 2025-10-16 03:04:13 +02:00
Dawid Bepierszcz
63ca44bb78 Delete Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.0.3-linux.tar.gz 2025-10-16 03:03:48 +02:00
Dawid Bepierszcz
3270403ea1 Delete Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.0.3-windows.tar.gz 2025-10-16 03:03:40 +02:00
65 changed files with 1489 additions and 5939 deletions

1
.gitignore vendored
View File

@@ -9,4 +9,3 @@ CS2-SimpleAdmin.sln.DotSettings.user
Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user
CS2-SimpleAdmin_BanSoundModule — kopia
*.user
CLAUDE.md

View File

@@ -1,57 +1,47 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Menus;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Localization;
namespace CS2_SimpleAdmin.Api;
public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
{
public event Action? OnSimpleAdminReady;
public void OnSimpleAdminReadyEvent() => OnSimpleAdminReady?.Invoke();
public PlayerInfo GetPlayerInfo(CCSPlayerController player)
{
return !player.UserId.HasValue
? throw new KeyNotFoundException("Player with specific UserId not found")
: CS2_SimpleAdmin.PlayersInfo[player.SteamID];
if (!player.UserId.HasValue)
throw new KeyNotFoundException("Player with specific UserId not found");
return CS2_SimpleAdmin.PlayersInfo[player.SteamID];
}
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
public int? GetServerId() => CS2_SimpleAdmin.ServerId;
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
public int? GetServerId() => CS2_SimpleAdmin.ServerId;
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(
CCSPlayerController player)
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(CCSPlayerController player)
{
return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot);
}
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied;
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded;
public event Action<string, string?, bool, object>? OnAdminShowActivity;
public event Action<int, bool>? OnAdminToggleSilent;
public void OnPlayerPenaltiedEvent(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration,
penaltyId, CS2_SimpleAdmin.ServerId);
int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration,
penaltyId, CS2_SimpleAdmin.ServerId);
int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false,
params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
public void OnAdminToggleSilentEvent(int slot, bool status) => OnAdminToggleSilent?.Invoke(slot, status);
public void OnAdminToggleSilentEvent(int slot, bool status) => OnAdminToggleSilent?.Invoke(slot, status);
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType,
string reason, int duration = -1)
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
{
switch (penaltyType)
{
@@ -89,9 +79,8 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
}
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason,
int duration = -1)
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
{
switch (penaltyType)
{
@@ -134,12 +123,12 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
{
Helper.LogCommand(caller, command);
}
public bool IsAdminSilent(CCSPlayerController player)
{
return CS2_SimpleAdmin.SilentPlayers.Contains(player.Slot);
}
}
public HashSet<int> ListSilentAdminsSlots()
{
return CS2_SimpleAdmin.SilentPlayers;
@@ -158,7 +147,7 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
list = new List<CommandDefinition>();
RegisterCommands._commandDefinitions[name] = list;
}
list.Add(definition);
}
@@ -173,145 +162,9 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
CS2_SimpleAdmin.Instance.RemoveCommand(commandName, definition.Callback);
}
}
public TargetResult? GetTarget(CommandInfo command)
{
return CS2_SimpleAdmin.GetTarget(command);
}
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false,
params object[] messageArgs)
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
{
Helper.ShowAdminActivity(messageKey, callerName, dontPublish, messageArgs);
}
public void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null,
bool dontPublish = false)
{
Helper.ShowAdminActivityTranslated(translatedMessage, callerName, dontPublish);
}
public void ShowAdminActivityLocalized(object moduleLocalizer, string messageKey, string? callerName = null,
bool dontPublish = false, params object[] messageArgs)
{
if (moduleLocalizer is not IStringLocalizer localizer)
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
Helper.ShowAdminActivityLocalized(localizer, messageKey, callerName, dontPublish, messageArgs);
}
public void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")
{
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
}
public void RegisterMenu(string categoryId, string menuId, string menuName,
Func<CCSPlayerController, object> menuFactory, string? permission = null)
{
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission);
return;
MenuBuilder BuilderFactory(CCSPlayerController player)
{
if (menuFactory(player) is not MenuBuilder menuBuilder)
throw new InvalidOperationException("Menu factory must return MenuBuilder");
// Dodaj automatyczną obsługę przycisku 'Wróć'
menuBuilder.WithBackAction(p =>
{
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
{
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
}
else
{
Menus.MenuManager.Instance.OpenMainMenu(p);
}
});
return menuBuilder;
}
}
public void UnregisterMenu(string categoryId, string menuId)
{
Menus.MenuManager.Instance.UnregisterMenu(categoryId, menuId);
}
public object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)
{
var builder = new MenuBuilder(title);
builder.WithBackAction(p =>
{
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
{
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
}
else
{
Menus.MenuManager.Instance.OpenMainMenu(p);
}
});
return builder;
}
public List<CCSPlayerController> GetValidPlayers()
{
return Helper.GetValidPlayers();
}
public object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin,
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
{
var menu = (MenuBuilder)CreateMenuWithBack(title, categoryId, admin);
var players = Helper.GetValidPlayers().Where(filter);
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
menu.AddOption(playerName, _ =>
{
if (player.IsValid)
{
onSelect(admin, player);
}
});
}
return menu;
}
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
string? permission = null)
{
if (menu is not MenuBuilder menuBuilder)
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
menuBuilder.AddOption(name, action, disabled, permission);
}
public void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory,
bool disabled = false, string? permission = null)
{
if (menu is not MenuBuilder menuBuilder)
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
menuBuilder.AddSubMenu(name, player =>
{
var subMenu = subMenuFactory(player);
if (subMenu is not MenuBuilder builder)
throw new InvalidOperationException("SubMenu factory must return MenuBuilder");
return builder;
}, disabled, permission);
}
public void OpenMenu(object menu, CCSPlayerController player)
{
if (menu is not MenuBuilder menuBuilder)
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
menuBuilder.OpenMenu(player);
}
}

View File

@@ -1,4 +1,5 @@
using CounterStrikeSharp.API;
using System.Reflection;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Capabilities;
@@ -7,7 +8,6 @@ using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Menus;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
using MySqlConnector;
@@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
public override string ModuleAuthor => "daffyy & Dliix66";
public override string ModuleVersion => "1.7.8-beta-1";
public override string ModuleVersion => "1.7.7-alpha-10";
public override void Load(bool hotReload)
{
@@ -67,9 +67,6 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
PlayersTimer?.Kill();
PlayersTimer = null;
PlayerManager.CheckPlayersTimer();
Menus.MenuManager.Instance.InitializeDefaultCategories();
BasicMenu.Initialize();
}
public override void OnAllPluginsLoaded(bool hotReload)
@@ -86,7 +83,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
AddTimer(6.0f, () => ReloadAdmins(null));
RegisterEvents();
AddTimer(0.5f, RegisterCommands.InitializeCommands);
RegisterCommands.InitializeCommands();
if (!CoreConfig.UnlockConCommands)
{
@@ -235,7 +232,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
WarnManager = new WarnManager(DatabaseProvider);
}
internal static TargetResult? GetTarget(CommandInfo command, int argument = 1)
private static TargetResult? GetTarget(CommandInfo command, int argument = 1)
{
var matches = command.GetArgTargetResult(argument);

View File

@@ -81,9 +81,6 @@
<None Update="Database\Migrations\Mysql\015_steamidToBigInt.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\016_OptimizeTablesAndIndexes.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\001_CreateTables.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -129,9 +126,6 @@
<None Update="Database\Migrations\Sqlite\015_steamidToBigInt.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\016_OptimizeTablesAndIndexes.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>

View File

@@ -65,11 +65,24 @@ public static class RegisterCommands
new("css_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
new("css_noclip", CS2_SimpleAdmin.Instance.OnNoclipCommand),
new("css_freeze", CS2_SimpleAdmin.Instance.OnFreezeCommand),
new("css_unfreeze", CS2_SimpleAdmin.Instance.OnUnfreezeCommand),
new("css_godmode", CS2_SimpleAdmin.Instance.OnGodCommand),
new("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
new("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
new("css_give", CS2_SimpleAdmin.Instance.OnGiveCommand),
new("css_strip", CS2_SimpleAdmin.Instance.OnStripCommand),
new("css_hp", CS2_SimpleAdmin.Instance.OnHpCommand),
new("css_speed", CS2_SimpleAdmin.Instance.OnSpeedCommand),
new("css_gravity", CS2_SimpleAdmin.Instance.OnGravityCommand),
new("css_resize", CS2_SimpleAdmin.Instance.OnResizeCommand),
new("css_money", CS2_SimpleAdmin.Instance.OnMoneyCommand),
new("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
new("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
new("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
new("css_respawn", CS2_SimpleAdmin.Instance.OnRespawnCommand),
new("css_tp", CS2_SimpleAdmin.Instance.OnGotoCommand),
new("css_bring", CS2_SimpleAdmin.Instance.OnBringCommand),
new("css_pluginsmanager", CS2_SimpleAdmin.Instance.OnPluginManagerCommand),
@@ -147,12 +160,23 @@ public static class RegisterCommands
{ "css_addsilence", new Command { Aliases = ["css_addsilence"] } },
{ "css_unsilence", new Command { Aliases = ["css_unsilence"] } },
{ "css_vote", new Command { Aliases = ["css_vote"] } },
{ "css_noclip", new Command { Aliases = ["css_noclip"] } },
{ "css_freeze", new Command { Aliases = ["css_freeze"] } },
{ "css_unfreeze", new Command { Aliases = ["css_unfreeze"] } },
{ "css_godmode", new Command { Aliases = ["css_godmode"] } },
{ "css_slay", new Command { Aliases = ["css_slay"] } },
{ "css_slap", new Command { Aliases = ["css_slap"] } },
{ "css_give", new Command { Aliases = ["css_give"] } },
{ "css_strip", new Command { Aliases = ["css_strip"] } },
{ "css_hp", new Command { Aliases = ["css_hp"] } },
{ "css_speed", new Command { Aliases = ["css_speed"] } },
{ "css_gravity", new Command { Aliases = ["css_gravity"] } },
{ "css_resize", new Command { Aliases = ["css_resize", "css_size"] } },
{ "css_money", new Command { Aliases = ["css_money"] } },
{ "css_team", new Command { Aliases = ["css_team"] } },
{ "css_rename", new Command { Aliases = ["css_rename"] } },
{ "css_prename", new Command { Aliases = ["css_prename"] } },
{ "css_resize", new Command { Aliases = ["css_resize", "css_size"] } },
{ "css_respawn", new Command { Aliases = ["css_respawn"] } },
{ "css_tp", new Command { Aliases = ["css_tp", "css_tpto", "css_goto"] } },
{ "css_bring", new Command { Aliases = ["css_bring", "css_tphere"] } },
{ "css_pluginsmanager", new Command { Aliases = ["css_pluginsmanager", "css_pluginmanager"] } },
@@ -181,26 +205,25 @@ public static class RegisterCommands
var commandsConfig = JsonSerializer.Deserialize<CommandsConfig>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (commandsConfig?.Commands != null)
if (commandsConfig?.Commands == null) return;
foreach (var command in commandsConfig.Commands)
{
foreach (var command in commandsConfig.Commands)
if (command.Value.Aliases == null) continue;
CS2_SimpleAdmin._logger?.LogInformation(
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
if (mapping == null || command.Value.Aliases.Length == 0) continue;
foreach (var alias in command.Value.Aliases)
{
if (command.Value.Aliases == null) continue;
CS2_SimpleAdmin._logger?.LogInformation(
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
if (mapping == null || command.Value.Aliases.Length == 0) continue;
foreach (var alias in command.Value.Aliases)
{
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
}
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
}
}
foreach (var (name, definitions) in _commandDefinitions)
foreach (var (name, definitions) in RegisterCommands._commandDefinitions)
{
foreach (var definition in definitions)
{

View File

@@ -316,7 +316,7 @@ public partial class CS2_SimpleAdmin
var canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
if (duration <= 0 && !canPermBan)
if (duration <= 0 && canPermBan == false)
{
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
return false;

View File

@@ -33,7 +33,7 @@ public partial class CS2_SimpleAdmin
[CommandHelper(usage: "[#userid or name]", whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void OnPenaltiesCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller == null || !caller.IsValid || !caller.UserId.HasValue || DatabaseProvider == null)
if (caller == null || caller.IsValid == false || !caller.UserId.HasValue || DatabaseProvider == null)
return;
var userId = caller.UserId.Value;
@@ -160,7 +160,7 @@ public partial class CS2_SimpleAdmin
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void OnAdminVoiceCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller == null || !caller.IsValid)
if (caller == null || caller.IsValid == false)
return;
if (command.ArgCount > 1)
@@ -205,7 +205,7 @@ public partial class CS2_SimpleAdmin
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void OnAdminCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller == null || !caller.IsValid)
if (caller == null || caller.IsValid == false)
return;
AdminMenu.OpenMenu(caller);

View File

@@ -959,7 +959,7 @@ public partial class CS2_SimpleAdmin
var canPermMute = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permmute");
if (duration <= 0 && !canPermMute)
if (duration <= 0 && canPermMute == false)
{
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
return false;

View File

@@ -1,307 +1,307 @@
// using System.Globalization;
// using CounterStrikeSharp.API;
// using CounterStrikeSharp.API.Core;
// using CounterStrikeSharp.API.Modules.Admin;
// using CounterStrikeSharp.API.Modules.Commands;
//
// namespace CS2_SimpleAdmin;
//
// public partial class CS2_SimpleAdmin
// {
// /// <summary>
// /// Enables or disables no-clip mode for specified player(s).
// /// </summary>
// /// <param name="caller">The player issuing the command.</param>
// /// <param name="command">The command input containing targets.</param>
// [CommandHelper(1, "<#userid or name>")]
// [RequiresPermissions("@css/cheats")]
// public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
//
// var targets = GetTarget(command);
// if (targets == null) return;
// var playersToTarget = targets.Players.Where(player =>
// player.IsValid &&
// player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
//
// playersToTarget.ForEach(player =>
// {
// if (caller!.CanTarget(player))
// {
// NoClip(caller, player, callerName);
// }
// });
//
// Helper.LogCommand(caller, command);
// }
//
// /// <summary>
// /// Toggles no-clip mode for a player and shows admin activity messages.
// /// </summary>
// /// <param name="caller">The player/admin toggling no-clip.</param>
// /// <param name="player">The target player whose no-clip state changes.</param>
// /// <param name="callerName">Optional caller name for messages.</param>
// /// <param name="command">Optional command info for logging.</param>
// internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
// {
// if (!player.IsValid) return;
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
//
// // Toggle no-clip mode for the player
// player.Pawn.Value?.ToggleNoclip();
//
// // Determine message keys and arguments for the no-clip notification
// var (activityMessageKey, adminActivityArgs) =
// ("sa_admin_noclip_message",
// new object[] { "CALLER", player.PlayerName });
//
// // Display admin activity message to other players
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
// {
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
// }
//
// // Log the command
// if (command == null)
// Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// }
//
// /// <summary>
// /// Enables or disables god mode for specified player(s).
// /// </summary>
// /// <param name="caller">The player issuing the command.</param>
// /// <param name="command">The command input containing targets.</param>
//
// [RequiresPermissions("@css/cheats")]
// [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
// public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// var targets = GetTarget(command);
// if (targets == null) return;
//
// var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
//
// playersToTarget.ForEach(player =>
// {
// if (player.Connected != PlayerConnectedState.PlayerConnected)
// return;
//
// if (caller!.CanTarget(player))
// {
// God(caller, player, command);
// }
// });
//
// Helper.LogCommand(caller, command);
// }
//
// /// <summary>
// /// Toggles god mode for a player and notifies admins.
// /// </summary>
// /// <param name="caller">The player/admin toggling god mode.</param>
// /// <param name="player">The target player whose god mode changes.</param>
// /// <param name="command">Optional command info for logging.</param>
// internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
// {
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
//
// // Toggle god mode for the player
// if (!GodPlayers.Add(player.Slot))
// {
// GodPlayers.Remove(player.Slot);
// }
//
// // Log the command
// if (command == null)
// Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
//
// // Determine message key and arguments for the god mode notification
// var (activityMessageKey, adminActivityArgs) =
// ("sa_admin_god_message",
// new object[] { "CALLER", player.PlayerName });
//
// // Display admin activity message to other players
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
// {
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
// }
// }
//
// /// <summary>
// /// Freezes target player(s) for an optional specified duration.
// /// </summary>
// /// <param name="caller">The player issuing the freeze command.</param>
// /// <param name="command">The command input containing targets and duration.</param>
// [CommandHelper(1, "<#userid or name> [duration]")]
// [RequiresPermissions("@css/slay")]
// public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// int.TryParse(command.GetArg(2), out var time);
//
// var targets = GetTarget(command);
// if (targets == null) return;
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
//
// playersToTarget.ForEach(player =>
// {
// if (caller!.CanTarget(player))
// {
// Freeze(caller, player, time, callerName, command);
// }
// });
//
// Helper.LogCommand(caller, command);
// }
//
// /// <summary>
// /// Resizes the target player(s) models to a specified scale.
// /// </summary>
// /// <param name="caller">The player issuing the resize command.</param>
// /// <param name="command">The command input containing targets and scale factor.</param>
// [CommandHelper(1, "<#userid or name> [size]")]
// [RequiresPermissions("@css/slay")]
// public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
//
// var targets = GetTarget(command);
// if (targets == null) return;
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
//
// playersToTarget.ForEach(player =>
// {
// if (!caller!.CanTarget(player)) return;
//
// var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
// if (sceneNode == null) return;
//
// sceneNode.GetSkeletonInstance().Scale = size;
// player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
//
// Server.NextWorldUpdate(() =>
// {
// Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
// });
//
// var (activityMessageKey, adminActivityArgs) =
// ("sa_admin_resize_message",
// new object[] { "CALLER", player.PlayerName });
//
// // Display admin activity message to other players
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
// {
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
// }
// });
//
// Helper.LogCommand(caller, command);
// }
//
// /// <summary>
// /// Freezes a single player and optionally schedules automatic unfreeze after a duration.
// /// </summary>
// /// <param name="caller">The player/admin freezing the player.</param>
// /// <param name="player">The player to freeze.</param>
// /// <param name="time">Duration of freeze in seconds.</param>
// /// <param name="callerName">Optional name for notifications.</param>
// /// <param name="command">Optional command info for logging.</param>
// internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
// {
// if (!player.IsValid) return;
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
//
// // Freeze player pawn
// player.Pawn.Value?.Freeze();
//
// // Determine message keys and arguments for the freeze notification
// var (activityMessageKey, adminActivityArgs) =
// ("sa_admin_freeze_message",
// new object[] { "CALLER", player.PlayerName });
//
// // Display admin activity message to other players
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
// {
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
// }
//
// // Schedule unfreeze for the player if time is specified
// if (time > 0)
// {
// Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
// }
//
// // Log the command and send Discord notification
// if (command == null)
// Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
// }
//
// /// <summary>
// /// Unfreezes target player(s).
// /// </summary>
// /// <param name="caller">The player issuing the unfreeze command.</param>
// /// <param name="command">The command input with targets.</param>
// [CommandHelper(1, "<#userid or name>")]
// [RequiresPermissions("@css/slay")]
// public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
//
// var targets = GetTarget(command);
// if (targets == null) return;
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
//
// playersToTarget.ForEach(player =>
// {
// Unfreeze(caller, player, callerName, command);
// });
//
// Helper.LogCommand(caller, command);
// }
//
// /// <summary>
// /// Unfreezes a single player and notifies admins.
// /// </summary>
// /// <param name="caller">The player/admin unfreezing the player.</param>
// /// <param name="player">The player to unfreeze.</param>
// /// <param name="callerName">Optional name for notifications.</param>
// /// <param name="command">Optional command info for logging.</param>
// internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
// {
// if (!player.IsValid) return;
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
//
// // Unfreeze player pawn
// player.Pawn.Value?.Unfreeze();
//
// // Determine message keys and arguments for the unfreeze notification
// var (activityMessageKey, adminActivityArgs) =
// ("sa_admin_unfreeze_message",
// new object[] { "CALLER", player.PlayerName });
//
// // Display admin activity message to other players
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
// {
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
// }
//
// // Log the command and send Discord notification
// if (command == null)
// Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// }
// }
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Enables or disables no-clip mode for specified player(s).
/// </summary>
/// <param name="caller">The player issuing the command.</param>
/// <param name="command">The command input containing targets.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player =>
player.IsValid &&
player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
NoClip(caller, player, callerName);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Toggles no-clip mode for a player and shows admin activity messages.
/// </summary>
/// <param name="caller">The player/admin toggling no-clip.</param>
/// <param name="player">The target player whose no-clip state changes.</param>
/// <param name="callerName">Optional caller name for messages.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Toggle no-clip mode for the player
player.Pawn.Value?.ToggleNoclip();
// Determine message keys and arguments for the no-clip notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_noclip_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
/// <summary>
/// Enables or disables god mode for specified player(s).
/// </summary>
/// <param name="caller">The player issuing the command.</param>
/// <param name="command">The command input containing targets.</param>
[RequiresPermissions("@css/cheats")]
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.PlayerConnected)
return;
if (caller!.CanTarget(player))
{
God(caller, player, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Toggles god mode for a player and notifies admins.
/// </summary>
/// <param name="caller">The player/admin toggling god mode.</param>
/// <param name="player">The target player whose god mode changes.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Toggle god mode for the player
if (!GodPlayers.Add(player.Slot))
{
GodPlayers.Remove(player.Slot);
}
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// Determine message key and arguments for the god mode notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_god_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Freezes target player(s) for an optional specified duration.
/// </summary>
/// <param name="caller">The player issuing the freeze command.</param>
/// <param name="command">The command input containing targets and duration.</param>
[CommandHelper(1, "<#userid or name> [duration]")]
[RequiresPermissions("@css/slay")]
public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
int.TryParse(command.GetArg(2), out var time);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
Freeze(caller, player, time, callerName, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Resizes the target player(s) models to a specified scale.
/// </summary>
/// <param name="caller">The player issuing the resize command.</param>
/// <param name="command">The command input containing targets and scale factor.</param>
[CommandHelper(1, "<#userid or name> [size]")]
[RequiresPermissions("@css/slay")]
public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
if (sceneNode == null) return;
sceneNode.GetSkeletonInstance().Scale = size;
player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
Server.NextWorldUpdate(() =>
{
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
});
var (activityMessageKey, adminActivityArgs) =
("sa_admin_resize_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Freezes a single player and optionally schedules automatic unfreeze after a duration.
/// </summary>
/// <param name="caller">The player/admin freezing the player.</param>
/// <param name="player">The player to freeze.</param>
/// <param name="time">Duration of freeze in seconds.</param>
/// <param name="callerName">Optional name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Freeze player pawn
player.Pawn.Value?.Freeze();
// Determine message keys and arguments for the freeze notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_freeze_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Schedule unfreeze for the player if time is specified
if (time > 0)
{
Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
}
/// <summary>
/// Unfreezes target player(s).
/// </summary>
/// <param name="caller">The player issuing the unfreeze command.</param>
/// <param name="command">The command input with targets.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/slay")]
public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
Unfreeze(caller, player, callerName, command);
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Unfreezes a single player and notifies admins.
/// </summary>
/// <param name="caller">The player/admin unfreezing the player.</param>
/// <param name="player">The player to unfreeze.</param>
/// <param name="callerName">Optional name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Unfreeze player pawn
player.Pawn.Value?.Unfreeze();
// Determine message keys and arguments for the unfreeze notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_unfreeze_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
}

View File

@@ -11,6 +11,9 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
internal static readonly Dictionary<CCSPlayerController, float> SpeedPlayers = [];
internal static readonly Dictionary<CCSPlayerController, float> GravityPlayers = [];
/// <summary>
/// Executes the 'slay' command, forcing the targeted players to commit suicide.
/// Checks player validity and permissions.
@@ -69,6 +72,451 @@ public partial class CS2_SimpleAdmin
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
/// <summary>
/// Executes the 'give' command to provide a specified weapon to targeted players.
/// Enforces server rules for prohibited weapons.
/// </summary>
/// <param name="caller">Player or console issuing the command.</param>
/// <param name="command">Command details, including targets and weapon name.</param>
[RequiresPermissions("@css/cheats")]
[CommandHelper(minArgs: 2, usage: "<#userid or name> <weapon>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGiveCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var weaponName = command.GetArg(2);
// check if weapon is knife
if (weaponName.Contains("_knife") || weaponName.Contains("bayonet"))
{
if (CoreConfig.FollowCS2ServerGuidelines)
{
command.ReplyToCommand($"Cannot Give {weaponName} because it's illegal to be given.");
return;
}
}
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.PlayerConnected)
return;
GiveWeapon(caller, player, weaponName, callerName, command);
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Gives a weapon identified by name to a player, handling ambiguous matches and logging.
/// </summary>
/// <param name="caller">Admin issuing the command.</param>
/// <param name="player">Target player to receive the weapon.</param>
/// <param name="weaponName">Weapon name or partial name.</param>
/// <param name="callerName">Optional name to display in notifications.</param>
/// <param name="command">Optional command info for logging.</param>
private static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, string weaponName, string? callerName = null, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
var weapons = WeaponHelper.GetWeaponsByPartialName(weaponName);
switch (weapons.Count)
{
case 0:
return;
case > 1:
{
var weaponList = string.Join(", ", weapons.Select(w => w.EnumMemberValue));
command?.ReplyToCommand($"Found weapons with a similar name: {weaponList}");
return;
}
}
// Give weapon to the player
player.GiveNamedItem(weapons.First().EnumValue);
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weaponName}");
// Determine message keys and arguments for the weapon give notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_give_message",
new object[] { "CALLER", player.PlayerName, weaponName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Gives a specific weapon to a player, with notifications and logging.
/// </summary>
/// <param name="caller">Admin issuing the command.</param>
/// <param name="player">Target player.</param>
/// <param name="weapon">Weapon item object.</param>
/// <param name="callerName">Optional caller name for notifications.</param>
/// <param name="command">Optional command info.</param>
internal static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, CsItem weapon, string? callerName = null, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Give weapon to the player
player.GiveNamedItem(weapon);
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weapon.ToString()}");
// Determine message keys and arguments for the weapon give notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_give_message",
new object[] { "CALLER", player.PlayerName, weapon.ToString() });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Executes the 'strip' command, removing all weapons from targeted players.
/// Checks player validity and permissions.
/// </summary>
/// <param name="caller">Player or console issuing the command.</param>
/// <param name="command">Command details including targets.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnStripCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
StripWeapons(caller, player, callerName, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Removes all weapons from a player, with notifications and logging.
/// </summary>
/// <param name="caller">Admin or console issuing the strip command.</param>
/// <param name="player">Target player.</param>
/// <param name="callerName">Optional caller name.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void StripWeapons(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Check if player is valid, alive, and connected
if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.Connected != PlayerConnectedState.PlayerConnected)
return;
// Strip weapons from the player
player.RemoveWeapons();
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_strip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// Determine message keys and arguments for the weapon strip notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_strip_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Sets health value on targeted players.
/// </summary>
/// <param name="caller">Admin or console issuing the command.</param>
/// <param name="command">Command details including targets and health value.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <health>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnHpCommand(CCSPlayerController? caller, CommandInfo command)
{
int.TryParse(command.GetArg(2), out var health);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
SetHp(caller, player, health, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Changes health of a player and logs the action.
/// </summary>
/// <param name="caller">Admin or console calling the method.</param>
/// <param name="player">Target player.</param>
/// <param name="health">Health value to set.</param>
/// <param name="command">Optional command info.</param>
internal static void SetHp(CCSPlayerController? caller, CCSPlayerController player, int health, CommandInfo? command = null)
{
if (!player.IsValid || player.IsHLTV) return;
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Set player's health
player.SetHp(health);
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_hp {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {health}");
// Determine message keys and arguments for the HP set notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_hp_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Sets movement speed on targeted players.
/// </summary>
/// <param name="caller">Admin or console issuing the command.</param>
/// <param name="command">Command details including targets and speed.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <speed>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSpeedCommand(CCSPlayerController? caller, CommandInfo command)
{
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var speed);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.PlayerConnected)
return;
if (caller!.CanTarget(player))
{
SetSpeed(caller, player, speed, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Changes speed of a player and logs the action.
/// </summary>
/// <param name="caller">Admin or console calling the method.</param>
/// <param name="player">Target player.</param>
/// <param name="speed">Speed value to set.</param>
/// <param name="command">Optional command info.</param>
internal static void SetSpeed(CCSPlayerController? caller, CCSPlayerController player, float speed, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Set player's speed
player.SetSpeed(speed);
if (speed == 1f)
SpeedPlayers.Remove(player);
else
SpeedPlayers[player] = speed;
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_speed {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {speed}");
// Determine message keys and arguments for the speed set notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_speed_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Sets gravity on targeted players.
/// </summary>
/// <param name="caller">Admin or console issuing the command.</param>
/// <param name="command">Command details including targets and gravity value.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <gravity>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGravityCommand(CCSPlayerController? caller, CommandInfo command)
{
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var gravity);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.PlayerConnected)
return;
if (caller!.CanTarget(player))
{
SetGravity(caller, player, gravity, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Changes gravity of a player and logs the action.
/// </summary>
/// <param name="caller">Admin or console calling the method.</param>
/// <param name="player">Target player.</param>
/// <param name="gravity">Gravity value to set.</param>
/// <param name="command">Optional command info.</param>
internal static void SetGravity(CCSPlayerController? caller, CCSPlayerController player, float gravity, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Set player's gravity
player.SetGravity(gravity);
if (gravity == 1f)
GravityPlayers.Remove(player);
else
GravityPlayers[player] = gravity;
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_gravity {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {gravity}");
// Determine message keys and arguments for the gravity set notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_gravity_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Sets the money amount for the targeted players.
/// </summary>
/// <param name="caller">The player/admin executing the command.</param>
/// <param name="command">The command containing target player and money value.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <money>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnMoneyCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
int.TryParse(command.GetArg(2), out var money);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.PlayerConnected)
return;
if (caller!.CanTarget(player))
{
SetMoney(caller, player, money, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Applies money value to a single targeted player and logs the operation.
/// </summary>
/// <param name="caller">The player/admin setting the money.</param>
/// <param name="player">The player whose money will be set.</param>
/// <param name="money">The value of money to set.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void SetMoney(CCSPlayerController? caller, CCSPlayerController player, int money, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Set player's money
player.SetMoney(money);
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_money {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {money}");
// Determine message keys and arguments for the money set notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_money_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Applies damage as a slap effect to the targeted players.
/// </summary>
@@ -354,6 +802,75 @@ public partial class CS2_SimpleAdmin
});
}
/// <summary>
/// Respawns targeted players, restoring their state.
/// </summary>
/// <param name="caller">The admin or player issuing respawn.</param>
/// <param name="command">The command including target players.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
public void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.PlayerConnected)
return;
if (caller!.CanTarget(player))
{
Respawn(caller, player, callerName, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Respawns a specified player and updates admin notifications.
/// </summary>
/// <param name="caller">Admin or player executing respawn.</param>
/// <param name="player">Player to respawn.</param>
/// <param name="callerName">Optional admin name.</param>
/// <param name="command">Optional command info.</param>
internal static void Respawn(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
// Check if the caller can target the player
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Ensure the player's pawn is valid before attempting to respawn
if (_cBasePlayerControllerSetPawnFunc == null || player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) return;
// Perform the respawn operation
var playerPawn = player.PlayerPawn.Value;
_cBasePlayerControllerSetPawnFunc.Invoke(player, playerPawn, true, false);
VirtualFunction.CreateVoid<CCSPlayerController>(player.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(player);
if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.SteamID, out var value) && value.DiePosition != null)
playerPawn.Teleport(value.DiePosition?.Position, value.DiePosition?.Angle);
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// Determine message key and arguments for the respawn notification
var activityMessageKey = "sa_admin_respawn_message";
var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
// Display admin activity message to other players
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
/// <summary>
/// Teleports targeted player(s) to another player's location.
/// </summary>

View File

@@ -17,27 +17,16 @@ public class Migration(string migrationsPath)
if (files.Count == 0) return;
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
await using (var cmd = connection.CreateCommand())
{
if (migrationsPath.Contains("sqlite", StringComparison.CurrentCultureIgnoreCase))
{
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS sa_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version TEXT NOT NULL
);
""";
}
else
{
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS sa_migrations (
id INT PRIMARY KEY AUTO_INCREMENT,
version VARCHAR(128) NOT NULL
);
""";
}
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS sa_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version TEXT NOT NULL
);
""";
await cmd.ExecuteNonQueryAsync();
}

View File

@@ -20,6 +20,7 @@ CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `sa_admins` ADD `group_id` INT NULL AFTER `created`;
ALTER TABLE `sa_groups_flags` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
ALTER TABLE `sa_groups_servers` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
ALTER TABLE `sa_admins` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE SET NULL;

View File

@@ -1,6 +1,8 @@
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`steamid` bigint(20) NOT NULL,
`address` varchar(64) NOT NULL,
`used_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`steamid`, `address`)
PRIMARY KEY (`id`),
UNIQUE KEY `steamid` (`steamid`,`address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@@ -1,3 +1,11 @@
DELETE FROM `sa_players_ips`
WHERE `id` NOT IN (
SELECT * FROM (
SELECT MIN(`id`)
FROM `sa_players_ips`
GROUP BY `steamid`
) AS `keep_ids`
);
DELETE FROM sa_players_ips WHERE INET_ATON(address) IS NULL AND address IS NOT NULL;
UPDATE `sa_players_ips` SET `address` = INET_ATON(address);
ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL;

View File

@@ -1,33 +0,0 @@
-- Migration 016: Optimize tables and indexes
-- Add proper indexes for all tables to improve query performance
-- Optimize sa_players_ips table indexes
-- Add index on used_at for efficient date-based queries
ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
-- Optimize sa_bans table indexes
-- Add composite indexes for common query patterns
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
-- Optimize sa_admins table indexes
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
-- Optimize sa_mutes table indexes (in addition to migration 014)
-- Add index for expire queries
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
-- Optimize sa_warns table indexes (if exists)
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
-- Add index on sa_servers for faster lookups
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);

View File

@@ -1,6 +1,7 @@
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`steamid` INTEGER NOT NULL,
`address` VARCHAR(64) NOT NULL,
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`steamid`, `address`)
);
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (`steamid`, `address`)
);

View File

@@ -1,33 +0,0 @@
-- Migration 016: Optimize tables and indexes
-- Add proper indexes for all tables to improve query performance
-- Optimize sa_players_ips table indexes
-- Add index on used_at for efficient date-based queries
CREATE INDEX IF NOT EXISTS `idx_used_at` ON `sa_players_ips` (`used_at` DESC);
-- Optimize sa_bans table indexes
-- Add composite indexes for common query patterns
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
-- Optimize sa_admins table indexes
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
-- Optimize sa_mutes table indexes (in addition to migration 014)
-- Add index for expire queries
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
-- Optimize sa_warns table indexes (if exists)
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
-- Add index on sa_servers for faster lookups
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);

View File

@@ -9,15 +9,6 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
{
var connection = new MySqlConnection(connectionString);
await connection.OpenAsync();
await using var cmd = connection.CreateCommand();
cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
await cmd.ExecuteNonQueryAsync();
cmd.CommandText = "SET time_zone = '+00:00';";
await cmd.ExecuteNonQueryAsync();
return connection;
}

View File

@@ -96,6 +96,9 @@ public partial class CS2_SimpleAdmin
CachedPlayers.Remove(player);
SilentPlayers.Remove(player.Slot);
GodPlayers.Remove(player.Slot);
SpeedPlayers.Remove(player);
GravityPlayers.Remove(player);
if (player.IsBot)
return HookResult.Continue;
@@ -244,6 +247,10 @@ public partial class CS2_SimpleAdmin
Logger.LogCritical("[OnRoundStart]");
#endif
GodPlayers.Clear();
SpeedPlayers.Clear();
GravityPlayers.Clear();
foreach (var player in PlayersInfo.Values)
{
player.DiePosition = null;
@@ -460,6 +467,8 @@ public partial class CS2_SimpleAdmin
GodPlayers.Clear();
SilentPlayers.Clear();
SpeedPlayers.Clear();
GravityPlayers.Clear();
PlayerPenaltyManager.RemoveAllPenalties();
}
@@ -485,9 +494,13 @@ public partial class CS2_SimpleAdmin
{
var player = @event.Userid;
if (player?.UserId == null || !player.IsValid || player.IsHLTV ||
player.Connected != PlayerConnectedState.PlayerConnected || !PlayersInfo.ContainsKey(player.SteamID) ||
@event.Attacker == null)
if (player?.UserId == null || !player.IsValid || player.IsHLTV || player.Connected != PlayerConnectedState.PlayerConnected)
return HookResult.Continue;
SpeedPlayers.Remove(player);
GravityPlayers.Remove(player);
if (!PlayersInfo.ContainsKey(player.SteamID) || @event.Attacker == null)
return HookResult.Continue;
var playerPosition = player.PlayerPawn.Value?.AbsOrigin;

View File

@@ -335,10 +335,10 @@ public static class PlayerExtensions
{
StringBuilder sb = new();
sb.Append(localizer[messageKey, messageArgs]);
foreach (var part in Helper.SeparateLines(sb.ToString()))
{
var lineWithPrefix = (CS2_SimpleAdmin._localizer?["sa_prefix"] ?? "") + part.Trim();
var lineWithPrefix = localizer["sa_prefix"] + part.Trim();
controller.PrintToChat(lineWithPrefix);
}
}

View File

@@ -441,20 +441,20 @@ internal static class Helper
public static void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
{
string[] publishActions = ["ban", "gag", "silence", "mute"];
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
if (CS2_SimpleAdmin._localizer == null) return;
if (string.IsNullOrWhiteSpace(callerName))
callerName = CS2_SimpleAdmin._localizer["sa_console"];
var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();
if (!dontPublish && publishActions.Any(messageKey.Contains))
if (dontPublish == false && publishActions.Any(messageKey.Contains))
{
CS2_SimpleAdmin.SimpleAdminApi?.OnAdminShowActivityEvent(messageKey, callerName, dontPublish, messageArgs);
}
// // Replace placeholder based on showActivityType
// for (var i = 0; i < formattedMessageArgs.Length; i++)
// {
@@ -478,7 +478,7 @@ internal static class Helper
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
}
foreach (var controller in validPlayers.ToList())
{
var currentMessageArgs = (string[])formattedMessageArgs.Clone();
@@ -499,89 +499,6 @@ internal static class Helper
}
}
/// <summary>
/// Shows admin activity with a custom translated message (for modules with their own localizer).
/// </summary>
public static void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false)
{
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
if (CS2_SimpleAdmin._localizer == null) return;
if (string.IsNullOrWhiteSpace(callerName))
callerName = CS2_SimpleAdmin._localizer["sa_console"];
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
if (!validPlayers.Any())
return;
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
{
validPlayers = validPlayers.Where(c =>
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
}
foreach (var controller in validPlayers.ToList())
{
// Replace "CALLER" placeholder based on showActivityType
var message = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
{
1 => translatedMessage.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
_ => translatedMessage.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
};
// Send the pre-translated message to the player
controller.PrintToChat(message);
}
}
/// <summary>
/// Shows admin activity using module's localizer for per-player language support.
/// Each player receives the message in their configured language using SendLocalizedMessage.
/// </summary>
public static void ShowAdminActivityLocalized(IStringLocalizer moduleLocalizer, string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
{
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
if (CS2_SimpleAdmin._localizer == null) return;
if (string.IsNullOrWhiteSpace(callerName))
callerName = CS2_SimpleAdmin._localizer["sa_console"];
var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
if (!validPlayers.Any())
return;
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
{
validPlayers = validPlayers.Where(c =>
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
}
foreach (var controller in validPlayers.ToList())
{
var currentMessageArgs = (string[])formattedMessageArgs.Clone();
// Replace "CALLER" placeholder based on showActivityType
for (var i = 0; i < currentMessageArgs.Length; i++)
{
var arg = currentMessageArgs[i];
currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
{
1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
_ => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
};
}
// Send the localized message to each player using their language
controller.SendLocalizedMessage(moduleLocalizer, messageKey, currentMessageArgs.Cast<object>().ToArray());
}
}
public static void DisplayCenterMessage(
CCSPlayerController player,
string messageKey,
@@ -877,7 +794,7 @@ internal static class Helper
if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || CS2_SimpleAdmin._localizer == null)
return;
if (caller != null && !caller.IsValid)
if (caller != null && caller.IsValid == false)
caller = null;
var callerName = caller == null ? CS2_SimpleAdmin._localizer["sa_console"] : caller.PlayerName;
@@ -889,38 +806,32 @@ internal static class Helper
commandString]));
}
#pragma warning disable CS8604
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null, Action<CCSPlayerController>? resetAction = null)
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
{
if (CS2_SimpleAdmin.MenuApi == null)
{
return null;
}
var menuType = CS2_SimpleAdmin.Instance.Config.MenuConfigs.MenuType.ToLower();
var menu = menuType switch
{
_ when menuType.Equals("selectable", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction),
CS2_SimpleAdmin.MenuApi?.GetMenu(title),
_ when menuType.Equals("dynamic", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ButtonMenu, backAction, resetAction),
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ButtonMenu),
_ when menuType.Equals("center", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.CenterMenu, backAction, resetAction),
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.CenterMenu),
_ when menuType.Equals("chat", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ChatMenu, backAction, resetAction),
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ChatMenu),
_ when menuType.Equals("console", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ConsoleMenu, backAction, resetAction),
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ConsoleMenu),
_ => CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction)
_ => CS2_SimpleAdmin.MenuApi?.GetMenu(title)
};
return menu;
}
#pragma warning restore CS8604
internal static IPluginManager? GetPluginManager()
{

View File

@@ -29,6 +29,7 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
try
{
var sql = databaseProvider.GetAddBanQuery();
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = player.SteamId.SteamId64,

View File

@@ -70,44 +70,39 @@ internal class CacheManager: IDisposable
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
{
// Optimization: Load IP history and build cache in single pass
var ipHistory = await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY steamid, address, used_at DESC");
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var currentSteamId = 0UL;
var currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
var latestIpTimestamps = new Dictionary<uint, DateTime>();
foreach (var record in ipHistory)
var ipHistory =
(await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC")).ToList();
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
{
// When we encounter a new steamid, save the previous one
if (record.steamid != currentSteamId && currentSteamId != 0)
{
_playerIpsCache[currentSteamId] = currentIpSet;
currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
latestIpTimestamps.Clear();
}
var ipSet = group
.GroupBy(x => x.address)
.Select(g =>
{
var latest = g.MaxBy(x => x.used_at);
return new IpRecord(
g.Key,
latest.used_at,
string.IsNullOrEmpty(latest.name)
? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
: latest.name
);
})
.ToHashSet(new IpRecordComparer());
currentSteamId = record.steamid;
// Only keep the latest timestamp for each IP
if (!latestIpTimestamps.TryGetValue(record.address, out var existingTimestamp) ||
record.used_at > existingTimestamp)
{
latestIpTimestamps[record.address] = record.used_at;
currentIpSet.Add(new IpRecord(
record.address,
record.used_at,
string.IsNullOrEmpty(record.name) ? unknownName : record.name
));
}
}
// Don't forget the last steamid
if (currentSteamId != 0)
{
_playerIpsCache[currentSteamId] = currentIpSet;
_playerIpsCache.AddOrUpdate(
group.Key,
_ => ipSet,
(_, existingSet) =>
{
foreach (var ip in ipSet)
{
existingSet.Remove(ip);
existingSet.Add(ip);
}
return existingSet;
});
}
}
@@ -155,10 +150,8 @@ internal class CacheManager: IDisposable
{
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
IEnumerable<BanRecord> updatedBans;
// Optimization: Only get IDs for comparison if we need to check for deletions
// Most of the time bans are just added/updated, not deleted
HashSet<int>? allIds = null;
var allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
@@ -168,19 +161,11 @@ internal class CacheManager: IDisposable
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
status AS Status
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime }
));
// Optimization: Only fetch all IDs if there were updates
var updatedList = updatedBans.ToList();
if (updatedList.Count > 0)
{
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
}
updatedBans = updatedList;
}
else
{
@@ -190,47 +175,32 @@ internal class CacheManager: IDisposable
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
status AS Status
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
));
// Optimization: Only fetch all IDs if there were updates
var updatedList = updatedBans.ToList();
if (updatedList.Count > 0)
{
allIds = (await connection.QueryAsync<int>(
"SELECT id FROM sa_bans WHERE server_id = @serverId",
new { serverId = CS2_SimpleAdmin.ServerId }
)).ToHashSet();
}
updatedBans = updatedList;
}
// Optimization: Only process deletions if we have the full ID list
if (allIds != null)
foreach (var id in _banCache.Keys)
{
foreach (var id in _banCache.Keys)
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
if (ban.PlayerSteamId != null &&
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
{
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
steamBans.RemoveAll(b => b.Id == id);
if (steamBans.Count == 0)
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
}
if (ban.PlayerSteamId != null &&
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
{
steamBans.RemoveAll(b => b.Id == id);
if (steamBans.Count == 0)
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
}
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
{
ipBans.RemoveAll(b => b.Id == id);
if (ipBans.Count == 0)
_ipIndex.TryRemove(ipUInt, out _);
}
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
{
ipBans.RemoveAll(b => b.Id == id);
if (ipBans.Count == 0)
_ipIndex.TryRemove(ipUInt, out _);
}
}
@@ -275,21 +245,12 @@ internal class CacheManager: IDisposable
}
}
// Update cache with new/modified bans
var hasUpdates = false;
foreach (var ban in updatedBans)
{
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
hasUpdates = true;
}
// Always rebuild indexes if there were any updates
// This ensures status changes (ACTIVE -> UNBANNED) are reflected
if (hasUpdates)
{
RebuildIndexes();
}
RebuildIndexes();
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
}
catch (Exception)
@@ -305,38 +266,38 @@ internal class CacheManager: IDisposable
{
_steamIdIndex.Clear();
_ipIndex.Clear();
// Optimization: Cache config value to avoid repeated property access
var banType = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType;
var checkIpBans = banType != 0;
// Optimization: Pre-filter only ACTIVE bans to avoid checking status in loop
var activeBans = _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE);
foreach (var ban in activeBans)
foreach (var ban in _banCache.Values)
{
// Index by Steam ID
if (ban.PlayerSteamId.HasValue)
{
var steamId = ban.PlayerSteamId.Value;
if (!_steamIdIndex.TryGetValue(steamId, out var steamList))
{
steamList = new List<BanRecord>();
_steamIdIndex[steamId] = steamList;
}
steamList.Add(ban);
}
if (ban.StatusEnum != BanStatus.ACTIVE)
continue;
// Index by IP (only if IP bans are enabled)
if (checkIpBans && !string.IsNullOrEmpty(ban.PlayerIp) &&
if (ban.PlayerSteamId != null)
{
var steamId = ban.PlayerSteamId;
_steamIdIndex.AddOrUpdate(
steamId.Value,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) continue;
if (ban.PlayerIp != null &&
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
{
if (!_ipIndex.TryGetValue(ipUInt, out var ipList))
{
ipList = new List<BanRecord>();
_ipIndex[ipUInt] = ipList;
}
ipList.Add(ban);
_ipIndex.AddOrUpdate(
ipUInt,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
}
}
@@ -371,16 +332,16 @@ internal class CacheManager: IDisposable
{
var ipAsUint = IpHelper.IpToUint(ipAddress);
var results = new List<(ulong, DateTime, string)>();
// Optimization: Direct lookup using HashSet.Contains instead of TryGetValue
var searchRecord = new IpRecord(ipAsUint, default, null!);
var comparer = _playerIpsCache.Comparer;
foreach (var (steamId, ipSet) in _playerIpsCache)
{
// Optimization: Single pass through the set
if (!ipSet.TryGetValue(new IpRecord(ipAsUint, Time.ActualDateTime(), "Unknown"), out var actualEntry)) continue;
results.Add((steamId, actualEntry.UsedAt, actualEntry.PlayerName));
foreach (var entry in ipSet)
{
if (entry.Ip == ipAsUint)
if (entry.Ip == ipAsUint && !Equals(entry, actualEntry))
{
results.Add((steamId, entry.UsedAt, entry.PlayerName));
}

View File

@@ -95,36 +95,66 @@ internal class PlayerManager
{
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
// Eliminates the need for SELECT COUNT and duplicate UPDATE queries
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
var ipUint = IpHelper.IpToUint(ipAddress);
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE pattern
const string upsertQuery = """
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
used_at = CURRENT_TIMESTAMP,
name = @playerName;
""";
await connection.ExecuteAsync(upsertQuery, new
if (CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(
steamId, ipAddress))
{
SteamID = steamId64,
playerName,
IPAddress = ipUint
});
const string updateQuery = """
UPDATE `sa_players_ips`
SET used_at = CURRENT_TIMESTAMP,
name = @playerName
WHERE steamid = @SteamID AND address = @IPAddress;
""";
await connection.ExecuteAsync(updateQuery, new
{
playerName,
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
else
{
const string selectQuery =
"SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
{
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
});
// // Cache will be updated on next refresh cycle
// if (!CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(steamId, ipAddress))
// {
// // IP association will be reflected after cache refresh
// }
if (recordExists > 0)
{
const string updateQuery = """
UPDATE `sa_players_ips`
SET used_at = CURRENT_TIMESTAMP,
name = @playerName
WHERE steamid = @SteamID AND address = @IPAddress;
""";
await connection.ExecuteAsync(updateQuery, new
{
playerName,
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
else
{
const string insertQuery = """
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP);
""";
await connection.ExecuteAsync(insertQuery, new
{
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
playerName,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
}
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(
$"Unable to save ip address for {playerInfo.Name} ({ipAddress}): {ex.Message}");
$"Unable to save ip address for {playerInfo.Name} ({ipAddress}) {ex.Message}");
}
playerInfo.AccountsAssociated =
@@ -283,6 +313,31 @@ internal class PlayerManager
/// </remarks>
public void CheckPlayersTimer()
{
CS2_SimpleAdmin.Instance.AddTimer(0.12f, () =>
{
if (CS2_SimpleAdmin.SpeedPlayers.Count > 0)
{
foreach (var (player, speed) in CS2_SimpleAdmin.SpeedPlayers)
{
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.SetSpeed(speed);
}
}
}
if (CS2_SimpleAdmin.GravityPlayers.Count > 0)
{
foreach (var (player, gravity) in CS2_SimpleAdmin.GravityPlayers)
{
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.SetGravity(gravity);
}
}
}
}, TimerFlags.REPEAT);
CS2_SimpleAdmin.Instance.PlayersTimer = CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
{
#if DEBUG
@@ -291,26 +346,18 @@ internal class PlayerManager
if (CS2_SimpleAdmin.DatabaseProvider == null)
return;
// Optimization: Get players once and avoid allocating anonymous types
var validPlayers = Helper.GetValidPlayers();
if (validPlayers.Count == 0)
return;
// Use ValueTuple instead of anonymous type - better performance and less allocations
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
foreach (var p in validPlayers)
{
tempPlayers.Add((p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot));
}
var tempPlayers = Helper.GetValidPlayers()
.Select(p => new
{
p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot,
})
.ToList();
var pluginInstance = CS2_SimpleAdmin.Instance;
var config = _config.OtherSettings; // Cache config access
_ = Task.Run(async () =>
{
try
{
// Run all expire tasks in parallel
var expireTasks = new[]
{
pluginInstance.BanManager.ExpireOldBans(),
@@ -337,33 +384,22 @@ internal class PlayerManager
if (pluginInstance.CacheManager == null)
return;
// Optimization: Cache ban type and multi-account check to avoid repeated config access
var banType = config.BanType;
var checkMultiAccounts = config.CheckMultiAccountsByIp;
var bannedPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>();
// Manual loop instead of LINQ - better performance
foreach (var player in tempPlayers)
{
var playerName = player.PlayerName;
var steamId = player.SteamID;
var ip = player.IpAddress?.Split(':')[0];
bool isBanned = banType switch
var bannedPlayers = tempPlayers.AsValueEnumerable()
.Where(player =>
{
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
_ => checkMultiAccounts
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
};
var playerName = player.PlayerName;
var steamId = player.SteamID;
var ip = player.IpAddress?.Split(':')[0];
if (isBanned)
{
bannedPlayers.Add(player);
}
}
return CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
{
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
};
}).ToList();
if (bannedPlayers.Count > 0)
{
@@ -374,38 +410,32 @@ internal class PlayerManager
}
}
if (config.TimeMode == 0)
if (_config.OtherSettings.TimeMode == 0)
{
// Optimization: Manual projection instead of LINQ
var onlinePlayers = new List<(ulong, int?, int)>(tempPlayers.Count);
foreach (var player in tempPlayers)
{
onlinePlayers.Add((player.SteamID, player.UserId, player.Slot));
}
if (onlinePlayers.Count > 0)
{
var onlinePlayers = tempPlayers.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList();
if (tempPlayers.Count == 0 || onlinePlayers.Count == 0) return;
await pluginInstance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
}
}
});
try
{
// Optimization: Process penalties without LINQ allocations
var players = Helper.GetValidPlayers();
foreach (var player in players)
var penalizedSlots = players
.Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
.Select(player => new
{
Player = player,
IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _),
IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _),
IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _)
});
foreach (var entry in penalizedSlots)
{
if (!PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
continue;
var isMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _);
var isSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _);
// Only reset voice flags if not muted or silenced
if (!isMuted && !isSilenced)
if (!entry.IsMuted && !entry.IsSilenced)
{
player.VoiceFlags = VoiceFlags.Normal;
entry.Player.VoiceFlags = VoiceFlags.Normal;
}
}

View File

@@ -35,43 +35,29 @@ public class ServerManager
{
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
// Optimization: Get server IP once and reuse
var serverIp = Helper.GetServerIp();
var isInvalidIp = string.IsNullOrEmpty(serverIp) || serverIp.StartsWith("0.0.0");
// Check if we've exceeded retry limit with invalid IP
if (_getIpTryCount > 32 && isInvalidIp)
if (_getIpTryCount > 32 && Helper.GetServerIp().StartsWith("0.0.0.0") || string.IsNullOrEmpty(Helper.GetServerIp()))
{
CS2_SimpleAdmin._logger?.LogError("Unable to load server data - can't fetch ip address!");
return;
}
// Optimization: Cache ConVar lookups
var ipConVar = ConVar.Find("ip");
var ipAddress = ipConVar?.StringValue;
// Use Helper IP if ConVar IP is invalid
var ipAddress = ConVar.Find("ip")?.StringValue;
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
{
ipAddress = serverIp;
ipAddress = Helper.GetServerIp();
// Retry if still invalid and under retry limit
if (_getIpTryCount <= 32 && isInvalidIp)
if (_getIpTryCount <= 32 && (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")))
{
_getIpTryCount++;
LoadServerData();
return;
}
}
// Optimization: Cache remaining ConVar lookups
var hostportConVar = ConVar.Find("hostport");
var hostnameConVar = ConVar.Find("hostname");
var rconPasswordConVar = ConVar.Find("rcon_password");
var address = $"{ipAddress}:{hostportConVar?.GetPrimitiveValue<int>()}";
var hostname = hostnameConVar?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var rconPassword = rconPasswordConVar?.StringValue ?? "";
var address = $"{ipAddress}:{ConVar.Find("hostport")?.GetPrimitiveValue<int>()}";
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var rconPassword = ConVar.Find("rcon_password")?.StringValue ?? "";
CS2_SimpleAdmin.IpAddress = address;
Task.Run(async () =>
@@ -132,8 +118,6 @@ public class ServerManager
}
}
});
CS2_SimpleAdmin.SimpleAdminApi?.OnSimpleAdminReadyEvent();
});
}
}

View File

@@ -7,11 +7,6 @@ namespace CS2_SimpleAdmin.Menus;
public static class AdminMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
MenuManager.Instance.OpenMainMenu(admin);
}
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
{
return Helper.CreateMenu(title, backAction);
@@ -32,44 +27,44 @@ public static class AdminMenu
// }
}
// public static void OpenMenu(CCSPlayerController admin)
// {
// if (admin.IsValid == false)
// return;
//
// var localizer = CS2_SimpleAdmin._localizer;
// if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
// {
// admin.PrintToChat(localizer?["sa_prefix"] ??
// "[SimpleAdmin] " +
// (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
// );
// return;
// }
//
// var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
// List<ChatMenuOptionData> options =
// [
// new(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
// new(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
// new(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
// ];
//
// var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
// if (customCommands.Count > 0)
// {
// options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
// }
//
// if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
// options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
//
// foreach (var menuOptionData in options)
// {
// var menuName = menuOptionData.Name;
// menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
// }
//
// if (menu != null) OpenMenu(admin, menu);
// }
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
List<ChatMenuOptionData> options =
[
new(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
new(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
new(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
];
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
if (customCommands.Count > 0)
{
options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
}
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) OpenMenu(admin, menu);
}
}

View File

@@ -1,602 +0,0 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
using CS2_SimpleAdminApi;
namespace CS2_SimpleAdmin.Menus;
public abstract class BasicMenu
{
/// <summary>
/// Initializes all menus in the system by registering them with the MenuManager.
/// </summary>
public static void Initialize()
{
var manager = MenuManager.Instance;
// Players category menus
manager.RegisterMenu("players", "slap", "Slap Player", CreateSlapMenu, "@css/slay");
manager.RegisterMenu("players", "slay", "Slay Player", CreateSlayMenu, "@css/slay");
manager.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
manager.RegisterMenu("players", "warn", "Warn Player", CreateWarnMenu, "@css/kick");
manager.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
manager.RegisterMenu("players", "gag", "Gag Player", CreateGagMenu, "@css/chat");
manager.RegisterMenu("players", "mute", "Mute Player", CreateMuteMenu, "@css/chat");
manager.RegisterMenu("players", "silence", "Silence Player", CreateSilenceMenu, "@css/chat");
manager.RegisterMenu("players", "team", "Force Team", CreateForceTeamMenu, "@css/kick");
// Server category menus
manager.RegisterMenu("server", "plugins", "Manage Plugins", CreatePluginsMenu, "@css/root");
manager.RegisterMenu("server", "changemap", "Change Map", CreateChangeMapMenu, "@css/changemap");
manager.RegisterMenu("server", "restart", "Restart Game", CreateRestartGameMenu, "@css/generic");
manager.RegisterMenu("server", "custom", "Custom Commands", CreateCustomCommandsMenu, "@css/generic");
// Admin category menus
manager.RegisterMenu("admin", "add", "Add Admin", CreateAddAdminMenu, "@css/root");
manager.RegisterMenu("admin", "remove", "Remove Admin", CreateRemoveAdminMenu, "@css/root");
manager.RegisterMenu("admin", "reload", "Reload Admins", CreateReloadAdminsMenu, "@css/root");
}
/// <summary>
/// Creates menu for slapping players with selectable damage amounts.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the slap menu.</returns>
private static MenuBuilder CreateSlapMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var slapMenu = new MenuBuilder(localizer?["sa_slap"] ?? "Slap Player");
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, player));
}
return slapMenu.WithBackButton();
}
/// <summary>
/// Creates damage selection submenu for slapping a specific player.
/// </summary>
/// <param name="admin">The admin player executing the slap.</param>
/// <param name="target">The target player to be slapped.</param>
/// <returns>A MenuBuilder instance for the slap damage menu.</returns>
private static MenuBuilder CreateSlapDamageMenu(CCSPlayerController admin, CCSPlayerController target)
{
var slapDamageMenu = new MenuBuilder($"Slap: {target.PlayerName}");
var damages = new[] { 0, 1, 5, 10, 50, 100 };
foreach (var damage in damages)
{
slapDamageMenu.AddOption($"{damage} HP", _ =>
{
if (target.IsValid)
{
CS2_SimpleAdmin.Slap(admin, target, damage);
// Keep menu open for consecutive slaps
CreateSlapDamageMenu(admin, target).OpenMenu(admin);
}
});
}
return slapDamageMenu.WithBackButton();
}
/// <summary>
/// Creates menu for slaying (killing) players.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the slay menu.</returns>
private static MenuBuilder CreateSlayMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var slayMenu = new MenuBuilder(localizer?["sa_slay"] ?? "Slay Player");
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
slayMenu.AddOption(playerName, _ =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Slay(admin, player);
}
});
}
return slayMenu.WithBackButton();
}
/// <summary>
/// Creates menu for kicking players with reason selection.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the kick menu.</returns>
private static MenuBuilder CreateKickMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var kickMenu = new MenuBuilder(localizer?["sa_kick"] ?? "Kick Player");
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
(_, _, reason) =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
}
}));
}
return kickMenu.WithBackButton();
}
/// <summary>
/// Creates menu for warning players with duration and reason selection.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the warn menu.</returns>
private static MenuBuilder CreateWarnMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var warnMenu = new MenuBuilder(localizer?["sa_warn"] ?? "Warn Player");
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Warn",
(_, _, duration) => CreateReasonMenu(admin, player, "Warn", PenaltyType.Warn,
(_, _, reason) =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
}
})));
}
return warnMenu.WithBackButton();
}
/// <summary>
/// Creates menu for banning players with duration and reason selection.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the ban menu.</returns>
private static MenuBuilder CreateBanMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var banMenu = new MenuBuilder(localizer?["sa_ban"] ?? "Ban Player");
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Ban",
(_, _, duration) => CreateReasonMenu(admin, player, "Ban", PenaltyType.Ban,
(_, _, reason) =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
}
})));
}
return banMenu.WithBackButton();
}
/// <summary>
/// Creates menu for gagging (text chat muting) players with duration and reason selection.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the gag menu.</returns>
private static MenuBuilder CreateGagMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var gagMenu = new MenuBuilder(localizer?["sa_gag"] ?? "Gag Player");
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Gag",
(_, _, duration) => CreateReasonMenu(admin, player, "Gag", PenaltyType.Gag,
(_, _, reason) =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
}
})));
}
return gagMenu.WithBackButton();
}
/// <summary>
/// Creates menu for muting (voice chat muting) players with duration and reason selection.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the mute menu.</returns>
private static MenuBuilder CreateMuteMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var muteMenu = new MenuBuilder(localizer?["sa_mute"] ?? "Mute Player");
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Mute",
(_, _, duration) => CreateReasonMenu(admin, player, "Mute", PenaltyType.Mute,
(_, _, reason) =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
}
})));
}
return muteMenu.WithBackButton();
}
/// <summary>
/// Creates menu for silencing (both text and voice chat muting) players with duration and reason selection.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the silence menu.</returns>
private static MenuBuilder CreateSilenceMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var silenceMenu = new MenuBuilder(localizer?["sa_silence"] ?? "Silence Player");
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Silence",
(_, _, duration) => CreateReasonMenu(admin, player, "Silence", PenaltyType.Silence,
(_, _, reason) =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
}
})));
}
return silenceMenu.WithBackButton();
}
/// <summary>
/// Creates menu for forcing players to switch teams.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the force team menu.</returns>
private static MenuBuilder CreateForceTeamMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var teamMenu = new MenuBuilder(localizer?["sa_team_force"] ?? "Force Team");
var players = Helper.GetValidPlayers().Where(p => admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, player));
}
return teamMenu.WithBackButton();
}
/// <summary>
/// Creates team selection submenu for forcing a specific player to a team.
/// </summary>
/// <param name="admin">The admin player executing the team change.</param>
/// <param name="target">The target player to be moved.</param>
/// <returns>A MenuBuilder instance for the team selection menu.</returns>
private static MenuBuilder CreateTeamSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
{
var localizer = CS2_SimpleAdmin._localizer;
var teamSelectionMenu = new MenuBuilder($"Force Team: {target.PlayerName}");
var teams = new[]
{
(localizer?["sa_team_ct"] ?? "CT", "ct", CsTeam.CounterTerrorist),
(localizer?["sa_team_t"] ?? "T", "t", CsTeam.Terrorist),
(localizer?["sa_team_swap"] ?? "Swap", "swap", CsTeam.Spectator),
(localizer?["sa_team_spec"] ?? "Spec", "spec", CsTeam.Spectator)
};
foreach (var (name, teamName, teamNum) in teams)
{
teamSelectionMenu.AddOption(name, _ =>
{
if (target.IsValid)
{
CS2_SimpleAdmin.ChangeTeam(admin, target, teamName, teamNum, true);
}
});
}
return teamSelectionMenu.WithBackButton();
}
/// <summary>
/// Creates menu for managing server plugins.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the plugins menu.</returns>
private static MenuBuilder CreatePluginsMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var pluginsMenu = new MenuBuilder(localizer?["sa_menu_pluginsmanager_title"] ?? "Manage Plugins");
pluginsMenu.AddOption("Open Plugins Manager", _ =>
{
admin.ExecuteClientCommandFromServer("css_pluginsmanager");
});
return pluginsMenu.WithBackButton();
}
/// <summary>
/// Creates menu for changing the current map (includes default and workshop maps).
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the change map menu.</returns>
private static MenuBuilder CreateChangeMapMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var mapMenu = new MenuBuilder(localizer?["sa_changemap"] ?? "Change Map");
// Add default maps
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
foreach (var map in maps)
{
mapMenu.AddOption(map, _ =>
{
CS2_SimpleAdmin.Instance.ChangeMap(admin, map);
});
}
// Add workshop maps
var wsMaps = CS2_SimpleAdmin.Instance.Config.WorkshopMaps;
foreach (var wsMap in wsMaps)
{
mapMenu.AddOption($"{wsMap.Key} (WS)", _ =>
{
CS2_SimpleAdmin.Instance.ChangeWorkshopMap(admin, wsMap.Value?.ToString() ?? wsMap.Key);
});
}
return mapMenu.WithBackButton();
}
/// <summary>
/// Creates menu for restarting the current game/round.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the restart game menu.</returns>
private static MenuBuilder CreateRestartGameMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var restartMenu = new MenuBuilder(localizer?["sa_restart_game"] ?? "Restart Game");
restartMenu.AddOption("Restart Round", _ =>
{
CS2_SimpleAdmin.RestartGame(admin);
});
return restartMenu.WithBackButton();
}
/// <summary>
/// Creates menu for executing custom server commands defined in configuration.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the custom commands menu.</returns>
private static MenuBuilder CreateCustomCommandsMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var customMenu = new MenuBuilder(localizer?["sa_menu_custom_commands"] ?? "Custom Commands");
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
foreach (var customCommand in customCommands)
{
if (string.IsNullOrEmpty(customCommand.DisplayName) || string.IsNullOrEmpty(customCommand.Command))
continue;
var steamId = new SteamID(admin.SteamID);
if (!AdminManager.PlayerHasPermissions(steamId, customCommand.Flag))
continue;
customMenu.AddOption(customCommand.DisplayName, _ =>
{
Helper.TryLogCommandOnDiscord(admin, customCommand.Command);
if (customCommand.ExecuteOnClient)
admin.ExecuteClientCommandFromServer(customCommand.Command);
else
Server.ExecuteCommand(customCommand.Command);
});
}
return customMenu.WithBackButton();
}
/// <summary>
/// Creates menu for adding admin privileges to players.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the add admin menu.</returns>
private static MenuBuilder CreateAddAdminMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var addAdminMenu = new MenuBuilder(localizer?["sa_admin_add"] ?? "Add Admin");
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, player));
}
return addAdminMenu.WithBackButton();
}
/// <summary>
/// Creates admin flags selection submenu for granting specific permissions to a player.
/// </summary>
/// <param name="admin">The admin player granting permissions.</param>
/// <param name="target">The target player to receive admin privileges.</param>
/// <returns>A MenuBuilder instance for the admin flags menu.</returns>
private static MenuBuilder CreateAdminFlagsMenu(CCSPlayerController admin, CCSPlayerController target)
{
var flagsMenu = new MenuBuilder($"Add Admin: {target.PlayerName}");
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
{
var hasFlag = AdminManager.PlayerHasPermissions(target, adminFlag.Flag);
flagsMenu.AddOption(adminFlag.Name, _ =>
{
if (target.IsValid)
{
CS2_SimpleAdmin.AddAdmin(admin, target.SteamID.ToString(), target.PlayerName, adminFlag.Flag, 10);
}
}, hasFlag); // Disabled if player already has this flag
}
return flagsMenu.WithBackButton();
}
/// <summary>
/// Creates menu for removing admin privileges from players.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the remove admin menu.</returns>
private static MenuBuilder CreateRemoveAdminMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var removeAdminMenu = new MenuBuilder(localizer?["sa_admin_remove"] ?? "Remove Admin");
var adminPlayers = Helper.GetValidPlayers().Where(p =>
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
p != admin &&
admin.CanTarget(p));
foreach (var player in adminPlayers)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
removeAdminMenu.AddOption(playerName, _ =>
{
if (player.IsValid)
{
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
}
});
}
return removeAdminMenu.WithBackButton();
}
/// <summary>
/// Creates menu for reloading admin list from database.
/// </summary>
/// <param name="admin">The admin player opening the menu.</param>
/// <returns>A MenuBuilder instance for the reload admins menu.</returns>
private static MenuBuilder CreateReloadAdminsMenu(CCSPlayerController admin)
{
var localizer = CS2_SimpleAdmin._localizer;
var reloadMenu = new MenuBuilder(localizer?["sa_admin_reload"] ?? "Reload Admins");
reloadMenu.AddOption("Reload Admins", _ =>
{
CS2_SimpleAdmin.Instance.ReloadAdmins(admin);
});
return reloadMenu.WithBackButton();
}
/// <summary>
/// Creates duration selection submenu for time-based penalties (ban, mute, gag, etc.).
/// </summary>
/// <param name="admin">The admin player selecting duration.</param>
/// <param name="player">The target player for the penalty.</param>
/// <param name="actionName">The name of the penalty action.</param>
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
/// <returns>A MenuBuilder instance for the duration menu.</returns>
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
{
var durationMenu = new MenuBuilder($"{actionName} Duration: {player.PlayerName}");
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
{
durationMenu.AddOption(durationItem.Name, _ =>
{
onSelectAction(admin, player, durationItem.Duration);
});
}
return durationMenu.WithBackButton();
}
/// <summary>
/// Creates reason selection submenu for penalties with predefined reasons from configuration.
/// </summary>
/// <param name="admin">The admin player selecting reason.</param>
/// <param name="player">The target player for the penalty.</param>
/// <param name="actionName">The name of the penalty action.</param>
/// <param name="penaltyType">The type of penalty to determine which reason list to use.</param>
/// <param name="onSelectAction">Callback action executed when reason is selected.</param>
/// <returns>A MenuBuilder instance for the reason menu.</returns>
private static MenuBuilder CreateReasonMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
PenaltyType penaltyType, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
{
var reasonMenu = new MenuBuilder($"{actionName} Reason: {player.PlayerName}");
var reasons = penaltyType switch
{
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons,
PenaltyType.Kick => CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons,
PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons,
PenaltyType.Gag or PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
_ => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons
};
foreach (var reason in reasons)
{
reasonMenu.AddOption(reason, _ =>
{
onSelectAction(admin, player, reason);
});
}
return reasonMenu.WithBackButton();
}
}

View File

@@ -9,11 +9,11 @@ public static class CustomCommandsMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (!admin.IsValid)
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +

View File

@@ -8,6 +8,7 @@ public static class DurationMenu
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
{
var menu = AdminMenu.CreateMenu(menuName);
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
{
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
@@ -19,6 +20,7 @@ public static class DurationMenu
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
{
var menu = AdminMenu.CreateMenu(menuName);
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
{
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });

View File

@@ -1,267 +1,267 @@
// using CounterStrikeSharp.API.Core;
// using CounterStrikeSharp.API.Modules.Admin;
// using CounterStrikeSharp.API.Modules.Entities;
// using CounterStrikeSharp.API.Modules.Entities.Constants;
//
// namespace CS2_SimpleAdmin.Menus;
//
// public static class FunActionsMenu
// {
// private static Dictionary<int, CsItem>? _weaponsCache;
//
// private static Dictionary<int, CsItem> GetWeaponsCache
// {
// get
// {
// if (_weaponsCache != null) return _weaponsCache;
//
// var weaponsArray = Enum.GetValues(typeof(CsItem));
//
// // avoid duplicates in the menu
// _weaponsCache = new Dictionary<int, CsItem>();
// foreach (CsItem item in weaponsArray)
// {
// if (item == CsItem.Tablet)
// continue;
//
// _weaponsCache[(int)item] = item;
// }
//
// return _weaponsCache;
// }
// }
//
// public static void OpenMenu(CCSPlayerController admin)
// {
// if (!admin.IsValid)
// return;
//
// var localizer = CS2_SimpleAdmin._localizer;
// if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
// {
// admin.PrintToChat(localizer?["sa_prefix"] ??
// "[SimpleAdmin] " +
// (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
// );
// return;
// }
//
// var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands");
// List<ChatMenuOptionData> options = [];
//
// //var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats");
// //var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay");
//
// // options added in order
//
// if (AdminManager.CommandIsOverriden("css_god")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_god"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
// options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
// if (AdminManager.CommandIsOverriden("css_noclip")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_noclip"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
// options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
// if (AdminManager.CommandIsOverriden("css_respawn")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_respawn"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
// options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
// if (AdminManager.CommandIsOverriden("css_give")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_give"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
// options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
//
// if (AdminManager.CommandIsOverriden("css_strip")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_strip"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
// options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
// if (AdminManager.CommandIsOverriden("css_freeze")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_freeze"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
// options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
// if (AdminManager.CommandIsOverriden("css_hp")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_hp"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
// options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
// if (AdminManager.CommandIsOverriden("css_speed")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_speed"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
// options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
// if (AdminManager.CommandIsOverriden("css_gravity")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gravity"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
// options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
// if (AdminManager.CommandIsOverriden("css_money")
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_money"))
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
// options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
//
// foreach (var menuOptionData in options)
// {
// var menuName = menuOptionData.Name;
// menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
// }
//
// private static void GodMode(CCSPlayerController admin, CCSPlayerController player)
// {
// CS2_SimpleAdmin.God(admin, player);
// }
//
// private static void NoClip(CCSPlayerController admin, CCSPlayerController player)
// {
// CS2_SimpleAdmin.NoClip(admin, player);
// }
//
// private static void Respawn(CCSPlayerController? admin, CCSPlayerController player)
// {
// CS2_SimpleAdmin.Respawn(admin, player);
// }
//
// private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player)
// {
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}");
//
// foreach (var weapon in GetWeaponsCache)
// {
// menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
// }
//
// private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue)
// {
// CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue);
// }
//
// private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player)
// {
// CS2_SimpleAdmin.StripWeapons(admin, player);
// }
//
// private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
// {
// if (!(player.PlayerPawn.Value?.IsValid ?? false))
// return;
//
// if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_INVALID)
// CS2_SimpleAdmin.Freeze(admin, player, -1);
// else
// CS2_SimpleAdmin.Unfreeze(admin, player);
// }
//
// private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player)
// {
// var hpArray = new[]
// {
// new Tuple<string, int>("1", 1),
// new Tuple<string, int>("10", 10),
// new Tuple<string, int>("25", 25),
// new Tuple<string, int>("50", 50),
// new Tuple<string, int>("100", 100),
// new Tuple<string, int>("200", 200),
// new Tuple<string, int>("500", 500),
// new Tuple<string, int>("999", 999)
// };
//
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}");
//
// foreach (var (optionName, value) in hpArray)
// {
// menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
// }
//
// private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp)
// {
// CS2_SimpleAdmin.SetHp(admin, player, hp);
// }
//
// private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player)
// {
// var speedArray = new[]
// {
// new Tuple<string, float>("0.1", .1f),
// new Tuple<string, float>("0.25", .25f),
// new Tuple<string, float>("0.5", .5f),
// new Tuple<string, float>("0.75", .75f),
// new Tuple<string, float>("1", 1),
// new Tuple<string, float>("2", 2),
// new Tuple<string, float>("3", 3),
// new Tuple<string, float>("4", 4)
// };
//
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}");
//
// foreach (var (optionName, value) in speedArray)
// {
// menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
// }
//
// private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed)
// {
// CS2_SimpleAdmin.SetSpeed(admin, player, speed);
// }
//
// private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player)
// {
// var gravityArray = new[]
// {
// new Tuple<string, float>("0.1", .1f),
// new Tuple<string, float>("0.25", .25f),
// new Tuple<string, float>("0.5", .5f),
// new Tuple<string, float>("0.75", .75f),
// new Tuple<string, float>("1", 1),
// new Tuple<string, float>("2", 2)
// };
//
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}");
//
// foreach (var (optionName, value) in gravityArray)
// {
// menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
// }
//
// private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity)
// {
// CS2_SimpleAdmin.SetGravity(admin, player, gravity);
// }
//
// private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player)
// {
// var moneyArray = new[]
// {
// new Tuple<string, int>("$0", 0),
// new Tuple<string, int>("$1000", 1000),
// new Tuple<string, int>("$2500", 2500),
// new Tuple<string, int>("$5000", 5000),
// new Tuple<string, int>("$10000", 10000),
// new Tuple<string, int>("$16000", 16000)
// };
//
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}");
//
// foreach (var (optionName, value) in moneyArray)
// {
// menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
// }
//
// private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money)
// {
// CS2_SimpleAdmin.SetMoney(admin, player, money);
// }
// }
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
namespace CS2_SimpleAdmin.Menus;
public static class FunActionsMenu
{
private static Dictionary<int, CsItem>? _weaponsCache;
private static Dictionary<int, CsItem> GetWeaponsCache
{
get
{
if (_weaponsCache != null) return _weaponsCache;
var weaponsArray = Enum.GetValues(typeof(CsItem));
// avoid duplicates in the menu
_weaponsCache = new Dictionary<int, CsItem>();
foreach (CsItem item in weaponsArray)
{
if (item == CsItem.Tablet)
continue;
_weaponsCache[(int)item] = item;
}
return _weaponsCache;
}
}
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands");
List<ChatMenuOptionData> options = [];
//var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats");
//var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay");
// options added in order
if (AdminManager.CommandIsOverriden("css_god")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_god"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
if (AdminManager.CommandIsOverriden("css_noclip")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_noclip"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
if (AdminManager.CommandIsOverriden("css_respawn")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_respawn"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
if (AdminManager.CommandIsOverriden("css_give")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_give"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
if (AdminManager.CommandIsOverriden("css_strip")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_strip"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
if (AdminManager.CommandIsOverriden("css_freeze")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_freeze"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
if (AdminManager.CommandIsOverriden("css_hp")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_hp"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
if (AdminManager.CommandIsOverriden("css_speed")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_speed"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
if (AdminManager.CommandIsOverriden("css_gravity")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gravity"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
if (AdminManager.CommandIsOverriden("css_money")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_money"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void GodMode(CCSPlayerController admin, CCSPlayerController player)
{
CS2_SimpleAdmin.God(admin, player);
}
private static void NoClip(CCSPlayerController admin, CCSPlayerController player)
{
CS2_SimpleAdmin.NoClip(admin, player);
}
private static void Respawn(CCSPlayerController? admin, CCSPlayerController player)
{
CS2_SimpleAdmin.Respawn(admin, player);
}
private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player)
{
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}");
foreach (var weapon in GetWeaponsCache)
{
menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue)
{
CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue);
}
private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player)
{
CS2_SimpleAdmin.StripWeapons(admin, player);
}
private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
{
if (!(player.PlayerPawn.Value?.IsValid ?? false))
return;
if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_INVALID)
CS2_SimpleAdmin.Freeze(admin, player, -1);
else
CS2_SimpleAdmin.Unfreeze(admin, player);
}
private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player)
{
var hpArray = new[]
{
new Tuple<string, int>("1", 1),
new Tuple<string, int>("10", 10),
new Tuple<string, int>("25", 25),
new Tuple<string, int>("50", 50),
new Tuple<string, int>("100", 100),
new Tuple<string, int>("200", 200),
new Tuple<string, int>("500", 500),
new Tuple<string, int>("999", 999)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}");
foreach (var (optionName, value) in hpArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp)
{
CS2_SimpleAdmin.SetHp(admin, player, hp);
}
private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player)
{
var speedArray = new[]
{
new Tuple<string, float>("0.1", .1f),
new Tuple<string, float>("0.25", .25f),
new Tuple<string, float>("0.5", .5f),
new Tuple<string, float>("0.75", .75f),
new Tuple<string, float>("1", 1),
new Tuple<string, float>("2", 2),
new Tuple<string, float>("3", 3),
new Tuple<string, float>("4", 4)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}");
foreach (var (optionName, value) in speedArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed)
{
CS2_SimpleAdmin.SetSpeed(admin, player, speed);
}
private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player)
{
var gravityArray = new[]
{
new Tuple<string, float>("0.1", .1f),
new Tuple<string, float>("0.25", .25f),
new Tuple<string, float>("0.5", .5f),
new Tuple<string, float>("0.75", .75f),
new Tuple<string, float>("1", 1),
new Tuple<string, float>("2", 2)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}");
foreach (var (optionName, value) in gravityArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity)
{
CS2_SimpleAdmin.SetGravity(admin, player, gravity);
}
private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player)
{
var moneyArray = new[]
{
new Tuple<string, int>("$0", 0),
new Tuple<string, int>("$1000", 1000),
new Tuple<string, int>("$2500", 2500),
new Tuple<string, int>("$5000", 5000),
new Tuple<string, int>("$10000", 10000),
new Tuple<string, int>("$16000", 16000)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}");
foreach (var (optionName, value) in moneyArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money)
{
CS2_SimpleAdmin.SetMoney(admin, player, money);
}
}

View File

@@ -8,11 +8,11 @@ public static class ManageAdminsMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (!admin.IsValid)
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +

View File

@@ -10,11 +10,11 @@ public static class ManagePlayersMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (!admin.IsValid)
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +

View File

@@ -8,11 +8,11 @@ public static class ManageServerMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (!admin.IsValid)
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +

View File

@@ -1,170 +0,0 @@
using CounterStrikeSharp.API.Core;
namespace CS2_SimpleAdmin.Menus;
public class MenuBuilder(string title)
{
private readonly List<MenuOption> _options = [];
private MenuBuilder? _parentMenu;
private Action<CCSPlayerController>? _backAction;
private Action<CCSPlayerController>? _resetAction;
/// <summary>
/// Adds a menu option with an action.
/// </summary>
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)
{
_options.Add(new MenuOption
{
Name = name,
Action = action,
Disabled = disabled,
Permission = permission
});
return this;
}
/// <summary>
/// Adds a menu option that opens a submenu.
/// </summary>
public MenuBuilder AddSubMenu(string name, Func<MenuBuilder> subMenuFactory, bool disabled = false, string? permission = null)
{
_options.Add(new MenuOption
{
Name = name,
Action = player =>
{
var subMenu = subMenuFactory();
subMenu.SetParent(this);
// Automatically add back button to submenu
subMenu.WithBackButton();
subMenu.OpenMenu(player);
},
Disabled = disabled,
Permission = permission
});
return this;
}
/// <summary>
/// Adds a menu option that opens a submenu (with player parameter in factory).
/// </summary>
public MenuBuilder AddSubMenu(string name, Func<CCSPlayerController, MenuBuilder> subMenuFactory, bool disabled = false, string? permission = null)
{
_options.Add(new MenuOption
{
Name = name,
Action = player =>
{
var subMenu = subMenuFactory(player);
subMenu.SetParent(this);
// Automatically add back button to submenu
subMenu.WithBackButton();
subMenu.OpenMenu(player);
},
Disabled = disabled,
Permission = permission
});
return this;
}
/// <summary>
/// Adds a back button to return to the previous menu.
/// </summary>
public MenuBuilder WithBackButton()
{
if (_parentMenu != null)
{
_backAction = player => _parentMenu.OpenMenu(player);
// Add back option at the end of menu
// AddOption(backButtonText, _backAction);
}
return this;
}
/// <summary>
/// Sets the parent menu (for navigation).
/// </summary>
private void SetParent(MenuBuilder parent)
{
_parentMenu = parent;
_backAction = player => _parentMenu.OpenMenu(player);
}
/// <summary>
/// Opens the menu for a player.
/// </summary>
/// <param name="player">The player to open the menu for.</param>
public void OpenMenu(CCSPlayerController player)
{
if (!player.IsValid) return;
// Use MenuManager dependency
var menu = Helper.CreateMenu(title, _backAction);
if (menu == null) return;
foreach (var option in _options)
{
// Check permissions if required
if (!string.IsNullOrEmpty(option.Permission))
{
var steamId = new CounterStrikeSharp.API.Modules.Entities.SteamID(player.SteamID);
if (!CounterStrikeSharp.API.Modules.Admin.AdminManager.PlayerHasPermissions(steamId, option.Permission))
{
continue; // Skip option if player doesn't have permission
}
}
menu.AddMenuOption(option.Name, (menuPlayer, menuOption) =>
{
option.Action?.Invoke(menuPlayer);
}, option.Disabled);
}
menu.Open(player);
}
/// <summary>
/// Clears all menu options.
/// </summary>
/// <returns>This MenuBuilder instance for chaining.</returns>
public MenuBuilder Clear()
{
_options.Clear();
return this;
}
/// <summary>
/// Sets a reset action for the menu.
/// </summary>
/// <param name="resetAction">The action to execute on reset.</param>
/// <returns>This MenuBuilder instance for chaining.</returns>
public MenuBuilder WithResetAction(Action<CCSPlayerController> resetAction)
{
_resetAction = resetAction;
return this;
}
/// <summary>
/// Sets a custom back action for the menu.
/// </summary>
/// <param name="backAction">The action to execute when going back (nullable).</param>
/// <returns>This MenuBuilder instance for chaining.</returns>
public MenuBuilder WithBackAction(Action<CCSPlayerController>? backAction)
{
_backAction = backAction;
return this;
}
}
/// <summary>
/// Represents an option within a menu.
/// </summary>
public class MenuOption
{
public string Name { get; set; } = string.Empty;
public Action<CCSPlayerController>? Action { get; set; }
public bool Disabled { get; set; }
public string? Permission { get; set; }
}

View File

@@ -1,190 +0,0 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Menus;
public class MenuManager
{
private static MenuManager? _instance;
public static MenuManager Instance => _instance ??= new MenuManager();
private readonly Dictionary<string, Func<CCSPlayerController, MenuBuilder>> _menuFactories = [];
private readonly Dictionary<string, MenuCategory> _menuCategories = [];
/// <summary>
/// Provides public access to menu categories (for API usage).
/// </summary>
/// <returns>Dictionary of menu categories keyed by category ID.</returns>
public Dictionary<string, MenuCategory> GetMenuCategories()
{
return _menuCategories;
}
/// <summary>
/// Registers a new menu category with specified permissions.
/// </summary>
/// <param name="categoryId">Unique identifier for the category.</param>
/// <param name="categoryName">Display name of the category.</param>
/// <param name="permission">Required permission to access this category (default: @css/generic).</param>
public void RegisterCategory(string categoryId, string categoryName, string permission = "@css/generic")
{
_menuCategories[categoryId] = new MenuCategory
{
Id = categoryId,
Name = categoryName,
Permission = permission,
MenuFactories = new Dictionary<string, Func<CCSPlayerController, MenuBuilder>>()
};
}
/// <summary>
/// Registers a menu within a category (API for other plugins).
/// </summary>
/// <param name="categoryId">The category to add this menu to.</param>
/// <param name="menuId">Unique identifier for the menu.</param>
/// <param name="menuName">Display name of the menu.</param>
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
/// <param name="permission">Required permission to access this menu (optional).</param>
public void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission = null)
{
if (!_menuCategories.ContainsKey(categoryId))
{
RegisterCategory(categoryId, categoryId); // Auto-create category if it doesn't exist
}
_menuCategories[categoryId].MenuFactories[menuId] = menuFactory;
_menuCategories[categoryId].MenuNames[menuId] = menuName;
if (permission != null)
{
_menuCategories[categoryId].MenuPermissions[menuId] = permission;
}
}
/// <summary>
/// Unregisters a menu from a category.
/// </summary>
/// <param name="categoryId">The category containing the menu.</param>
/// <param name="menuId">The menu to unregister.</param>
public void UnregisterMenu(string categoryId, string menuId)
{
if (!_menuCategories.TryGetValue(categoryId, out var category)) return;
category.MenuFactories.Remove(menuId);
_menuCategories[categoryId].MenuNames.Remove(menuId);
_menuCategories[categoryId].MenuPermissions.Remove(menuId);
}
/// <summary>
/// Creates the main admin menu for a player with accessible categories.
/// </summary>
/// <param name="player">The player to create the menu for.</param>
/// <returns>A MenuBuilder instance for the main menu.</returns>
public MenuBuilder CreateMainMenu(CCSPlayerController player)
{
var localizer = CS2_SimpleAdmin._localizer;
var mainMenu = new MenuBuilder(localizer?["sa_title"] ?? "SimpleAdmin");
foreach (var category in _menuCategories.Values)
{
if (category.MenuFactories.Count <= 0) continue;
// Check category permissions
var steamId = new SteamID(player.SteamID);
if (!AdminManager.PlayerHasPermissions(steamId, category.Permission))
continue;
// Pass player to CreateCategoryMenu
mainMenu.AddSubMenu(category.Name, () => CreateCategoryMenu(category, player),
permission: category.Permission);
}
return mainMenu;
}
/// <summary>
/// Creates a category submenu containing all registered menus in that category.
/// </summary>
/// <param name="category">The menu category to create.</param>
/// <param name="player">The player to create the menu for.</param>
/// <returns>A MenuBuilder instance for the category menu.</returns>
private MenuBuilder CreateCategoryMenu(MenuCategory category, CCSPlayerController player)
{
var categoryMenu = new MenuBuilder(category.Name);
foreach (var kvp in category.MenuFactories)
{
var menuId = kvp.Key;
var menuFactory = kvp.Value;
var menuName = category.MenuNames.TryGetValue(menuId, out var name) ? name : menuId;
var permission = category.MenuPermissions.TryGetValue(menuId, out var perm) ? perm : null;
// Check permissions
if (!string.IsNullOrEmpty(permission))
{
var steamId = new SteamID(player.SteamID);
if (!AdminManager.PlayerHasPermissions(steamId, permission))
continue;
}
// Call the actual factory with player parameter
categoryMenu.AddSubMenu(menuName, () => menuFactory(player), permission: permission);
}
return categoryMenu.WithBackButton();
}
/// <summary>
/// Opens the main admin menu for a player.
/// </summary>
/// <param name="player">The player to open the menu for.</param>
public void OpenMainMenu(CCSPlayerController player)
{
var localizer = CS2_SimpleAdmin._localizer;
var steamId = new SteamID(player.SteamID);
if (!AdminManager.PlayerHasPermissions(steamId, "@css/generic"))
{
player.PrintToChat(localizer?["sa_prefix"] ?? "[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command"));
return;
}
CreateMainMenu(player).OpenMenu(player);
}
/// <summary>
/// Initializes default menu categories (Players, Server, Admin).
/// </summary>
public void InitializeDefaultCategories()
{
var localizer = CS2_SimpleAdmin._localizer;
RegisterCategory("players", localizer?["sa_menu_players_manage"] ?? "Manage Players", "@css/generic");
RegisterCategory("server", localizer?["sa_menu_server_manage"] ?? "Server Management", "@css/generic");
// RegisterCategory("fun", localizer?["sa_menu_fun_commands"] ?? "Fun Commands", "@css/generic");
RegisterCategory("admin", localizer?["sa_menu_admins_manage"] ?? "Admin Management", "@css/root");
}
/// <summary>
/// Public method for creating category menus (for API usage).
/// </summary>
/// <param name="category">The menu category to create.</param>
/// <param name="player">The player to create the menu for.</param>
/// <returns>A MenuBuilder instance for the category menu.</returns>
public MenuBuilder CreateCategoryMenuPublic(MenuCategory category, CCSPlayerController player)
{
return CreateCategoryMenu(category, player);
}
}
/// <summary>
/// Represents a menu category containing multiple menus.
/// </summary>
public class MenuCategory
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Permission { get; set; } = "@css/generic";
public Dictionary<string, Func<CCSPlayerController, MenuBuilder>> MenuFactories { get; set; } = [];
public Dictionary<string, string> MenuNames { get; set; } = [];
public Dictionary<string, string> MenuPermissions { get; set; } = [];
}

View File

@@ -8,7 +8,7 @@ public static class PlayersMenu
{
public static void OpenRealPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
{
OpenMenu(admin, menuName, onSelectAction, p => !p.IsBot);
OpenMenu(admin, menuName, onSelectAction, p => p.IsBot == false);
}
public static void OpenAdminPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController?, bool>? enableFilter = null)
@@ -37,7 +37,7 @@ public static class PlayersMenu
var playerName = player != null && player.PlayerName.Length > 26 ? player.PlayerName[..26] : player?.PlayerName;
var optionName = HttpUtility.HtmlEncode(playerName);
if (player != null && enableFilter != null && !enableFilter(player))
if (player != null && enableFilter != null && enableFilter(player) == false)
continue;
var enabled = admin.CanTarget(player);
@@ -47,7 +47,7 @@ public static class PlayersMenu
{
if (player != null) onSelectAction.Invoke(admin, player);
},
!enabled);
enabled == false);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);

View File

@@ -1 +1 @@
1.7.8-beta-1
1.7.7-alpha-10

View File

@@ -1,7 +1,6 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdminApi;
@@ -9,8 +8,6 @@ namespace CS2_SimpleAdminApi;
public interface ICS2_SimpleAdminApi
{
public static readonly PluginCapability<ICS2_SimpleAdminApi?> PluginCapability = new("simpleadmin:api");
public event Action? OnSimpleAdminReady;
/// <summary>
/// Gets player information associated with the specified player controller.
@@ -86,25 +83,6 @@ public interface ICS2_SimpleAdminApi
/// </summary>
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs);
/// <summary>
/// Shows an admin activity message with a custom translated message (for modules with their own localizer).
/// </summary>
/// <param name="translatedMessage">Already translated message to display to players.</param>
/// <param name="callerName">Name of the admin executing the action (optional).</param>
/// <param name="dontPublish">If true, won't trigger publish events.</param>
public void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false);
/// <summary>
/// Shows an admin activity message using module's localizer for per-player language support.
/// This method sends messages in each player's configured language.
/// </summary>
/// <param name="moduleLocalizer">The module's IStringLocalizer instance.</param>
/// <param name="messageKey">The translation key from the module's lang files.</param>
/// <param name="callerName">Name of the admin executing the action (optional).</param>
/// <param name="dontPublish">If true, won't trigger publish events.</param>
/// <param name="messageArgs">Arguments to format the localized message.</param>
public void ShowAdminActivityLocalized(object moduleLocalizer, string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs);
/// <summary>
/// Returns true if the specified admin player is in silent mode (not broadcasting activity).
/// </summary>
@@ -124,54 +102,4 @@ public interface ICS2_SimpleAdminApi
/// Unregisters an existing command by its name.
/// </summary>
public void UnRegisterCommand(string name);
/// <summary>
/// Gets target players from command
/// </summary>
TargetResult? GetTarget(CommandInfo command);
/// <summary>
/// Returns the list of current valid players, available to call from other plugins.
/// </summary>
List<CCSPlayerController> GetValidPlayers();
/// <summary>
/// Registers a menu category.
/// </summary>
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
/// <summary>
/// Registers a menu in a category.
/// </summary>
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null);
/// <summary>
/// Unregisters a menu from a category.
/// </summary>
void UnregisterMenu(string categoryId, string menuId);
/// <summary>
/// Creates a menu with an automatic back button.
/// </summary>
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player);
/// <summary>
/// Creates a menu with a list of players with filter and action.
/// </summary>
object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect);
/// <summary>
/// Adds an option to the menu (extension method helper).
/// </summary>
void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null);
/// <summary>
/// Adds a submenu to the menu (extension method helper).
/// </summary>
void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory, bool disabled = false, string? permission = null);
/// <summary>
/// Opens a menu for a player.
/// </summary>
void OpenMenu(object menu, CCSPlayerController player);
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,268 +0,0 @@
# 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!