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..bea9180 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-zio7"; 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/basechat.cs b/CS2-SimpleAdmin/Commands/basechat.cs index b4cae68..8df9685 100644 --- a/CS2-SimpleAdmin/Commands/basechat.cs +++ b/CS2-SimpleAdmin/Commands/basechat.cs @@ -32,7 +32,7 @@ public partial class CS2_SimpleAdmin if (_localizer == null) continue; // zio: players get ">[STAFF][RANK] name: message", like @ in team chat. player.PrintToChat(caller == null - ? _localizer["sa_adminchat_template_admin", _localizer["sa_console"], utf8String.Trim()] + ? _localizer["sa_adminchat_template_admin", _localizer["sa_console"], utf8String] : StaffChatMessage(caller, utf8String.Trim())); } } @@ -101,7 +101,7 @@ public partial class CS2_SimpleAdmin { player.SendLocalizedMessage(_localizer, "sa_adminsay_prefix", - utf8String.Trim().ReplaceColorTags()); // zio: Trim, the message starts with the space after the command + utf8String.ReplaceColorTags()); } } @@ -131,7 +131,7 @@ public partial class CS2_SimpleAdmin // zio: from a player, "(PM) [RANK] name: message". var rank = caller == null ? null : Helper.GetStaffRankTag(caller); var line = caller == null || _localizer == null - ? $"({callerName}) {utf8String.Trim()}".ReplaceColorTags() + ? $"({callerName}) {utf8String}".ReplaceColorTags() : rank == null ? _localizer["sa_psay_template_norank", callerName, utf8String.Trim()] : _localizer["sa_psay_template", rank, callerName, utf8String.Trim()]; diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs index e0c362b..9513cbc 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -39,9 +39,7 @@ public partial class CS2_SimpleAdmin var userId = caller.UserId.Value; var steamId = caller.SteamID; - // zio: other players' penalties need @zio/penalties (or root), not @css/kick. - if (!string.IsNullOrEmpty(command.GetArg(1)) && (AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@zio/penalties") - || AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/root"))) + if (!string.IsNullOrEmpty(command.GetArg(1)) && AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/kick")) { var targets = GetTarget(command); @@ -286,9 +284,7 @@ public partial class CS2_SimpleAdmin if (DatabaseProvider == null) return; var flagsList = flags.Split(',').Select(flag => flag.Trim()).ToList(); - // zio: who did it, for the rank log (null caller is Console, which includes RCON). - _ = Instance.PermissionManager.AddAdminBySteamId(steamid, name, flagsList, immunity, time, globalAdmin, - caller?.SteamID ?? 0, caller?.PlayerName ?? "Console"); + _ = Instance.PermissionManager.AddAdminBySteamId(steamid, name, flagsList, immunity, time, globalAdmin); Helper.LogCommand(caller, $"css_addadmin {steamid} {name} {flags} {immunity} {time}"); @@ -333,8 +329,7 @@ public partial class CS2_SimpleAdmin public void RemoveAdmin(CCSPlayerController? caller, string steamid, bool globalDelete = false, CommandInfo? command = null) { if (DatabaseProvider == null) return; - // zio: who did it, for the rank log (null caller is Console, which includes RCON). - _ = PermissionManager.DeleteAdminBySteamId(steamid, globalDelete, caller?.SteamID ?? 0, caller?.PlayerName ?? "Console"); + _ = PermissionManager.DeleteAdminBySteamId(steamid, globalDelete); AddTimer(2, () => { @@ -507,31 +502,26 @@ public partial class CS2_SimpleAdmin Task.Run(async () => { await PermissionManager.CreateGroupsJsonFile(); - var admins = await PermissionManager.CreateAdminsJsonFile(); - if (admins == null) return; - - await Server.NextWorldUpdateAsync(() => + await PermissionManager.CreateAdminsJsonFile(); + + var adminsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/admins.json"); + var groupsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/groups.json"); + + await Server.NextWorldUpdateAsync(() => { - // zio: CSS's loaders only ever add, so first remove everyone loaded last time (removed or - // demoted admins kept their old rank until a restart otherwise), then load the new set, - // all in one tick so nobody is left without their flags in between. Groups can't be - // removed from CSS, so flags taken off a group still need a restart. - foreach (var steamId in PermissionManager.AdminCache.Keys.ToList()) - AdminManager.RemovePlayerAdminData(steamId); - PermissionManager.AdminCache.Clear(); - - foreach (var admin in admins.GroupBy(a => a.SteamId)) + AddTimer(1, () => { - var ends = admin.Any(a => a.Ends == null) ? null : admin.Max(a => a.Ends); - PermissionManager.AdminCache[admin.Key] = (ends, admin.SelectMany(a => a.Flags).Distinct().ToList()); - } + if (!string.IsNullOrEmpty(adminsFile)) + AddTimer(2.0f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); + if (!string.IsNullOrEmpty(groupsFile)) + AddTimer(3.0f, () => AdminManager.LoadAdminGroups(ModuleDirectory + "/data/groups.json")); + if (!string.IsNullOrEmpty(adminsFile)) + AddTimer(4.0f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); - AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json"); - AdminManager.LoadAdminGroups(ModuleDirectory + "/data/groups.json"); - - _logger?.LogInformation("Loaded {count} admins!", PermissionManager.AdminCache.Count); + _logger?.LogInformation("Loaded admins!"); + }); }); - }); + }); //_ = _adminManager.GiveAllGroupsFlags(); //_ = _adminManager.GiveAllFlags(); @@ -846,99 +836,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..8227425 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) @@ -147,35 +144,21 @@ internal static class Helper return string.Join(" ", parts); } - // zio: whether an admin_overrides.json override lets the player run the command, checked the way - // CSS checks a typed command: "all" needs every flag, anything else ("any") needs one of them. - internal static bool CanRunOverriddenCommand(CCSPlayerController player, string commandName) - { - var data = AdminManager.GetCommandOverrideData(commandName); - if (data == null) return false; - var flags = data.Flags.ToArray(); - BaseRequiresPermissions check = data.CheckType == "all" - ? new RequiresPermissions(flags) - : new RequiresPermissionsOr(flags); - check.Command = commandName; - return check.CanExecuteCommand(player); - } - - // zio: rank groups, highest first, and their chat tags (the same as CS2-Tags' chat prefixes). - // Guardian (VIP) is last, so a staff rank wins. + // zio: rank groups, highest first, and their chat abbreviations. Guardian (VIP) is last, so a + // staff rank wins. private static readonly (string Group, string Tag)[] StaffRanks = [ ("#rank/owner", "O"), - ("#rank/senioradmin", "S.Admin"), - ("#rank/admin", "Admin"), - ("#rank/trialadmin", "T.Admin"), - ("#rank/mod", "Mod"), - ("#rank/trialmod", "T.Mod"), - ("#rank/helper", "Helper"), - ("#rank/guardian", "Guardian"), + ("#rank/senioradmin", "SA"), + ("#rank/admin", "A"), + ("#rank/trialadmin", "TA"), + ("#rank/mod", "M"), + ("#rank/trialmod", "TM"), + ("#rank/helper", "H"), + ("#rank/guardian", "G"), ]; - // zio: the player's highest rank tag, or null if they're in no rank group. + // zio: the player's highest rank abbreviation, or null if they're in no rank group. internal static string? GetStaffRankTag(CCSPlayerController player) { var groups = AdminManager.GetPlayerAdminData(new SteamID(player.SteamID))?.Groups; @@ -1006,6 +989,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/PermissionManager.cs b/CS2-SimpleAdmin/Managers/PermissionManager.cs index 4104a8a..5826e74 100644 --- a/CS2-SimpleAdmin/Managers/PermissionManager.cs +++ b/CS2-SimpleAdmin/Managers/PermissionManager.cs @@ -17,8 +17,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) /// /// Retrieves all players' flags and associated data asynchronously. /// - /// A list of tuples containing player SteamID, name, flags, immunity, and expiration time, or null if the database query failed. - private async Task, int, DateTime?)>?> GetAllPlayersFlags() + /// A list of tuples containing player SteamID, name, flags, immunity, and expiration time. + private async Task, int, DateTime?)>> GetAllPlayersFlags() { if (databaseProvider == null) return new List<(ulong, string, List, int, DateTime?)>(); @@ -68,8 +68,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) catch (Exception ex) { CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message); - // zio: null rather than empty, so ReloadAdmins keeps the loaded admins instead of removing them all. - return null; + return []; } } @@ -151,50 +150,96 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) /// /// Creates a JSON file containing admins data asynchronously. /// - /// zio: every admin row loaded, for ReloadAdmins to rebuild AdminCache from, or null if the database query failed (the file is then left alone). [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - public async Task Flags)>?> CreateAdminsJsonFile() + public async Task CreateAdminsJsonFile() { - var allPlayers = await GetAllPlayersFlags(); - if (allPlayers == null) return null; - + List<(ulong identity, string name, List flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags(); var validPlayers = allPlayers - .Select(player => (player, steamId: SteamID.TryParse(player.Item1.ToString(), out var steamId) ? steamId : null)) - .Where(p => p.steamId != null) + .Where(player => SteamID.TryParse(player.identity.ToString(), out _)) .ToList(); - // zio: keyed by SteamID, not name: CSS ignores the key, and two admins with the same name were - // merged into one entry under the first one's SteamID. Upstream also cleared and refilled - // AdminCache here once per admin, so it only ever kept the last admin and a reload never - // removed anyone else; ReloadAdmins now does that once, from the list returned here. - var jsonData = validPlayers - .GroupBy(p => p.steamId!.SteamId64) - .ToDictionary( - group => group.Key.ToString(), - object (group) => - { - var rows = group.Select(p => p.player).ToList(); - var flags = rows.SelectMany(r => r.Item3).Distinct().ToList(); - return new - { - identity = group.Key.ToString(), - immunity = rows.Max(r => r.Item4), - flags = flags.Where(flag => flag.StartsWith($"@")).ToList(), - groups = flags.Where(flag => flag.StartsWith($"#")).ToList() - }; - }); + var jsonData = validPlayers + .GroupBy(player => player.name) + .ToDictionary( + group => group.Key, + object (group) => + { + var consolidatedData = group.Aggregate( + new + { + identity = string.Empty, + immunity = 0, + flags = new List(), + groups = new List() + }, + (acc, player) => + { + if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString())) + { + acc = acc with { identity = player.identity.ToString() }; + } - var options = new JsonSerializerOptions - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; + acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) }; - var json = JsonSerializer.Serialize(jsonData, options); + acc = acc with + { + flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(), + groups = acc.groups.Concat(player.flags.Where(flag => flag.StartsWith($"#"))).Distinct().ToList() + }; + + return acc; + }); + + Server.NextWorldUpdate(() => + { + var keysToRemove = new List(); + + foreach (var steamId in AdminCache.Keys.ToList()) + { + var data = AdminManager.GetPlayerAdminData(steamId); + if (data != null) + { + var flagsArray = AdminCache[steamId].Flags.ToArray(); + AdminManager.RemovePlayerPermissions(steamId, flagsArray); + AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray); + } + + keysToRemove.Add(steamId); + } + + foreach (var steamId in keysToRemove) + { + if (!AdminCache.TryRemove(steamId, out _)) continue; + + var data = AdminManager.GetPlayerAdminData(steamId); + if (data == null) continue; + if (data.Flags.Count != 0 && data.Groups.Count != 0) continue; + + AdminManager.ClearPlayerPermissions(steamId); + AdminManager.RemovePlayerAdminData(steamId); + } + + foreach (var player in group) + { + if (SteamID.TryParse(player.identity.ToString(), out var steamId) && steamId != null) + { + AdminCache.TryAdd(steamId, (player.ends, player.flags)); + } + } + }); + + return consolidatedData; + }); + + var options = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + var json = JsonSerializer.Serialize(jsonData, options); var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json"); await File.WriteAllTextAsync(filePath, json); - - return validPlayers.Select(p => (p.steamId!, p.player.Item5, p.player.Item3)).ToList(); } /// @@ -202,9 +247,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) /// /// The SteamID of the admin to delete. /// Whether to delete the admin globally or only for the current server. - /// zio: SteamID64 of who removed them, 0 for Console, for the rank log. - /// zio: name of who removed them, for the rank log. - public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false, ulong adminSteamId = 0, string adminName = "Console") + public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false) { if (databaseProvider == null) return; if (string.IsNullOrEmpty(playerSteamId)) return; @@ -212,14 +255,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) try { await using var connection = await databaseProvider.CreateConnectionAsync(); - // zio: note the rank and name before they're gone, for the rank log. - var before = await GetRank(connection, playerSteamId); - var playerName = await connection.ExecuteScalarAsync( - "SELECT player_name FROM sa_admins WHERE player_steamid = @playerSteamId ORDER BY server_id IS NULL DESC, id LIMIT 1", - new { playerSteamId }) ?? ""; var sql = databaseProvider.GetDeleteAdminQuery(globalDelete); await connection.ExecuteAsync(sql, new { PlayerSteamID = playerSteamId, CS2_SimpleAdmin.ServerId }); - await LogRankChange(connection, playerSteamId, playerName, before, await GetRank(connection, playerSteamId), adminSteamId, adminName); } catch (Exception ex) { @@ -236,9 +273,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) /// Immunity level. /// Duration in minutes for admin expiration; 0 means permanent. /// Whether the admin is global or server-specific. - /// zio: SteamID64 of who added them, 0 for Console, for the rank log. - /// zio: name of who added them, for the rank log. - public async Task AddAdminBySteamId(string playerSteamId, string playerName, List flagsList, int immunity = 0, int time = 0, bool globalAdmin = false, ulong adminSteamId = 0, string adminName = "Console") + public async Task AddAdminBySteamId(string playerSteamId, string playerName, List flagsList, int immunity = 0, int time = 0, bool globalAdmin = false) { if (databaseProvider == null) return; @@ -250,7 +285,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) try { await using var connection = await databaseProvider.CreateConnectionAsync(); - var before = await GetRank(connection, playerSteamId); // zio: for the rank log var insertAdminSql = databaseProvider.GetAddAdminQuery(); var adminId = await connection.ExecuteScalarAsync(insertAdminSql, new @@ -273,8 +307,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) }); } - await LogRankChange(connection, playerSteamId, playerName, before, await GetRank(connection, playerSteamId), adminSteamId, adminName); - await Server.NextWorldUpdateAsync(() => { CS2_SimpleAdmin.Instance.ReloadAdmins(null); @@ -286,69 +318,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) } } - // zio: rank history in sa_rank_log, a table owned by our web panel (simpleadmin-web's - // sql/rank-log.sql), which logs its own changes there too. A player's rank is their - // highest-immunity group; a change is only logged when that changes. - private sealed class RankRow - { - public string Name { get; set; } = ""; - public int Immunity { get; set; } - } - - private static bool _rankLogMissingWarned; - - // zio: the player's current rank, or null. Only unexpired admin rows and groups that apply to - // this server count, the same set SimpleAdmin loads. - private static async Task GetRank(System.Data.Common.DbConnection connection, string playerSteamId) - { - return await connection.QueryFirstOrDefaultAsync(""" - SELECT g.name AS Name, g.immunity AS Immunity - FROM sa_admins a - JOIN sa_admins_flags f ON f.admin_id = a.id - JOIN sa_groups g ON g.name = f.flag - JOIN sa_groups_servers gs ON gs.group_id = g.id AND (gs.server_id IS NULL OR gs.server_id = @serverid) - WHERE a.player_steamid = @playerSteamId AND (a.ends IS NULL OR a.ends > @now) - AND (a.server_id IS NULL OR a.server_id = @serverid) - ORDER BY g.immunity DESC, g.id LIMIT 1 - """, new { playerSteamId, now = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId }); - } - - // zio: records a rank change in sa_rank_log. Never fails the admin change itself: if the table - // is missing (the panel hasn't created it), it warns once and carries on. - private static async Task LogRankChange(System.Data.Common.DbConnection connection, string playerSteamId, string playerName, - RankRow? before, RankRow? after, ulong adminSteamId, string adminName) - { - if (before?.Name == after?.Name) return; - var kind = before == null ? "added" - : after == null ? "removed" - : after.Immunity > before.Immunity ? "promoted" - : after.Immunity < before.Immunity ? "demoted" - : "changed"; - try - { - await connection.ExecuteAsync(""" - INSERT INTO sa_rank_log (player_steamid, player_name, old_rank, new_rank, kind, admin_steamid, admin_name, source, created) - VALUES (@playerSteamId, @playerName, @oldRank, @newRank, @kind, @adminSteamId, @adminName, 'game', @created) - """, new - { - playerSteamId = ulong.Parse(playerSteamId), - playerName, - oldRank = before?.Name, - newRank = after?.Name, - kind, - adminSteamId, - adminName, - created = Time.ActualDateTime() - }); - } - catch (Exception ex) - { - if (_rankLogMissingWarned) return; - _rankLogMissingWarned = true; - CS2_SimpleAdmin._logger?.LogWarning($"Couldn't write sa_rank_log (the web panel creates it): {ex.Message}"); - } - } - /// /// Adds a new group with flags and immunity asynchronously. /// 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..1234079 100644 --- a/CS2-SimpleAdmin/Menus/BasicMenu.cs +++ b/CS2-SimpleAdmin/Menus/BasicMenu.cs @@ -20,22 +20,20 @@ public abstract class BasicMenu var manager = MenuManager.Instance; // Players category menus - using translation keys - // zio: each entry names its command, so admin_overrides.json applies to the menu too. - manager.RegisterMenu("players", "slap", "sa_slap", CreateSlapMenu, "@css/slay", "css_slap"); - manager.RegisterMenu("players", "slay", "sa_slay", CreateSlayMenu, "@css/slay", "css_slay"); - manager.RegisterMenu("players", "kick", "sa_kick", CreateKickMenu, "@css/kick", "css_kick"); - manager.RegisterMenu("players", "warn", "sa_warn", CreateWarnMenu, "@css/kick", "css_warn"); - manager.RegisterMenu("players", "ban", "sa_ban", CreateBanMenu, "@css/ban", "css_ban"); - manager.RegisterMenu("players", "gag", "sa_gag", CreateGagMenu, "@css/chat", "css_gag"); - 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"); + manager.RegisterMenu("players", "slap", "sa_slap", CreateSlapMenu, "@css/slay"); + manager.RegisterMenu("players", "slay", "sa_slay", CreateSlayMenu, "@css/slay"); + manager.RegisterMenu("players", "kick", "sa_kick", CreateKickMenu, "@css/kick"); + manager.RegisterMenu("players", "warn", "sa_warn", CreateWarnMenu, "@css/kick"); + manager.RegisterMenu("players", "ban", "sa_ban", CreateBanMenu, "@css/ban"); + manager.RegisterMenu("players", "gag", "sa_gag", CreateGagMenu, "@css/chat"); + manager.RegisterMenu("players", "mute", "sa_mute", CreateMuteMenu, "@css/chat"); + manager.RegisterMenu("players", "silence", "sa_silence", CreateSilenceMenu, "@css/chat"); + manager.RegisterMenu("players", "team", "sa_team_force", CreateForceTeamMenu, "@css/kick"); // Server category menus - using translation keys manager.RegisterMenu("server", "plugins", "sa_menu_pluginsmanager_title", CreatePluginsMenu, "@css/root"); - manager.RegisterMenu("server", "changemap", "sa_changemap", CreateChangeMapMenu, "@css/changemap", "css_map"); - manager.RegisterMenu("server", "restart", "sa_restart_game", CreateRestartGameMenu, "@css/generic", "css_rr"); + manager.RegisterMenu("server", "changemap", "sa_changemap", CreateChangeMapMenu, "@css/changemap"); + manager.RegisterMenu("server", "restart", "sa_restart_game", CreateRestartGameMenu, "@css/generic"); manager.RegisterMenu("server", "custom", "sa_menu_custom_commands", CreateCustomCommandsMenu, "@css/generic"); // Admin category menus - using translation keys @@ -129,62 +127,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/Menus/MenuManager.cs b/CS2-SimpleAdmin/Menus/MenuManager.cs index f850a33..6abf3ea 100644 --- a/CS2-SimpleAdmin/Menus/MenuManager.cs +++ b/CS2-SimpleAdmin/Menus/MenuManager.cs @@ -219,9 +219,8 @@ public class MenuManager // Check if command has overridden permissions if (AdminManager.CommandIsOverriden(commandName)) { - // zio: honour the override's check_type ("any" passes with one flag), as CSS does - // for the typed command, instead of requiring every flag. - hasPermission = Helper.CanRunOverriddenCommand(player, commandName); + var overriddenPermission = AdminManager.GetPermissionOverrides(commandName); + hasPermission = AdminManager.PlayerHasPermissions(steamId, overriddenPermission); } else if (!string.IsNullOrEmpty(permission)) { 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..2dd7b7e 100644 --- a/CS2-SimpleAdmin/lang/en.json +++ b/CS2-SimpleAdmin/lang/en.json @@ -1,10 +1,10 @@ { "sa_title": "SimpleAdmin", - "sa_prefix": "{default}[{red}zio{default}] ", + "sa_prefix": "{lightred}[SA] {default}", "sa_unknown": "Unknown", "sa_no_permission": "You do not have permissions to use this command.", - "sa_ban_max_duration_exceeded": "Ban duration cannot exceed {red}{0}{default} minutes.", + "sa_ban_max_duration_exceeded": "Ban duration cannot exceed {lightred}{0}{default} minutes.", "sa_ban_perm_restricted": "You do not have the right to permanently ban.", "sa_admin_add": "Add Admin", @@ -64,30 +64,30 @@ "sa_discord_penalty_warn": "Warn registered", "sa_discord_penalty_unknown": "Unknown registered", - "sa_player_penalty_chat_active": "{red}Your chat is blocked until: {grey}{0}", - "sa_player_penalty_gag_active": "You are gagged for {red}{0}{default}.\nReason: {red}{1}", - "sa_player_penalty_gag_active_perm": "You are gagged {red}permanently{default}.\nReason: {red}{0}", - "sa_player_penalty_silence_active": "You are silenced for {red}{0}{default}.\nReason: {red}{1}", - "sa_player_penalty_silence_active_perm": "You are silenced {red}permanently{default}.\nReason: {red}{0}", + "sa_player_penalty_chat_active": "{lightred}Your chat is blocked to: {grey}{0}", + "sa_player_penalty_gag_active": "{lightred}You are gagged for {grey}{0}{lightred}. Reason: {grey}{1}\n{grey}Team chat starting with {lightred}@{grey} still reaches online staff.", + "sa_player_penalty_gag_active_perm": "{lightred}You are gagged permanently. Reason: {grey}{0}\n{grey}Team chat starting with {lightred}@{grey} still reaches online staff.", + "sa_player_penalty_silence_active": "{lightred}You are silenced for {grey}{0}{lightred}. Reason: {grey}{1}\n{grey}Team chat starting with {lightred}@{grey} still reaches online staff.", + "sa_player_penalty_silence_active_perm": "{lightred}You are silenced permanently. Reason: {grey}{0}\n{grey}Team chat starting with {lightred}@{grey} still reaches online staff.", "sa_player_penalty_no_reason": "none given", - "sa_player_ban_active": "You are banned from this server for {red}{0}{default}.\nReason: {red}{1}", - "sa_player_ban_active_perm": "You are banned from this server {red}permanently{default}.\nReason: {red}{0}", - "sa_player_ban_active_other_account": "The ban is on another account that has played from your IP address.", - "sa_player_ban_active_kick": "You will be disconnected in a few seconds.", + "sa_player_ban_active": "{lightred}You are banned from this server for {grey}{0}{lightred}. Reason: {grey}{1}", + "sa_player_ban_active_perm": "{lightred}You are banned from this server permanently. Reason: {grey}{0}", + "sa_player_ban_active_other_account": "{grey}The ban is on another account that has played from your IP address.", + "sa_player_ban_active_kick": "{grey}You will be disconnected in a few seconds.", - "sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Expire [{red}{0}{default}]", - "sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{red}{0}{default}]", - "sa_player_penalty_info_active_silence": "➔ Silence [{lightred}❌{default}] - Expire [{red}{0}{default}]", - "sa_player_penalty_info_active_warn": "➔ Warn [{lightred}❌{default}] - Expire [{red}{0}{default}] - Reason [{red}{1}{default}]", + "sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Silence [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Warn [{lightred}❌{default}] - Expire [{lightred}{0}{default}] - Reason [{lightred}{1}{default}]", "sa_player_penalty_info_no_active_mute": "➔ Mute [{lime}✔{default}]", "sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]", "sa_player_penalty_info_no_active_silence": "➔ Silence [{lime}✔{default}]", "sa_player_penalty_info_no_active_warn": "➔ Warn [{lime}✔{default}]", - "sa_player_penalty_info": "===========================\nPlayer penalties for {yellow}{0}{default},\nNumber of bans: {red}{1}{default}, Number of gags: {red}{2}{default}, Number of mutes: {red}{3}{default}, Number of silences: {red}{4}{default}, Number of warnings: {red}{5}\nActive penalties:\n{6}\nActive warnings:\n{7}\n===========================", - "sa_admin_penalty_info": "Player penalties for {yellow}{0}{default}, Bans: {red}{1}{default}, Gags: {red}{2}{default}, Mutes: {red}{3}{default}, Silences: {red}{4}{default}, Warns: {red}{5}", - "sa_admin_associated_accounts": "Associated accounts of player {yellow}{0}{default}: {1}", + "sa_player_penalty_info": "===========================\nPlayer penalties for {lightred}{0}{default},\nNumber of bans: {lightred}{1}{default}, Number of gags: {lightred}{2}{default}, Number of mutes: {lightred}{3}{default}, Number of silences: {lightred}{4}{default}, Number of warnings: {lightred}{5}{default}\nActive penalties:\n{6}\nActive warnings:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Player penalties for {lightred}{0}{grey}, Bans: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Warns: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Associated accounts of player {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "You have been banned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!", "sa_player_ban_message_perm": "You have been banned permanently for {lightred}{0}{default} by {lightred}{1}{default}!", "sa_player_kick_message": "You have been kicked for {lightred}{0}{default} by {lightred}{1}{default}!", @@ -99,60 +99,57 @@ "sa_player_silence_message_perm": "You have been silenced permanently for {lightred}{0}{default} by {lightred}{1}{default}!", "sa_player_warn_message_time": "You have been warned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!", "sa_player_warn_message_perm": "You have been warned permanently for {lightred}{0}{default} by {lightred}{1}{default}!", - "sa_admin_ban_message_time": "{red}{0}{default} banned {yellow}{1}{default} for {red}{2}{default} for {red}{3}{default} minutes", - "sa_admin_ban_message_perm": "{red}{0}{default} banned {yellow}{1}{default} permanently for {red}{2}", - "sa_admin_kick_message": "{red}{0}{default} kicked {yellow}{1}{default} for {red}{2}", - "sa_admin_gag_message_time": "{red}{0}{default} gagged {yellow}{1}{default} for {red}{2}{default} for {red}{3}{default} minutes", - "sa_admin_gag_message_perm": "{red}{0}{default} gagged {yellow}{1}{default} permanently for {red}{2}", - "sa_admin_mute_message_time": "{red}{0}{default} muted {yellow}{1}{default} for {red}{2}{default} for {red}{3}{default} minutes", - "sa_admin_mute_message_perm": "{red}{0}{default} muted {yellow}{1}{default} permanently for {red}{2}", - "sa_admin_silence_message_time": "{red}{0}{default} silenced {yellow}{1}{default} for {red}{2}{default} for {red}{3}{default} minutes", - "sa_admin_silence_message_perm": "{red}{0}{default} silenced {yellow}{1}{default} permanently for {red}{2}", - "sa_admin_warn_message_time": "{red}{0}{default} warned {yellow}{1}{default} for {red}{2}{default} for {red}{3}{default} minutes", - "sa_admin_warn_message_perm": "{red}{0}{default} warned {yellow}{1}{default} permanently for {red}{2}{default}!", - "sa_admin_give_message": "{red}{0}{default} gave {yellow}{1}{default} a {grey}{2}", - "sa_admin_strip_message": "{red}{0}{default} took all of player {yellow}{1}{default} weapons", - "sa_admin_hp_message": "{red}{0}{default} changed {yellow}{1}{default} hp amount", - "sa_admin_speed_message": "{red}{0}{default} changed speed for {yellow}{1}", - "sa_admin_gravity_message": "{red}{0}{default} changed gravity for {yellow}{1}", - "sa_admin_money_message": "{red}{0}{default} changed money for {yellow}{1}", - "sa_admin_god_message": "{red}{0}{default} changed god mode for {yellow}{1}", - "sa_admin_resize_message": "{red}{0}{default} changed size for {yellow}{1}", - "sa_admin_slay_message": "{red}{0}{default} slayed {yellow}{1}", - "sa_admin_slap_message": "{red}{0}{default} slapped {yellow}{1}", - "sa_admin_changemap_message": "{red}{0}{default} changed map to {green}{1}", - "sa_admin_noclip_message": "{red}{0}{default} toggled noclip for {yellow}{1}", - "sa_admin_freeze_message": "{red}{0}{default} {blue}froze{default} {yellow}{1}", - "sa_admin_unfreeze_message": "{red}{0}{default} {blue}unfroze{default} {yellow}{1}", - "sa_admin_rename_message": "{red}{0}{default} changed {yellow}{1}{default} nickname to {yellow}{2}", - "sa_admin_respawn_message": "{red}{0}{default} respawned {yellow}{1}", - "sa_admin_tp_message": "{red}{0}{default} teleported to {yellow}{1}", - "sa_admin_bring_message": "{red}{0}{default} teleported {yellow}{1}{default} to themselves", - "sa_admin_team_message": "{red}{0}{default} transfered {yellow}{1}{default} to {grey}{2}", + "sa_admin_ban_message_time": "{lightred}{0}{default} banned {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} banned {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} kicked {lightred}{1}{default} for {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} gagged {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} gagged {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} muted {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} muted {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} silenced {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} silenced {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} warned {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} warned {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} gave {lightred}{1}{default} a {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} took all of player {lightred}{1}{default} weapons!", + "sa_admin_hp_message": "{lightred}{0}{default} changed {lightred}{1}{default} hp amount{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} changed speed for {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} changed gravity for {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} changed money for {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!", + "sa_admin_resize_message": "{lightred}{0}{default} changed size for {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} slayed {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} slapped {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} changed map to {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} toggled noclip for {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} froze {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} unfroze {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} changed {lightred}{1}{default} nickname to {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} respawned {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} teleported to {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} teleported to himself {lightred}{1}{default}!", + "sa_admin_team_message": "{lightred}{0}{default} transfered {lightred}{1}{default} to {lightred}{2}{default}!", "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_warns_unwarn": "{lime}Successfully{default} unwarned {gold}{0} {default}for {gold}{1}{default}!", "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}", - "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {orange}{1}", - "sa_admin_voice_mute_all": "{red}You muted {default}all players", - "sa_admin_voice_unmute_all": "{lime}You unmuted {default}all players", - "sa_admin_voice_listen_all": "You can now hear {lime}all {default}players", - "sa_admin_voice_unlisten_all": "You no longer hear {lime}all {default}players", - "sa_adminsay_prefix": "{lightred}{0}", - "sa_adminchat_template_admin": ">[{yellow}STAFF{default}][{lime}{0}{default}] {grey}{1}", - "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_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}", - "sa_adminchat_template_player": "{SILVER}(PLAYER) {lightred}{0}{default}: {lightred}{1}", + "sa_admin_vote_message": "{lightred}{0}{default} started voting for {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}VOTING RESULTS FOR {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_admin_voice_mute_all": "{Lightred}You muted {default}all players during your speech", + "sa_admin_voice_unmute_all": "{Lime}You unmuted {default}all players", + "sa_admin_voice_listen_all": "{Default}You can now hear {lime}all {default}players", + "sa_admin_voice_unlisten_all": "{Default}You no longer hear {lime}all {default}players", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_staffchat_template": "{default}>[{yellow}STAFF{default}][{lime}{0}{default}] {yellow}{1}{default}: {grey}{2}{default}", + "sa_staffchat_template_norank": "{default}>[{yellow}STAFF{default}] {yellow}{0}{default}: {grey}{1}{default}", + "sa_staffchat_template_player": "{default}>[{purple}STAFF{default}] {purple}{0}{default}: {purple}{1}{default}", + "sa_staffsay_template": "[{lime}{0}{default}] {darkred}{1}{default}: {red}{2}{default}", + "sa_staffsay_template_norank": "{darkred}{0}{default}: {red}{1}{default}", + "sa_psay_template": "{default}(PM) [{lime}{0}{default}] {yellow}{1}{default}: {grey}{2}{default}", + "sa_psay_template_norank": "{default}(PM) {yellow}{0}{default}: {grey}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(PLAYER) {lightred}{0}{default}: {lightred}{1}{default}", "sa_discord_log_command": "**{0}** issued command `{1}` on server `HOSTNAME`", "sa_menu_pluginsmanager_loaded": "{lime}Enabled {default}plugin {lime}{0}", - "sa_menu_pluginsmanager_unloaded": "{red}Disabled {default}plugin {yellow}{0}" + "sa_menu_pluginsmanager_unloaded": "{lightred}Disabled {default}plugin {lightred}{0}" } \ No newline at end of file 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..5425f65 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ from the `build-1.8.2b` tag). Changes, all marked `zio:` in the code: - New permission `@css/bypassipban`, for players or groups: IP bans, and bans on other accounts that share their IP, don't apply to them; a ban on their own SteamID still does. `@css/root` has it, as it has every `@css` flag. -- Staff chat shows the sender's rank, from their highest `#rank/...` group (O, S.Admin, Admin, - T.Admin, Mod, T.Mod, Helper, Guardian, the same as CS2-Tags' chat prefixes): `@message` in team chat and `css_asay` read +- Staff chat shows the sender's rank, from their highest `#rank/...` group (O, SA, A, TA, M, TM, + H for helper, G for guardian): `@message` in team chat and `css_asay` read `>[STAFF][RANK] name: message`, and `@message` in all chat and `css_say` from a player read `[RANK] name: message` (`name: message` if they have no rank group). A non-staff player's `@message` in team chat reads @@ -24,39 +24,6 @@ from the `build-1.8.2b` tag). Changes, all marked `zio:` in the code: `sa_staffchat_template_player`, in `lang/en.json` only. - `css_psay` from a player reads `(PM) [RANK] name: message` (`(PM) name: message` without a rank group); console's is unchanged. Lang keys `sa_psay_template` and `sa_psay_template_norank`. -- `admin_overrides.json` reaches the admin menu too: every player and server entry names its command, - and an overridden entry is checked with the override's `check_type` (`any` passes with one flag), - as CSS checks the typed command. Upstream required every flag in the override. -- `css_penalties ` (someone else's penalties) needs `@zio/penalties` or `@css/root` - instead of `@css/kick`. -- `css_addadmin`, `css_deladmin` and the admin menu's add/remove log rank changes (the player's - 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