Migrate menus and votes to Panorama UI Kit

Replace MenuManagerCS2 with Panorama UI Kit for the admin menus and
css_vote. Layout names (menu, vote, banner, toast) are configurable
under MenuConfig and preloaded on startup; MenuType is removed and
ConfigVersion bumped to 26.

- Drop MenuManagerApi.dll and the old chat menu classes
- Reference PanoramaUiKit.Shared.dll, bump CounterStrikeSharp to 1.0.376
- Add .editorconfig and CODING_CONVENTIONS.md, apply them codebase-wide
- Remove the CS2-SimpleAdmin-docs site
- Rewrite README for the fork
This commit is contained in:
Salvatore 2026-10-21 18:51:18 +02:00
parent 03a454b1b5
commit 460d8b2b85
112 changed files with 5559 additions and 36371 deletions

View file

@ -11,9 +11,9 @@ public static class RegisterCommands
{
internal static readonly Dictionary<string, IList<CommandDefinition>> _commandDefinitions =
new(StringComparer.InvariantCultureIgnoreCase);
private delegate void CommandCallback(CCSPlayerController? caller, CommandInfo.CommandCallback callback);
private static readonly string CommandsPath = Path.Combine(CS2_SimpleAdmin.ConfigDirectory, "Commands.json");
private static readonly List<CommandMapping> CommandMappings =
[
@ -101,7 +101,7 @@ public static class RegisterCommands
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void CreateConfig()
{
var commands = new CommandsConfig
CommandsConfig commands = new CommandsConfig
{
Commands = new Dictionary<string, Command>
{
@ -161,14 +161,14 @@ public static class RegisterCommands
{ "css_adminvoice", new Command { Aliases = ["css_adminvoice", "css_listenall"] } }
}
};
var options = new JsonSerializerOptions
JsonSerializerOptions options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(commands, options);
string json = JsonSerializer.Serialize(commands, options);
File.WriteAllText(CommandsPath, json);
}
@ -179,46 +179,52 @@ public static class RegisterCommands
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void Register()
{
var json = File.ReadAllText(CommandsPath);
var commandsConfig = JsonSerializer.Deserialize<CommandsConfig>(json,
string json = File.ReadAllText(CommandsPath);
CommandsConfig? commandsConfig = JsonSerializer.Deserialize<CommandsConfig>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (commandsConfig?.Commands != null)
{
foreach (var command in commandsConfig.Commands)
foreach (KeyValuePair<string, Command> command in commandsConfig.Commands)
{
if (command.Value.Aliases == null) continue;
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)
CommandMapping? mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
if (mapping == null || command.Value.Aliases.Length == 0)
{
continue;
}
foreach (string alias in command.Value.Aliases)
{
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
}
}
// Commands added after Commands.json was generated are registered under their default name
foreach (var mapping in CommandMappings.Where(m => !commandsConfig.Commands.ContainsKey(m.CommandKey)))
foreach (CommandMapping? mapping in CommandMappings.Where(m => !commandsConfig.Commands.ContainsKey(m.CommandKey)))
{
CS2_SimpleAdmin._logger?.LogInformation($"Registering command: `{mapping.CommandKey}` (not in Commands.json, using default alias)");
CS2_SimpleAdmin.Instance.AddCommand(mapping.CommandKey, "", mapping.Callback);
}
}
foreach (var (name, definitions) in _commandDefinitions)
foreach ((string? name, IList<CommandDefinition>? definitions) in _commandDefinitions)
{
foreach (var definition in definitions)
foreach (CommandDefinition definition in definitions)
{
CS2_SimpleAdmin._logger?.LogInformation($"Registering custom command: `{name}`");
CS2_SimpleAdmin.Instance.AddCommand(name, definition.Description, definition.Callback);
}
}
}
/// <summary>
/// Represents the JSON configuration structure for commands.
/// </summary>
@ -226,7 +232,7 @@ public static class RegisterCommands
{
public Dictionary<string, Command>? Commands { get; init; }
}
/// <summary>
/// Represents a command definition containing a list of aliases.
/// </summary>
@ -234,7 +240,7 @@ public static class RegisterCommands
{
public string[]? Aliases { get; init; }
}
/// <summary>
/// Maps a command key to its respective command callback handler.
/// </summary>
@ -243,4 +249,4 @@ public static class RegisterCommands
public string CommandKey { get; } = commandKey;
public CommandInfo.CommandCallback Callback { get; } = callback;
}
}
}

View file

