diff --git a/.gitignore b/.gitignore index 870e2ac..79b4f27 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ obj/ .git .vscode/ .idea/ +Modules/CS2-SimpleAdmin_PlayTimeModule CS2-SimpleAdmin.sln.DotSettings.user Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs index e64f76f..1fc5b20 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs @@ -19,7 +19,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 & Dliix66"; - public override string ModuleVersion => "1.7.5a"; + public override string ModuleVersion => "1.7.7-alpha"; public override void Load(bool hotReload) { @@ -31,6 +31,8 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig + foreach (var player in Helper.GetValidPlayers()) { playerManager.LoadPlayerData(player); - }); + }; }); } _cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid(GameData.GetSignature("CBasePlayerController_SetPawn")); @@ -58,7 +60,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig ReloadAdmins(null)); + AddTimer(5.0f, () => ReloadAdmins(null)); try { @@ -151,4 +153,10 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig - + + @@ -25,6 +26,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + diff --git a/CS2-SimpleAdmin/Commands/RegisterCommands.cs b/CS2-SimpleAdmin/Commands/RegisterCommands.cs index fd2938c..625ad4e 100644 --- a/CS2-SimpleAdmin/Commands/RegisterCommands.cs +++ b/CS2-SimpleAdmin/Commands/RegisterCommands.cs @@ -34,6 +34,7 @@ public static class RegisterCommands new CommandMapping("css_addgroup", CS2_SimpleAdmin.Instance.OnAddGroup), new CommandMapping("css_delgroup", CS2_SimpleAdmin.Instance.OnDelGroupCommand), new CommandMapping("css_reloadadmins", CS2_SimpleAdmin.Instance.OnRelAdminCommand), + new CommandMapping("css_reloadbans", CS2_SimpleAdmin.Instance.OnRelBans), new CommandMapping("css_hide", CS2_SimpleAdmin.Instance.OnHideCommand), new CommandMapping("css_hidecomms", CS2_SimpleAdmin.Instance.OnHideCommsCommand), new CommandMapping("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand), @@ -122,6 +123,7 @@ public static class RegisterCommands { "css_addgroup", new Command { Aliases = ["css_addgroup"] } }, { "css_delgroup", new Command { Aliases = ["css_delgroup"] } }, { "css_reloadadmins", new Command { Aliases = ["css_reloadadmins"] } }, + { "css_reloadbans", new Command { Aliases = ["css_reloadbans"] } }, { "css_hide", new Command { Aliases = ["css_hide", "css_stealth"] } }, { "css_hidecomms", new Command { Aliases = ["css_hidecomms"] } }, { "css_who", new Command { Aliases = ["css_who"] } }, diff --git a/CS2-SimpleAdmin/Commands/basebans.cs b/CS2-SimpleAdmin/Commands/basebans.cs index 6859b03..33dc456 100644 --- a/CS2-SimpleAdmin/Commands/basebans.cs +++ b/CS2-SimpleAdmin/Commands/basebans.cs @@ -73,12 +73,6 @@ public partial class CS2_SimpleAdmin SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId); }); - // Update banned players list - if (playerInfo.IpAddress != null && !BannedPlayers.Contains(playerInfo.IpAddress)) - BannedPlayers.Add(playerInfo.IpAddress); - if (!BannedPlayers.Contains(player.SteamID.ToString())) - BannedPlayers.Add(player.SteamID.ToString()); - // Determine message keys and arguments based on ban time var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0 ? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm", @@ -129,8 +123,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; - var matches = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); if (player != null && player.IsValid) { @@ -184,8 +177,7 @@ public partial class CS2_SimpleAdmin ? PlayersInfo[caller.UserId.Value] : null; - var matches = Helper.GetPlayerFromSteamid64(steamid); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid); if (player != null && player.IsValid) { @@ -247,8 +239,7 @@ public partial class CS2_SimpleAdmin ? PlayersInfo[caller.UserId.Value] : null; - var matches = Helper.GetPlayerFromIp(ipAddress); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromIp(ipAddress); if (player != null && player.IsValid) { @@ -366,10 +357,10 @@ public partial class CS2_SimpleAdmin : (_localizer?["sa_console"] ?? "Console"); // Freeze player pawn if alive - if (player.PawnIsAlive) + if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE) { - player.Pawn.Value?.Freeze(); - AddTimer(5.0f, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + player.PlayerPawn?.Value?.Freeze(); + AddTimer(5.0f, () => player.PlayerPawn?.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); } // Get player and admin information @@ -442,8 +433,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; - var matches = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); if (player != null && player.IsValid) { diff --git a/CS2-SimpleAdmin/Commands/basechat.cs b/CS2-SimpleAdmin/Commands/basechat.cs index f193705..58b6c3c 100644 --- a/CS2-SimpleAdmin/Commands/basechat.cs +++ b/CS2-SimpleAdmin/Commands/basechat.cs @@ -100,7 +100,6 @@ public partial class CS2_SimpleAdmin var utf8String = Encoding.UTF8.GetString(utf8BytesString); Helper.LogCommand(caller, command); - Helper.PrintToCenterAll(utf8String.ReplaceColorTags()); } diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs index 27b8045..54a68ba 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -386,20 +386,21 @@ public partial class CS2_SimpleAdmin command.ReplyToCommand("Reloaded sql admins and groups"); } + + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void OnRelBans(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + _ = Instance.CacheManager?.ForceReInitializeCacheAsync(); + command.ReplyToCommand("Reloaded bans"); + } public void ReloadAdmins(CCSPlayerController? caller) { if (Database == null) return; - - for (var index = 0; index < PermissionManager.AdminCache.Keys.ToList().Count; index++) - { - var steamId = PermissionManager.AdminCache.Keys.ToList()[index]; - if (!PermissionManager.AdminCache.TryRemove(steamId, out _)) continue; - - AdminManager.ClearPlayerPermissions(steamId); - AdminManager.RemovePlayerAdminData(steamId); - } - + if(!Config.IsCSSPanel) { Task.Run(async () => @@ -413,11 +414,11 @@ public partial class CS2_SimpleAdmin await Server.NextWorldUpdateAsync(() => { if (!string.IsNullOrEmpty(adminsFile)) - AddTimer(1.8f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); + AddTimer(1.3f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); if (!string.IsNullOrEmpty(groupsFile)) AddTimer(2.5f, () => AdminManager.LoadAdminGroups(ModuleDirectory + "/data/groups.json")); if (!string.IsNullOrEmpty(adminsFile)) - AddTimer(3.0f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); + AddTimer(3.5f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); _logger?.LogInformation("Loaded admins!"); }); @@ -445,8 +446,8 @@ public partial class CS2_SimpleAdmin { Server.ExecuteCommand("sv_disable_teamselect_menu 1"); - if (caller.PlayerPawn.Value != null && caller.PawnIsAlive) - caller.PlayerPawn.Value.CommitSuicide(true, false); + if (caller.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE) + caller.PlayerPawn.Value?.CommitSuicide(true, false); AddTimer(1.0f, () => { Server.NextFrame(() => caller.ChangeTeam(CsTeam.Spectator)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); AddTimer(1.4f, () => { Server.NextFrame(() => caller.ChangeTeam(CsTeam.None)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); @@ -520,6 +521,10 @@ public partial class CS2_SimpleAdmin printMethod($"• Total Mutes: \"{playerInfo.TotalMutes}\""); printMethod($"• Total Silences: \"{playerInfo.TotalSilences}\""); printMethod($"• Total Warns: \"{playerInfo.TotalWarns}\""); + + var chunkedAccounts = playerInfo.AccountsAssociated.ChunkBy(3).ToList(); + foreach (var chunk in chunkedAccounts) + printMethod($"• Associated Accounts: \"{string.Join(", ", chunk.Select(a => $"{a.PlayerName} ({a.SteamId})"))}\""); } printMethod($"--------- END INFO ABOUT \"{player.PlayerName}\" ---------"); @@ -661,7 +666,6 @@ public partial class CS2_SimpleAdmin Helper.LogCommand(caller, command); var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsBot: false }).ToList(); - if (playersToTarget.Count > 1) return; @@ -719,20 +723,20 @@ public partial class CS2_SimpleAdmin if (caller != null) { caller.PrintToConsole("--------- PLAYER LIST ---------"); - playersToTarget.ForEach(player => + foreach (var player in playersToTarget) { caller.PrintToConsole( $"• [#{player.UserId}] \"{player.PlayerName}\" (IP Address: \"{(AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/showip") ? player.IpAddress?.Split(":")[0] : "Unknown")}\" SteamID64: \"{player.SteamID}\")"); - }); + }; caller.PrintToConsole("--------- END PLAYER LIST ---------"); } else { Server.PrintToConsole("--------- PLAYER LIST ---------"); - playersToTarget.ForEach(player => + foreach (var player in playersToTarget) { Server.PrintToConsole($"• [#{player.UserId}] \"{player.PlayerName}\" (IP Address: \"{player.IpAddress?.Split(":")[0]}\" SteamID64: \"{player.SteamID}\")"); - }); + }; Server.PrintToConsole("--------- END PLAYER LIST ---------"); } } @@ -800,6 +804,8 @@ public partial class CS2_SimpleAdmin Kick(caller, player, reason, callerName, command); } }); + + Helper.LogCommand(caller, command); } public void Kick(CCSPlayerController? caller, CCSPlayerController player, string? reason = "Unknown", string? callerName = null, CommandInfo? command = null) @@ -839,8 +845,6 @@ public partial class CS2_SimpleAdmin // Log the command and send Discord notification if (command == null) Helper.LogCommand(caller, $"css_kick {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {reason}"); - else - Helper.LogCommand(caller, command); SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Kick, reason, -1, null); } diff --git a/CS2-SimpleAdmin/Commands/basecomms.cs b/CS2-SimpleAdmin/Commands/basecomms.cs index bd8abe9..f671828 100644 --- a/CS2-SimpleAdmin/Commands/basecomms.cs +++ b/CS2-SimpleAdmin/Commands/basecomms.cs @@ -114,8 +114,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; - var matches = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); if (player != null && player.IsValid) { @@ -173,8 +172,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; // Attempt to match player based on SteamID - var matches = Helper.GetPlayerFromSteamid64(steamid); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid); if (player != null && player.IsValid) { @@ -230,8 +228,7 @@ public partial class CS2_SimpleAdmin // Check if pattern is a valid SteamID64 if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null) { - var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); if (player != null && player.IsValid) { @@ -407,8 +404,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; // Attempt to match player based on SteamID - var matches = Helper.GetPlayerFromSteamid64(steamid); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid); if (player != null && player.IsValid) { @@ -448,8 +444,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; - var matches = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); if (player != null && player.IsValid) { @@ -499,8 +494,7 @@ public partial class CS2_SimpleAdmin // Check if pattern is a valid SteamID64 if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null) { - var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); if (player != null && player.IsValid) { @@ -681,8 +675,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; // Attempt to match player based on SteamID - var matches = Helper.GetPlayerFromSteamid64(steamid); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid); if (player != null && player.IsValid) { @@ -722,8 +715,7 @@ public partial class CS2_SimpleAdmin var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; - var matches = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString()); if (player != null && player.IsValid) { @@ -773,8 +765,7 @@ public partial class CS2_SimpleAdmin // Check if pattern is a valid SteamID64 if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null) { - var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); - var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); if (player != null && player.IsValid) { diff --git a/CS2-SimpleAdmin/Commands/funcommands.cs b/CS2-SimpleAdmin/Commands/funcommands.cs index 05a2378..efa1f9d 100644 --- a/CS2-SimpleAdmin/Commands/funcommands.cs +++ b/CS2-SimpleAdmin/Commands/funcommands.cs @@ -18,7 +18,7 @@ public partial class CS2_SimpleAdmin if (targets == null) return; var playersToTarget = targets.Players.Where(player => player.IsValid && - player is { PawnIsAlive: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList(); + player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -27,6 +27,8 @@ public partial class CS2_SimpleAdmin NoClip(caller, player, callerName); } }); + + Helper.LogCommand(caller, command); } internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) @@ -53,13 +55,7 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) - { Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); - } - else - { - Helper.LogCommand(caller, command); - } } [RequiresPermissions("@css/cheats")] @@ -70,7 +66,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -82,6 +78,8 @@ public partial class CS2_SimpleAdmin God(caller, player, command); } }); + + Helper.LogCommand(caller, command); } internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null) @@ -100,8 +98,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); - else - Helper.LogCommand(caller, command); // Determine message key and arguments for the god mode notification var (activityMessageKey, adminActivityArgs) = @@ -124,7 +120,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -133,6 +129,8 @@ public partial class CS2_SimpleAdmin Freeze(caller, player, time, callerName, command); } }); + + Helper.LogCommand(caller, command); } [CommandHelper(1, "<#userid or name> [size]")] @@ -144,7 +142,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -206,8 +204,6 @@ public partial class CS2_SimpleAdmin // Log the command and send Discord notification if (command == null) Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}"); - else - Helper.LogCommand(caller, command); } [CommandHelper(1, "<#userid or name>")] @@ -218,12 +214,14 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { Unfreeze(caller, player, callerName, command); }); + + Helper.LogCommand(caller, command); } internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) @@ -251,7 +249,5 @@ public partial class CS2_SimpleAdmin // Log the command and send Discord notification if (command == null) Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); - else - Helper.LogCommand(caller, command); } } \ No newline at end of file diff --git a/CS2-SimpleAdmin/Commands/playercommands.cs b/CS2-SimpleAdmin/Commands/playercommands.cs index 67c5b0e..3be0b36 100644 --- a/CS2-SimpleAdmin/Commands/playercommands.cs +++ b/CS2-SimpleAdmin/Commands/playercommands.cs @@ -22,12 +22,14 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { Slay(caller, player, callerName, command); }); + + Helper.LogCommand(caller, command); } internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) @@ -55,8 +57,6 @@ public partial class CS2_SimpleAdmin // Log the command and send Discord notification if (command == null) Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); - else - Helper.LogCommand(caller, command); } [RequiresPermissions("@css/cheats")] @@ -67,7 +67,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); var weaponName = command.GetArg(2); // check if item is typed @@ -94,6 +94,8 @@ public partial class CS2_SimpleAdmin GiveWeapon(caller, player, weaponName, callerName, command); }); + + Helper.LogCommand(caller, command); } private static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, string weaponName, string? callerName = null, CommandInfo? command = null) @@ -122,8 +124,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weaponName}"); - else - Helper.LogCommand(caller, command); // Determine message keys and arguments for the weapon give notification var (activityMessageKey, adminActivityArgs) = @@ -150,8 +150,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weapon.ToString()}"); - else - Helper.LogCommand(caller, command); // Determine message keys and arguments for the weapon give notification var (activityMessageKey, adminActivityArgs) = @@ -173,7 +171,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -182,6 +180,8 @@ public partial class CS2_SimpleAdmin StripWeapons(caller, player, callerName, command); } }); + + Helper.LogCommand(caller, command); } internal static void StripWeapons(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) @@ -192,7 +192,7 @@ public partial class CS2_SimpleAdmin callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console"; // Check if player is valid, alive, and connected - if (!player.IsValid || !player.PawnIsAlive || player.Connected != PlayerConnectedState.PlayerConnected) + if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.Connected != PlayerConnectedState.PlayerConnected) return; // Strip weapons from the player @@ -201,8 +201,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_strip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); - else - Helper.LogCommand(caller, command); // Determine message keys and arguments for the weapon strip notification var (activityMessageKey, adminActivityArgs) = @@ -225,7 +223,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -234,6 +232,8 @@ public partial class CS2_SimpleAdmin SetHp(caller, player, health, command); } }); + + Helper.LogCommand(caller, command); } internal static void SetHp(CCSPlayerController? caller, CCSPlayerController player, int health, CommandInfo? command = null) @@ -250,8 +250,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_hp {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {health}"); - else - Helper.LogCommand(caller, command); // Determine message keys and arguments for the HP set notification var (activityMessageKey, adminActivityArgs) = @@ -274,7 +272,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -286,6 +284,8 @@ public partial class CS2_SimpleAdmin SetSpeed(caller, player, speed, command); } }); + + Helper.LogCommand(caller, command); } internal static void SetSpeed(CCSPlayerController? caller, CCSPlayerController player, float speed, CommandInfo? command = null) @@ -306,8 +306,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_speed {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {speed}"); - else - Helper.LogCommand(caller, command); // Determine message keys and arguments for the speed set notification var (activityMessageKey, adminActivityArgs) = @@ -330,7 +328,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -342,6 +340,8 @@ public partial class CS2_SimpleAdmin SetGravity(caller, player, gravity, command); } }); + + Helper.LogCommand(caller, command); } internal static void SetGravity(CCSPlayerController? caller, CCSPlayerController player, float gravity, CommandInfo? command = null) @@ -362,8 +362,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_gravity {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {gravity}"); - else - Helper.LogCommand(caller, command); // Determine message keys and arguments for the gravity set notification var (activityMessageKey, adminActivityArgs) = @@ -387,7 +385,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); playersToTarget.ForEach(player => { @@ -399,6 +397,8 @@ public partial class CS2_SimpleAdmin SetMoney(caller, player, money, command); } }); + + Helper.LogCommand(caller, command); } internal static void SetMoney(CCSPlayerController? caller, CCSPlayerController player, int money, CommandInfo? command = null) @@ -414,8 +414,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_money {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {money}"); - else - Helper.LogCommand(caller, command); // Determine message keys and arguments for the money set notification var (activityMessageKey, adminActivityArgs) = @@ -438,7 +436,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList(); if (command.ArgCount >= 2) { @@ -455,6 +453,8 @@ public partial class CS2_SimpleAdmin Slap(caller, player, damage, command); } }); + + Helper.LogCommand(caller, command); } internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, CommandInfo? command = null) @@ -470,9 +470,7 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}"); - else - Helper.LogCommand(caller, command); - + // Determine message key and arguments for the slap notification var (activityMessageKey, adminActivityArgs) = ("sa_admin_slap_message", @@ -532,6 +530,8 @@ public partial class CS2_SimpleAdmin { ChangeTeam(caller, player, _teamName, teamNum, kill, command); }); + + Helper.LogCommand(caller, command); } internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null) @@ -549,7 +549,7 @@ public partial class CS2_SimpleAdmin // Change team based on the provided teamName and conditions if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase)) { - if (player.PawnIsAlive && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1) + if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1) player.SwitchTeam(teamNum); else player.ChangeTeam(teamNum); @@ -560,7 +560,7 @@ public partial class CS2_SimpleAdmin { var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist; teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT"; - if (player.PawnIsAlive && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1) + if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1) player.SwitchTeam(_teamNum); else player.ChangeTeam(_teamNum); @@ -570,8 +570,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}"); - else - Helper.LogCommand(caller, command); // Determine message key and arguments for the team change notification var activityMessageKey = "sa_admin_team_message"; @@ -698,6 +696,8 @@ public partial class CS2_SimpleAdmin Respawn(caller, player, callerName, command); } }); + + Helper.LogCommand(caller, command); } internal static void Respawn(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) @@ -722,8 +722,6 @@ public partial class CS2_SimpleAdmin // Log the command if (command == null) Helper.LogCommand(caller, $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); - else - Helper.LogCommand(caller, command); // Determine message key and arguments for the respawn notification var activityMessageKey = "sa_admin_respawn_message"; @@ -740,7 +738,7 @@ public partial class CS2_SimpleAdmin public void OnGotoCommand(CCSPlayerController? caller, CommandInfo command) { // Check if the caller is valid and has a live pawn - if (caller == null || !caller.PawnIsAlive) return; + if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return; // Get the target players var targets = GetTarget(command); @@ -754,7 +752,7 @@ public partial class CS2_SimpleAdmin Helper.LogCommand(caller, command); // Process each player to teleport - foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }).Where(caller.CanTarget)) + foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).Where(caller.CanTarget)) { if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null) continue; @@ -778,7 +776,7 @@ public partial class CS2_SimpleAdmin // Set a timer to toggle collision back after 4 seconds AddTimer(4, () => { - if (!caller.IsValid || !caller.PawnIsAlive) + if (!caller.IsValid || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return; caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER; @@ -811,7 +809,8 @@ public partial class CS2_SimpleAdmin public void OnBringCommand(CCSPlayerController? caller, CommandInfo command) { // Check if the caller is valid and has a live pawn - if (caller == null || !caller.PawnIsAlive) return; + if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) + return; // Get the target players var targets = GetTarget(command); @@ -825,7 +824,7 @@ public partial class CS2_SimpleAdmin Helper.LogCommand(caller, command); // Process each player to teleport - foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }).Where(caller.CanTarget)) + foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).Where(caller.CanTarget)) { if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null) continue; @@ -849,7 +848,7 @@ public partial class CS2_SimpleAdmin // Set a timer to toggle collision back after 4 seconds AddTimer(4, () => { - if (!player.IsValid || !player.PawnIsAlive) + if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return; caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER; diff --git a/CS2-SimpleAdmin/Config.cs b/CS2-SimpleAdmin/Config.cs index 5e4a5c2..64577bb 100644 --- a/CS2-SimpleAdmin/Config.cs +++ b/CS2-SimpleAdmin/Config.cs @@ -89,6 +89,17 @@ public class Discord new DiscordPenaltySetting { Name = "Footer", Value = "" }, new DiscordPenaltySetting { Name = "Time", Value = "{relative}" }, ]; + + [JsonPropertyName("DiscordAssociatedAccountsSettings")] + public DiscordPenaltySetting[] DiscordAssociatedAccountsSettings { get; set; } = + [ + new DiscordPenaltySetting { Name = "Color", Value = "" }, + new DiscordPenaltySetting { Name = "Webhook", Value = "" }, + new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" }, + new DiscordPenaltySetting { Name = "ImageUrl", Value = "" }, + new DiscordPenaltySetting { Name = "Footer", Value = "" }, + new DiscordPenaltySetting { Name = "Time", Value = "{relative}" }, + ]; } public class ChatLog @@ -247,7 +258,10 @@ public class OtherSettings public List AdditionalCommandsToLog { get; set; } = new(); [JsonPropertyName("HideStealthPlayersFromSpecList")] - public bool HideStealthPlayersFromSpecList {get; set; } = false; + public bool HideStealthPlayersFromSpecList { get; set; } = false; + + [JsonPropertyName("IgnoredIps")] + public List IgnoredIps { get; set; } = new(); } public class CS2_SimpleAdminConfig : BasePluginConfig diff --git a/CS2-SimpleAdmin/Database/Migrations/012_AddUpdatedAtColumnToSaBansTable.sql b/CS2-SimpleAdmin/Database/Migrations/012_AddUpdatedAtColumnToSaBansTable.sql new file mode 100644 index 0000000..bc306e6 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/012_AddUpdatedAtColumnToSaBansTable.sql @@ -0,0 +1 @@ +ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `status`; \ No newline at end of file diff --git a/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql b/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql new file mode 100644 index 0000000..17c9647 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql @@ -0,0 +1,4 @@ +UPDATE `sa_players_ips` SET `address` = INET_ATON(address); +ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL; +ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`; +ALTER TABLE `sa_players_ips` ADD INDEX(`used_at`); diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs index 608096b..7253ded 100644 --- a/CS2-SimpleAdmin/Events.cs +++ b/CS2-SimpleAdmin/Events.cs @@ -149,14 +149,22 @@ public partial class CS2_SimpleAdmin if (player.UserId.HasValue) PlayersInfo.TryRemove(player.UserId.Value, out _); - - var authorizedSteamId = player.AuthorizedSteamID; - if (authorizedSteamId == null || !PermissionManager.AdminCache.TryGetValue(authorizedSteamId, - out var expirationTime) - || !(expirationTime <= Time.ActualDateTime())) return HookResult.Continue; - - AdminManager.ClearPlayerPermissions(authorizedSteamId); - AdminManager.RemovePlayerAdminData(authorizedSteamId); + + if (!PermissionManager.AdminCache.TryGetValue(steamId, out var data) + || !(data.ExpirationTime <= Time.ActualDateTime())) + { + return HookResult.Continue; + } + + AdminManager.RemovePlayerPermissions(steamId, PermissionManager.AdminCache[steamId].Flags.ToArray()); + AdminManager.RemovePlayerFromGroup(steamId, true, PermissionManager.AdminCache[steamId].Flags.ToArray()); + var adminData = AdminManager.GetPlayerAdminData(steamId); + + if (adminData == null || data.Flags.ToList().Count != 0 && adminData.Groups.ToList().Count != 0) + return HookResult.Continue; + + AdminManager.ClearPlayerPermissions(steamId); + AdminManager.RemovePlayerAdminData(steamId); return HookResult.Continue; } @@ -172,8 +180,11 @@ public partial class CS2_SimpleAdmin #if DEBUG Logger.LogCritical("[OnClientConnect]"); #endif - if (!CS2_SimpleAdmin.BannedPlayers.Contains(ipaddress.Split(":")[0])) + if (Config.OtherSettings.BanType == 0) return; + + if (Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0])) + return; Server.NextFrame((() => { @@ -262,13 +273,26 @@ public partial class CS2_SimpleAdmin if (!PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Gag, out DateTime? endDateTime) && !PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Silence, out endDateTime)) return HookResult.Continue; + + var message = um.ReadString("param2"); + + if (_localizer == null || endDateTime is null) return HookResult.Continue; + + if (CoreConfig.PublicChatTrigger.Concat(CoreConfig.SilentChatTrigger).Any(trigger => message.StartsWith(trigger))) + { + foreach (var recipient in um.Recipients) + { + if (recipient == author) + continue; + + um.Recipients.Remove(recipient); + } + + return HookResult.Continue; + } - if (_localizer != null && endDateTime is not null) - author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage())); + author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage())); return HookResult.Stop; - - // um.Recipients.Clear(); - } private HookResult ComamndListenerHandler(CCSPlayerController? player, CommandInfo info) @@ -448,9 +472,9 @@ public partial class CS2_SimpleAdmin private void OnMapStart(string mapName) { if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null) - AddTimer(3.0f, () => ReloadAdmins(null)); + AddTimer(5.0f, () => ReloadAdmins(null)); - AddTimer(1.0f, () => new ServerManager().CheckHibernationStatus()); + AddTimer(1.0f, () => ServerManager.CheckHibernationStatus()); // AddTimer(34, () => // { @@ -471,9 +495,8 @@ public partial class CS2_SimpleAdmin { var player = @event.Userid; - if (player is null || @event.Attacker is null || !player.PawnIsAlive || player.PlayerPawn.Value == null) + if (player is null || @event.Attacker is null || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.PlayerPawn.Value == null) return HookResult.Continue; - if (SpeedPlayers.TryGetValue(player.Slot, out var speedPlayer)) AddTimer(0.15f, () => player.SetSpeed(speedPlayer)); diff --git a/CS2-SimpleAdmin/Extensions/EnumerableExtensions.cs b/CS2-SimpleAdmin/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..1cf052f --- /dev/null +++ b/CS2-SimpleAdmin/Extensions/EnumerableExtensions.cs @@ -0,0 +1,12 @@ +namespace CS2_SimpleAdmin; + +public static class EnumerableExtensions +{ + public static IEnumerable> ChunkBy(this IEnumerable source, int chunkSize) + { + return source + .Select((x, i) => new { Index = i, Value = x }) + .GroupBy(x => x.Index / chunkSize) + .Select(x => x.Select(v => v.Value)); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs b/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs index c4ac802..93088af 100644 --- a/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs +++ b/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs @@ -73,7 +73,7 @@ public static class PlayerExtensions public static void SetHp(this CCSPlayerController? controller, int health = 100) { if (controller == null) return; - if ((health <= 0 || !controller.PawnIsAlive || controller.PlayerPawn.Value == null)) return; + if (health <= 0 || controller.PlayerPawn.Value == null || controller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return; controller.PlayerPawn.Value.Health = health; diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs index d6a2551..fa58772 100644 --- a/CS2-SimpleAdmin/Helper.cs +++ b/CS2-SimpleAdmin/Helper.cs @@ -22,6 +22,7 @@ using CounterStrikeSharp.API.Core.Plugin.Host; using CounterStrikeSharp.API.Modules.Entities.Constants; using CS2_SimpleAdmin.Managers; using MenuManager; +using ZLinq; namespace CS2_SimpleAdmin; @@ -78,33 +79,29 @@ internal static class Helper return Utilities.GetPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase)); } - public static List GetPlayerFromSteamid64(string steamid) + public static CCSPlayerController? GetPlayerFromSteamid64(string steamid) { - return GetValidPlayers().FindAll(x => - x.SteamID.ToString().Equals(steamid, StringComparison.OrdinalIgnoreCase) - ); + return GetValidPlayers().FirstOrDefault(x => x.SteamID.ToString().Equals(steamid, StringComparison.OrdinalIgnoreCase)); } - public static List GetPlayerFromIp(string ipAddress) + public static CCSPlayerController? GetPlayerFromIp(string ipAddress) { - return GetValidPlayers().FindAll(x => - x.IpAddress != null && - x.IpAddress.Split(":")[0].Equals(ipAddress) - ); + return GetValidPlayers().FirstOrDefault(x => x.IpAddress != null && x.IpAddress.Split(":")[0].Equals(ipAddress)); } public static List GetValidPlayers() { - return Utilities.GetPlayers().FindAll(p => p is - { IsValid: true, IsBot: false, Connected: PlayerConnectedState.PlayerConnected }); + return Utilities.GetPlayers().AsValueEnumerable() + .Where(p => p is { IsValid: true, IsBot: false, Connected: PlayerConnectedState.PlayerConnected }) + .ToList(); + } + + public static List GetValidPlayersWithBots() + { + return Utilities.GetPlayers().AsValueEnumerable() + .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList(); } - public static IEnumerable GetValidPlayersWithBots() - { - return Utilities.GetPlayers().FindAll(p => - p is { IsValid: true, IsBot: false, IsHLTV: false } or { IsValid: true, IsBot: true, IsHLTV: false } - ); - } // public static bool IsValidSteamId64(string input) // { @@ -699,12 +696,11 @@ internal static class Helper } catch (Exception ex) { - // Log or handle the exception Console.WriteLine(ex); } }); } - + private static string GenerateMessageDiscord(string message) { var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; @@ -827,6 +823,7 @@ internal static class Helper return pluginManager; } + } public static class PluginInfo @@ -1000,3 +997,35 @@ public static class WeaponHelper return filteredWeapons; // Return all relevant matches for the partial input } } + +public static class IpHelper +{ + public static uint IpToUint(string ipAddress) + { + return (uint)BitConverter.ToInt32(System.Net.IPAddress.Parse(ipAddress).GetAddressBytes().Reverse().ToArray(), + 0); + } + + public static bool TryConvertIpToUint(string ipString, out uint ipUint) + { + ipUint = 0; + if (string.IsNullOrWhiteSpace(ipString)) + return false; + + if (!System.Net.IPAddress.TryParse(ipString, out var ipAddress)) + return false; + + var bytes = ipAddress.GetAddressBytes(); + if (bytes.Length != 4) + return false; + + ipUint = IpToUint(ipString); + return true; + } + + public static string UintToIp(uint ipAddress) + { + var bytes = BitConverter.GetBytes(ipAddress).Reverse().ToArray(); + return new System.Net.IPAddress(bytes).ToString(); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/BanManager.cs b/CS2-SimpleAdmin/Managers/BanManager.cs index 1f01c5f..368f8a8 100644 --- a/CS2-SimpleAdmin/Managers/BanManager.cs +++ b/CS2-SimpleAdmin/Managers/BanManager.cs @@ -145,7 +145,7 @@ internal class BanManager(Database.Database? database) { string sql; - if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp) + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp && !CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress)) { sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """ SELECT COALESCE(( @@ -232,7 +232,8 @@ internal class BanManager(Database.Database? database) { PlayerSteamID = player.SteamId.SteamId64.ToString(), PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || - string.IsNullOrEmpty(player.IpAddress) + string.IsNullOrEmpty(player.IpAddress) || + CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress) ? null : player.IpAddress, PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty, @@ -393,7 +394,7 @@ internal class BanManager(Database.Database? database) { SteamIDs = steamIds, IpAddresses = checkIpBans ? ipAddresses : [], - ServerId = CS2_SimpleAdmin.ServerId + CS2_SimpleAdmin.ServerId }); var valueTuples = bannedPlayers.ToList(); diff --git a/CS2-SimpleAdmin/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs new file mode 100644 index 0000000..be097d0 --- /dev/null +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -0,0 +1,397 @@ +using System.Collections.Concurrent; +using CS2_SimpleAdmin.Models; +using Dapper; +using ZLinq; + +namespace CS2_SimpleAdmin.Managers; + +internal class CacheManager: IDisposable +{ + private readonly ConcurrentDictionary _banCache = []; + private readonly ConcurrentDictionary> _steamIdIndex = []; + private readonly ConcurrentDictionary> _ipIndex = []; + + private readonly ConcurrentDictionary> _playerIpsCache = []; + private HashSet _cachedIgnoredIps = []; + + private DateTime _lastUpdateTime = DateTime.MinValue; + private bool _isInitialized; + private bool _disposed; + + public async Task InitializeCacheAsync() + { + if (CS2_SimpleAdmin.Database == null) return; + if (!CS2_SimpleAdmin.ServerLoaded) return; + if (_isInitialized) return; + + try + { + Clear(); + _cachedIgnoredIps = new HashSet( + CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps + .Select(IpHelper.IpToUint)); + + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + List bans; + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + bans = (await connection.QueryAsync( + """ + SELECT + id AS Id, + player_steamid AS PlayerSteamId, + player_ip AS PlayerIp, + status AS Status + FROM sa_bans + """)).ToList(); + } + else + { + bans = (await connection.QueryAsync( + """ + SELECT + id AS Id, + player_steamid AS PlayerSteamId, + player_ip AS PlayerIp, + status AS Status + FROM sa_bans + WHERE server_id = @serverId + """, new {serverId = CS2_SimpleAdmin.ServerId})).ToList(); + } + + var ipHistory = + await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>( + "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC"); + + foreach (var ban in bans) + { + _banCache.TryAdd(ban.Id, ban); + } + + foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid)) + { + var ipSet = new HashSet( + group + .GroupBy(x => x.address) + .Select(g => + { + var latest = g.MaxBy(x => x.used_at); + return new IpRecord( + g.Key, + latest.used_at, + !string.IsNullOrEmpty(latest.name) + ? latest.name + : CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" + ); + }), + new IpRecordComparer() + ); + + _playerIpsCache.AddOrUpdate( + group.Key, + _ => ipSet, + (_, existingSet) => + { + foreach (var ip in ipSet) + { + existingSet.Remove(ip); + existingSet.Add(ip); + } + + return existingSet; + }); + } + + RebuildIndexes(); + + _lastUpdateTime = DateTime.Now.AddSeconds(-1); + _isInitialized = true; + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + } + + public async Task ForceReInitializeCacheAsync() + { + _isInitialized = false; + + _banCache.Clear(); + _playerIpsCache.Clear(); + _cachedIgnoredIps.Clear(); + _lastUpdateTime = DateTime.MinValue; + + await InitializeCacheAsync(); + } + + public async Task RefreshCacheAsync() + { + if (CS2_SimpleAdmin.Database == null) return; + if (!_isInitialized) return; + + try + { + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + List updatedBans; + + var allIds = (await connection.QueryAsync("SELECT id FROM sa_bans")).ToHashSet(); + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + updatedBans = (await connection.QueryAsync( + """ + SELECT id AS Id, + 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 + """, + new { lastUpdate = _lastUpdateTime } + )).ToList(); + } + else + { + updatedBans = (await connection.QueryAsync( + """ + SELECT id AS Id, + player_steamid AS PlayerSteamId, + player_ip AS PlayerIp, + status AS Status + FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC + """, + new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId } + )).ToList(); + } + + foreach (var id in _banCache.Keys) + { + if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue; + + // Remove from steamIdIndex + if (!string.IsNullOrWhiteSpace(ban.PlayerSteamId) && + _steamIdIndex.TryGetValue(ban.PlayerSteamId, out var steamBans)) + { + steamBans.RemoveAll(b => b.Id == id); + if (steamBans.Count == 0) + _steamIdIndex.TryRemove(ban.PlayerSteamId, out _); + } + + // Remove from ipIndex + if (!string.IsNullOrWhiteSpace(ban.PlayerIp) && + IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) && + _ipIndex.TryGetValue(ipUInt, out var ipBans)) + { + ipBans.RemoveAll(b => b.Id == id); + if (ipBans.Count == 0) + _ipIndex.TryRemove(ipUInt, out _); + } + } + + var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>( + "SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300", new {lastUpdate = _lastUpdateTime})).ToList(); + + foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid)) + { + var ipSet = new HashSet( + group + .GroupBy(x => x.address) + .Select(g => + { + var latest = g.MaxBy(x => x.used_at); + return new IpRecord( + g.Key, + latest.used_at, + !string.IsNullOrEmpty(latest.name) + ? latest.name + : CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" + ); + }), + new IpRecordComparer() + ); + + _playerIpsCache.AddOrUpdate( + group.Key, + _ => ipSet, + (_, existingSet) => + { + foreach (var newEntry in ipSet) + { + existingSet.Remove(newEntry); + existingSet.Add(newEntry); + } + return existingSet; + }); + } + + if (updatedBans.Count == 0) + return; + + foreach (var ban in updatedBans) + { + _banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban); + } + + RebuildIndexes(); + _lastUpdateTime = DateTime.Now.AddSeconds(-1); + } + catch (Exception e) + { + // ignored + } + } + + private void RebuildIndexes() + { + _steamIdIndex.Clear(); + _ipIndex.Clear(); + + foreach (var ban in _banCache.Values) + { + if (ban.Status != "ACTIVE") + continue; + + if (!string.IsNullOrWhiteSpace(ban.PlayerSteamId)) + { + var steamId = ban.PlayerSteamId; + _steamIdIndex.AddOrUpdate( + steamId, + key => [ban], + (key, list) => + { + list.Add(ban); + return list; + }); + } + + if (ban.PlayerIp != null && + IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt)) + { + _ipIndex.AddOrUpdate( + ipUInt, + key => [ban], + (key, list) => + { + list.Add(ban); + return list; + }); + } + } + } + + public List GetAllBans() => _banCache.Values.ToList(); + public List GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList(); + public List GetPlayerBansBySteamId(string steamId) => _steamIdIndex.TryGetValue(steamId, out var bans) ? bans : []; + public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress) + { + var ipAsUint = IpHelper.IpToUint(ipAddress); + + return _playerIpsCache.AsValueEnumerable() + .SelectMany(kvp => kvp.Value + .Where(entry => entry.Ip == ipAsUint) + .Select(entry => (kvp.Key, entry.UsedAt, entry.PlayerName))) + .ToList(); + } + + private bool IsIpBanned(string ipAddress) + { + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false; + var ipUInt = IpHelper.IpToUint(ipAddress); + return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt); + } + + + public bool IsPlayerBanned(string? steamId, string? ipAddress) + { + if (steamId != null && _steamIdIndex.ContainsKey(steamId)) + return true; + + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false; + + if (ipAddress == null) + return false; + + if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)) + return false; + + return !_cachedIgnoredIps.Contains(ipUInt) && + _ipIndex.ContainsKey(ipUInt); + } + + public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress) + { + var steamIdStr = steamId.ToString(); + + if (_steamIdIndex.ContainsKey(steamIdStr)) + return true; + + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false; + + if (!_playerIpsCache.TryGetValue(steamId, out var ipData)) + return false; + + var now = DateTime.Now; + var cutoff = now.AddDays(-7); + + if (ipAddress != null) + { + var ipAsUint = IpHelper.IpToUint(ipAddress); + + if (!_cachedIgnoredIps.Contains(ipAsUint)) + { + ipData.Add(new IpRecord( + ipAsUint, + now.AddSeconds(-2), // artificially recent + CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" + )); + } + } + + foreach (var ipRecord in ipData) + { + if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip)) + continue; + + if (_ipIndex.ContainsKey(ipRecord.Ip)) + return true; + } + + return false; + } + + public bool HasIpForPlayer(ulong steamId, string ipAddress) + { + if (string.IsNullOrWhiteSpace(ipAddress)) + return false; + + return _playerIpsCache.TryGetValue(steamId, out var ipData) + && ipData.Any(x => x.Ip == IpHelper.IpToUint(ipAddress)); + } + + private void Clear() + { + _steamIdIndex.Clear(); + _ipIndex.Clear(); + + _banCache.Clear(); + _playerIpsCache.Clear(); + _cachedIgnoredIps.Clear(); + } + + public void Dispose() + { + if (_disposed) return; + Clear(); + _disposed = true; + } +} + +public class IpRecordComparer : IEqualityComparer +{ + public bool Equals(IpRecord x, IpRecord y) + => x.Ip == y.Ip; + + public int GetHashCode(IpRecord obj) + => obj.Ip.GetHashCode(); +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/MuteManager.cs b/CS2-SimpleAdmin/Managers/MuteManager.cs index be3e9bf..55df4b6 100644 --- a/CS2-SimpleAdmin/Managers/MuteManager.cs +++ b/CS2-SimpleAdmin/Managers/MuteManager.cs @@ -187,13 +187,13 @@ internal class MuteManager(Database.Database? database) } } - public async Task CheckOnlineModeMutes(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players) + public async Task CheckOnlineModeMutes(List<(ulong SteamID, int? UserId, int Slot)> players) { if (database == null) return; try { - var batchSize = 10; + const int batchSize = 20; await using var connection = await database.GetConnectionAsync(); var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode @@ -205,7 +205,7 @@ internal class MuteManager(Database.Database? database) var batch = players.Skip(i).Take(batchSize); var parametersList = new List(); - foreach (var (_, steamId, _, _) in batch) + foreach (var (steamId, _, _) in batch) { parametersList.Add(new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId }); } @@ -218,7 +218,7 @@ internal class MuteManager(Database.Database? database) : "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid"; - foreach (var (_, steamId, _, slot) in players) + foreach (var (steamId, _, slot) in players) { var muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId }); diff --git a/CS2-SimpleAdmin/Managers/PermissionManager.cs b/CS2-SimpleAdmin/Managers/PermissionManager.cs index 8c8e1c4..98bc007 100644 --- a/CS2-SimpleAdmin/Managers/PermissionManager.cs +++ b/CS2-SimpleAdmin/Managers/PermissionManager.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using MySqlConnector; using Newtonsoft.Json; using System.Collections.Concurrent; +using CounterStrikeSharp.API.Modules.Admin; namespace CS2_SimpleAdmin.Managers; @@ -13,7 +14,8 @@ public class PermissionManager(Database.Database? database) { // Unused for now //public static readonly ConcurrentDictionary> _adminCache = new ConcurrentDictionary>(); - public static readonly ConcurrentDictionary AdminCache = new(); + // public static readonly ConcurrentDictionary AdminCache = new(); + public static readonly ConcurrentDictionary Flags)> AdminCache = new(); // Get the relevant server groups from the sa_servers_groups table by searching if the serverId is in the servers set column public async Task> GetServerGroups() @@ -236,7 +238,7 @@ public class PermissionManager(Database.Database? database) { if (!AdminCache.ContainsKey(steamId)) { - AdminCache.TryAdd(steamId, ends); + AdminCache.TryAdd(steamId, (ends, flags)); //_adminCacheTimestamps.Add(steamId, ends); } @@ -400,7 +402,7 @@ public class PermissionManager(Database.Database? database) .GroupBy(player => player.name) // Group by player name .ToDictionary( group => group.Key, // Use the player name as the key - group => + object (group) => { // Consolidate data for players with the same name var consolidatedData = group.Aggregate( @@ -431,17 +433,77 @@ public class PermissionManager(Database.Database? database) return acc; }); - - foreach (var player in group) + + Server.NextFrameAsync(() => { - SteamID.TryParse(player.identity, out var steamId); - if (steamId != null && !AdminCache.ContainsKey(steamId)) - { - AdminCache.TryAdd(steamId, player.ends); - } - } + var keysToRemove = new List(); - return (object)consolidatedData; + 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, out var steamId) && steamId != null) + { + AdminCache.TryAdd(steamId, (player.ends, player.flags)); + } + } + }); + + // Server.NextFrameAsync(() => + // { + // for (var index = 0; index < AdminCache.Keys.ToList().Count; index++) + // { + // var steamId = AdminCache.Keys.ToList()[index]; + // + // var data = AdminManager.GetPlayerAdminData(steamId); + // if (data != null) + // { + // AdminManager.RemovePlayerPermissions(steamId, AdminCache[steamId].Flags.ToArray()); + // AdminManager.RemovePlayerFromGroup(steamId, true, AdminCache[steamId].Flags.ToArray()); + // } + // + // if (!AdminCache.TryRemove(steamId, out _)) continue; + // + // if (data == null) continue; + // if (data.Flags.ToList().Count != 0 && data.Groups.ToList().Count != 0) + // continue; + // + // AdminManager.ClearPlayerPermissions(steamId); + // AdminManager.RemovePlayerAdminData(steamId); + // } + // + // foreach (var player in group) + // { + // SteamID.TryParse(player.identity, out var steamId); + // if (steamId == null) continue; + // AdminCache.TryAdd(steamId, (player.ends, player.flags)); + // } + // }); + + return consolidatedData; }); var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented); diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index 0b965a1..b80cb1e 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -7,6 +7,7 @@ using CounterStrikeSharp.API.ValveConstants.Protobuf; using CS2_SimpleAdminApi; using Dapper; using Microsoft.Extensions.Logging; +using ZLinq; namespace CS2_SimpleAdmin.Managers; @@ -26,9 +27,9 @@ public class PlayerManager } var ipAddress = player.IpAddress?.Split(":")[0]; - CS2_SimpleAdmin.PlayersInfo[player.UserId.Value] = new PlayerInfo(player.UserId.Value, player.Slot, new SteamID(player.SteamID), player.PlayerName, ipAddress); + // if (!player.UserId.HasValue) // { @@ -37,35 +38,48 @@ public class PlayerManager // } var userId = player.UserId.Value; - - // Check if the player's IP or SteamID is in the bannedPlayers list - if (_config.OtherSettings.BanType > 0 && CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress) || - CS2_SimpleAdmin.BannedPlayers.Contains(player.SteamID.ToString())) + if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(userId)) { - // Kick the player if banned - Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); - return; + Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION); } - + + var steamId64 = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64; + var steamId = steamId64.ToString(); + if (CS2_SimpleAdmin.Database == null) return; // Perform asynchronous database operations within a single method Task.Run(async () => { - if (_config.OtherSettings.CheckMultiAccountsByIp) + var isBanned = CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch + { + 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, null), + _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp + ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(steamId64, ipAddress) + : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, ipAddress) + }; + + if (isBanned) + { + // Kick the player if banned + await Server.NextFrameAsync(() => + { + if (!player.UserId.HasValue) return; + Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + }); + + return; + } + + if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null) { try { - await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); - const string selectQuery = "SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;"; - var recordExists = await connection.ExecuteScalarAsync(selectQuery, new - { - SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, - IPAddress = ipAddress - }); - - if (recordExists > 0) + if (CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer( + CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, ipAddress)) { + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + const string updateQuery = """ UPDATE `sa_players_ips` SET used_at = CURRENT_TIMESTAMP @@ -74,20 +88,46 @@ public class PlayerManager await connection.ExecuteAsync(updateQuery, new { SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, - IPAddress = ipAddress + IPAddress = IpHelper.IpToUint(ipAddress) }); } else { - const string insertQuery = """ - INSERT INTO `sa_players_ips` (steamid, address, used_at) - VALUES (@SteamID, @IPAddress, CURRENT_TIMESTAMP); - """; - await connection.ExecuteAsync(insertQuery, new + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + + const string selectQuery = + "SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;"; + var recordExists = await connection.ExecuteScalarAsync(selectQuery, new { SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, - IPAddress = ipAddress + IPAddress = IpHelper.IpToUint(ipAddress) }); + + if (recordExists > 0) + { + const string updateQuery = """ + UPDATE `sa_players_ips` + SET used_at = CURRENT_TIMESTAMP + WHERE steamid = @SteamID AND address = @IPAddress; + """; + await connection.ExecuteAsync(updateQuery, new + { + SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, + IPAddress = IpHelper.IpToUint(ipAddress) + }); + } + else + { + const string insertQuery = """ + INSERT INTO `sa_players_ips` (steamid, address, used_at) + VALUES (@SteamID, @IPAddress, CURRENT_TIMESTAMP); + """; + await connection.ExecuteAsync(insertQuery, new + { + SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, + IPAddress = IpHelper.IpToUint(ipAddress) + }); + } } } catch (Exception ex) @@ -95,55 +135,29 @@ public class PlayerManager CS2_SimpleAdmin._logger?.LogError( $"Unable to save ip address for {CS2_SimpleAdmin.PlayersInfo[userId].Name} ({ipAddress}) {ex.Message}"); } + + // Get all accounts associated to the player (ip address) + CS2_SimpleAdmin.PlayersInfo[userId].AccountsAssociated = + CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable().Select(x => (x.SteamId, x.PlayerName)).ToList() ?? []; } - try + try { - if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(userId)) - { - await Server.NextFrameAsync(() => Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION)); - } + // var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 + // ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned( + // CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString(), null) + // : CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp + // ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(CS2_SimpleAdmin + // .PlayersInfo[userId].SteamId.SteamId64) + // : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString(), ipAddress); - // Check if the player is banned - var isBanned = await CS2_SimpleAdmin.Instance.BanManager.IsPlayerBanned(CS2_SimpleAdmin.PlayersInfo[userId]); - - if (isBanned) - { - // Add player's IP and SteamID to bannedPlayers list if not already present - if (_config.OtherSettings.BanType > 0 && ipAddress != null && - !CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress)) - { - CS2_SimpleAdmin.BannedPlayers.Add(ipAddress); - } - - if (!CS2_SimpleAdmin.BannedPlayers.Contains(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString())) - { - CS2_SimpleAdmin.BannedPlayers.Add(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()); - } - - // Kick the player if banned - await Server.NextFrameAsync(() => - { - var victim = Utilities.GetPlayerFromUserid(userId); - if (victim == null || !victim.UserId.HasValue) return; - - if (CS2_SimpleAdmin.UnlockedCommands && _config.BanIDBan) - Server.ExecuteCommand($"banid 1 {userId}"); - - Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); - }); - - return; - } - if (fullConnect || !fullConnect) // Temp skip { var warns = await CS2_SimpleAdmin.Instance.WarnManager.GetPlayerWarns(CS2_SimpleAdmin.PlayersInfo[userId], false); var (totalMutes, totalGags, totalSilences) = await CS2_SimpleAdmin.Instance.MuteManager.GetPlayerMutes(CS2_SimpleAdmin.PlayersInfo[userId]); - CS2_SimpleAdmin.PlayersInfo[userId].TotalBans = - await CS2_SimpleAdmin.Instance.BanManager.GetPlayerBans(CS2_SimpleAdmin.PlayersInfo[userId]); + CS2_SimpleAdmin.PlayersInfo[userId].TotalBans = CS2_SimpleAdmin.Instance.CacheManager?.GetPlayerBansBySteamId(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()).Count ?? 0; CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes = totalMutes; CS2_SimpleAdmin.PlayersInfo[userId].TotalGags = totalGags; CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences = totalSilences; @@ -192,6 +206,8 @@ public class PlayerManager if (CS2_SimpleAdmin.Instance.Config.OtherSettings.NotifyPenaltiesToAdminOnConnect && fullConnect) { + var associatedAcccountsChunks = CS2_SimpleAdmin.PlayersInfo[userId].AccountsAssociated.ChunkBy(5).ToList(); + await Server.NextFrameAsync(() => { foreach (var admin in Helper.GetValidPlayers() @@ -199,9 +215,8 @@ public class PlayerManager AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/ban")) && p.Connected == PlayerConnectedState.PlayerConnected && !CS2_SimpleAdmin.AdminDisabledJoinComms.Contains(p.SteamID))) { - if (CS2_SimpleAdmin._localizer != null && admin != player - && (CS2_SimpleAdmin.PlayersInfo[userId].TotalBans > 0 || CS2_SimpleAdmin.PlayersInfo[userId].TotalGags > 0 || CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes > 0 || CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences > 0 || CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns > 0) - ) + if (CS2_SimpleAdmin._localizer != null && admin != player) + { admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_penalty_info", player.PlayerName, CS2_SimpleAdmin.PlayersInfo[userId].TotalBans, @@ -210,6 +225,16 @@ public class PlayerManager CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences, CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns ); + + foreach (var chunk in associatedAcccountsChunks) + { + admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_associated_accounts", + player.PlayerName, + string.Join(", ", + chunk.Select(a => $"{a.PlayerName} ({a.SteamId})")) + ); + } + } } }); } @@ -231,13 +256,10 @@ public class PlayerManager CS2_SimpleAdmin.Instance.AddTimer(0.1f, () => { if (CS2_SimpleAdmin.GravityPlayers.Count <= 0) return; - - foreach (var value in CS2_SimpleAdmin.GravityPlayers) - { - if (value.Key is not - { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }) - continue; + foreach (var value in CS2_SimpleAdmin.GravityPlayers.Where(value => value.Key is + { IsValid: true, Connected: PlayerConnectedState.PlayerConnected } || value.Key.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)) + { value.Key.SetGravity(value.Value); } }, TimerFlags.REPEAT); @@ -250,88 +272,79 @@ public class PlayerManager if (CS2_SimpleAdmin.Database == null) return; - var players = Helper.GetValidPlayers(); - var onlinePlayers = new List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)>(); - // var onlinePlayers = players - // .Where(player => player.IpAddress != null) - // .Select(player => (player.IpAddress, player.SteamID, player.UserId, player.Slot)) - // .ToList(); - - foreach (var player in players) - { - if (player.IpAddress != null) - onlinePlayers.Add((player.IpAddress, player.SteamID, player.UserId, player.Slot)); - } - - try - { - var expireTasks = new[] + var tempPlayers = Helper.GetValidPlayers() + .Select(p => new { - CS2_SimpleAdmin.Instance.BanManager.ExpireOldBans(), - CS2_SimpleAdmin.Instance.MuteManager.ExpireOldMutes(), - CS2_SimpleAdmin.Instance.WarnManager.ExpireOldWarns(), - CS2_SimpleAdmin.Instance.PermissionManager.DeleteOldAdmins() - }; - - Task.WhenAll(expireTasks).ContinueWith(t => - { - if (t is not { IsFaulted: true, Exception: not null }) return; - - foreach (var ex in t.Exception.InnerExceptions) - { - CS2_SimpleAdmin._logger?.LogError($"Error expiring penalties: {ex.Message}"); - } - }); - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError("Unexpected error: {exception}", ex.Message); - } + p.SteamID, p.IpAddress, p.UserId, p.Slot, + }) + .ToList(); - CS2_SimpleAdmin.BannedPlayers.Clear(); - - if (onlinePlayers.Count > 0) + _ = Task.Run(async () => { try { - Task.Run(async () => + var expireTasks = new Task[] { - await CS2_SimpleAdmin.Instance.BanManager.CheckOnlinePlayers(onlinePlayers); + CS2_SimpleAdmin.Instance.BanManager.ExpireOldBans(), + CS2_SimpleAdmin.Instance.MuteManager.ExpireOldMutes(), + CS2_SimpleAdmin.Instance.WarnManager.ExpireOldWarns(), + CS2_SimpleAdmin.Instance.CacheManager?.RefreshCacheAsync() ?? Task.CompletedTask, + CS2_SimpleAdmin.Instance.PermissionManager.DeleteOldAdmins() + }; - if (_config.OtherSettings.TimeMode == 0) - { - await CS2_SimpleAdmin.Instance.MuteManager.CheckOnlineModeMutes(onlinePlayers); - } - }).ContinueWith(t => - { - if (t is not { IsFaulted: true, Exception: not null }) return; - - foreach (var ex in t.Exception.InnerExceptions) - { - CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}"); - } - }); + await Task.WhenAll(expireTasks); } catch (Exception ex) { - CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}"); - } - } + CS2_SimpleAdmin._logger?.LogError($"Error processing players timer tasks: {ex.Message}"); - if (onlinePlayers.Count <= 0) return; - - { - try + if (ex is AggregateException aggregate) + { + foreach (var inner in aggregate.InnerExceptions) + { + CS2_SimpleAdmin._logger?.LogError($"Inner exception: {inner.Message}"); + } + } + } + + var bannedPlayers = tempPlayers.AsValueEnumerable() + .Where(player => + { + return CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch + { + 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), null), + _ => + CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress?.Split(":")[0]) + }; + }) + .ToList(); + + foreach (var player in bannedPlayers) { - var penalizedSlots = players - .Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot)) - .Select(player => new - { - Player = player, - IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _), - IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _), - IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _) - }); + if (player.UserId.HasValue) + await Server.NextFrameAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED)); + } + + var onlinePlayers = tempPlayers.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList(); + if (tempPlayers.Count == 0 || onlinePlayers.Count == 0) return; + if (_config.OtherSettings.TimeMode == 0) + { + await CS2_SimpleAdmin.Instance.MuteManager.CheckOnlineModeMutes(onlinePlayers); + } + }); + + try + { + var players = Helper.GetValidPlayers(); + var penalizedSlots = players + .Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot)) + .Select(player => new + { + Player = player, + IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _), + IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _), + IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _) + }); foreach (var entry in penalizedSlots) { @@ -344,14 +357,13 @@ public class PlayerManager } } - PlayerPenaltyManager.RemoveExpiredPenalties(); - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}"); - } + PlayerPenaltyManager.RemoveExpiredPenalties(); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}"); } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT); + }, TimerFlags.REPEAT); } } \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/ServerManager.cs b/CS2-SimpleAdmin/Managers/ServerManager.cs index 7046146..756f0ed 100644 --- a/CS2-SimpleAdmin/Managers/ServerManager.cs +++ b/CS2-SimpleAdmin/Managers/ServerManager.cs @@ -9,9 +9,9 @@ public class ServerManager { private int _getIpTryCount; - public void CheckHibernationStatus() + public static void CheckHibernationStatus() { - ConVar? convar = ConVar.Find("sv_hibernate_when_empty"); + var convar = ConVar.Find("sv_hibernate_when_empty"); if (convar == null || !convar.GetPrimitiveValue()) return; @@ -95,6 +95,8 @@ public class ServerManager new { address }); CS2_SimpleAdmin.ServerId = serverId; + + CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress); if (CS2_SimpleAdmin.ServerId != null) { @@ -102,6 +104,8 @@ public class ServerManager } CS2_SimpleAdmin.ServerLoaded = true; + if (CS2_SimpleAdmin.Instance.CacheManager != null) + await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync(); } catch (Exception ex) { diff --git a/CS2-SimpleAdmin/Menus/PlayersMenu.cs b/CS2-SimpleAdmin/Menus/PlayersMenu.cs index 092787f..19b76c2 100644 --- a/CS2-SimpleAdmin/Menus/PlayersMenu.cs +++ b/CS2-SimpleAdmin/Menus/PlayersMenu.cs @@ -18,12 +18,12 @@ public static class PlayersMenu public static void OpenAliveMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) { - OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive); + OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE); } public static void OpenDeadMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) { - OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive == false); + OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE); } public static void OpenMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) diff --git a/CS2-SimpleAdmin/Models/BanRecord.cs b/CS2-SimpleAdmin/Models/BanRecord.cs new file mode 100644 index 0000000..479a26a --- /dev/null +++ b/CS2-SimpleAdmin/Models/BanRecord.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace CS2_SimpleAdmin.Models; + +public record BanRecord +{ + [Column("id")] + public int Id { get; set; } + + [Column("player_steamid")] + public string? PlayerSteamId { get; set; } + + [Column("player_ip")] + public string? PlayerIp { get; set; } + + [Column("status")] + public string Status { get; set; } +} diff --git a/CS2-SimpleAdmin/Models/IpRecord.cs b/CS2-SimpleAdmin/Models/IpRecord.cs new file mode 100644 index 0000000..af9ef95 --- /dev/null +++ b/CS2-SimpleAdmin/Models/IpRecord.cs @@ -0,0 +1,3 @@ +namespace CS2_SimpleAdmin.Models; + +public readonly record struct IpRecord(uint Ip, DateTime UsedAt, string PlayerName); diff --git a/CS2-SimpleAdmin/VERSION b/CS2-SimpleAdmin/VERSION index 1b574bd..58630bc 100644 --- a/CS2-SimpleAdmin/VERSION +++ b/CS2-SimpleAdmin/VERSION @@ -1 +1 @@ -1.7.5a \ No newline at end of file +1.7.7-alpha \ No newline at end of file diff --git a/CS2-SimpleAdmin/Variables.cs b/CS2-SimpleAdmin/Variables.cs index b08d591..78db477 100644 --- a/CS2-SimpleAdmin/Variables.cs +++ b/CS2-SimpleAdmin/Variables.cs @@ -32,14 +32,13 @@ public partial class CS2_SimpleAdmin // Command and Server Settings public static readonly bool UnlockedCommands = CoreConfig.UnlockConCommands; internal static string IpAddress = string.Empty; - public static bool ServerLoaded; - public static int? ServerId = null; + internal static bool ServerLoaded; + internal static int? ServerId = null; internal static readonly HashSet AdminDisabledJoinComms = []; // Player Management private static readonly HashSet GodPlayers = []; internal static readonly HashSet SilentPlayers = []; - internal static readonly ConcurrentBag BannedPlayers = []; internal static readonly Dictionary RenamedPlayers = []; internal static readonly ConcurrentDictionary PlayersInfo = []; private static readonly List DisconnectedPlayers = []; @@ -69,6 +68,7 @@ public partial class CS2_SimpleAdmin internal BanManager BanManager = new(Database); internal MuteManager MuteManager = new(Database); internal WarnManager WarnManager = new(Database); + internal CacheManager? CacheManager = new(); internal ChatManager ChatManager = new(); static string firstMessage = ""; diff --git a/CS2-SimpleAdmin/lang/ar.json b/CS2-SimpleAdmin/lang/ar.json index 8128946..9a35b90 100644 --- a/CS2-SimpleAdmin/lang/ar.json +++ b/CS2-SimpleAdmin/lang/ar.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nعقوبات اللاعبين لـ {lightred}{0}{default},\nعدد الحظر: {lightred}{1}{default}, عدد الصمت: {lightred}{2}{default}, عدد الكتم: {lightred}{3}{default}, عدد السكوت: {lightred}{4}{default}, عدد التحذيرات: {lightred}{5}{default}\nالعقوبات النشطة:\n{6}\nالتحذيرات النشطة:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}عقوبات اللاعبين لـ {lightred}{0}{grey}, حظر: {lightred}{1}{grey}, صمت: {lightred}{2}{grey}, كتم: {lightred}{3}{grey}, سكوت: {lightred}{4}{grey}, تحذيرات: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}الحسابات المرتبطة باللاعب {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "تم حظرك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!", "sa_player_ban_message_perm": "تم حظرك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!", "sa_player_kick_message": "تم طردك لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/de.json b/CS2-SimpleAdmin/lang/de.json index 697b59d..a58b49f 100644 --- a/CS2-SimpleAdmin/lang/de.json +++ b/CS2-SimpleAdmin/lang/de.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nSpielerstrafe für {lightred}{0}{default},\nAnzahl der Sperren: {lightred}{1}{default}, Anzahl der Mundtot: {lightred}{2}{default}, Anzahl der Stummschaltungen: {lightred}{3}{default}, Anzahl der Stille: {lightred}{4}{default}, Anzahl der Warnungen: {lightred}{5}{default}\nAktive Strafen:\n{6}\nAktive Warnungen:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Spielerstrafe für {lightred}{0}{grey}, Sperren: {lightred}{1}{grey}, Mundtot: {lightred}{2}{grey}, Stummschaltungen: {lightred}{3}{grey}, Stille: {lightred}{4}{grey}, Warnungen: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Verknüpfte Konten des Spielers {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} gebannt!", "sa_player_ban_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent gebannt!", "sa_player_kick_message": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} gekickt!", diff --git a/CS2-SimpleAdmin/lang/en.json b/CS2-SimpleAdmin/lang/en.json index 8715040..b3d87cf 100644 --- a/CS2-SimpleAdmin/lang/en.json +++ b/CS2-SimpleAdmin/lang/en.json @@ -78,6 +78,7 @@ "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}!", diff --git a/CS2-SimpleAdmin/lang/es.json b/CS2-SimpleAdmin/lang/es.json index de36db1..525bd3d 100644 --- a/CS2-SimpleAdmin/lang/es.json +++ b/CS2-SimpleAdmin/lang/es.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nPenalizaciones del jugador para {lightred}{0}{default},\nNúmero de prohibiciones: {lightred}{1}{default}, Número de boqueos: {lightred}{2}{default}, Número de silenciamientos: {lightred}{3}{default}, Número de silencios: {lightred}{4}{default}, Número de advertencias: {lightred}{5}{default}\nPenalizaciones activas:\n{6}\nAdvertencias activas:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Penalizaciones del jugador para {lightred}{0}{grey}, Prohibiciones: {lightred}{1}{grey}, Boqueos: {lightred}{2}{grey}, Silenciamientos: {lightred}{3}{grey}, Silencios: {lightred}{4}{grey}, Advertencias: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Cuentas asociadas del jugador {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "Has sido baneado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!", "sa_player_ban_message_perm": "Has sido baneado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", "sa_player_kick_message": "Has sido expulsado por {lightred}{0}{default} durante {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/fa.json b/CS2-SimpleAdmin/lang/fa.json index 738e265..20ba95a 100644 --- a/CS2-SimpleAdmin/lang/fa.json +++ b/CS2-SimpleAdmin/lang/fa.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nتنبیهات بازیکن برای {lightred}{0}{default},\nتعداد مسدودیت‌ها: {lightred}{1}{default}, تعداد سکوت‌ها: {lightred}{2}{default}, تعداد بی‌صدا کردن‌ها: {lightred}{3}{default}, تعداد سکوت‌ها: {lightred}{4}{default}, تعداد هشدارها: {lightred}{5}{default}\nتنبیهات فعال:\n{6}\nهشدارهای فعال:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}تنبیهات بازیکن برای {lightred}{0}{grey}, مسدودیت‌ها: {lightred}{1}{grey}, سکوت‌ها: {lightred}{2}{grey}, بی‌صدا کردن‌ها: {lightred}{3}{grey}, سکوت‌ها: {lightred}{4}{grey}, هشدارها: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}حساب‌های مرتبط با بازیکن {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} مسدود شده‌اید!", "sa_player_ban_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه مسدود شده‌اید!", "sa_player_kick_message": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} اخراج شده‌اید!", diff --git a/CS2-SimpleAdmin/lang/fr.json b/CS2-SimpleAdmin/lang/fr.json index 1cc7e5c..303ae38 100644 --- a/CS2-SimpleAdmin/lang/fr.json +++ b/CS2-SimpleAdmin/lang/fr.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nPénalités du joueur pour {lightred}{0}{default},\nNombre de bannissements: {lightred}{1}{default}, Nombre de gag: {lightred}{2}{default}, Nombre de mutes: {lightred}{3}{default}, Nombre de silences: {lightred}{4}{default}, Nombre d’avertissements: {lightred}{5}{default}\nPénalités actives:\n{6}\nAvertissements actifs:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Pénalités du joueur pour {lightred}{0}{grey}, Bannissements: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Avertissements: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Comptes associés du joueur {lightred}{0}{grey} : {1}", "sa_player_ban_message_time": "Vous avez été banni pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!", "sa_player_ban_message_perm": "Vous avez été banni définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!", "sa_player_kick_message": "Vous avez été expulsé pour {lightred}{0}{default} par {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/lv.json b/CS2-SimpleAdmin/lang/lv.json index 324dfc9..70f4678 100644 --- a/CS2-SimpleAdmin/lang/lv.json +++ b/CS2-SimpleAdmin/lang/lv.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nSpēlētāja sods priekš {lightred}{0}{default},\nAizliegumu skaits: {lightred}{1}{default}, Klusumu skaits: {lightred}{2}{default}, Izslēgšanas skaits: {lightred}{3}{default}, Klusēšanas skaits: {lightred}{4}{default}, Brīdinājumu skaits: {lightred}{5}{default}\nAktīvie sodi:\n{6}\nAktīvie brīdinājumi:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Spēlētāja sods priekš {lightred}{0}{grey}, Aizliegumi: {lightred}{1}{grey}, Klusumi: {lightred}{2}{grey}, Izslēgšana: {lightred}{3}{grey}, Klusēšana: {lightred}{4}{grey}, Brīdinājumi: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Spēlētāja {lightred}{0}{grey} saistītie konti: {1}", "sa_player_ban_message_time": "Tu esi nobanots uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}!", "sa_player_ban_message_perm": "Tevis bans ir uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!", "sa_player_kick_message": "Tu esi izmests, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/pl.json b/CS2-SimpleAdmin/lang/pl.json index b436f69..c7f7bbf 100644 --- a/CS2-SimpleAdmin/lang/pl.json +++ b/CS2-SimpleAdmin/lang/pl.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nBlokady gracza {lightred}{0}{default},\nIlość banów: {lightred}{1}{default}, Ilość zakneblowań: {lightred}{2}{default}, Ilość wyciszeń: {lightred}{3}{default}, Ilość uciszeń: {lightred}{4}{default}Ilość ostrzeżeń: {lightred}{5}{default}\nAktywne blokady:\n{6}\nAktywne ostrzeżenia:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Blokady gracza {lightred}{0}{grey} - bany: {lightred}{1}{grey}, zakneblowania: {lightred}{2}{grey}, wyciszenia: {lightred}{3}{grey}, uciszenia: {lightred}{4}{grey}, ostrzeżenia: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Powiązane konta gracza {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "Zostałeś zbanowany za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!", "sa_player_ban_message_perm": "Zostałeś zbanowany na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!", "sa_player_kick_message": "Zostałeś wyrzucony za {lightred}{0}{default} przez {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/pt-BR.json b/CS2-SimpleAdmin/lang/pt-BR.json index 17c6977..ed6d9ba 100644 --- a/CS2-SimpleAdmin/lang/pt-BR.json +++ b/CS2-SimpleAdmin/lang/pt-BR.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "Você foi banido por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!", "sa_player_ban_message_perm": "Você foi banido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", "sa_player_kick_message": "Você foi expulso por {lightred}{0}{default} por {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/pt-PT.json b/CS2-SimpleAdmin/lang/pt-PT.json index 04838f2..3ec755c 100644 --- a/CS2-SimpleAdmin/lang/pt-PT.json +++ b/CS2-SimpleAdmin/lang/pt-PT.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "Foste banido pelo administrador {lightred}{0}{default} durante {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!", "sa_player_ban_message_perm": "Foste banido permanentemente pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!", "sa_player_kick_message": "Foste expulso pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/ru.json b/CS2-SimpleAdmin/lang/ru.json index 9339a63..7418422 100644 --- a/CS2-SimpleAdmin/lang/ru.json +++ b/CS2-SimpleAdmin/lang/ru.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nШтрафы игрока для {lightred}{0}{default},\nКоличество банов: {lightred}{1}{default}, Количество гэгов: {lightred}{2}{default}, Количество мутов: {lightred}{3}{default}, Количество тишин: {lightred}{4}{default}, Количество предупреждений: {lightred}{5}{default}\nАктивные штрафы:\n{6}\nАктивные предупреждения:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Штрафы игрока для {lightred}{0}{grey}, Баны: {lightred}{1}{grey}, Гэги: {lightred}{2}{grey}, Муты: {lightred}{3}{grey}, Тишины: {lightred}{4}{grey}, Предупреждения: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}Связанные аккаунты игрока {lightred}{0}{grey}: {1}", "sa_player_ban_message_time": "Вы были забанены по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!", "sa_player_ban_message_perm": "Вас забанили навсегда по причине {lightred}{0}{default} администратором {lightred}{1}{default}!", "sa_player_kick_message": "Вы были выгнаны {lightred}{0}{default} администратором {lightred}{1}{default}!", diff --git a/CS2-SimpleAdmin/lang/tr.json b/CS2-SimpleAdmin/lang/tr.json index 89c8790..08e7cb4 100644 --- a/CS2-SimpleAdmin/lang/tr.json +++ b/CS2-SimpleAdmin/lang/tr.json @@ -78,6 +78,7 @@ "sa_player_penalty_info": "===========================\nOyuncunun cezaları {lightred}{0}{default} için,\nBan sayısı: {lightred}{1}{default}, Gag sayısı: {lightred}{2}{default}, Mute sayısı: {lightred}{3}{default}, Sessizlik sayısı: {lightred}{4}{default}, Uyarı sayısı: {lightred}{5}{default}\nAktif cezalar:\n{6}\nAktif uyarılar:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}Oyuncunun cezaları {lightred}{0}{grey}, Banlar: {lightred}{1}{grey}, Gaglar: {lightred}{2}{grey}, Mute'lar: {lightred}{3}{grey}, Sessizlikler: {lightred}{4}{grey}, Uyarılar: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}{lightred}{0}{grey} oyuncusunun bağlı hesapları: {1}", "sa_player_ban_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından yasaklandınız!", "sa_player_ban_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından yasaklandınız!", "sa_player_kick_message": "Senaryo nedeniyle {lightred}{0}{default} tarafından atıldınız!", diff --git a/CS2-SimpleAdmin/lang/zh-Hans.json b/CS2-SimpleAdmin/lang/zh-Hans.json index 72ca4e3..53ac6d0 100644 --- a/CS2-SimpleAdmin/lang/zh-Hans.json +++ b/CS2-SimpleAdmin/lang/zh-Hans.json @@ -76,6 +76,7 @@ "sa_player_penalty_info": "===========================\n玩家 {lightred}{0}{default} 的处罚信息,\n禁止次数: {lightred}{1}{default}, 禁言次数: {lightred}{2}{default}, 静音次数: {lightred}{3}{default}, 沉默次数: {lightred}{4}{default}, 警告次数: {lightred}{5}{default}\n活跃的处罚:\n{6}\n活跃的警告:\n{7}\n===========================", "sa_admin_penalty_info": "{grey}玩家 {lightred}{0}{grey} 的处罚信息, 禁止: {lightred}{1}{grey}, 禁言: {lightred}{2}{grey}, 静音: {lightred}{3}{grey}, 沉默: {lightred}{4}{grey}, 警告: {lightred}{5}", + "sa_admin_associated_accounts": "{grey}玩家 {lightred}{0}{grey} 的关联账户:{1}", "sa_player_ban_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 禁止 {lightred}{1}{default} 分钟!", "sa_player_ban_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久禁止!", "sa_player_kick_message": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 踢出!", diff --git a/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj index 228359e..97feaf5 100644 --- a/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj +++ b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj @@ -8,7 +8,7 @@ - + diff --git a/CS2-SimpleAdminApi/PlayerInfo.cs b/CS2-SimpleAdminApi/PlayerInfo.cs index f367ed7..b492322 100644 --- a/CS2-SimpleAdminApi/PlayerInfo.cs +++ b/CS2-SimpleAdminApi/PlayerInfo.cs @@ -26,6 +26,7 @@ public class PlayerInfo( public int TotalSilences { get; set; } = totalSilences; public int TotalWarns { get; set; } = totalWarns; public bool WaitingForKick { get; set; } = false; + public List<(ulong SteamId, string PlayerName)> AccountsAssociated { get; set; } = []; public DiePosition? DiePosition { get; set; } }