diff --git a/.gitignore b/.gitignore index dad4536..55358e7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,3 @@ CLAUDE.md /Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN release-out/ -.claude/ diff --git a/CS2-SimpleAdmin-docs/docs/user/commands/basecommands.md b/CS2-SimpleAdmin-docs/docs/user/commands/basecommands.md index b56f80f..5414380 100644 --- a/CS2-SimpleAdmin-docs/docs/user/commands/basecommands.md +++ b/CS2-SimpleAdmin-docs/docs/user/commands/basecommands.md @@ -313,36 +313,6 @@ css_warns PlayerName --- -### Show Penalty History - -Print every past and current penalty for a player to your console. - -```bash -css_history <#userid or name or steamid64> [bans|gags|mutes|silences|warns] -``` - -**Permission:** `@css/kick` - -**Shows:** -- Bans, gags, mutes, silences and warns, newest first -- Status (active, expired, unbanned, unmuted), duration and reason -- Admin who issued it -- Who lifted it, when and why, for unbans and unmutes - -**Notes:** -- Accepts a SteamID64, so it works for players who are not online -- The optional second argument narrows the list to one penalty type -- Also available in the admin menu under Players > History - -**Examples:** -```bash -css_history #123 -css_history PlayerName bans -css_history 76561198012345678 -``` - ---- - ### Show Online Players Show information about all online players. diff --git a/CS2-SimpleAdmin-docs/docs/user/commands/playercommands.md b/CS2-SimpleAdmin-docs/docs/user/commands/playercommands.md index e51c959..1db3d93 100644 --- a/CS2-SimpleAdmin-docs/docs/user/commands/playercommands.md +++ b/CS2-SimpleAdmin-docs/docs/user/commands/playercommands.md @@ -251,9 +251,9 @@ css_prename PlayerName "NewIdentity" ``` **Notes:** -- Name is enforced even after reconnect, map change and server restart -- Stored in the database (`sa_renames`) and shared by every server using that database -- Player cannot change it; `css_prename ` with no name removes the rename +- Name is enforced even after reconnect +- Stored in database +- Player cannot change it - Useful for offensive names --- diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs index 63c23d1..0fd0cf4 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs @@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)"); public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)"; public override string ModuleAuthor => "daffyy"; - public override string ModuleVersion => "1.8.2b-zio17"; + public override string ModuleVersion => "1.8.2b-zio13"; public override void Load(bool hotReload) { @@ -217,8 +217,8 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig await PluginInfo.CheckVersion(ModuleVersion, Logger)); PermissionManager = new PermissionManager(DatabaseProvider); BanManager = new BanManager(DatabaseProvider); @@ -242,13 +242,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig p.PlayerName.Equals(command.GetArg(argument), StringComparison.OrdinalIgnoreCase)).ToList(); - if (exact.Count == 1) - return new TargetResult { Players = exact }; - - command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(argument)}\": {string.Join(", ", matches.Players.Select(p => p.PlayerName))}."); + command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(argument)}\"."); return null; } diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj index 98ecdcd..a3ad25d 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj @@ -36,10 +36,106 @@ + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + - + diff --git a/CS2-SimpleAdmin/Commands/RegisterCommands.cs b/CS2-SimpleAdmin/Commands/RegisterCommands.cs index 2edfa43..584f3ec 100644 --- a/CS2-SimpleAdmin/Commands/RegisterCommands.cs +++ b/CS2-SimpleAdmin/Commands/RegisterCommands.cs @@ -45,7 +45,6 @@ public static class RegisterCommands new("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand), new("css_disconnected", CS2_SimpleAdmin.Instance.OnDisconnectedCommand), new("css_warns", CS2_SimpleAdmin.Instance.OnWarnsCommand), - new("css_history", CS2_SimpleAdmin.Instance.OnHistoryCommand), new("css_players", CS2_SimpleAdmin.Instance.OnPlayersCommand), new("css_kick", CS2_SimpleAdmin.Instance.OnKickCommand), new("css_map", CS2_SimpleAdmin.Instance.OnMapCommand), @@ -131,7 +130,6 @@ public static class RegisterCommands { "css_who", new Command { Aliases = ["css_who"] } }, { "css_disconnected", new Command { Aliases = ["css_disconnected", "css_last"] } }, { "css_warns", new Command { Aliases = ["css_warns"] } }, - { "css_history", new Command { Aliases = ["css_history", "css_penaltyhistory"] } }, { "css_players", new Command { Aliases = ["css_players"] } }, { "css_kick", new Command { Aliases = ["css_kick"] } }, { "css_map", new Command { Aliases = ["css_map", "css_changemap"] } }, @@ -200,13 +198,6 @@ public static class RegisterCommands 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))) - { - 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) diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs index e0c362b..4b2302e 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -846,99 +846,6 @@ public partial class CS2_SimpleAdmin }); } - /// - /// Prints a player's full penalty history (bans, gags, mutes, silences, warns) to the caller's console. - /// Accepts a SteamID64 so it also works for players who are not online. - /// - /// The player issuing the command or null for console. - /// The command containing the target and an optional penalty type filter. - [RequiresPermissions("@css/kick")] - [CommandHelper(minArgs: 1, usage: "<#userid or name or steamid64> [bans|gags|mutes|silences|warns]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] - public void OnHistoryCommand(CCSPlayerController? caller, CommandInfo command) - { - if (DatabaseProvider == null || _localizer == null) return; - - var filter = command.ArgCount > 2 ? command.GetArg(2).ToLowerInvariant() : null; - if (filter is not (null or "bans" or "gags" or "mutes" or "silences" or "warns")) - { - command.ReplyToCommand("Usage: css_history <#userid or name or steamid64> [bans|gags|mutes|silences|warns]"); - return; - } - - ulong steamId; - string? name = null; - - if (Helper.ValidateSteamId(command.GetArg(1), out var parsedSteamId) && parsedSteamId != null) - { - if (caller != null && !caller.CanTarget(parsedSteamId)) return; - - steamId = parsedSteamId.SteamId64; - if (PlayersInfo.TryGetValue(steamId, out var info)) name = info.Name; - } - else - { - var targets = GetTarget(command); - if (targets == null) return; - - var players = targets.Players.Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false }).ToList(); - if (players.Count != 1) - { - command.ReplyToCommand("Only one target is allowed."); - return; - } - - var player = players[0]; - if (caller != null && !caller.CanTarget(player)) return; - - steamId = player.SteamID; - name = player.PlayerName; - } - - Helper.LogCommand(caller, command); - Action print = caller == null ? Server.PrintToConsole : caller.PrintToConsole; - var callerSteamId = caller?.SteamID; - - Task.Run(async () => - { - var rows = await PlayerManager.GetPenaltyHistory(steamId, filter); - // Offline players are only known by SteamID; borrow the name stored on their latest record - name ??= rows.Count > 0 ? (string?)rows[0].player_name : null; - - await Server.NextWorldUpdateAsync(() => - { - // The slot may have been reused by another player while the query ran - if (caller != null && (!caller.IsValid || caller.SteamID != callerSteamId)) return; - PrintHistory(print, name ?? steamId.ToString(), steamId, rows); - }); - }); - } - - /// - /// Writes history rows from as one console line each. - /// - internal void PrintHistory(Action print, string name, ulong steamId, List rows) - { - if (_localizer == null) return; - - if (rows.Count == 0) - { - print(_localizer["sa_history_none", name]); - return; - } - - print(_localizer["sa_history_header", name, steamId, rows.Count]); - foreach (var r in rows) - { - var duration = (int)r.duration == 0 ? "perm" : $"{(int)r.duration}m"; - var line = $"[{Date(r.created)}] {(string)r.type,-7} {(string)r.status,-8} {duration,-6} by {(string)r.admin_name}: {(string)r.reason}"; - if (r.lift_date != null) - line += $" | lifted {Date(r.lift_date)} by {(string?)r.lift_admin ?? "?"}: {(string?)r.lift_reason}"; - print(line); - } - - static string Date(object? value) => PlayerManager.ToDateTime(value)?.ToString("yyyy-MM-dd HH:mm") ?? "?"; - } - /// /// Lists players currently connected to the server with options to output JSON or filter duplicate IPs. /// diff --git a/CS2-SimpleAdmin/Commands/basecomms.cs b/CS2-SimpleAdmin/Commands/basecomms.cs index e3b5656..6305f40 100644 --- a/CS2-SimpleAdmin/Commands/basecomms.cs +++ b/CS2-SimpleAdmin/Commands/basecomms.cs @@ -292,13 +292,6 @@ public partial class CS2_SimpleAdmin var nameMatches = Helper.GetPlayerFromName(pattern); var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null; - // zio: several online players match part of the name; don't fall through to the offline search. - if (nameMatches.Count > 1) - { - command.ReplyToCommand($"Multiple targets found for \"{pattern}\": {string.Join(", ", nameMatches.Select(p => p.PlayerName))}."); - return; - } - if (namePlayer != null && namePlayer.IsValid) { PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Gag); @@ -611,13 +604,6 @@ public partial class CS2_SimpleAdmin var nameMatches = Helper.GetPlayerFromName(pattern); var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null; - // zio: several online players match part of the name; don't fall through to the offline search. - if (nameMatches.Count > 1) - { - command.ReplyToCommand($"Multiple targets found for \"{pattern}\": {string.Join(", ", nameMatches.Select(p => p.PlayerName))}."); - return; - } - if (namePlayer != null && namePlayer.IsValid) { PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Mute); @@ -933,13 +919,6 @@ public partial class CS2_SimpleAdmin var nameMatches = Helper.GetPlayerFromName(pattern); var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null; - // zio: several online players match part of the name; don't fall through to the offline search. - if (nameMatches.Count > 1) - { - command.ReplyToCommand($"Multiple targets found for \"{pattern}\": {string.Join(", ", nameMatches.Select(p => p.PlayerName))}."); - return; - } - if (namePlayer != null && namePlayer.IsValid) { PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Silence); diff --git a/CS2-SimpleAdmin/Commands/playercommands.cs b/CS2-SimpleAdmin/Commands/playercommands.cs index 209f122..2330ccf 100644 --- a/CS2-SimpleAdmin/Commands/playercommands.cs +++ b/CS2-SimpleAdmin/Commands/playercommands.cs @@ -292,9 +292,10 @@ public partial class CS2_SimpleAdmin var 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); + if (caller != null && SilentPlayers.Contains(caller.Slot)) return; + Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs); + // Rename the player player.Rename(newName); }); @@ -343,18 +344,15 @@ public partial class CS2_SimpleAdmin } // Determine if the new name is valid and update the renamed players list - var steamId = player.SteamID; if (!string.IsNullOrEmpty(newName)) { - RenamedPlayers[steamId] = newName; + RenamedPlayers[player.SteamID] = newName; player.Rename(newName); } else { - RenamedPlayers.Remove(steamId); + RenamedPlayers.Remove(player.SteamID); } - - Task.Run(() => PlayerManager.SaveRenamedPlayer(steamId, newName)); }); } diff --git a/CS2-SimpleAdmin/Database/IDatabaseProvider.cs b/CS2-SimpleAdmin/Database/IDatabaseProvider.cs index cdfea20..e2a4ae4 100644 --- a/CS2-SimpleAdmin/Database/IDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/IDatabaseProvider.cs @@ -41,12 +41,7 @@ public interface IDatabaseProvider string GetExpireBansQuery(bool multiServer); string GetExpireIpBansQuery(bool multiServer); string GetExpireOldPlayerIpsQuery(); - - // Renames (css_prename) - string GetRenamesQuery(); - string GetUpsertRenameQuery(); - string GetDeleteRenameQuery(); - + // MuteManager string GetAddMuteQuery(bool includePlayerName); string GetIsMutedQuery(bool multiServer, int timeMode); @@ -66,7 +61,4 @@ public interface IDatabaseProvider string GetUnwarnByIdQuery(bool multiServer); string GetUnwarnLastQuery(bool multiServer); string GetExpireWarnsQuery(bool multiServer); - - // Penalty history (css_history) - string GetPenaltyHistoryQuery(bool multiServer); } \ No newline at end of file diff --git a/CS2-SimpleAdmin/Database/Migrations/Mysql/017_CreateRenamesTable.sql b/CS2-SimpleAdmin/Database/Migrations/Mysql/017_CreateRenamesTable.sql deleted file mode 100644 index 634bd07..0000000 --- a/CS2-SimpleAdmin/Database/Migrations/Mysql/017_CreateRenamesTable.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE IF NOT EXISTS `sa_renames` ( - `player_steamid` BIGINT NOT NULL, - `name` VARCHAR(128) NOT NULL, - PRIMARY KEY (`player_steamid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/CS2-SimpleAdmin/Database/Migrations/Sqlite/017_CreateRenamesTable.sql b/CS2-SimpleAdmin/Database/Migrations/Sqlite/017_CreateRenamesTable.sql deleted file mode 100644 index ec893be..0000000 --- a/CS2-SimpleAdmin/Database/Migrations/Sqlite/017_CreateRenamesTable.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS `sa_renames` ( - `player_steamid` INTEGER NOT NULL PRIMARY KEY, - `name` TEXT NOT NULL -); diff --git a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs index baf4cac..fa9a27e 100644 --- a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs @@ -217,8 +217,8 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider public string GetUnbanRetrieveBansQuery(bool multiServer) { return multiServer - ? "SELECT id, player_steamid FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'" - : "SELECT id, player_steamid FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid"; + ? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'" + : "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid"; } public string GetUnbanAdminIdQuery() @@ -238,15 +238,6 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider return "UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId"; } - public string GetRenamesQuery() => - "SELECT player_steamid, name FROM sa_renames"; - - public string GetUpsertRenameQuery() => - "INSERT INTO sa_renames (player_steamid, name) VALUES (@steamId, @name) ON DUPLICATE KEY UPDATE name = @name"; - - public string GetDeleteRenameQuery() => - "DELETE FROM sa_renames WHERE player_steamid = @steamId"; - public string GetExpireBansQuery(bool multiServer) { return multiServer @@ -413,29 +404,6 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider ? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime" : "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid"; - public string GetPenaltyHistoryQuery(bool multiServer) => - $""" - SELECT b.id, 'BAN' AS type, b.player_name, b.admin_name, b.reason, b.duration, b.created, b.ends, b.status, - ub.reason AS lift_reason, ub.date AS lift_date, ua.player_name AS lift_admin - FROM sa_bans b - LEFT JOIN sa_unbans ub ON ub.id = b.unban_id - LEFT JOIN sa_admins ua ON ua.id = ub.admin_id - WHERE b.player_steamid = @PlayerSteamID {(multiServer ? "" : "AND b.server_id = @serverid")} - UNION ALL - SELECT m.id, m.type, m.player_name, m.admin_name, m.reason, m.duration, m.created, m.ends, m.status, - um.reason AS lift_reason, um.date AS lift_date, ua.player_name AS lift_admin - FROM sa_mutes m - LEFT JOIN sa_unmutes um ON um.id = m.unmute_id - LEFT JOIN sa_admins ua ON ua.id = um.admin_id - WHERE m.player_steamid = @PlayerSteamID {(multiServer ? "" : "AND m.server_id = @serverid")} - UNION ALL - SELECT w.id, 'WARN' AS type, w.player_name, w.admin_name, w.reason, w.duration, w.created, w.ends, w.status, - NULL AS lift_reason, NULL AS lift_date, NULL AS lift_admin - FROM sa_warns w - WHERE w.player_steamid = @PlayerSteamID {(multiServer ? "" : "AND w.server_id = @serverid")} - ORDER BY created DESC - """; - public ValueTask DisposeAsync() => ValueTask.CompletedTask; } diff --git a/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs b/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs index 53b2191..d6186fb 100644 --- a/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs @@ -143,8 +143,8 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider public string GetUnbanRetrieveBansQuery(bool multiServer) => multiServer - ? "SELECT id, player_steamid FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'" - : "SELECT id, player_steamid FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid"; + ? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'" + : "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid"; public string GetUnbanAdminIdQuery() => "SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId"; @@ -154,14 +154,13 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider ? "INSERT INTO sa_unbans (ban_id, admin_id, reason) VALUES (@banId, @adminId, @reason); SELECT last_insert_rowid();" : "INSERT INTO sa_unbans (ban_id, admin_id) VALUES (@banId, @adminId); SELECT last_insert_rowid();"; - // SQLite has no ON UPDATE CURRENT_TIMESTAMP, so updated_at must be set explicitly or the cache refresh never sees the change public string GetUpdateBanStatusQuery() => - "UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId, updated_at = CURRENT_TIMESTAMP WHERE id = @banId"; + "UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId"; public string GetExpireBansQuery(bool multiServer) => multiServer - ? "UPDATE sa_bans SET status = 'EXPIRED', updated_at = CURRENT_TIMESTAMP WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime" - : "UPDATE sa_bans SET status = 'EXPIRED', updated_at = CURRENT_TIMESTAMP WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime AND server_id = @serverid"; + ? "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime" + : "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime AND server_id = @serverid"; public string GetExpireIpBansQuery(bool multiServer) => multiServer @@ -170,16 +169,7 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider public string GetExpireOldPlayerIpsQuery() => "DELETE FROM sa_players_ips WHERE used_at <= @ipBansTime"; - - public string GetRenamesQuery() => - "SELECT player_steamid, name FROM sa_renames"; - - public string GetUpsertRenameQuery() => - "INSERT INTO sa_renames (player_steamid, name) VALUES (@steamId, @name) ON CONFLICT(player_steamid) DO UPDATE SET name = excluded.name"; - - public string GetDeleteRenameQuery() => - "DELETE FROM sa_renames WHERE player_steamid = @steamId"; - + public string GetAdminsQuery() => """ SELECT sa_admins.player_steamid, sa_admins.player_name, sa_admins_flags.flag, sa_admins.immunity, sa_admins.ends @@ -387,27 +377,4 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider multiServer ? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime" : "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime AND server_id = @serverid"; - - public string GetPenaltyHistoryQuery(bool multiServer) => - $""" - SELECT b.id, 'BAN' AS type, b.player_name, b.admin_name, b.reason, b.duration, b.created, b.ends, b.status, - ub.reason AS lift_reason, ub.date AS lift_date, ua.player_name AS lift_admin - FROM sa_bans b - LEFT JOIN sa_unbans ub ON ub.id = b.unban_id - LEFT JOIN sa_admins ua ON ua.id = ub.admin_id - WHERE b.player_steamid = @PlayerSteamID {(multiServer ? "" : "AND b.server_id = @serverid")} - UNION ALL - SELECT m.id, m.type, m.player_name, m.admin_name, m.reason, m.duration, m.created, m.ends, m.status, - um.reason AS lift_reason, um.date AS lift_date, ua.player_name AS lift_admin - FROM sa_mutes m - LEFT JOIN sa_unmutes um ON um.id = m.unmute_id - LEFT JOIN sa_admins ua ON ua.id = um.admin_id - WHERE m.player_steamid = @PlayerSteamID {(multiServer ? "" : "AND m.server_id = @serverid")} - UNION ALL - SELECT w.id, 'WARN' AS type, w.player_name, w.admin_name, w.reason, w.duration, w.created, w.ends, w.status, - NULL AS lift_reason, NULL AS lift_date, NULL AS lift_admin - FROM sa_warns w - WHERE w.player_steamid = @PlayerSteamID {(multiServer ? "" : "AND w.server_id = @serverid")} - ORDER BY created DESC - """; } diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs index 7559a72..bd0a404 100644 --- a/CS2-SimpleAdmin/Events.cs +++ b/CS2-SimpleAdmin/Events.cs @@ -543,31 +543,18 @@ public partial class CS2_SimpleAdmin [GameEventHandler] public HookResult OnPlayerInfo(EventPlayerInfo @event, GameEventInfo _) { - EnforceRename(@event.Userid); + var player = @event.Userid; + + if (player is null || !player.IsValid || player.IsBot) + return HookResult.Continue; + + if (!RenamedPlayers.TryGetValue(player.SteamID, out var name)) return HookResult.Continue; + + if (player.PlayerName.Equals(name)) + return HookResult.Continue; + + player.Rename(name); + return HookResult.Continue; } - - [GameEventHandler] - public HookResult OnPlayerChangename(EventPlayerChangename @event, GameEventInfo _) - { - EnforceRename(@event.Userid); - return HookResult.Continue; - } - - /// - /// Re-applies a permanent rename. Checked after a short delay because the engine may not - /// have written the client's new name yet when the event fires, and Rename itself - /// takes 0.4s to settle (it briefly writes the name with a trailing space). - /// - private void EnforceRename(CCSPlayerController? player) - { - if (player is null || !player.IsValid || player.IsBot) return; - if (!RenamedPlayers.TryGetValue(player.SteamID, out var name)) return; - - AddTimer(0.5f, () => - { - if (player.IsValid && !player.PlayerName.Equals(name)) - player.Rename(name); - }); - } } \ No newline at end of file diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs index 98d07a9..105be3b 100644 --- a/CS2-SimpleAdmin/Helper.cs +++ b/CS2-SimpleAdmin/Helper.cs @@ -49,12 +49,9 @@ internal static class Helper } } - // zio: an exact (case-insensitive) name match, or else every player whose name contains it. public static List GetPlayerFromName(string name) { - var players = GetValidPlayers(); - var exact = players.FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase)); - return exact.Count > 0 ? exact : players.FindAll(x => x.PlayerName.Contains(name, StringComparison.OrdinalIgnoreCase)); + return GetValidPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase)); } public static CCSPlayerController? GetPlayerFromSteamid64(ulong steamid) @@ -1006,6 +1003,74 @@ internal static class Helper } +public static class PluginInfo +{ + internal static async Task CheckVersion(string localVersion, ILogger logger) + { + ShowAd(localVersion); + const string versionUrl = "https://raw.githubusercontent.com/daffyyyy/CS2-SimpleAdmin/main/CS2-SimpleAdmin/VERSION"; + var client = CS2_SimpleAdmin.HttpClient; + client.Timeout = TimeSpan.FromSeconds(3); + + try + { + var response = await client.GetAsync(versionUrl); + + if (response.IsSuccessStatusCode) + { + var remoteVersion = await response.Content.ReadAsStringAsync(); + remoteVersion = remoteVersion.Trim(); + + var comparisonResult = string.CompareOrdinal(localVersion, remoteVersion); + + switch (comparisonResult) + { + case < 0: + logger.LogWarning("Plugin is outdated! Check https://github.com/daffyyyy/CS2-SimpleAdmin"); + break; + case > 0: + logger.LogInformation("Probably dev version detected"); + break; + default: + logger.LogInformation("Plugin is up to date"); + break; + } + } + else + { + logger.LogWarning($"Failed to check version. Status Code: {response.StatusCode}"); + } + } + catch (HttpRequestException ex) + { + logger.LogError(ex, "Failed to connect to the version server."); + } + catch (Exception ex) + { + logger.LogError(ex, "An error occurred while checking version."); + } + } + + private static void ShowAd(string moduleVersion) + { + Console.WriteLine(" "); + Console.WriteLine(" _______ ___ __ __ _______ ___ _______ _______ ______ __ __ ___ __ _ "); + Console.WriteLine("| || | | |_| || || | | || _ || | | |_| || | | | | |"); + Console.WriteLine("| _____|| | | || _ || | | ___|| |_| || _ || || | | |_| |"); + Console.WriteLine("| |_____ | | | || |_| || | | |___ | || | | || || | | |"); + Console.WriteLine("|_____ || | | || ___|| |___ | ___|| || |_| || || | | _ |"); + Console.WriteLine(" _____| || | | ||_|| || | | || |___ | _ || || ||_|| || | | | | |"); + Console.WriteLine("|_______||___| |_| |_||___| |_______||_______||__| |__||______| |_| |_||___| |_| |__|"); + Console.WriteLine(" >> Version: " + moduleVersion); + Console.WriteLine(" >> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin"); + var _m1 = new List { 70,117,99,107,32,121,111,117,32,114,97,118,105,100,44,32,119,104,121,32,121,111,117,32,108,121,105,110,103,32,97,98,111,117,116,32,110,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,63,32,74,117,115,116,32,116,101,108,108,32,116,114,117,116,104,44,32,110,105,99,101,32,116,114,121,32,116,111,32,112,114,111,106,101,99,116,32,116,97,107,101,111,118,101,114 }; + var _m2 = new List { 32,32,58,68,32,40,97,108,108,32,119,104,111,32,116,114,105,101,100,32,116,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,44,32,106,117,115,116,32,119,114,105,116,101,32,109,101,115,115,97,103,101,32,116,111,32,109,101,32,120,68,41 }; + Console.WriteLine(" >> " + new string(_m1.Select(c => (char)c).ToArray())); + Console.WriteLine(" >> " + new string(_m2.Select(c => (char)c).ToArray())); + Console.WriteLine(" "); + } +} + public class SchemaString(TSchemaClass instance, string member) : NativeObject(Schema.GetSchemaValue(instance.Handle, typeof(TSchemaClass).Name, member)) where TSchemaClass : NativeObject diff --git a/CS2-SimpleAdmin/Managers/BanManager.cs b/CS2-SimpleAdmin/Managers/BanManager.cs index f8a04b2..5863b59 100644 --- a/CS2-SimpleAdmin/Managers/BanManager.cs +++ b/CS2-SimpleAdmin/Managers/BanManager.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.ValveConstants.Protobuf; -using CounterStrikeSharp.API.Modules.Entities; using CS2_SimpleAdminApi; using Dapper; using Microsoft.Extensions.Logging; @@ -340,34 +339,8 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string var sqlUpdateBan = databaseProvider.GetUpdateBanStatusQuery(); await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId }); } - - // Apply immediately; the periodic cache refresh would otherwise keep rejecting the player for up to a minute - CS2_SimpleAdmin.Instance.CacheManager?.SetBanStatus(bansList.Select(b => (int)b.id), Models.BanStatus.UNBANNED); - - // css_ban also issues a native banid when UnlockedCommands is on; clear it so the engine stops - // rejecting the player. Done here, after the rows are updated, and for whichever pattern matched them. - if (CS2_SimpleAdmin.UnlockedCommands) - { - var steamIds = bansList - .Select(b => (object?)b.player_steamid) - .Where(id => id != null) - .Select(id => Convert.ToUInt64(id)) - .Where(id => id > 0) - .Distinct() - .ToList(); - - if (steamIds.Count > 0) - await Server.NextWorldUpdateAsync(() => - { - foreach (var steamId in steamIds) - Server.ExecuteCommand($"removeid {new SteamID(steamId).SteamId3}"); - }); - } - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError("Unable to unban player: {exception}", ex.Message); } + catch { } } // public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players) diff --git a/CS2-SimpleAdmin/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index 165efac..db74f2c 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -10,10 +10,8 @@ namespace CS2_SimpleAdmin.Managers; internal class CacheManager: IDisposable { private readonly ConcurrentDictionary _banCache = []; - // Rebuilt into fresh instances and swapped, so unlocked readers on the game thread never see a half-built index - private ConcurrentDictionary> _steamIdIndex = []; - private ConcurrentDictionary> _ipIndex = []; - private readonly object _indexLock = new(); + private readonly ConcurrentDictionary> _steamIdIndex = []; + private readonly ConcurrentDictionary> _ipIndex = []; private readonly ConcurrentDictionary> _playerIpsCache = []; private HashSet _cachedIgnoredIps = []; @@ -180,7 +178,7 @@ internal class CacheManager: IDisposable player_steamid AS PlayerSteamId, player_ip AS PlayerIp, status AS Status - FROM `sa_bans` WHERE updated_at >= @lastUpdate OR created >= @lastUpdate ORDER BY updated_at DESC + FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC """, new { lastUpdate = lastCheckTime } )).ToList(); @@ -223,7 +221,7 @@ internal class CacheManager: IDisposable player_steamid AS PlayerSteamId, player_ip AS PlayerIp, status AS Status - FROM `sa_bans` WHERE server_id = @serverId AND (updated_at >= @lastUpdate OR created >= @lastUpdate) ORDER BY updated_at DESC + FROM `sa_bans` WHERE server_id = @serverId AND (updated_at > @lastUpdate OR created > @lastUpdate) ORDER BY updated_at DESC """, new { serverId = CS2_SimpleAdmin.ServerId, lastUpdate = lastCheckTime } )).ToList(); @@ -296,7 +294,7 @@ internal class CacheManager: IDisposable .GroupBy(x => x.Address) .Select(g => { - var latest = g.MaxBy(x => x.Used_at)!; // group is never empty + var latest = g.MaxBy(x => x.Used_at); return new IpRecord( g.Key, latest.Used_at, @@ -355,36 +353,8 @@ internal class CacheManager: IDisposable /// private void RebuildIndexes() { - // SetBanStatus (unban thread) and RefreshCacheAsync (timer thread) can both get here - lock (_indexLock) - { - RebuildIndexesCore(); - } - } - - /// - /// Updates the status of the given bans in the cache and rebuilds the indexes, - /// so unbans take effect immediately instead of waiting for the next refresh. - /// - /// Ban ids to update. - /// New status. - public void SetBanStatus(IEnumerable banIds, BanStatus status) - { - var changed = false; - foreach (var id in banIds) - { - if (!_banCache.TryGetValue(id, out var ban) || ban.StatusEnum == status) continue; - _banCache[id] = ban with { Status = status.ToString() }; - changed = true; - } - - if (changed) RebuildIndexes(); - } - - private void RebuildIndexesCore() - { - var steamIdIndex = new ConcurrentDictionary>(); - var ipIndex = new ConcurrentDictionary>(); + _steamIdIndex.Clear(); + _ipIndex.Clear(); // Optimization: Cache config value to avoid repeated property access var banType = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType; @@ -399,10 +369,10 @@ internal class CacheManager: IDisposable if (ban.PlayerSteamId.HasValue) { var steamId = ban.PlayerSteamId.Value; - if (!steamIdIndex.TryGetValue(steamId, out var steamList)) + if (!_steamIdIndex.TryGetValue(steamId, out var steamList)) { steamList = new List(); - steamIdIndex[steamId] = steamList; + _steamIdIndex[steamId] = steamList; } steamList.Add(ban); } @@ -411,18 +381,14 @@ internal class CacheManager: IDisposable if (checkIpBans && !string.IsNullOrEmpty(ban.PlayerIp) && IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt)) { - if (!ipIndex.TryGetValue(ipUInt, out var ipList)) + if (!_ipIndex.TryGetValue(ipUInt, out var ipList)) { ipList = new List(); - ipIndex[ipUInt] = ipList; + _ipIndex[ipUInt] = ipList; } ipList.Add(ban); } } - - // Reference swap is atomic; readers finish on the old index or start on the complete new one - Volatile.Write(ref _steamIdIndex, steamIdIndex); - Volatile.Write(ref _ipIndex, ipIndex); } /// diff --git a/CS2-SimpleAdmin/Managers/MuteManager.cs b/CS2-SimpleAdmin/Managers/MuteManager.cs index 6f9b65b..01d83df 100644 --- a/CS2-SimpleAdmin/Managers/MuteManager.cs +++ b/CS2-SimpleAdmin/Managers/MuteManager.cs @@ -212,15 +212,7 @@ internal class MuteManager(IDatabaseProvider? databaseProvider) foreach (var muteRecord in muteRecords) { DateTime endDateTime = muteRecord.ends; - // zio: match by type and approximate end time; see RemovePenaltiesByDateTime. - string muteType = muteRecord.type; - var penaltyType = muteType switch - { - "MUTE" => PenaltyType.Mute, - "SILENCE" => PenaltyType.Silence, - _ => PenaltyType.Gag - }; - PlayerPenaltyManager.RemovePenaltiesByDateTime(slot, penaltyType, endDateTime); + PlayerPenaltyManager.RemovePenaltiesByDateTime(slot, endDateTime); } } diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index a7422ef..44debaa 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -1,4 +1,3 @@ -using System.Globalization; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Admin; @@ -93,11 +92,6 @@ internal class PlayerManager // zio: read on the main thread; the ban check below runs on a worker thread. var bypassIpBans = AdminManager.PlayerHasPermissions(new SteamID(steamId), IpBanBypassPermission); - if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(steamId, out var renamedTo)) - { - player.Rename(renamedTo); - } - if (CS2_SimpleAdmin.DatabaseProvider == null || CS2_SimpleAdmin.Instance.CacheManager == null) return; Task.Run(async () => @@ -303,92 +297,10 @@ internal class PlayerManager _loadPlayerSemaphore.Release(); } }); - } - /// - /// Returns every ban, gag/mute/silence and warn ever recorded for a SteamID, newest first. - /// Rows carry: type, status, reason, duration, created, ends, admin_name, lift_reason, lift_date, lift_admin. - /// - /// SteamID64 of the player, online or not. - /// Optional filter: bans, gags, mutes, silences or warns. - public async Task> GetPenaltyHistory(ulong steamId, string? type = null) - { - if (CS2_SimpleAdmin.DatabaseProvider == null) return []; - - try + if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name)) { - await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); - var sql = CS2_SimpleAdmin.DatabaseProvider.GetPenaltyHistoryQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode); - var rows = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId })).ToList(); - - // Filter word is the plural (bans, gags, mutes, silences, warns); rows carry BAN/GAG/MUTE/SILENCE/WARN - if (type != null) - rows.RemoveAll(r => !type.Equals((string)r.type + "s", StringComparison.OrdinalIgnoreCase)); - - return rows; - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError("Unable to load penalty history: {exception}", ex.Message); - return []; - } - } - - /// - /// Dapper gives DateTime on MySQL but can give a string on SQLite; accept both. - /// - internal static DateTime? ToDateTime(object? value) => - value switch - { - DateTime d => d, - string s when DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed) => parsed, - _ => null - }; - - /// - /// Loads permanent renames from the database into memory. - /// - public async Task LoadRenamedPlayers() - { - if (CS2_SimpleAdmin.DatabaseProvider == null) return; - - try - { - await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); - var rows = await connection.QueryAsync<(long steamId, string name)>(CS2_SimpleAdmin.DatabaseProvider.GetRenamesQuery()); - - await Server.NextWorldUpdateAsync(() => - { - foreach (var (steamId, name) in rows) - CS2_SimpleAdmin.RenamedPlayers[(ulong)steamId] = name; - }); - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError("Unable to load renames: {exception}", ex.Message); - } - } - - /// - /// Persists or removes a permanent rename (css_prename) for a player. - /// - /// SteamID64 of the player. - /// Forced name, or null/empty to remove the rename. - public async Task SaveRenamedPlayer(ulong steamId, string? name) - { - if (CS2_SimpleAdmin.DatabaseProvider == null) return; - - try - { - await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); - if (string.IsNullOrEmpty(name)) - await connection.ExecuteAsync(CS2_SimpleAdmin.DatabaseProvider.GetDeleteRenameQuery(), new { steamId }); - else - await connection.ExecuteAsync(CS2_SimpleAdmin.DatabaseProvider.GetUpsertRenameQuery(), new { steamId, name }); - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError("Unable to save rename: {exception}", ex.Message); + player.Rename(name); } } diff --git a/CS2-SimpleAdmin/Managers/PlayerPenaltyManager.cs b/CS2-SimpleAdmin/Managers/PlayerPenaltyManager.cs index 9acdc04..7931fe3 100644 --- a/CS2-SimpleAdmin/Managers/PlayerPenaltyManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerPenaltyManager.cs @@ -95,11 +95,9 @@ public static class PlayerPenaltyManager if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0) { - // zio: a penalty marked passed is over, even before RemoveExpiredPenalties drops it. - var active = penaltiesList.FirstOrDefault(p => !p.Passed || p.Duration == 0); - if (active == default) return false; - - endDateTime = active.EndDateTime; + if (penaltiesList.Count == 0) return false; + + endDateTime = penaltiesList.First().EndDateTime; return true; } @@ -257,26 +255,6 @@ public static class PlayerPenaltyManager } } - /// - /// zio: marks a penalty of one type as passed when its end is within a few seconds of the - /// database's. A penalty added in game keeps the end time from the command, which has - /// sub-second precision and is taken a moment before the one the database stores (to the - /// second), so an exact match never finds it and the penalty outlives its database row. - /// - public static void RemovePenaltiesByDateTime(int slot, PenaltyType penaltyType, DateTime dateTime) - { - if (!Penalties.TryGetValue(slot, out var penaltyDict) || - !penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) return; - - for (var i = 0; i < penaltiesList.Count; i++) - { - if ((penaltiesList[i].EndDateTime - dateTime).Duration() > TimeSpan.FromSeconds(5)) continue; - var penalty = penaltiesList[i]; - penalty.Passed = true; - penaltiesList[i] = penalty; - } - } - /// /// Removes or expires penalties automatically across all players based on their duration or "passed" flag. /// diff --git a/CS2-SimpleAdmin/Managers/ServerManager.cs b/CS2-SimpleAdmin/Managers/ServerManager.cs index 10dcc16..ec80aaa 100644 --- a/CS2-SimpleAdmin/Managers/ServerManager.cs +++ b/CS2-SimpleAdmin/Managers/ServerManager.cs @@ -43,7 +43,6 @@ public class ServerManager if (_getIpTryCount > 32 && isInvalidIp) { CS2_SimpleAdmin._logger?.LogError("Unable to load server data - can't fetch ip address!"); - _ = CS2_SimpleAdmin.PlayerManager.LoadRenamedPlayers(); return; } @@ -104,7 +103,7 @@ public class ServerManager CS2_SimpleAdmin.ServerId = serverId; CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress); - CS2_SimpleAdmin.ServerLoaded = true; +CS2_SimpleAdmin.ServerLoaded = true; if (CS2_SimpleAdmin.Instance.CacheManager != null) { @@ -116,9 +115,6 @@ public class ServerManager CS2_SimpleAdmin._logger?.LogCritical("Unable to create or get server_id: " + ex.Message); } - // Renames are server-independent, so load them even when the server row could not be resolved - await CS2_SimpleAdmin.PlayerManager.LoadRenamedPlayers(); - if (CS2_SimpleAdmin.Instance.Config.EnableMetrics) { var queryString = $"?address={address}&hostname={hostname}"; diff --git a/CS2-SimpleAdmin/Menus/BasicMenu.cs b/CS2-SimpleAdmin/Menus/BasicMenu.cs index 41c8ea1..ae9ef3f 100644 --- a/CS2-SimpleAdmin/Menus/BasicMenu.cs +++ b/CS2-SimpleAdmin/Menus/BasicMenu.cs @@ -30,7 +30,6 @@ public abstract class BasicMenu manager.RegisterMenu("players", "mute", "sa_mute", CreateMuteMenu, "@css/chat", "css_mute"); manager.RegisterMenu("players", "silence", "sa_silence", CreateSilenceMenu, "@css/chat", "css_silence"); manager.RegisterMenu("players", "team", "sa_team_force", CreateForceTeamMenu, "@css/kick", "css_team"); - manager.RegisterMenu("players", "history", "sa_history", CreateHistoryMenu, "@css/kick", "css_history"); // Server category menus - using translation keys manager.RegisterMenu("server", "plugins", "sa_menu_pluginsmanager_title", CreatePluginsMenu, "@css/root"); @@ -129,62 +128,6 @@ public abstract class BasicMenu return slayMenu.WithBackButton(); } - /// - /// Creates the penalty history menu: pick an online player, then one entry per past penalty. - /// Selecting an entry prints its full details to the admin's console. - /// - /// The admin player opening the menu. - /// A MenuBuilder instance for the history menu. - private static MenuBuilder CreateHistoryMenu(CCSPlayerController admin) - { - var localizer = CS2_SimpleAdmin._localizer; - var historyMenu = new MenuBuilder("sa_history", admin, localizer); - - 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; - historyMenu.AddOption(playerName, _ => - { - var steamId = player.SteamID; - var name = player.PlayerName; - var adminSteamId = admin.SteamID; - - // The list is built after the DB round trip, so it is opened from the world update rather than returned here - Task.Run(async () => - { - var rows = await CS2_SimpleAdmin.PlayerManager.GetPenaltyHistory(steamId); - await Server.NextWorldUpdateAsync(() => - { - // The slot may have been reused by another player while the query ran - if (!admin.IsValid || admin.SteamID != adminSteamId) return; - - if (rows.Count == 0) - { - CS2_SimpleAdmin.Instance.PrintHistory(admin.PrintToConsole, name, steamId, rows); - return; - } - - var list = new MenuBuilder("sa_history", admin, localizer); - foreach (var row in rows) - { - var active = (string)row.status == "ACTIVE"; - var label = $"[{(active ? $"{ChatColors.LightRed}X" : $"{ChatColors.Lime}✔️")}{ChatColors.Default}] {(string)row.type} {(string)row.reason}"; - list.AddOption(label.Length > 40 ? label[..40] : label, - _ => CS2_SimpleAdmin.Instance.PrintHistory(admin.PrintToConsole, name, steamId, [row])); - } - - // Built standalone, so the back button must be wired to the player list explicitly - list.WithBackAction(historyMenu.OpenMenu).OpenMenu(admin); - }); - }); - }); - } - - return historyMenu.WithBackButton(); - } - /// /// Creates menu for kicking players with reason selection. /// diff --git a/CS2-SimpleAdmin/Variables.cs b/CS2-SimpleAdmin/Variables.cs index ab65b36..d3566d6 100644 --- a/CS2-SimpleAdmin/Variables.cs +++ b/CS2-SimpleAdmin/Variables.cs @@ -77,7 +77,7 @@ public partial class CS2_SimpleAdmin internal MuteManager MuteManager = new(DatabaseProvider); internal WarnManager WarnManager = new(DatabaseProvider); internal CacheManager? CacheManager = new(); - internal static readonly PlayerManager PlayerManager = new(); + private static readonly PlayerManager PlayerManager = new(); // Timers internal Timer? PlayersTimer = null; diff --git a/CS2-SimpleAdmin/admin_help.txt b/CS2-SimpleAdmin/admin_help.txt index 8a01ab5..e8ce7af 100644 --- a/CS2-SimpleAdmin/admin_help.txt +++ b/CS2-SimpleAdmin/admin_help.txt @@ -1,6 +1,5 @@ {GREEN}[ CS2-SimpleAdmin HELP ]{DEFAULT} - css_who <#userid or name> - Display informations about player -- css_history <#userid or name or steamid64> [bans|gags|mutes|silences|warns] - Print player's penalty history to console - css_players - Display player list - css_ban <#userid or name> [time in minutes/0 perm] [reason] - Ban player - css_addban [time in minutes/0 perm] [reason] - Ban player via steamid64 diff --git a/CS2-SimpleAdmin/lang/ar.json b/CS2-SimpleAdmin/lang/ar.json index 1236e7f..4533510 100644 --- a/CS2-SimpleAdmin/lang/ar.json +++ b/CS2-SimpleAdmin/lang/ar.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} نقل {lightred}{1}{default} إلى {lightred}{2}{default}!", "sa_admin_warns_menu_title": "{gold}{0} {lime}تحذيرات", "sa_admin_warns_unwarn": "{lime}تم إلغاء التحذير بنجاح{default} لـ {gold}{0} {default}بسبب {gold}{1}{default}!", - "sa_history": "السجل", - "sa_history_header": "===== سجل العقوبات للاعب {0} ({1}) - {2} سجل =====", - "sa_history_none": "لم يتم العثور على عقوبات للاعب {0}.", "sa_admin_vote_menu_title": "{lime}تصويت لـ {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} بدأ التصويت لـ {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}نتائج التصويت لـ {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/de.json b/CS2-SimpleAdmin/lang/de.json index dfdfc85..f32db52 100644 --- a/CS2-SimpleAdmin/lang/de.json +++ b/CS2-SimpleAdmin/lang/de.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} hat {lightred}{1}{default} zu {lightred}{2}{default} transferiert!", "sa_admin_warns_menu_title": "{gold}{0} {lime}VERWARNUNGEN", "sa_admin_warns_unwarn": "{lime}Erfolgreich{default} Verwarnung von {gold}{0} {default}aufgehoben wegen {gold}{1}{default}!", - "sa_history": "Verlauf", - "sa_history_header": "===== Strafverlauf für {0} ({1}) - {2} Eintrag/Einträge =====", - "sa_history_none": "Keine Strafen für {0} gefunden.", "sa_admin_vote_menu_title": "{lime}ABSTIMMUNG FÜR {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} hat eine Abstimmung für {lightred}{1}{default} gestartet", "sa_admin_vote_message_results": "{lime}ABSTIMMUNGSERGEBNISSE FÜR {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/en.json b/CS2-SimpleAdmin/lang/en.json index d808e55..f39210e 100644 --- a/CS2-SimpleAdmin/lang/en.json +++ b/CS2-SimpleAdmin/lang/en.json @@ -131,9 +131,6 @@ "sa_admin_team_message": "{red}{0}{default} transfered {yellow}{1}{default} to {grey}{2}", "sa_admin_warns_menu_title": "{gold}{0} {lime}WARNS", "sa_admin_warns_unwarn": "{lime}Successfully{default} unwarned {yellow}{0} {default}for {orange}{1}", - "sa_history": "History", - "sa_history_header": "===== Penalty history for {0} ({1}) - {2} record(s) =====", - "sa_history_none": "No penalties found for {0}.", "sa_admin_vote_menu_title": "{lime}VOTING FOR {gold}{0}", "sa_admin_vote_message": "{red}{0}{default} started voting for {orange}{1}", "sa_admin_vote_message_results": "{lime}VOTING RESULTS FOR {orange}{0}", @@ -147,7 +144,7 @@ "sa_staffchat_template": ">[{yellow}STAFF{default}][{lime}{0}{default}] {yellow}{1}{default}: {grey}{2}", "sa_staffchat_template_norank": ">[{yellow}STAFF{default}] {yellow}{0}{default}: {grey}{1}", "sa_staffchat_template_player": ">[{red}STAFF{default}] {silver}{0}{default}: {silver}{1}", - "sa_staffsay_template": "{default}[{red}ALL{default}] [{lime}{0}{default}] {yellow}{1}{default}: {yellow}{2}{default}", + "sa_staffsay_template": "[{lime}{0}{default}] {yellow}{1}{default}: {red}{2}", "sa_staffsay_template_norank": "{yellow}{0}{default}: {red}{1}", "sa_psay_template": "[{red}PM{default}] [{lime}{0}{default}] {yellow}{1}{default}: {grey}{2}", "sa_psay_template_norank": "[{red}PM{default}] {yellow}{0}{default}: {grey}{1}", diff --git a/CS2-SimpleAdmin/lang/es.json b/CS2-SimpleAdmin/lang/es.json index a966057..e5b0edd 100644 --- a/CS2-SimpleAdmin/lang/es.json +++ b/CS2-SimpleAdmin/lang/es.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} transfirió a {lightred}{1}{default} al {lightred}{2}{default}!", "sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTENCIAS", "sa_admin_warns_unwarn": "{lime}Desadvertencia{default} exitosa de {gold}{0} {default}por {gold}{1}{default}!", - "sa_history": "Historial", - "sa_history_header": "===== Historial de sanciones de {0} ({1}) - {2} registro(s) =====", - "sa_history_none": "No se encontraron sanciones para {0}.", "sa_admin_vote_menu_title": "{lime}VOTACIÓN PARA {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} inició una votación para {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}RESULTADOS DE LA VOTACIÓN PARA {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/fa.json b/CS2-SimpleAdmin/lang/fa.json index a15fbd8..77d8273 100644 --- a/CS2-SimpleAdmin/lang/fa.json +++ b/CS2-SimpleAdmin/lang/fa.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} {lightred}{1}{default} را به {lightred}{2}{default} انتقال داد!", "sa_admin_warns_menu_title": "{gold}{0} {lime}هشدارها", "sa_admin_warns_unwarn": "{lime}هشدار با موفقیت{default} برای {gold}{0} {default}لغو شد به دلیل {gold}{1}{default}!", - "sa_history": "تاریخچه", - "sa_history_header": "===== تاریخچه مجازات‌های {0} ({1}) - {2} مورد =====", - "sa_history_none": "هیچ مجازاتی برای {0} یافت نشد.", "sa_admin_vote_menu_title": "{lime}رأی‌گیری برای {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} رأی‌گیری برای {lightred}{1}{default} را شروع کرد", "sa_admin_vote_message_results": "{lime}نتایج رأی‌گیری برای {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/fr.json b/CS2-SimpleAdmin/lang/fr.json index 391a3a5..27a4731 100644 --- a/CS2-SimpleAdmin/lang/fr.json +++ b/CS2-SimpleAdmin/lang/fr.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} a transféré {lightred}{1}{default} à l'équipe {lightred}{2}{default}!", "sa_admin_warns_menu_title": "{gold}{0} {lime}AVERTISSEMENTS", "sa_admin_warns_unwarn": "{lime}Avertissement annulé{default} avec succès pour {gold}{0} {default}en raison de {gold}{1}{default}!", - "sa_history": "Historique", - "sa_history_header": "===== Historique des sanctions de {0} ({1}) - {2} entrée(s) =====", - "sa_history_none": "Aucune sanction trouvée pour {0}.", "sa_admin_vote_menu_title": "{lime}VOTE POUR {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} a lancé un vote pour {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}RÉSULTATS DU VOTE POUR {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/lv.json b/CS2-SimpleAdmin/lang/lv.json index 1128ff3..e27aed8 100644 --- a/CS2-SimpleAdmin/lang/lv.json +++ b/CS2-SimpleAdmin/lang/lv.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} pārvietoja {lightred}{1}{default} uz {lightred}{2}{default} komandu!", "sa_admin_warns_menu_title": "{gold}{0} {lime}BRĪDINĀJUMI", "sa_admin_warns_unwarn": "{lime}Brīdinājums veiksmīgi{default} atsaukts priekš {gold}{0} {default}dēļ {gold}{1}{default}!", - "sa_history": "Vēsture", - "sa_history_header": "===== Sodu vēsture spēlētājam {0} ({1}) - {2} ieraksts(-i) =====", - "sa_history_none": "Spēlētājam {0} nav atrasts neviens sods.", "sa_admin_vote_menu_title": "{lime}BALSOŠANA PAR {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} uzsāka balsošanu par {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}BALSOŠANAS REZULTĀTI PAR {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/pl.json b/CS2-SimpleAdmin/lang/pl.json index ae6be91..3b6b3a7 100644 --- a/CS2-SimpleAdmin/lang/pl.json +++ b/CS2-SimpleAdmin/lang/pl.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} przerzucił {lightred}{1}{default} do {lightred}{2}{default}!", "sa_admin_warns_menu_title": "{lime}OSTRZEŻENIA {gold}{0}", "sa_admin_warns_unwarn": "{lime}Pomyślnie{default} usunięto ostrzeżenie dla {gold}{0} {default}za {gold}{1}{default}!", - "sa_history": "Historia", - "sa_history_header": "===== Historia kar gracza {0} ({1}) - {2} wpis(ow) =====", - "sa_history_none": "Nie znaleziono kar dla {0}.", "sa_admin_vote_menu_title": "{lime}GŁOSOWANIE NA {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} rozpoczął głosowanie na {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}WYNIKI GŁOSOWANIA {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/pt-BR.json b/CS2-SimpleAdmin/lang/pt-BR.json index 95f08cb..299ebc2 100644 --- a/CS2-SimpleAdmin/lang/pt-BR.json +++ b/CS2-SimpleAdmin/lang/pt-BR.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} transferiu {lightred}{1}{default} para a equipe {lightred}{2}{default}!", "sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTÊNCIAS", "sa_admin_warns_unwarn": "{lime}Advertência removida com sucesso{default} para {gold}{0} {default}por {gold}{1}{default}!", - "sa_history": "Histórico", - "sa_history_header": "===== Histórico de punições de {0} ({1}) - {2} registro(s) =====", - "sa_history_none": "Nenhuma punição encontrada para {0}.", "sa_admin_vote_menu_title": "{lime}VOTAÇÃO PARA {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} iniciou uma votação para {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}RESULTADOS DA VOTAÇÃO PARA {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/pt-PT.json b/CS2-SimpleAdmin/lang/pt-PT.json index 93fc675..94cce8c 100644 --- a/CS2-SimpleAdmin/lang/pt-PT.json +++ b/CS2-SimpleAdmin/lang/pt-PT.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} transferiu {lightred}{1}{default} para a equipe {lightred}{2}{default}!", "sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTÊNCIAS", "sa_admin_warns_unwarn": "{lime}Advertência removida com sucesso{default} para {gold}{0} {default}por {gold}{1}{default}!", - "sa_history": "Histórico", - "sa_history_header": "===== Histórico de punições de {0} ({1}) - {2} registo(s) =====", - "sa_history_none": "Nenhuma punição encontrada para {0}.", "sa_admin_vote_menu_title": "{lime}VOTAÇÃO PARA {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} iniciou uma votação para {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}RESULTADOS DA VOTAÇÃO PARA {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/ru.json b/CS2-SimpleAdmin/lang/ru.json index de16a5f..f92fb5a 100644 --- a/CS2-SimpleAdmin/lang/ru.json +++ b/CS2-SimpleAdmin/lang/ru.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} перевел {lightred}{1}{default} в команду {lightred}{2}{default}!", "sa_admin_warns_menu_title": "{gold}{0} {lime}ПРЕДУПРЕЖДЕНИЯ", "sa_admin_warns_unwarn": "{lime}Предупреждение успешно удалено{default} для {gold}{0} {default}за {gold}{1}{default}!", - "sa_history": "История", - "sa_history_header": "===== История наказаний игрока {0} ({1}) - записей: {2} =====", - "sa_history_none": "Наказания для {0} не найдены.", "sa_admin_vote_menu_title": "{lime}ГОЛОСОВАНИЕ ЗА {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} начал голосование за {lightred}{1}{default}", "sa_admin_vote_message_results": "{lime}РЕЗУЛЬТАТЫ ГОЛОСОВАНИЯ ЗА {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/tr.json b/CS2-SimpleAdmin/lang/tr.json index 12c9cbd..c520397 100644 --- a/CS2-SimpleAdmin/lang/tr.json +++ b/CS2-SimpleAdmin/lang/tr.json @@ -122,9 +122,6 @@ "sa_admin_team_message": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} takımına transfer etti!", "sa_admin_warns_menu_title": "{gold}{0} {lime}UYARILAR", "sa_admin_warns_unwarn": "{lime}Uyarı başarıyla kaldırıldı{default} {gold}{0} {default} için {gold}{1}{default}!", - "sa_history": "Geçmiş", - "sa_history_header": "===== {0} ({1}) ceza geçmişi - {2} kayıt =====", - "sa_history_none": "{0} için ceza bulunamadı.", "sa_admin_vote_menu_title": "{lime}OY VERME {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} {lightred}{1}{default} için oy kullanmaya başladı", "sa_admin_vote_message_results": "{lime}OY SONUÇLARI {gold}{0}", diff --git a/CS2-SimpleAdmin/lang/zh-Hans.json b/CS2-SimpleAdmin/lang/zh-Hans.json index d8597da..c3d2331 100644 --- a/CS2-SimpleAdmin/lang/zh-Hans.json +++ b/CS2-SimpleAdmin/lang/zh-Hans.json @@ -120,9 +120,6 @@ "sa_admin_team_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 转移到 {lightred}{2}{default} 队伍!", "sa_admin_warns_menu_title": "{gold}{0} {lime}警告", "sa_admin_warns_unwarn": "{lime}成功移除警告{default} {gold}{0} 对于 {gold}{1}{default}!", - "sa_history": "历史记录", - "sa_history_header": "===== {0} ({1}) 的处罚记录 - 共 {2} 条 =====", - "sa_history_none": "未找到 {0} 的处罚记录。", "sa_admin_vote_menu_title": "{lime}投票 {gold}{0}", "sa_admin_vote_message": "{lightred}{0}{default} 发起了 [{lightred}{1}{default}] 的投票", "sa_admin_vote_message_results": "{lime}投票结果 {gold}{0}", diff --git a/README.md b/README.md index 6a3254e..4a2bdb0 100644 --- a/README.md +++ b/README.md @@ -33,30 +33,6 @@ from the `build-1.8.2b` tag). Changes, all marked `zio:` in the code: highest-immunity group, and who changed it) to `sa_rank_log`, the table simpleadmin-web creates and lists as the public promotions/demotions log. If the table is missing, it warns once in the log and the admin change goes ahead. -- With `TimeMode` 0 (online time only), a gag, mute or silence given in game now ends when its - database row expires. Upstream matched the two by exact end time, which never matched for one - given in game (the database stores the time to the second), so it lasted until the player - reconnected. A penalty marked passed also stops applying at once, not on the next 61-second tick. -- Targeting by part of a name: when several names contain it, one that is exactly it (any case) - is picked, and otherwise the reply lists the matches. `css_ungag`, `css_unmute` and - `css_unsilence` match online players by part of the name too (upstream needed the full name), - falling back to the offline search only when no one online matches. -- No startup version check or banner: upstream's `PluginInfo` (`CheckVersion`, which asked upstream's - GitHub for its `VERSION` on every load, and `ShowAd`, the console banner with two encoded lines - of personal messages) is removed. `EnableUpdateCheck` stays in the config, so existing files - still load, and does nothing. - -Backported from upstream 1.9.0 (commit `530f603`), not marked `zio:`: - -- `css_unban` applies at once: the ban cache is updated straight away instead of on the next - 61-second refresh, and `removeid` clears the engine ban when `UnlockedCommands` is on. -- `css_prename` survives restarts: renames are stored in the new `sa_renames` table (migration - 017) and re-applied on connect and name change. `css_rename` from a hidden admin renames again. -- New `css_history <#userid|name|steamid64> [bans|gags|mutes|silences|warns]` (`@css/kick`), and - Players > History in the admin menu. Commands missing from an existing `Commands.json` are - registered under their default name. -- Left out: upstream's admin-loading changes (ours differ, see `1.8.2b-zio11`), its `css_say` - change and its `css_penalties` fix. Publish with `FORGEJO_TOKEN=... ./release.sh v` (ModuleVersion is `1.8.2b-zioN`). It rebuilds from the committed source and uploads `SimpleAdmin-.tar.gz` to git.zio.sh diff --git a/release.sh b/release.sh index 6913b4e..0ccb29b 100755 --- a/release.sh +++ b/release.sh @@ -22,13 +22,8 @@ set -euo pipefail FORGEJO_URL="https://git.zio.sh" OWNER="cs2" REPO="SimpleAdmin" -# Needs write:repository. Only uploading needs it; downloading from the public repo doesn't. From the -# environment, or else from .forgejo-token in the cs2/ folder above every plugin (outside their -# repos, mode 600). Don't commit a real token. -TOKEN_FILE="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/../../.forgejo-token" -if [[ -z "${FORGEJO_TOKEN:-}" && -r "$TOKEN_FILE" ]]; then - FORGEJO_TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")" -fi +# Placeholder - replace, or set FORGEJO_TOKEN in the environment instead. Needs write:repository. +# Only uploading needs it; downloading from the public repo doesn't. Don't commit a real token. FORGEJO_TOKEN="${FORGEJO_TOKEN:-CHANGE_ME_FORGEJO_TOKEN}" TAG="${1:-}" @@ -37,7 +32,7 @@ if [[ -z "$TAG" ]]; then exit 1 fi if [[ "$FORGEJO_TOKEN" == "CHANGE_ME_FORGEJO_TOKEN" ]]; then - echo "Set FORGEJO_TOKEN (export it, or put it in $TOKEN_FILE) first." >&2 + echo "Set FORGEJO_TOKEN (edit release.sh or export it) first." >&2 exit 1 fi for tool in podman jq curl git; do