@ -2,6 +2,7 @@ using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdmin.Managers;
@ -21,34 +22,38 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
string callerName = Helper.GetCallerName(caller);
if (command.ArgCount < 2)
{
return;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToList();
}
TargetResult? targets = GetTarget(command);
if (targets == null)
{
return;
}
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
string reason = Helper.GetArgsReason(command, 3);
int time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller.CanTarget(player)) return;
if (!caller.CanTarget(player))
{
return;
}
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player,
ManagePlayersMenu.BanMenu);
BasicMenu.CreatePenaltyMenu(caller, player, PenaltyType.Ban).OpenMenu(caller);
return;
}
@ -69,18 +74,27 @@ public partial class CS2_SimpleAdmin
/// <param name="silent">If true, suppresses command logging.</param>
internal void Ban(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, BanManager? banManager = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue)
{
return;
}
if (!caller.CanTarget(player))
{
return;
}
if (!CheckValidBan(caller, time))
{
return;
}
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
callerName = Helper.GetCallerName(caller);
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
PlayerInfo playerInfo = PlayersInfo[player.SteamID];
PlayerInfo? adminInfo = Helper.GetAdminInfo(caller);
// Asynchronously handle banning logic
Task.Run(async () =>
@ -93,13 +107,25 @@ public partial class CS2_SimpleAdmin
});
// Determine message keys and arguments based on ban time
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm",
[reason, "CALLER"],
["CALLER", player.PlayerName, reason])
: ("sa_player_ban_message_time", "sa_admin_ban_message_time",
new object[] { reason, time, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason, time });
string? messageKey;
string? activityMessageKey;
object[]? centerArgs;
object[]? adminActivityArgs;
if (time == 0)
{
messageKey = "sa_player_ban_message_perm";
activityMessageKey = "sa_admin_ban_message_perm";
centerArgs = [reason, "CALLER"];
adminActivityArgs = ["CALLER", player.PlayerName, reason];
}
else
{
messageKey = "sa_player_ban_message_time";
activityMessageKey = "sa_admin_ban_message_time";
centerArgs = new object[] { reason, time, "CALLER" };
adminActivityArgs = new object[] { "CALLER", player.PlayerName, reason, time };
}
// Display center message to the player
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
@ -125,11 +151,15 @@ public partial class CS2_SimpleAdmin
if (!silent)
{
if (command == null)
Helper.LogCommand(caller, $"css_ban {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
{
Helper.LogCommand(caller, $"css_ban {Helper.GetLogName(player)} {time} {reason}");
}
else
{
Helper.LogCommand(caller, command);
}
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer);
}
@ -144,23 +174,26 @@ public partial class CS2_SimpleAdmin
internal void AddBan(CCSPlayerController? caller, SteamID steamid, int time, string reason, BanManager? banManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
string callerName = Helper.GetCallerName(caller);
PlayerInfo? adminInfo = Helper.GetAdminInfo(caller);
CCSPlayerController? player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
{
return;
}
Ban(caller, player, time, reason, callerName, silent: true);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(steamid))
{
return;
}
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
@ -171,7 +204,7 @@ public partial class CS2_SimpleAdmin
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Ban, _localizer);
}
}
@ -185,34 +218,41 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
if (DatabaseProvider == null)
{
return;
}
string callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1)))
{
return;
}
if (!Helper.ValidateSteamId(command.GetArg(1), out SteamID? steamId) || steamId == null)
{
command.ReplyToCommand("Invalid SteamID64.");
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
ulong steamid = steamId.SteamId64;
string reason = Helper.GetArgsReason(command, 3);
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidBan(caller, time)) return;
int time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidBan(caller, time))
{
return;
}
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
: null;
PlayerInfo? adminInfo = Helper.GetAdminInfo(caller);
var player = Helper.GetPlayerFromSteamid64(steamid);
CCSPlayerController? player = Helper.GetPlayerFromSteamid64(steamid);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
{
return;
}
Ban(caller, player, time, reason, callerName, silent: true);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
@ -220,8 +260,10 @@ public partial class CS2_SimpleAdmin
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
{
return;
}
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
@ -232,7 +274,7 @@ public partial class CS2_SimpleAdmin
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Ban, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline.");
@ -241,7 +283,9 @@ public partial class CS2_SimpleAdmin
Helper.LogCommand(caller, command);
if (UnlockedCommands)
{
Server.ExecuteCommand($"banid 1 {steamId.SteamId3}");
}
}
/// <summary>
@ -253,10 +297,18 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 1, usage: "<ip> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanIpCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
var ipAddress = command.GetArg(1);
if (DatabaseProvider == null)
{
return;
}
string callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1)))
{
return;
}
string ipAddress = command.GetArg(1);
if (!Helper.IsValidIp(ipAddress))
{
@ -264,28 +316,31 @@ public partial class CS2_SimpleAdmin
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
string reason = Helper.GetArgsReason(command, 3);
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
int time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidBan(caller, time)) return;
if (!CheckValidBan(caller, time))
{
return;
}
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
: null;
PlayerInfo? adminInfo = Helper.GetAdminInfo(caller);
var players = Helper.GetPlayerFromIp(ipAddress);
List<CCSPlayerController> players = Helper.GetPlayerFromIp(ipAddress);
if (players.Count >= 1)
{
foreach (var player in players)
foreach (CCSPlayerController player in players)
{
if (player == null || !player.IsValid) continue;
if (player == null || !player.IsValid)
{
continue;
}
if (!caller.CanTarget(player))
{
return;
}
Ban(caller, player, time, reason, callerName, silent: true);
}
@ -312,9 +367,12 @@ public partial class CS2_SimpleAdmin
/// <returns>True if ban duration is valid; otherwise, false.</returns>
private bool CheckValidBan(CCSPlayerController? caller, int duration)
{
if (caller == null) return true;
if (caller == null)
{
return true;
}
var canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
bool canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
if (duration <= 0 && !canPermBan)
{
@ -322,7 +380,10 @@ public partial class CS2_SimpleAdmin
return false;
}
if (duration <= Config.OtherSettings.MaxBanDuration || canPermBan) return true;
if (duration <= Config.OtherSettings.MaxBanDuration || canPermBan)
{
return true;
}
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_max_duration_exceeded", Config.OtherSettings.MaxBanDuration]}");
return false;
@ -337,20 +398,20 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnbanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
if (DatabaseProvider == null)
{
return;
}
string callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
if (command.GetArg(1).Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
return;
}
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
string pattern = command.GetArg(1);
string reason = Helper.GetArgsReason(command, 2);
Task.Run(async () => await BanManager.UnbanPlayer(pattern, callerSteamId, reason));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unbanned player with pattern {pattern}.");
@ -366,26 +427,31 @@ public partial class CS2_SimpleAdmin
public void OnWarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null)
{
return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
}
string callerName = Helper.GetCallerName(caller);
if (command.ArgCount < 2)
{
return;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.Connected && !player.IsHLTV).ToList();
}
TargetResult? targets = GetTarget(command);
if (targets == null)
{
return;
}
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.Connected && !player.IsHLTV).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
int time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
string reason = Helper.GetArgsReason(command, 3);
playersToTarget.ForEach(player =>
{
@ -407,14 +473,23 @@ public partial class CS2_SimpleAdmin
/// <param name="command">Optional command info for logging.</param>
internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue)
{
return;
}
if (!caller.CanTarget(player))
{
return;
}
if (!CheckValidBan(caller, time))
{
return;
}
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
callerName = Helper.GetCallerName(caller);
// Freeze player pawn if alive
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
@ -424,8 +499,8 @@ public partial class CS2_SimpleAdmin
}
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
PlayerInfo playerInfo = PlayersInfo[player.SteamID];
PlayerInfo? adminInfo = Helper.GetAdminInfo(caller);
// Asynchronously handle warning logic
Task.Run(async () =>
@ -438,16 +513,20 @@ public partial class CS2_SimpleAdmin
});
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(player.SteamID);
int totalWarns = await WarnManager.GetPlayerWarnsCount(player.SteamID);
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
var lastKey = Config.WarnThreshold.Keys.Max();
int lastKey = Config.WarnThreshold.Keys.Max();
if (totalWarns >= lastKey)
{
punishCommand = Config.WarnThreshold[lastKey];
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
}
else if (Config.WarnThreshold.TryGetValue(totalWarns, out string? value))
{
punishCommand = value;
}
if (!string.IsNullOrEmpty(punishCommand))
{
@ -460,13 +539,25 @@ public partial class CS2_SimpleAdmin
});
// Determine message keys and arguments based on warning time
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
? ("sa_player_warn_message_perm", "sa_admin_warn_message_perm",
new object[] { reason, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason })
: ("sa_player_warn_message_time", "sa_admin_warn_message_time",
[reason, time, "CALLER"],
["CALLER", player.PlayerName, reason, time]);
string? messageKey;
string? activityMessageKey;
object[]? centerArgs;
object[]? adminActivityArgs;
if (time == 0)
{
messageKey = "sa_player_warn_message_perm";
activityMessageKey = "sa_admin_warn_message_perm";
centerArgs = new object[] { reason, "CALLER" };
adminActivityArgs = new object[] { "CALLER", player.PlayerName, reason };
}
else
{
messageKey = "sa_player_warn_message_time";
activityMessageKey = "sa_admin_warn_message_time";
centerArgs = [reason, time, "CALLER"];
adminActivityArgs = ["CALLER", player.PlayerName, reason, time];
}
// Display center message to the playser
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
@ -479,14 +570,18 @@ public partial class CS2_SimpleAdmin
// Log the warning command
if (command == null)
Helper.LogCommand(caller, $"css_warn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
{
Helper.LogCommand(caller, $"css_warn {Helper.GetLogName(player)} {time} {reason}");
}
else
{
Helper.LogCommand(caller, command);
}
// Send Discord notification for the warning
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer);
}
/// <summary>
/// Adds a warning to a player by their SteamID, including support for offline players.
/// </summary>
@ -498,18 +593,18 @@ public partial class CS2_SimpleAdmin
internal void AddWarn(CCSPlayerController? caller, SteamID steamid, int time, string reason, WarnManager? warnManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
string callerName = Helper.GetCallerName(caller);
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
PlayerInfo? adminInfo = Helper.GetAdminInfo(caller);
CCSPlayerController? player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
{
return;
}
Warn(caller, player, time, reason, callerName);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
@ -517,8 +612,10 @@ public partial class CS2_SimpleAdmin
else
{
if (!caller.CanTarget(steamid))
{
return;
}
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
@ -530,16 +627,20 @@ public partial class CS2_SimpleAdmin
});
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
int totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
var lastKey = Config.WarnThreshold.Keys.Max();
int lastKey = Config.WarnThreshold.Keys.Max();
if (totalWarns >= lastKey)
{
punishCommand = Config.WarnThreshold[lastKey];
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
}
else if (Config.WarnThreshold.TryGetValue(totalWarns, out string? value))
{
punishCommand = value;
}
if (!string.IsNullOrEmpty(punishCommand))
{
@ -550,11 +651,11 @@ public partial class CS2_SimpleAdmin
}
}
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Warn, _localizer);
}
}
/// <summary>
/// Handles removing a warning (unwarn) by a pattern string.
/// </summary>
@ -564,7 +665,10 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnwarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (DatabaseProvider == null)
{
return;
}
if (command.GetArg(1).Length <= 1)
{
@ -572,9 +676,9 @@ public partial class CS2_SimpleAdmin
return;
}
var pattern = command.GetArg(1);
string pattern = command.GetArg(1);
Task.Run(async () => await WarnManager.UnwarnPlayer(pattern));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unwarned player with pattern {pattern}.");
}
}
}

View file

@ -1,16 +1,21 @@
using CounterStrikeSharp.API.Core;
using System.Text;
using System.Text.RegularExpressions;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using System.Text;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdmin.Menus;
using PanoramaUiKit.Shared.Api;
using PanoramaUiKit.Shared.Common;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
private const float AnnouncementSeconds = 7f;
/// <summary>
/// Sends a chat message only to admins that have chat permission.
/// The message is encoded properly to handle UTF-8 characters.
@ -23,16 +28,18 @@ public partial class CS2_SimpleAdmin
{
Helper.LogCommand(caller, command);
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
byte[] utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
string utf8String = Encoding.UTF8.GetString(utf8BytesString);
foreach (var player in Helper.GetValidPlayers()
foreach (CCSPlayerController? player in Helper.GetValidPlayers()
.Where(p => AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
{
if (_localizer != null)
{
player.PrintToChat(_localizer["sa_adminchat_template_admin",
caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName,
Helper.GetCallerName(caller),
utf8String]);
}
}
}
@ -45,14 +52,17 @@ public partial class CS2_SimpleAdmin
[RequiresPermissions("@css/chat")]
public void OnAdminCustomSayCommand(CCSPlayerController? caller, CommandInfo command)
{
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return;
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0)
{
return;
}
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
byte[] utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
string utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
foreach (var player in Helper.GetValidPlayers())
foreach (CCSPlayerController player in Helper.GetValidPlayers())
{
player.PrintToChat(utf8String.ReplaceColorTags());
}
@ -67,14 +77,17 @@ public partial class CS2_SimpleAdmin
[RequiresPermissions("@css/chat")]
public void OnAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
{
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return;
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0)
{
return;
}
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
var callerName = caller?.PlayerName;
byte[] utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
string utf8String = Encoding.UTF8.GetString(utf8BytesString);
string? callerName = caller?.PlayerName;
Helper.LogCommand(caller, command);
foreach (var player in Helper.GetValidPlayers())
foreach (CCSPlayerController player in Helper.GetValidPlayers())
{
// The message always goes out (it is a broadcast, not an activity notice),
// but the admin's name follows ShowActivityType like every other activity message.
@ -93,19 +106,23 @@ public partial class CS2_SimpleAdmin
[RequiresPermissions("@css/chat")]
public void OnAdminPrivateSayCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
string callerName = Helper.GetCallerName(caller);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
TargetResult? targets = GetTarget(command);
if (targets == null)
{
return;
}
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
//Helper.LogCommand(caller, command);
var range = command.GetArg(0).Length + command.GetArg(1).Length + 2;
var message = command.GetCommandString[range..];
int range = command.GetArg(0).Length + command.GetArg(1).Length + 2;
string message = command.GetCommandString[range..];
var utf8BytesString = Encoding.UTF8.GetBytes(message);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
byte[] utf8BytesString = Encoding.UTF8.GetBytes(message);
string utf8String = Encoding.UTF8.GetString(utf8BytesString);
playersToTarget.ForEach(player =>
{
@ -116,7 +133,7 @@ public partial class CS2_SimpleAdmin
}
/// <summary>
/// Broadcasts a center-screen message to all players.
/// Broadcasts a message to all players in a Panorama UI Kit banner.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
@ -124,15 +141,15 @@ public partial class CS2_SimpleAdmin
[RequiresPermissions("@css/chat")]
public void OnAdminCenterSayCommand(CCSPlayerController? caller, CommandInfo command)
{
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
byte[] utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
string utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
Helper.PrintToCenterAll(utf8String.ReplaceColorTags());
UiKit.Instance?.Banner(UiKit.BannerLayout, StripColorTags(utf8String), UiStyle.Info, AnnouncementSeconds);
}
/// <summary>
/// Sends a HUD alert message to all players.
/// Sends a message to all players in a Panorama UI Kit toast, titled with the admin's name.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
@ -140,14 +157,31 @@ public partial class CS2_SimpleAdmin
[RequiresPermissions("@css/chat")]
public void OnAdminHudSayCommand(CCSPlayerController? caller, CommandInfo command)
{
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
byte[] utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
string utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
VirtualFunctions.ClientPrintAll(
HudDestination.Alert,
utf8String.ReplaceColorTags(),
0, 0, 0, 0, 0);
IPanoramaUiKit? kit = UiKit.Instance;
if (kit == null)
{
return;
}
string message = StripColorTags(utf8String);
string? callerName = caller?.PlayerName;
foreach (CCSPlayerController player in Helper.GetValidPlayers())
{
kit.Toast(player, UiKit.ToastLayout, Helper.ResolveActivityName(player, callerName), message, UiStyle.Info, AnnouncementSeconds);
}
}
}
/// <summary>
/// Panorama does not render chat color tags such as {red}: drop them.
/// </summary>
private static string StripColorTags(string message)
{
return Regex.Replace(message, @"\{[A-Za-z]+\}", string.Empty).Trim();
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,21 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Timers;
using CS2_SimpleAdmin.Menus;
using PanoramaUiKit.Shared.Api;
using PanoramaUiKit.Shared.Votes;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
private const float VoteDurationSeconds = 30f;
/// <summary>
/// Handles the vote command, creates voting menu for players, and collects answers.
/// Displays results after timeout and resets voting state.
/// Handles the vote command: shows the vote screen to every player, collects one answer each
/// and prints the results when the countdown ends.
/// </summary>
/// <param name="caller">The player/admin who initiated the vote, or null for console.</param>
/// <param name="command">Command object containing question and options.</param>
@ -19,80 +24,123 @@ public partial class CS2_SimpleAdmin
public void OnVoteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (command.ArgCount < 2 || _localizer == null)
{
return;
}
Helper.LogCommand(caller, command);
VoteAnswers.Clear();
var question = command.GetArg(1);
var answersCount = command.ArgCount;
if (caller == null || !SilentPlayers.Contains(caller.Slot))
if (caller != null && SilentPlayers.Contains(caller.Slot))
{
for (var i = 2; i <= answersCount - 1; i++)
{
VoteAnswers.Add(command.GetArg(i), 0);
}
foreach (var player in Helper.GetValidPlayers())
{
using (new WithTemporaryCulture(player.GetLanguage()))
{
IMenu? voteMenu = Helper.CreateMenu(_localizer["sa_admin_vote_menu_title", question]);
if (voteMenu == null)
return;
//ChatMenu voteMenu = new(_localizer!["sa_admin_vote_menu_title", question]);
for (var i = 2; i <= answersCount - 1; i++)
{
voteMenu.AddMenuOption(command.GetArg(i), Helper.HandleVotes);
}
voteMenu.PostSelectAction = PostSelectAction.Close;
Helper.PrintToCenterAll(_localizer["sa_admin_vote_message", caller == null ? _localizer["sa_console"] : caller.PlayerName, question]);
player.SendLocalizedMessage(_localizer,
"sa_admin_vote_message",
caller == null ? _localizer["sa_console"] : caller.PlayerName,
question);
voteMenu.Open(player);
//MenuManager.OpenChatMenu(player, voteMenu);
}
}
VoteInProgress = true;
return;
}
if (VoteInProgress)
IPanoramaUiKit? kit = UiKit.Instance;
if (kit == null)
{
AddTimer(30, () =>
return;
}
string question = command.GetArg(1);
string callerName = Helper.GetCallerName(caller);
List<string> answers = [];
for (int i = 2; i < command.ArgCount; i++)
{
answers.Add(command.GetArg(i));
}
int[] counts = new int[answers.Count];
Dictionary<ulong, int> ballots = [];
VoteInProgress = true;
foreach (CCSPlayerController player in Helper.GetValidPlayers())
{
kit.Votes.Show(player, BuildVote(player, question, answers, counts, null, OnVote));
player.SendLocalizedMessage(_localizer, "sa_admin_vote_message", callerName, question);
}
Helper.PrintToCenterAll(_localizer["sa_admin_vote_message", callerName, question]);
AddTimer(VoteDurationSeconds, () =>
{
UiKit.Instance?.Votes.HideAll(UiKit.VoteLayout);
VoteInProgress = false;
if (_localizer == null)
{
foreach (var player in Helper.GetValidPlayers())
return;
}
foreach (CCSPlayerController player in Helper.GetValidPlayers())
{
player.SendLocalizedMessage(_localizer, "sa_admin_vote_message_results", question);
for (int i = 0; i < answers.Count; i++)
{
if (_localizer != null)
player.SendLocalizedMessage(_localizer,
"sa_admin_vote_message_results",
question);
player.SendLocalizedMessage(_localizer, "sa_admin_vote_message_results_answer", answers[i], counts[i]);
}
}
}, TimerFlags.STOP_ON_MAPCHANGE);
return;
void OnVote(VoteEvent vote)
{
if (!VoteInProgress || ballots.ContainsKey(vote.Player.SteamID))
{
return;
}
ballots[vote.Player.SteamID] = vote.Index;
counts[vote.Index]++;
IPanoramaUiKit? currentKit = UiKit.Instance;
if (currentKit == null)
{
return;
}
foreach (CCSPlayerController player in Helper.GetValidPlayers())
{
int? selected = null;
if (ballots.TryGetValue(player.SteamID, out int index))
{
selected = index;
}
foreach (var (key, value) in VoteAnswers)
{
foreach (var player in Helper.GetValidPlayers())
{
if (_localizer != null)
player.SendLocalizedMessage(_localizer,
"sa_admin_vote_message_results_answer",
key,
value);
}
}
VoteAnswers.Clear();
VoteInProgress = false;
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
currentKit.Votes.Update(player, BuildVote(player, question, answers, counts, selected, OnVote));
}
}
}
}
/// <summary>
/// The vote screen for one player, locked once that player has voted.
/// </summary>
private static VoteOptions BuildVote(CCSPlayerController player, string question, List<string> answers, int[] counts,
int? selected, Action<VoteEvent> onVote)
{
string title;
using (new WithTemporaryCulture(player.GetLanguage()))
{
title = _localizer?["sa_admin_vote_menu_title", question] ?? question;
}
List<VoteChoice> choices = [];
for (int i = 0; i < answers.Count; i++)
{
choices.Add(new VoteChoice(answers[i], counts[i]));
}
return new VoteOptions
{
Layout = UiKit.VoteLayout,
Title = title,
Choices = choices,
Selected = selected,
Locked = selected.HasValue,
Seconds = VoteDurationSeconds,
OnVote = onVote
};
}
}

View file

@ -49,7 +49,7 @@
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// callerName ??= Helper.GetCallerName(caller);
//
// // Toggle no-clip mode for the player
// player.Pawn.Value?.ToggleNoclip();
@ -67,7 +67,7 @@
//
// // Log the command
// if (command == null)
// Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// Helper.LogCommand(caller, $"css_noclip {Helper.GetLogName(player)}");
// }
//
// /// <summary>
@ -80,7 +80,7 @@
// [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 callerName = Helper.GetCallerName(caller);
// var targets = GetTarget(command);
// if (targets == null) return;
//
@ -111,7 +111,7 @@
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// var callerName = Helper.GetCallerName(caller);
//
// // Toggle god mode for the player
// if (!GodPlayers.Add(player.Slot))
@ -121,7 +121,7 @@
//
// // Log the command
// if (command == null)
// Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// Helper.LogCommand(caller, $"css_god {Helper.GetLogName(player)}");
//
// // Determine message key and arguments for the god mode notification
// var (activityMessageKey, adminActivityArgs) =
@ -144,7 +144,7 @@
// [RequiresPermissions("@css/slay")]
// public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// var callerName = Helper.GetCallerName(caller);
// int.TryParse(command.GetArg(2), out var time);
//
// var targets = GetTarget(command);
@ -171,7 +171,7 @@
// [RequiresPermissions("@css/slay")]
// public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// var callerName = Helper.GetCallerName(caller);
// float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
//
// var targets = GetTarget(command);
@ -221,7 +221,7 @@
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// callerName ??= Helper.GetCallerName(caller);
//
// // Freeze player pawn
// player.Pawn.Value?.Freeze();
@ -245,7 +245,7 @@
//
// // 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}");
// Helper.LogCommand(caller, $"css_freeze {Helper.GetLogName(player)} {time}");
// }
//
// /// <summary>
@ -257,7 +257,7 @@
// [RequiresPermissions("@css/slay")]
// public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
// {
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// var callerName = Helper.GetCallerName(caller);
//
// var targets = GetTarget(command);
// if (targets == null) return;
@ -284,7 +284,7 @@
// if (!caller.CanTarget(player)) return;
//
// // Set default caller name if not provided
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// callerName ??= Helper.GetCallerName(caller);
//
// // Unfreeze player pawn
// player.Pawn.Value?.Unfreeze();
@ -302,6 +302,6 @@
//
// // Log the command and send Discord notification
// if (command == null)
// Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// Helper.LogCommand(caller, $"css_unfreeze {Helper.GetLogName(player)}");
// }
// }
// }

View file

@ -1,10 +1,9 @@
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
namespace CS2_SimpleAdmin;
@ -21,17 +20,20 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
string callerName = Helper.GetCallerName(caller);
TargetResult? 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();
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
Slay(caller, player, callerName, command);
});
Helper.LogCommand(caller, command);
}
@ -44,18 +46,25 @@ public partial class CS2_SimpleAdmin
/// <param name="command">Optional command info for logging.</param>
internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid || player.Connected != PlayerConnectedState.Connected) return;
if (!caller.CanTarget(player)) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.Connected)
{
return;
}
if (!caller.CanTarget(player))
{
return;
}
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
callerName ??= Helper.GetCallerName(caller);
// Make the player commit suicide
player.CommitSuicide(false, true);
player.EmitSound("Player.Death");
// Determine message keys and arguments for the slay notification
var (activityMessageKey, adminActivityArgs) =
(string? activityMessageKey, object[]? adminActivityArgs) =
("sa_admin_slay_message",
new object[] { "CALLER", player.PlayerName });
@ -67,7 +76,9 @@ public partial class CS2_SimpleAdmin
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
{
Helper.LogCommand(caller, $"css_slay {Helper.GetLogName(player)}");
}
}
/// <summary>
@ -79,12 +90,15 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 1, usage: "<#userid or name> [damage]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSlapCommand(CCSPlayerController? caller, CommandInfo command)
{
var damage = 0;
int damage = 0;
var targets = GetTarget(command);
if (targets == null) return;
TargetResult? 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();
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
if (command.ArgCount >= 2)
{
@ -94,14 +108,16 @@ public partial class CS2_SimpleAdmin
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.Connected)
{
return;
}
if (caller!.CanTarget(player))
{
Slap(caller, player, damage, command);
}
});
Helper.LogCommand(caller, command);
}
@ -114,26 +130,34 @@ public partial class CS2_SimpleAdmin
/// <param name="command">Optional command info for logging.</param>
internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
if (!caller.CanTarget(player))
{
return;
}
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
string callerName = Helper.GetCallerName(caller);
// Apply slap damage to the player
player.Pawn.Value?.Slap(damage);
player.EmitSound("Player.DamageFall");
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}");
{
Helper.LogCommand(caller, $"css_slap {Helper.GetLogName(player)} {damage}");
}
// Determine message key and arguments for the slap notification
var (activityMessageKey, adminActivityArgs) =
(string? activityMessageKey, object[]? adminActivityArgs) =
("sa_admin_slap_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
if (caller != null && SilentPlayers.Contains(caller.Slot))
{
return;
}
if (_localizer != null)
{
@ -150,15 +174,18 @@ public partial class CS2_SimpleAdmin
[CommandHelper(minArgs: 2, usage: "<#userid or name> [<ct/tt/spec>] [-k]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnTeamCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var teamName = command.GetArg(2).ToLower();
string callerName = Helper.GetCallerName(caller);
string teamName = command.GetArg(2).ToLower();
string _teamName;
var teamNum = CsTeam.Spectator;
CsTeam teamNum = CsTeam.Spectator;
var targets = GetTarget(command);
if (targets == null) return;
TargetResult? targets = GetTarget(command);
if (targets == null)
{
return;
}
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
switch (teamName)
{
@ -185,13 +212,13 @@ public partial class CS2_SimpleAdmin
break;
}
var kill = command.GetArg(3).ToLower().Equals("-k");
bool kill = command.GetArg(3).ToLower().Equals("-k");
playersToTarget.ForEach(player =>
{
ChangeTeam(caller, player, _teamName, teamNum, kill, command);
});
Helper.LogCommand(caller, command);
}
@ -208,45 +235,79 @@ public partial class CS2_SimpleAdmin
{
// Check if the player is valid and connected
if (!player.IsValid || player.Connected != PlayerConnectedState.Connected)
{
return;
}
// Ensure the caller can target the player
if (!caller.CanTarget(player)) return;
if (!caller.CanTarget(player))
{
return;
}
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
string callerName = Helper.GetCallerName(caller);
// Change team based on the provided teamName and conditions
if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase))
{
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
{
player.SwitchTeam(teamNum);
}
else
{
player.ChangeTeam(teamNum);
}
}
else
{
if (player.TeamNum != (byte)CsTeam.Spectator)
{
var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist;
teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT";
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
player.SwitchTeam(_teamNum);
CsTeam _teamNum;
if ((CsTeam)player.TeamNum == CsTeam.Terrorist)
{
_teamNum = CsTeam.CounterTerrorist;
}
else
{
_teamNum = CsTeam.Terrorist;
}
if (_teamNum == CsTeam.Terrorist)
{
teamName = "TT";
}
else
{
teamName = "CT";
}
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
{
player.SwitchTeam(_teamNum);
}
else
{
player.ChangeTeam(_teamNum);
}
}
}
// Log the command
if (command == null)
{
Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}");
}
// Determine message key and arguments for the team change notification
var activityMessageKey = "sa_admin_team_message";
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, teamName };
string activityMessageKey = "sa_admin_team_message";
object[] adminActivityArgs = new object[] { "CALLER", player.PlayerName, teamName };
// Display admin activity message to other players
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
if (caller != null && SilentPlayers.Contains(caller.Slot))
{
return;
}
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
@ -261,21 +322,26 @@ public partial class CS2_SimpleAdmin
public void OnRenameCommand(CCSPlayerController? caller, CommandInfo command)
{
// Set default caller name if not provided
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
string callerName = Helper.GetCallerName(caller);
// Get the new name from the command arguments
var newName = command.GetArg(2);
string newName = command.GetArg(2);
// Check if the new name is valid
if (string.IsNullOrEmpty(newName))
{
return;
}
// Retrieve the targets based on the command
var targets = GetTarget(command);
if (targets == null) return;
TargetResult? targets = GetTarget(command);
if (targets == null)
{
return;
}
// Filter out valid players from the targets
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
// Log the command
Helper.LogCommand(caller, command);
@ -285,15 +351,19 @@ public partial class CS2_SimpleAdmin
{
// Check if the player is connected and can be targeted
if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player))
{
return;
}
// Determine message key and arguments for the rename notification
var activityMessageKey = "sa_admin_rename_message";
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
string activityMessageKey = "sa_admin_rename_message";
object[] adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
// Display admin activity message to other players
if (caller != null && !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Rename the player
player.Rename(newName);
@ -310,17 +380,20 @@ public partial class CS2_SimpleAdmin
public void OnPrenameCommand(CCSPlayerController? caller, CommandInfo command)
{
// Set default caller name if not provided
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
string callerName = Helper.GetCallerName(caller);
// Get the new name from the command arguments
var newName = command.GetArg(2);
string newName = command.GetArg(2);
// Retrieve the targets based on the command
var targets = GetTarget(command);
if (targets == null) return;
TargetResult? targets = GetTarget(command);
if (targets == null)
{
return;
}
// Filter out valid players from the targets
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
List<CCSPlayerController> playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
// Log the command
Helper.LogCommand(caller, command);
@ -330,20 +403,22 @@ public partial class CS2_SimpleAdmin
{
// Check if the player is connected and can be targeted
if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player))
{
return;
}
// Determine message key and arguments for the rename notification
var activityMessageKey = "sa_admin_rename_message";
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
string activityMessageKey = "sa_admin_rename_message";
object[] adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
// Display admin activity message to other players
if (caller != null && !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Determine if the new name is valid and update the renamed players list
var steamId = player.SteamID;
ulong steamId = player.SteamID;
if (!string.IsNullOrEmpty(newName))
{
RenamedPlayers[steamId] = newName;
@ -370,51 +445,65 @@ public partial class CS2_SimpleAdmin
IEnumerable<CCSPlayerController> playersToTeleport;
CCSPlayerController? destinationPlayer;
var targets = GetTarget(command);
TargetResult? targets = GetTarget(command);
if (command.ArgCount < 3)
{
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
{
return;
}
if (targets == null || targets.Count() != 1)
{
return;
}
destinationPlayer = targets.Players.FirstOrDefault(p =>
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null)
{
return;
}
playersToTeleport = [caller];
}
else
{
var destination = GetTarget(command, 2);
TargetResult? destination = GetTarget(command, 2);
if (targets == null || destination == null || destination.Count() != 1)
{
return;
}
destinationPlayer = destination.Players.FirstOrDefault(p =>
p is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
if (destinationPlayer == null)
{
return;
}
playersToTeleport = targets.Players
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
.ToList();
if (!playersToTeleport.Any())
{
return;
}
}
// Log command
Helper.LogCommand(caller, command);
foreach (var player in playersToTeleport)
foreach (CCSPlayerController player in playersToTeleport)
{
if (player.PlayerPawn?.Value == null || destinationPlayer?.PlayerPawn?.Value == null)
{
continue;
}
player.TeleportPlayer(destinationPlayer);
@ -453,7 +542,7 @@ public partial class CS2_SimpleAdmin
}
}
}
/// <summary>
/// Brings targeted player(s) to the caller or specified destination player's location.
/// </summary>
@ -469,11 +558,15 @@ public partial class CS2_SimpleAdmin
if (command.ArgCount < 3)
{
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
{
return;
}
var targets = GetTarget(command);
TargetResult? targets = GetTarget(command);
if (targets == null || !targets.Any())
{
return;
}
destinationPlayer = caller;
@ -483,20 +576,26 @@ public partial class CS2_SimpleAdmin
}
else
{
var destination = GetTarget(command);
TargetResult? destination = GetTarget(command);
if (destination == null || destination.Count() != 1)
{
return;
}
destinationPlayer = destination.Players.FirstOrDefault(p =>
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
if (destinationPlayer == null)
{
return;
}
// Rest args = targets to teleport
var targets = GetTarget(command, 2);
TargetResult? targets = GetTarget(command, 2);
if (targets == null || !targets.Any())
{
return;
}
playersToTeleport = targets.Players
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
@ -504,15 +603,19 @@ public partial class CS2_SimpleAdmin
}
if (destinationPlayer == null || !playersToTeleport.Any())
{
return;
}
// Log command
Helper.LogCommand(caller, command);
foreach (var player in playersToTeleport)
foreach (CCSPlayerController player in playersToTeleport)
{
if (player.PlayerPawn?.Value == null || destinationPlayer.PlayerPawn?.Value == null)
{
continue;
}
// Teleport
player.TeleportPlayer(destinationPlayer);
@ -624,4 +727,4 @@ public partial class CS2_SimpleAdmin
// }
// }
// }
}
}