From b6c876d709c4a5a89e171b944b031315ef84bb77 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Tue, 25 Mar 2025 11:39:00 +0100 Subject: [PATCH 1/8] 1.7.6a - Changed PawnIsAlive to LifeState - Changed AdminCache - now it only removes flags added in the database - not all assigned to player - Added config variable `IgnoredIps` to ignore ip check on connect, useful when proxying --- CS2-SimpleAdmin/CS2-SimpleAdmin.cs | 4 +- CS2-SimpleAdmin/Commands/basebans.cs | 6 +- CS2-SimpleAdmin/Commands/basecommands.cs | 19 ++--- CS2-SimpleAdmin/Commands/funcommands.cs | 10 +-- CS2-SimpleAdmin/Commands/playercommands.cs | 35 ++++---- CS2-SimpleAdmin/Config.cs | 2 + CS2-SimpleAdmin/Events.cs | 29 ++++--- .../Extensions/PlayerExtensions.cs | 2 +- CS2-SimpleAdmin/Managers/BanManager.cs | 5 +- CS2-SimpleAdmin/Managers/PermissionManager.cs | 84 ++++++++++++++++--- CS2-SimpleAdmin/Managers/PlayerManager.cs | 2 +- CS2-SimpleAdmin/Menus/PlayersMenu.cs | 4 +- CS2-SimpleAdmin/VERSION | 2 +- 13 files changed, 134 insertions(+), 70 deletions(-) diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs index 0b0c38d..6cb7bf7 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.6a"; public override void Load(bool hotReload) { @@ -56,7 +56,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig ReloadAdmins(null)); + AddTimer(5.0f, () => ReloadAdmins(null)); try { diff --git a/CS2-SimpleAdmin/Commands/basebans.cs b/CS2-SimpleAdmin/Commands/basebans.cs index 393feae..ac6b064 100644 --- a/CS2-SimpleAdmin/Commands/basebans.cs +++ b/CS2-SimpleAdmin/Commands/basebans.cs @@ -366,10 +366,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 diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs index bc34802..98a9e52 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -390,16 +390,7 @@ public partial class CS2_SimpleAdmin 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); - } - + Task.Run(async () => { await PermissionManager.CrateGroupsJsonFile(); @@ -411,11 +402,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!"); }); @@ -443,8 +434,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); diff --git a/CS2-SimpleAdmin/Commands/funcommands.cs b/CS2-SimpleAdmin/Commands/funcommands.cs index 05a2378..c336c81 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 => { @@ -70,7 +70,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 => { @@ -124,7 +124,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 => { @@ -144,7 +144,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 => { @@ -218,7 +218,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 => { diff --git a/CS2-SimpleAdmin/Commands/playercommands.cs b/CS2-SimpleAdmin/Commands/playercommands.cs index 67c5b0e..08102fa 100644 --- a/CS2-SimpleAdmin/Commands/playercommands.cs +++ b/CS2-SimpleAdmin/Commands/playercommands.cs @@ -22,7 +22,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 => { @@ -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 @@ -173,7 +173,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 => { @@ -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 @@ -225,7 +225,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 => { @@ -274,7 +274,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 => { @@ -330,7 +330,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 => { @@ -387,7 +387,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 => { @@ -438,7 +438,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) { @@ -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); @@ -740,7 +740,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 +754,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 +778,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 +811,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 +826,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 +850,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 d61e678..76b8f30 100644 --- a/CS2-SimpleAdmin/Config.cs +++ b/CS2-SimpleAdmin/Config.cs @@ -230,6 +230,8 @@ public class OtherSettings [JsonPropertyName("AdditionalCommandsToLog")] public List AdditionalCommandsToLog { get; set; } = new(); + [JsonPropertyName("IgnoredIps")] + public List IgnoredIps { get; set; } = new(); } public class CS2_SimpleAdminConfig : BasePluginConfig diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs index 457e1ce..e8ef371 100644 --- a/CS2-SimpleAdmin/Events.cs +++ b/CS2-SimpleAdmin/Events.cs @@ -106,14 +106,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; } @@ -372,7 +380,7 @@ 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()); @@ -395,9 +403,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/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/Managers/BanManager.cs b/CS2-SimpleAdmin/Managers/BanManager.cs index 1f01c5f..4012059 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, diff --git a/CS2-SimpleAdmin/Managers/PermissionManager.cs b/CS2-SimpleAdmin/Managers/PermissionManager.cs index 65bfaea..bd8fb46 100644 --- a/CS2-SimpleAdmin/Managers/PermissionManager.cs +++ b/CS2-SimpleAdmin/Managers/PermissionManager.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using MySqlConnector; using Newtonsoft.Json; using System.Collections.Concurrent; +using CounterStrikeSharp.API.Modules.Admin; namespace CS2_SimpleAdmin.Managers; @@ -12,7 +13,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(); /* public async Task, int)>> GetAdminFlags(string steamId) @@ -334,7 +336,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( @@ -365,17 +367,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 c51ec28..487e9dd 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -229,7 +229,7 @@ public class PlayerManager foreach (var value in CS2_SimpleAdmin.GravityPlayers) { if (value.Key is not - { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }) + { IsValid: true, Connected: PlayerConnectedState.PlayerConnected } && value.Key.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE) continue; value.Key.SetGravity(value.Value); 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/VERSION b/CS2-SimpleAdmin/VERSION index 1b574bd..4e7dcbe 100644 --- a/CS2-SimpleAdmin/VERSION +++ b/CS2-SimpleAdmin/VERSION @@ -1 +1 @@ -1.7.5a \ No newline at end of file +1.7.6a \ No newline at end of file From f69f1277f88e079c0ff9d7e973b990f1fa20297c Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Tue, 20 May 2025 23:51:59 +0200 Subject: [PATCH 2/8] 1.7.7-alpha - Fixed (@all,@team,@lifestate) spam with discord webhook - Currently `small` actions for ban optimizations --- .gitignore | 1 + CS2-SimpleAdmin/CS2-SimpleAdmin.cs | 4 +- CS2-SimpleAdmin/CS2-SimpleAdmin.csproj | 3 +- CS2-SimpleAdmin/Commands/basebans.cs | 18 +- CS2-SimpleAdmin/Commands/basechat.cs | 1 - CS2-SimpleAdmin/Commands/basecommands.cs | 13 +- CS2-SimpleAdmin/Commands/basecomms.cs | 27 +- CS2-SimpleAdmin/Commands/funcommands.cs | 20 +- CS2-SimpleAdmin/Commands/playercommands.cs | 44 ++-- CS2-SimpleAdmin/Events.cs | 29 +- CS2-SimpleAdmin/Helper.cs | 34 ++- CS2-SimpleAdmin/Managers/BanManager.cs | 2 +- CS2-SimpleAdmin/Managers/CacheManager.cs | 168 ++++++++++++ CS2-SimpleAdmin/Managers/MuteManager.cs | 8 +- CS2-SimpleAdmin/Managers/PlayerManager.cs | 264 ++++++++++--------- CS2-SimpleAdmin/Managers/ServerManager.cs | 10 +- CS2-SimpleAdmin/Models/BanRecord.cs | 45 ++++ CS2-SimpleAdmin/VERSION | 2 +- CS2-SimpleAdmin/Variables.cs | 6 +- CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj | 2 +- 20 files changed, 460 insertions(+), 241 deletions(-) create mode 100644 CS2-SimpleAdmin/Managers/CacheManager.cs create mode 100644 CS2-SimpleAdmin/Models/BanRecord.cs 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 6cb7bf7..c16e37f 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.6a"; + public override string ModuleVersion => "1.7.7-alpha"; public override void Load(bool hotReload) { @@ -40,7 +40,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig + Parallel.ForEach(Helper.GetValidPlayers(), player => { playerManager.LoadPlayerData(player); }); diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj index 78d0918..e3ddb47 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj @@ -10,10 +10,11 @@ - + + diff --git a/CS2-SimpleAdmin/Commands/basebans.cs b/CS2-SimpleAdmin/Commands/basebans.cs index ac6b064..f71e757 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) { @@ -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 98a9e52..88b935b 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -650,7 +650,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; @@ -708,20 +707,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 ---------"); } } @@ -789,6 +788,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) @@ -828,8 +829,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 29b1e26..c30b832 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) { @@ -406,8 +403,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) { @@ -447,8 +443,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) { @@ -498,8 +493,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) { @@ -674,8 +668,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) { @@ -715,8 +708,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) { @@ -766,8 +758,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 c336c81..efa1f9d 100644 --- a/CS2-SimpleAdmin/Commands/funcommands.cs +++ b/CS2-SimpleAdmin/Commands/funcommands.cs @@ -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")] @@ -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) = @@ -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]")] @@ -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>")] @@ -224,6 +220,8 @@ public partial class CS2_SimpleAdmin { 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 08102fa..3be0b36 100644 --- a/CS2-SimpleAdmin/Commands/playercommands.cs +++ b/CS2-SimpleAdmin/Commands/playercommands.cs @@ -28,6 +28,8 @@ public partial class CS2_SimpleAdmin { 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")] @@ -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) = @@ -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) @@ -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) = @@ -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) = @@ -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) = @@ -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) = @@ -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) = @@ -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) @@ -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"; diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs index e8ef371..f4ade1c 100644 --- a/CS2-SimpleAdmin/Events.cs +++ b/CS2-SimpleAdmin/Events.cs @@ -137,8 +137,8 @@ public partial class CS2_SimpleAdmin #if DEBUG Logger.LogCritical("[OnClientConnect]"); #endif - if (!CS2_SimpleAdmin.BannedPlayers.Contains(ipaddress.Split(":")[0])) - return; + if (Config.OtherSettings.BanType == 1 && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0])) + return; Server.NextFrame((() => { @@ -225,13 +225,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) @@ -382,7 +395,7 @@ public partial class CS2_SimpleAdmin if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null) AddTimer(5.0f, () => ReloadAdmins(null)); - AddTimer(1.0f, () => new ServerManager().CheckHibernationStatus()); + AddTimer(1.0f, () => ServerManager.CheckHibernationStatus()); // AddTimer(34, () => // { diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs index 29df5a9..e62ba7d 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; @@ -51,33 +52,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() + public static IReadOnlyList 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 IReadOnlyList 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) // { @@ -800,6 +797,7 @@ internal static class Helper return pluginManager; } + } public static class PluginInfo diff --git a/CS2-SimpleAdmin/Managers/BanManager.cs b/CS2-SimpleAdmin/Managers/BanManager.cs index 4012059..368f8a8 100644 --- a/CS2-SimpleAdmin/Managers/BanManager.cs +++ b/CS2-SimpleAdmin/Managers/BanManager.cs @@ -394,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..c8298d8 --- /dev/null +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -0,0 +1,168 @@ +using System.Collections.Concurrent; +using CS2_SimpleAdmin.Models; +using Dapper; +using Microsoft.Extensions.Logging; + +namespace CS2_SimpleAdmin.Managers; + +internal class CacheManager +{ + private readonly ConcurrentDictionary _banCache = new(); + private readonly ConcurrentDictionary ips, DateTime used_at)> _playerIpsCache = new(); + + private DateTime _lastUpdateTime = DateTime.MinValue; + private bool _isInitialized; + + public async Task InitializeCacheAsync() + { + if (CS2_SimpleAdmin.Database == null) return; + if (!CS2_SimpleAdmin.ServerLoaded) + return; + if (_isInitialized) return; + + try + { + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + var bans = await connection.QueryAsync( + """ + SELECT + id AS Id, + player_name AS PlayerName, + player_steamid AS PlayerSteamId, + player_ip AS PlayerIp, + admin_steamid AS AdminSteamId, + admin_name AS AdminName, + reason AS Reason, + duration AS Duration, + ends AS Ends, + created AS Created, + server_id AS ServerId, + status AS Status, + updated_at AS UpdatedAt + FROM sa_bans + """); + var ipHistory = await connection.QueryAsync<(ulong steamid, string address, DateTime used_at)>( + "SELECT steamid, address, used_at FROM sa_players_ips"); + + + foreach (var ban in bans) + { + _banCache.TryAdd(ban.Id, ban); + } + + foreach (var group in ipHistory.GroupBy(x => x.steamid)) + { + var ips = new HashSet(group.Select(x => x.address)); + var lastUsed = group.Max(x => x.used_at); + _playerIpsCache[group.Key] = (ips, lastUsed); + } + + _lastUpdateTime = DateTime.Now; + _isInitialized = true; + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + } + + public async Task RefreshCacheAsync() + { + if (CS2_SimpleAdmin.Database == null) return; + if (!_isInitialized) return; + + try + { + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + var updatedBans = (await connection.QueryAsync( + "SELECT * FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC", + new { lastUpdate = _lastUpdateTime } + )).ToList(); + + var ipHistory = await connection.QueryAsync<(ulong steamid, string address, DateTime used_at)>( + "SELECT steamid, address, used_at FROM sa_players_ips"); + + // foreach (var group in ipHistory.GroupBy(x => x.steamid)) + // { + // var ips = new HashSet(group.Select(x => x.address)); + // var lastUsed = group.Max(x => x.used_at); + // _playerIpsCache[group.Key] = (ips, lastUsed); + // } + + var groupedData = ipHistory.GroupBy(x => x.steamid).ToList(); + Parallel.ForEach(groupedData, group => + { + var ips = new HashSet(group.Select(x => x.address)); + var lastUsed = group.Max(x => x.used_at); + + _playerIpsCache.AddOrUpdate( + group.Key, + _ => (ips, lastUsed), + (_, existing) => + { + existing.ips.UnionWith(ips); + return (existing.ips, + lastUsed > existing.used_at ? lastUsed : existing.used_at); + }); + }); + + if (updatedBans.Count == 0) + return; + + foreach (var ban in updatedBans) + { + _banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban); + } + + _lastUpdateTime = DateTime.Now; + } + catch (Exception e) + { + // ignored + } + } + + public List GetAllBans() => _banCache.Values.ToList(); + public List GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList(); + public List GetPlayerBansBySteamId(string steamId) => _banCache.Values.Where(b => b.PlayerSteamId == steamId).ToList(); + + + private bool IsIpBanned(string ipAddress) + { + return _banCache.Values.Any(b => + b.Status == "ACTIVE" && + !string.IsNullOrEmpty(b.PlayerIp) && + b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase)); + } + + public bool IsPlayerBanned(string? steamId, string? ipAddress) => + _banCache.Values.Any(b => + b.Status == "ACTIVE" && ( + (steamId != null && + b.PlayerSteamId != null && + b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)) || + (ipAddress != null && + b.PlayerIp != null && + b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase)) + )); + + public bool IsPlayerOrAnyIpBanned(ulong steamId) + { + var steamIdStr = steamId.ToString(); + if (_banCache.Values.Any(b => + b.Status == "ACTIVE" && + b.PlayerSteamId?.Equals(steamIdStr, StringComparison.OrdinalIgnoreCase) == true)) + { + return true; + } + + return _playerIpsCache.TryGetValue(steamId, out var ipList) && ipList.ips.Any(IsIpBanned); + } + + public bool HasIpForPlayer(ulong steamId, string ipAddress) + { + return _playerIpsCache.TryGetValue(steamId, out var ipList) + && ipList.ips.Contains(ipAddress); + } + +} \ 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/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index 487e9dd..8923df3 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; @@ -29,6 +30,7 @@ public class PlayerManager 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,13 +39,32 @@ public class PlayerManager // } var userId = player.UserId.Value; + if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(userId)) + { + Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION); + } + + var steamId64 = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64; + var steamId = steamId64.ToString(); - // 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())) + var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch + { + 0 => // SteamID only check + CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, null), + _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp // SteamID and IP address check + ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(steamId64) // All associated IP addresses + : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, ipAddress) // Only current IP address + }; + + if (isBanned) { // Kick the player if banned - Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + Server.NextFrame(() => + { + if (!player.UserId.HasValue) return; + Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + }); + return; } @@ -52,20 +73,15 @@ public class PlayerManager // Perform asynchronous database operations within a single method Task.Run(async () => { - if (_config.OtherSettings.CheckMultiAccountsByIp) + 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.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 @@ -79,15 +95,41 @@ public class PlayerManager } 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 }); + + 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 = 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 = ipAddress + }); + } } } catch (Exception ex) @@ -99,47 +141,21 @@ public class PlayerManager 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; - 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; CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes = totalMutes; CS2_SimpleAdmin.PlayersInfo[userId].TotalGags = totalGags; CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences = totalSilences; @@ -225,13 +241,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 } && value.Key.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE) - 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); @@ -244,19 +257,29 @@ 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(); + var players = Helper.GetValidPlayers().ToList(); + + var bannedPlayers = players.AsValueEnumerable() + .Where(player => + { + return CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch + { + 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), null), + _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp + ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(player.SteamID) + : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress) + }; + }) + .ToList(); - foreach (var player in players) + foreach (var player in bannedPlayers) { - if (player.IpAddress != null) - onlinePlayers.Add((player.IpAddress, player.SteamID, player.UserId, player.Slot)); + Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + players.Remove(player); } + var onlinePlayers = players.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList(); + try { var expireTasks = new[] @@ -264,6 +287,7 @@ public class PlayerManager CS2_SimpleAdmin.Instance.BanManager.ExpireOldBans(), CS2_SimpleAdmin.Instance.MuteManager.ExpireOldMutes(), CS2_SimpleAdmin.Instance.WarnManager.ExpireOldWarns(), + CS2_SimpleAdmin.Instance.CacheManager.RefreshCacheAsync(), CS2_SimpleAdmin.Instance.PermissionManager.DeleteOldAdmins() }; @@ -273,77 +297,71 @@ public class PlayerManager foreach (var ex in t.Exception.InnerExceptions) { - CS2_SimpleAdmin._logger?.LogError($"Error expiring penalties: {ex.Message}"); + CS2_SimpleAdmin._logger?.LogError($"Error processing players timer tasks: {ex.Message}"); } }); + } catch (Exception ex) { CS2_SimpleAdmin._logger?.LogError("Unexpected error: {exception}", ex.Message); } - CS2_SimpleAdmin.BannedPlayers.Clear(); + if (players.Count == 0 || onlinePlayers.Count == 0) return; - if (onlinePlayers.Count > 0) + try { - try + Task.Run(async () => { - Task.Run(async () => - { - await CS2_SimpleAdmin.Instance.BanManager.CheckOnlinePlayers(onlinePlayers); + // await CS2_SimpleAdmin.Instance.BanManager.CheckOnlinePlayers(onlinePlayers); - if (_config.OtherSettings.TimeMode == 0) - { - await CS2_SimpleAdmin.Instance.MuteManager.CheckOnlineModeMutes(onlinePlayers); - } - }).ContinueWith(t => + if (_config.OtherSettings.TimeMode == 0) { - 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}"); - } - }); - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}"); - } - } - - if (onlinePlayers.Count <= 0) return; - - { - try - { - var penalizedSlots = players - .Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot)) - .Select(player => new - { - Player = player, - IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _), - IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _), - IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _) - }); - - foreach (var entry in penalizedSlots) - { - // If the player is not muted or silenced, set voice flags to normal - if (!entry.IsMuted && !entry.IsSilenced) - { - entry.Player.VoiceFlags = VoiceFlags.Normal; - } + await CS2_SimpleAdmin.Instance.MuteManager.CheckOnlineModeMutes(onlinePlayers); } - - PlayerPenaltyManager.RemoveExpiredPenalties(); - } - catch (Exception ex) + }).ContinueWith(t => { - CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}"); - } + 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}"); + } + }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}"); } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT); + try + { + var penalizedSlots = players + .Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot)) + .Select(player => new + { + Player = player, + IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _), + IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _), + IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _) + }); + + foreach (var entry in penalizedSlots) + { + // If the player is not muted or silenced, set voice flags to normal + if (!entry.IsMuted && !entry.IsSilenced) + { + entry.Player.VoiceFlags = VoiceFlags.Normal; + } + } + + PlayerPenaltyManager.RemoveExpiredPenalties(); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}"); + } + + }, TimerFlags.REPEAT); } } \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/ServerManager.cs b/CS2-SimpleAdmin/Managers/ServerManager.cs index bb14cc1..c1988ce 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; @@ -51,8 +51,6 @@ public class ServerManager var rconPassword = ConVar.Find("rcon_password")!.StringValue; CS2_SimpleAdmin.IpAddress = address; - CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress); - Task.Run(async () => { try @@ -81,6 +79,8 @@ public class ServerManager } CS2_SimpleAdmin.ServerId = serverId; + + CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress); if (CS2_SimpleAdmin.ServerId != null) { @@ -88,6 +88,8 @@ public class ServerManager } CS2_SimpleAdmin.ServerLoaded = true; + + await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync(); } catch (Exception ex) { diff --git a/CS2-SimpleAdmin/Models/BanRecord.cs b/CS2-SimpleAdmin/Models/BanRecord.cs new file mode 100644 index 0000000..818745b --- /dev/null +++ b/CS2-SimpleAdmin/Models/BanRecord.cs @@ -0,0 +1,45 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace CS2_SimpleAdmin.Models; + +public record BanRecord +{ + [Column("id")] + public int Id { get; set; } + + [Column("player_name")] + public string PlayerName { get; set; } + + [Column("player_steamid")] + public string? PlayerSteamId { get; set; } + + [Column("player_ip")] + public string? PlayerIp { get; set; } + + [Column("admin_steamid")] + public string AdminSteamId { get; set; } + + [Column("admin_name")] + public string AdminName { get; set; } + + [Column("reason")] + public string Reason { get; set; } + + [Column("duration")] + public int Duration { get; set; } + + [Column("ends")] + public DateTime? Ends { get; set; } + + [Column("created")] + public DateTime Created { get; set; } + + [Column("server_id")] + public int? ServerId { get; set; } + + [Column("status")] + public string Status { get; set; } + + [Column("updated_at")] + public DateTime UpdatedAt { get; set; } +} diff --git a/CS2-SimpleAdmin/VERSION b/CS2-SimpleAdmin/VERSION index 4e7dcbe..58630bc 100644 --- a/CS2-SimpleAdmin/VERSION +++ b/CS2-SimpleAdmin/VERSION @@ -1 +1 @@ -1.7.6a \ 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 8436e54..c12cbc0 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,4 +68,5 @@ public partial class CS2_SimpleAdmin internal BanManager BanManager = new(Database); internal MuteManager MuteManager = new(Database); internal WarnManager WarnManager = new(Database); + internal readonly CacheManager CacheManager = new(); } \ No newline at end of file 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 @@ - + From d34ca6497081923e4a0fc073db8bfd65a631d30f Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Wed, 21 May 2025 00:38:54 +0200 Subject: [PATCH 3/8] 1.7.7-alpha-fix - Added missing migration - Added missing ignoredips --- CS2-SimpleAdmin/CS2-SimpleAdmin.csproj | 3 +++ .../012_AddUpdatedAtColumnToSaBansTable.sql | 1 + CS2-SimpleAdmin/Managers/CacheManager.cs | 12 ++++++++---- 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 CS2-SimpleAdmin/Database/Migrations/012_AddUpdatedAtColumnToSaBansTable.sql diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj index e3ddb47..85c3e89 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj @@ -26,6 +26,9 @@ PreserveNewest + + PreserveNewest + 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/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index c8298d8..009635b 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -9,6 +9,7 @@ internal class CacheManager { private readonly ConcurrentDictionary _banCache = new(); private readonly ConcurrentDictionary ips, DateTime used_at)> _playerIpsCache = new(); + private readonly HashSet _cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps]; private DateTime _lastUpdateTime = DateTime.MinValue; private bool _isInitialized; @@ -132,7 +133,8 @@ internal class CacheManager return _banCache.Values.Any(b => b.Status == "ACTIVE" && !string.IsNullOrEmpty(b.PlayerIp) && - b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase)); + b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase) && + !_cachedIgnoredIps.Contains(ipAddress)); } public bool IsPlayerBanned(string? steamId, string? ipAddress) => @@ -143,7 +145,8 @@ internal class CacheManager b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)) || (ipAddress != null && b.PlayerIp != null && - b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase)) + b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase) && + !_cachedIgnoredIps.Contains(ipAddress)) )); public bool IsPlayerOrAnyIpBanned(ulong steamId) @@ -155,8 +158,9 @@ internal class CacheManager { return true; } - - return _playerIpsCache.TryGetValue(steamId, out var ipList) && ipList.ips.Any(IsIpBanned); + + return _playerIpsCache.TryGetValue(steamId, out var ipList) && ipList.ips.Any(ip => + !_cachedIgnoredIps.Contains(ip) && IsIpBanned(ip)); } public bool HasIpForPlayer(ulong steamId, string ipAddress) From d75a092047c24f1935a1fa85b8fac25f79f95f46 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Wed, 21 May 2025 03:08:02 +0200 Subject: [PATCH 4/8] 1.7.7-alpha-associated-accounts - Added possibility to view player associated account (by ip) --- CS2-SimpleAdmin/Commands/basecommands.cs | 4 ++ CS2-SimpleAdmin/Config.cs | 11 ++++ .../Extensions/EnumerableExtensions.cs | 12 ++++ CS2-SimpleAdmin/Helper.cs | 3 +- CS2-SimpleAdmin/Managers/CacheManager.cs | 61 ++++++++++++++----- CS2-SimpleAdmin/Managers/PlayerManager.cs | 17 ++++++ CS2-SimpleAdmin/lang/ar.json | 1 + CS2-SimpleAdmin/lang/de.json | 1 + CS2-SimpleAdmin/lang/en.json | 1 + CS2-SimpleAdmin/lang/es.json | 1 + CS2-SimpleAdmin/lang/fa.json | 1 + CS2-SimpleAdmin/lang/fr.json | 1 + CS2-SimpleAdmin/lang/lv.json | 1 + CS2-SimpleAdmin/lang/pl.json | 1 + CS2-SimpleAdmin/lang/pt-BR.json | 1 + CS2-SimpleAdmin/lang/pt-PT.json | 1 + CS2-SimpleAdmin/lang/ru.json | 1 + CS2-SimpleAdmin/lang/tr.json | 1 + CS2-SimpleAdmin/lang/zh-Hans.json | 1 + CS2-SimpleAdminApi/PlayerInfo.cs | 1 + 20 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 CS2-SimpleAdmin/Extensions/EnumerableExtensions.cs diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs index 88b935b..4dc7517 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -509,6 +509,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}\" ---------"); diff --git a/CS2-SimpleAdmin/Config.cs b/CS2-SimpleAdmin/Config.cs index 76b8f30..3b7e041 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 CustomServerCommandData 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/Helper.cs b/CS2-SimpleAdmin/Helper.cs index e62ba7d..479fcf9 100644 --- a/CS2-SimpleAdmin/Helper.cs +++ b/CS2-SimpleAdmin/Helper.cs @@ -669,12 +669,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"; diff --git a/CS2-SimpleAdmin/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index 009635b..90d9254 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -2,14 +2,15 @@ using System.Collections.Concurrent; using CS2_SimpleAdmin.Models; using Dapper; using Microsoft.Extensions.Logging; +using ZLinq; namespace CS2_SimpleAdmin.Managers; internal class CacheManager { private readonly ConcurrentDictionary _banCache = new(); - private readonly ConcurrentDictionary ips, DateTime used_at)> _playerIpsCache = new(); - private readonly HashSet _cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps]; + private readonly ConcurrentDictionary ips, DateTime usedAt, string playerName)> _playerIpsCache = new(); + private HashSet _cachedIgnoredIps; private DateTime _lastUpdateTime = DateTime.MinValue; private bool _isInitialized; @@ -20,6 +21,8 @@ internal class CacheManager if (!CS2_SimpleAdmin.ServerLoaded) return; if (_isInitialized) return; + + _cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps]; try { @@ -42,8 +45,8 @@ internal class CacheManager updated_at AS UpdatedAt FROM sa_bans """); - var ipHistory = await connection.QueryAsync<(ulong steamid, string address, DateTime used_at)>( - "SELECT steamid, address, used_at FROM sa_players_ips"); + var ipHistory = await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>( + "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC"); foreach (var ban in bans) @@ -55,7 +58,10 @@ internal class CacheManager { var ips = new HashSet(group.Select(x => x.address)); var lastUsed = group.Max(x => x.used_at); - _playerIpsCache[group.Key] = (ips, lastUsed); + var playerName = group.FirstOrDefault(x => !string.IsNullOrEmpty(x.name)).name + ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; + + _playerIpsCache[group.Key] = (ips, lastUsed, playerName); } _lastUpdateTime = DateTime.Now; @@ -80,8 +86,8 @@ internal class CacheManager new { lastUpdate = _lastUpdateTime } )).ToList(); - var ipHistory = await connection.QueryAsync<(ulong steamid, string address, DateTime used_at)>( - "SELECT steamid, address, used_at FROM sa_players_ips"); + var ipHistory = await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>( + "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC"); // foreach (var group in ipHistory.GroupBy(x => x.steamid)) // { @@ -90,20 +96,39 @@ internal class CacheManager // _playerIpsCache[group.Key] = (ips, lastUsed); // } - var groupedData = ipHistory.GroupBy(x => x.steamid).ToList(); + var groupedData = ipHistory + .GroupBy(x => x.steamid) + .AsParallel() + .ToList(); + Parallel.ForEach(groupedData, group => { - var ips = new HashSet(group.Select(x => x.address)); - var lastUsed = group.Max(x => x.used_at); + var ips = new HashSet( + group.Select(x => x.address), + StringComparer.OrdinalIgnoreCase + ); + var lastUsed = group.Max(x => x.used_at); + var playerName = group + .OrderByDescending(x => x.used_at) // Prefer newer records + .Select(x => x.name) + .FirstOrDefault(name => !string.IsNullOrEmpty(name)) + ?? CS2_SimpleAdmin._localizer?["sa_unknown"] + ?? "Unknown"; + _playerIpsCache.AddOrUpdate( group.Key, - _ => (ips, lastUsed), + // Add new entry + _ => (ips, lastUsed, playerName), + // Update existing (_, existing) => { - existing.ips.UnionWith(ips); - return (existing.ips, - lastUsed > existing.used_at ? lastUsed : existing.used_at); + existing.ips.UnionWith(ips); + return ( + existing.ips, + lastUsed > existing.usedAt ? lastUsed : existing.usedAt, + string.IsNullOrEmpty(existing.playerName) ? playerName : existing.playerName + ); }); }); @@ -126,7 +151,13 @@ internal class CacheManager public List GetAllBans() => _banCache.Values.ToList(); public List GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList(); public List GetPlayerBansBySteamId(string steamId) => _banCache.Values.Where(b => b.PlayerSteamId == steamId).ToList(); - + public List<(ulong SteamId, string PlayerName)> GetAccountsByIp(string ipAddress) + { + return _playerIpsCache.AsValueEnumerable() + .Where(kvp => kvp.Value.ips.Contains(ipAddress)) + .Select(kvp => (kvp.Key, kvp.Value.playerName)) + .ToList(); + } private bool IsIpBanned(string ipAddress) { diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index 8923df3..19d7288 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -137,6 +137,10 @@ 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); } try @@ -204,6 +208,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() @@ -212,6 +218,7 @@ public class PlayerManager p.Connected == PlayerConnectedState.PlayerConnected && !CS2_SimpleAdmin.AdminDisabledJoinComms.Contains(p.SteamID))) { if (CS2_SimpleAdmin._localizer != null && admin != player) + { admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_penalty_info", player.PlayerName, CS2_SimpleAdmin.PlayersInfo[userId].TotalBans, @@ -220,6 +227,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})")) + ); + } + } } }); } 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/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; } } From 676a18d9b421a994e19942dd40d5cd9dbdac6a0e Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Wed, 21 May 2025 03:13:05 +0200 Subject: [PATCH 5/8] 1.7.7-alpha-associated-accounts (missing migration) --- CS2-SimpleAdmin/CS2-SimpleAdmin.csproj | 3 +++ .../Migrations/013_AddNameColumnToSaPlayerIpsTable.sql | 1 + 2 files changed, 4 insertions(+) create mode 100644 CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj index 85c3e89..89e6e97 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj @@ -29,6 +29,9 @@ PreserveNewest + + PreserveNewest + diff --git a/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql b/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql new file mode 100644 index 0000000..5ca368a --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql @@ -0,0 +1 @@ +ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`; \ No newline at end of file From f654d6b0855b93832c948646bd5594976d1bcd16 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Wed, 21 May 2025 13:11:01 +0200 Subject: [PATCH 6/8] 1.7.7-alpha-connection-fix - Fixed lag when player connecting - Added command to force refresh ban cache (css_reloadbans) --- CS2-SimpleAdmin/CS2-SimpleAdmin.cs | 4 +- CS2-SimpleAdmin/Commands/RegisterCommands.cs | 2 + CS2-SimpleAdmin/Commands/basecommands.cs | 10 ++++ CS2-SimpleAdmin/Managers/CacheManager.cs | 50 ++++++++++++-------- CS2-SimpleAdmin/Managers/PlayerManager.cs | 43 ++++++++--------- 5 files changed, 64 insertions(+), 45 deletions(-) diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs index c16e37f..d974dbc 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs @@ -40,10 +40,10 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig + foreach (var player in Helper.GetValidPlayers()) { playerManager.LoadPlayerData(player); - }); + }; }); } _cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid(GameData.GetSignature("CBasePlayerController_SetPawn")); 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/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs index 4dc7517..26ac7d5 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -386,6 +386,16 @@ 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) { diff --git a/CS2-SimpleAdmin/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index 90d9254..194d02e 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -1,7 +1,6 @@ using System.Collections.Concurrent; using CS2_SimpleAdmin.Models; using Dapper; -using Microsoft.Extensions.Logging; using ZLinq; namespace CS2_SimpleAdmin.Managers; @@ -14,16 +13,15 @@ internal class CacheManager private DateTime _lastUpdateTime = DateTime.MinValue; private bool _isInitialized; - + public async Task InitializeCacheAsync() { if (CS2_SimpleAdmin.Database == null) return; - if (!CS2_SimpleAdmin.ServerLoaded) - return; + if (!CS2_SimpleAdmin.ServerLoaded) return; if (_isInitialized) return; _cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps]; - + try { await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); @@ -44,21 +42,22 @@ internal class CacheManager status AS Status, updated_at AS UpdatedAt FROM sa_bans - """); - var ipHistory = await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>( - "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC"); + """); + var ipHistory = + await connection.QueryAsync<(ulong steamid, string? name, string 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.GroupBy(x => x.steamid)) { var ips = new HashSet(group.Select(x => x.address)); var lastUsed = group.Max(x => x.used_at); - var playerName = group.FirstOrDefault(x => !string.IsNullOrEmpty(x.name)).name + var playerName = group.FirstOrDefault(x => !string.IsNullOrEmpty(x.name)).name ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; _playerIpsCache[group.Key] = (ips, lastUsed, playerName); @@ -72,6 +71,18 @@ internal class CacheManager 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() { @@ -84,10 +95,9 @@ internal class CacheManager var updatedBans = (await connection.QueryAsync( "SELECT * FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC", new { lastUpdate = _lastUpdateTime } - )).ToList(); - - var ipHistory = await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>( - "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC"); + )).ToList().AsValueEnumerable(); + var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>( + "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC LIMIT 500")).ToList(); // foreach (var group in ipHistory.GroupBy(x => x.steamid)) // { @@ -95,13 +105,12 @@ internal class CacheManager // var lastUsed = group.Max(x => x.used_at); // _playerIpsCache[group.Key] = (ips, lastUsed); // } - - var groupedData = ipHistory + + var groupedData = ipHistory.AsValueEnumerable() .GroupBy(x => x.steamid) - .AsParallel() .ToList(); - Parallel.ForEach(groupedData, group => + groupedData.ForEach(group => { var ips = new HashSet( group.Select(x => x.address), @@ -132,7 +141,7 @@ internal class CacheManager }); }); - if (updatedBans.Count == 0) + if (updatedBans.Count() == 0) return; foreach (var ban in updatedBans) @@ -199,5 +208,4 @@ internal class CacheManager return _playerIpsCache.TryGetValue(steamId, out var ipList) && ipList.ips.Contains(ipAddress); } - } \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index 19d7288..d3d0c16 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -46,33 +46,32 @@ public class PlayerManager var steamId64 = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64; var steamId = steamId64.ToString(); - - var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch - { - 0 => // SteamID only check - CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, null), - _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp // SteamID and IP address check - ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(steamId64) // All associated IP addresses - : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, ipAddress) // Only current IP address - }; - - if (isBanned) - { - // Kick the player if banned - Server.NextFrame(() => - { - if (!player.UserId.HasValue) return; - Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); - }); - - return; - } - + if (CS2_SimpleAdmin.Database == null) return; // Perform asynchronous database operations within a single method Task.Run(async () => { + var isBanned = 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) + : 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 From 3ab63c05db9085e03e33eed2061167e3a46125c9 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Fri, 23 May 2025 03:28:10 +0200 Subject: [PATCH 7/8] 1.7.7-alpha-small-optimizations - Clear bans cache on plugin reload - Changed ip history to int --- CS2-SimpleAdmin/CS2-SimpleAdmin.cs | 8 + CS2-SimpleAdmin/Commands/basecommands.cs | 2 +- .../013_AddNameColumnToSaPlayerIpsTable.sql | 2 + CS2-SimpleAdmin/Events.cs | 2 +- CS2-SimpleAdmin/Helper.cs | 36 ++- CS2-SimpleAdmin/Managers/CacheManager.cs | 249 ++++++++++++------ CS2-SimpleAdmin/Managers/PlayerManager.cs | 143 +++++----- CS2-SimpleAdmin/Managers/ServerManager.cs | 4 +- CS2-SimpleAdmin/Models/BanRecord.cs | 2 +- CS2-SimpleAdmin/Models/IpRecord.cs | 3 + CS2-SimpleAdmin/Variables.cs | 2 +- 11 files changed, 284 insertions(+), 169 deletions(-) create mode 100644 CS2-SimpleAdmin/Models/IpRecord.cs diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs index d974dbc..2a9a98e 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs @@ -31,6 +31,8 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs index 479fcf9..a17ec62 100644 --- a/CS2-SimpleAdmin/Helper.cs +++ b/CS2-SimpleAdmin/Helper.cs @@ -62,14 +62,14 @@ internal static class Helper return GetValidPlayers().FirstOrDefault(x => x.IpAddress != null && x.IpAddress.Split(":")[0].Equals(ipAddress)); } - public static IReadOnlyList GetValidPlayers() + public static List GetValidPlayers() { return Utilities.GetPlayers().AsValueEnumerable() .Where(p => p is { IsValid: true, IsBot: false, Connected: PlayerConnectedState.PlayerConnected }) .ToList(); } - public static IReadOnlyList GetValidPlayersWithBots() + public static List GetValidPlayersWithBots() { return Utilities.GetPlayers().AsValueEnumerable() .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList(); @@ -970,3 +970,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(); + } +} diff --git a/CS2-SimpleAdmin/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index 194d02e..11fb12e 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -5,25 +5,29 @@ using ZLinq; namespace CS2_SimpleAdmin.Managers; -internal class CacheManager +internal class CacheManager: IDisposable { - private readonly ConcurrentDictionary _banCache = new(); - private readonly ConcurrentDictionary ips, DateTime usedAt, string playerName)> _playerIpsCache = new(); - private HashSet _cachedIgnoredIps; + private readonly ConcurrentDictionary _banCache = []; + 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; - - _cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps]; - + try { + Clear(); + _cachedIgnoredIps = new HashSet( + CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps + .Select(IpHelper.IpToUint)); + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); var bans = await connection.QueryAsync( """ @@ -44,26 +48,49 @@ internal class CacheManager FROM sa_bans """); var ipHistory = - await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>( + 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.GroupBy(x => x.steamid)) + foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid)) { - var ips = new HashSet(group.Select(x => x.address)); - var lastUsed = group.Max(x => x.used_at); - var playerName = group.FirstOrDefault(x => !string.IsNullOrEmpty(x.name)).name - ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; + 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[group.Key] = (ips, lastUsed, playerName); + _playerIpsCache.AddOrUpdate( + group.Key, + _ => ipSet, + (_, existingSet) => + { + foreach (var ip in ipSet) + { + existingSet.Remove(ip); + existingSet.Add(ip); + } + + return existingSet; + }); } - _lastUpdateTime = DateTime.Now; + _lastUpdateTime = DateTime.Now.AddSeconds(-1); _isInitialized = true; } catch (Exception e) @@ -95,53 +122,44 @@ internal class CacheManager var updatedBans = (await connection.QueryAsync( "SELECT * FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC", new { lastUpdate = _lastUpdateTime } - )).ToList().AsValueEnumerable(); - var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>( - "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC LIMIT 500")).ToList(); + )).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 WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300", new {lastUpdate = _lastUpdateTime})).ToList(); - // foreach (var group in ipHistory.GroupBy(x => x.steamid)) - // { - // var ips = new HashSet(group.Select(x => x.address)); - // var lastUsed = group.Max(x => x.used_at); - // _playerIpsCache[group.Key] = (ips, lastUsed); - // } - - var groupedData = ipHistory.AsValueEnumerable() - .GroupBy(x => x.steamid) - .ToList(); - - groupedData.ForEach(group => + foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid)) { - var ips = new HashSet( - group.Select(x => x.address), - StringComparer.OrdinalIgnoreCase + 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() ); - - var lastUsed = group.Max(x => x.used_at); - var playerName = group - .OrderByDescending(x => x.used_at) // Prefer newer records - .Select(x => x.name) - .FirstOrDefault(name => !string.IsNullOrEmpty(name)) - ?? CS2_SimpleAdmin._localizer?["sa_unknown"] - ?? "Unknown"; _playerIpsCache.AddOrUpdate( group.Key, - // Add new entry - _ => (ips, lastUsed, playerName), - // Update existing - (_, existing) => + _ => ipSet, + (_, existingSet) => { - existing.ips.UnionWith(ips); - return ( - existing.ips, - lastUsed > existing.usedAt ? lastUsed : existing.usedAt, - string.IsNullOrEmpty(existing.playerName) ? playerName : existing.playerName - ); + foreach (var newEntry in ipSet) + { + existingSet.Remove(newEntry); + existingSet.Add(newEntry); + } + return existingSet; }); - }); - - if (updatedBans.Count() == 0) + } + + if (updatedBans.Count == 0) return; foreach (var ban in updatedBans) @@ -149,7 +167,7 @@ internal class CacheManager _banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban); } - _lastUpdateTime = DateTime.Now; + _lastUpdateTime = DateTime.Now.AddSeconds(-1); } catch (Exception e) { @@ -160,52 +178,119 @@ internal class CacheManager public List GetAllBans() => _banCache.Values.ToList(); public List GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList(); public List GetPlayerBansBySteamId(string steamId) => _banCache.Values.Where(b => b.PlayerSteamId == steamId).ToList(); - public List<(ulong SteamId, string PlayerName)> GetAccountsByIp(string ipAddress) + public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress) { + var ipAsUint = IpHelper.IpToUint(ipAddress); + return _playerIpsCache.AsValueEnumerable() - .Where(kvp => kvp.Value.ips.Contains(ipAddress)) - .Select(kvp => (kvp.Key, kvp.Value.playerName)) + .SelectMany(kvp => kvp.Value + .Where(entry => entry.Ip == ipAsUint) + .Select(entry => (kvp.Key, entry.UsedAt, entry.PlayerName))) .ToList(); } private bool IsIpBanned(string ipAddress) { - return _banCache.Values.Any(b => - b.Status == "ACTIVE" && - !string.IsNullOrEmpty(b.PlayerIp) && - b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase) && - !_cachedIgnoredIps.Contains(ipAddress)); + var ipUInt = IpHelper.IpToUint(ipAddress); + + return _banCache.Values.Any(b => + b is { Status: "ACTIVE", PlayerIp: not null } && + IpHelper.IpToUint(b.PlayerIp) == ipUInt && + !_cachedIgnoredIps.Contains(ipUInt)); } - public bool IsPlayerBanned(string? steamId, string? ipAddress) => - _banCache.Values.Any(b => - b.Status == "ACTIVE" && ( - (steamId != null && - b.PlayerSteamId != null && - b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)) || - (ipAddress != null && - b.PlayerIp != null && - b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase) && - !_cachedIgnoredIps.Contains(ipAddress)) - )); + public bool IsPlayerBanned(string? steamId, string? ipAddress) + { + if (ipAddress == null) + return _banCache.Values.Any(b => + b.Status == "ACTIVE" && + steamId != null && + b.PlayerSteamId != null && + b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)); + + if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)) + return false; + + return _banCache.Values.Any(b => + b is { Status: "ACTIVE", PlayerIp: not null } && + ( + (steamId != null && + b.PlayerSteamId != null && + b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)) + || + (IpHelper.TryConvertIpToUint(b.PlayerIp, out var bIpUint) && + bIpUint == ipUInt && + !_cachedIgnoredIps.Contains(ipUInt)) + ) + ); + } - public bool IsPlayerOrAnyIpBanned(ulong steamId) + public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress) { var steamIdStr = steamId.ToString(); - if (_banCache.Values.Any(b => + if (_banCache.Values.Any(b => b.Status == "ACTIVE" && b.PlayerSteamId?.Equals(steamIdStr, StringComparison.OrdinalIgnoreCase) == true)) { return true; } - - return _playerIpsCache.TryGetValue(steamId, out var ipList) && ipList.ips.Any(ip => - !_cachedIgnoredIps.Contains(ip) && IsIpBanned(ip)); + + 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); + + ipData.Add(new IpRecord( + ipAsUint, + now.AddSeconds(-2), + CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" + )); + } + + return ipData.Any(x => + x.UsedAt >= cutoff && + !_cachedIgnoredIps.Contains(x.Ip) && + _banCache.Values.Any(b => + b is { Status: "ACTIVE", PlayerIp: not null } && + IpHelper.TryConvertIpToUint(b.PlayerIp, out var banIpUint) && + banIpUint == x.Ip + )); } public bool HasIpForPlayer(ulong steamId, string ipAddress) { - return _playerIpsCache.TryGetValue(steamId, out var ipList) - && ipList.ips.Contains(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() + { + _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/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index d3d0c16..95f85fe 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -27,7 +27,6 @@ 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); @@ -52,11 +51,11 @@ public class PlayerManager // Perform asynchronous database operations within a single method Task.Run(async () => { - var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch + 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) + ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(steamId64, ipAddress) : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, ipAddress) }; @@ -76,7 +75,7 @@ public class PlayerManager { try { - if (CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer( + 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(); @@ -89,7 +88,7 @@ public class PlayerManager await connection.ExecuteAsync(updateQuery, new { SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, - IPAddress = ipAddress + IPAddress = IpHelper.IpToUint(ipAddress) }); } else @@ -101,7 +100,7 @@ public class PlayerManager var recordExists = await connection.ExecuteScalarAsync(selectQuery, new { SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, - IPAddress = ipAddress + IPAddress = IpHelper.IpToUint(ipAddress) }); if (recordExists > 0) @@ -114,7 +113,7 @@ public class PlayerManager await connection.ExecuteAsync(updateQuery, new { SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, - IPAddress = ipAddress + IPAddress = IpHelper.IpToUint(ipAddress) }); } else @@ -126,7 +125,7 @@ public class PlayerManager await connection.ExecuteAsync(insertQuery, new { SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, - IPAddress = ipAddress + IPAddress = IpHelper.IpToUint(ipAddress) }); } } @@ -139,10 +138,10 @@ public class PlayerManager // Get all accounts associated to the player (ip address) CS2_SimpleAdmin.PlayersInfo[userId].AccountsAssociated = - CS2_SimpleAdmin.Instance.CacheManager.GetAccountsByIp(ipAddress); + CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable().Select(x => (x.SteamId, x.PlayerName)).ToList() ?? []; } - try + try { // var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 // ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned( @@ -158,7 +157,7 @@ public class PlayerManager var (totalMutes, totalGags, totalSilences) = await CS2_SimpleAdmin.Instance.MuteManager.GetPlayerMutes(CS2_SimpleAdmin.PlayersInfo[userId]); - CS2_SimpleAdmin.PlayersInfo[userId].TotalBans = CS2_SimpleAdmin.Instance.CacheManager.GetPlayerBansBySteamId(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()).Count; + 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; @@ -273,85 +272,71 @@ public class PlayerManager if (CS2_SimpleAdmin.Database == null) return; - var players = Helper.GetValidPlayers().ToList(); - - var bannedPlayers = players.AsValueEnumerable() - .Where(player => + var tempPlayers = Helper.GetValidPlayers() + .Select(p => new { - return CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch - { - 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), null), - _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp - ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(player.SteamID) - : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress) - }; + p.SteamID, p.IpAddress, p.UserId, p.Slot, }) .ToList(); - - foreach (var player in bannedPlayers) - { - Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); - players.Remove(player); - } - var onlinePlayers = players.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList(); - - try + _ = Task.Run(async () => { - var expireTasks = new[] + try { - CS2_SimpleAdmin.Instance.BanManager.ExpireOldBans(), - CS2_SimpleAdmin.Instance.MuteManager.ExpireOldMutes(), - CS2_SimpleAdmin.Instance.WarnManager.ExpireOldWarns(), - CS2_SimpleAdmin.Instance.CacheManager.RefreshCacheAsync(), - 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) + var expireTasks = new Task[] { - CS2_SimpleAdmin._logger?.LogError($"Error processing players timer tasks: {ex.Message}"); - } - }); + 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() + }; - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError("Unexpected error: {exception}", ex.Message); - } + await Task.WhenAll(expireTasks); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError($"Error processing players timer tasks: {ex.Message}"); + + 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.Config.OtherSettings.CheckMultiAccountsByIp + ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(player.SteamID, player.IpAddress?.Split(":")[0]) + : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress?.Split(":")[0]) + }; + }) + .ToList(); + + foreach (var player in bannedPlayers) + { + 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); + } + }); - if (players.Count == 0 || onlinePlayers.Count == 0) return; - - try - { - Task.Run(async () => - { - // await CS2_SimpleAdmin.Instance.BanManager.CheckOnlinePlayers(onlinePlayers); - - 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}"); - } - }); - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}"); - } - try { + var players = Helper.GetValidPlayers(); var penalizedSlots = players .Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot)) .Select(player => new diff --git a/CS2-SimpleAdmin/Managers/ServerManager.cs b/CS2-SimpleAdmin/Managers/ServerManager.cs index c1988ce..66131a0 100644 --- a/CS2-SimpleAdmin/Managers/ServerManager.cs +++ b/CS2-SimpleAdmin/Managers/ServerManager.cs @@ -88,8 +88,8 @@ public class ServerManager } CS2_SimpleAdmin.ServerLoaded = true; - - await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync(); + if (CS2_SimpleAdmin.Instance.CacheManager != null) + await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync(); } catch (Exception ex) { diff --git a/CS2-SimpleAdmin/Models/BanRecord.cs b/CS2-SimpleAdmin/Models/BanRecord.cs index 818745b..dd7712a 100644 --- a/CS2-SimpleAdmin/Models/BanRecord.cs +++ b/CS2-SimpleAdmin/Models/BanRecord.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace CS2_SimpleAdmin.Models; -public record BanRecord +public record struct BanRecord { [Column("id")] public int Id { 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/Variables.cs b/CS2-SimpleAdmin/Variables.cs index c12cbc0..39c53a3 100644 --- a/CS2-SimpleAdmin/Variables.cs +++ b/CS2-SimpleAdmin/Variables.cs @@ -68,5 +68,5 @@ public partial class CS2_SimpleAdmin internal BanManager BanManager = new(Database); internal MuteManager MuteManager = new(Database); internal WarnManager WarnManager = new(Database); - internal readonly CacheManager CacheManager = new(); + internal CacheManager? CacheManager = new(); } \ No newline at end of file From b97426313ba6ae8447247d73379fc0d82fed3ea9 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Sun, 25 May 2025 01:00:14 +0200 Subject: [PATCH 8/8] 1.7.7-alpha - Fixed steamid only bans - Added missing multiservermode - Better caching --- .../013_AddNameColumnToSaPlayerIpsTable.sql | 3 +- CS2-SimpleAdmin/Events.cs | 5 +- CS2-SimpleAdmin/Helper.cs | 2 +- CS2-SimpleAdmin/Managers/CacheManager.cs | 225 +++++++++++++----- CS2-SimpleAdmin/Managers/PlayerManager.cs | 5 +- CS2-SimpleAdmin/Models/BanRecord.cs | 29 +-- 6 files changed, 173 insertions(+), 96 deletions(-) diff --git a/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql b/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql index 0c6bd63..17c9647 100644 --- a/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql +++ b/CS2-SimpleAdmin/Database/Migrations/013_AddNameColumnToSaPlayerIpsTable.sql @@ -1,3 +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`; \ No newline at end of file +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 d34129a..d872795 100644 --- a/CS2-SimpleAdmin/Events.cs +++ b/CS2-SimpleAdmin/Events.cs @@ -137,7 +137,10 @@ public partial class CS2_SimpleAdmin #if DEBUG Logger.LogCritical("[OnClientConnect]"); #endif - if (Config.OtherSettings.BanType == 1 && Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0])) + if (Config.OtherSettings.BanType == 0) + return; + + if (Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0])) return; Server.NextFrame((() => diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs index a17ec62..8b81c8a 100644 --- a/CS2-SimpleAdmin/Helper.cs +++ b/CS2-SimpleAdmin/Helper.cs @@ -1001,4 +1001,4 @@ public static class IpHelper 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/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index 11fb12e..be097d0 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -8,6 +8,9 @@ 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 = []; @@ -29,24 +32,34 @@ internal class CacheManager: IDisposable .Select(IpHelper.IpToUint)); await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); - var bans = await connection.QueryAsync( - """ - SELECT + List bans; + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + bans = (await connection.QueryAsync( + """ + SELECT id AS Id, - player_name AS PlayerName, player_steamid AS PlayerSteamId, player_ip AS PlayerIp, - admin_steamid AS AdminSteamId, - admin_name AS AdminName, - reason AS Reason, - duration AS Duration, - ends AS Ends, - created AS Created, - server_id AS ServerId, - status AS Status, - updated_at AS UpdatedAt - FROM sa_bans - """); + 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"); @@ -89,7 +102,9 @@ internal class CacheManager: IDisposable return existingSet; }); } - + + RebuildIndexes(); + _lastUpdateTime = DateTime.Now.AddSeconds(-1); _isInitialized = true; } @@ -119,10 +134,61 @@ internal class CacheManager: IDisposable try { await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); - var updatedBans = (await connection.QueryAsync( - "SELECT * FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC", - new { lastUpdate = _lastUpdateTime } - )).ToList(); + 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(); @@ -166,7 +232,8 @@ internal class CacheManager: IDisposable { _banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban); } - + + RebuildIndexes(); _lastUpdateTime = DateTime.Now.AddSeconds(-1); } catch (Exception e) @@ -174,10 +241,48 @@ internal class CacheManager: IDisposable // 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) => _banCache.Values.Where(b => b.PlayerSteamId == steamId).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); @@ -191,49 +296,37 @@ internal class CacheManager: IDisposable private bool IsIpBanned(string ipAddress) { + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false; var ipUInt = IpHelper.IpToUint(ipAddress); - - return _banCache.Values.Any(b => - b is { Status: "ACTIVE", PlayerIp: not null } && - IpHelper.IpToUint(b.PlayerIp) == ipUInt && - !_cachedIgnoredIps.Contains(ipUInt)); + 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 _banCache.Values.Any(b => - b.Status == "ACTIVE" && - steamId != null && - b.PlayerSteamId != null && - b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)); + return false; if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)) return false; - return _banCache.Values.Any(b => - b is { Status: "ACTIVE", PlayerIp: not null } && - ( - (steamId != null && - b.PlayerSteamId != null && - b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)) - || - (IpHelper.TryConvertIpToUint(b.PlayerIp, out var bIpUint) && - bIpUint == ipUInt && - !_cachedIgnoredIps.Contains(ipUInt)) - ) - ); + return !_cachedIgnoredIps.Contains(ipUInt) && + _ipIndex.ContainsKey(ipUInt); } public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress) { var steamIdStr = steamId.ToString(); - if (_banCache.Values.Any(b => - b.Status == "ACTIVE" && - b.PlayerSteamId?.Equals(steamIdStr, StringComparison.OrdinalIgnoreCase) == true)) - { + + 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; @@ -245,21 +338,26 @@ internal class CacheManager: IDisposable { var ipAsUint = IpHelper.IpToUint(ipAddress); - ipData.Add(new IpRecord( - ipAsUint, - now.AddSeconds(-2), - CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" - )); + if (!_cachedIgnoredIps.Contains(ipAsUint)) + { + ipData.Add(new IpRecord( + ipAsUint, + now.AddSeconds(-2), // artificially recent + CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" + )); + } } - return ipData.Any(x => - x.UsedAt >= cutoff && - !_cachedIgnoredIps.Contains(x.Ip) && - _banCache.Values.Any(b => - b is { Status: "ACTIVE", PlayerIp: not null } && - IpHelper.TryConvertIpToUint(b.PlayerIp, out var banIpUint) && - banIpUint == x.Ip - )); + 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) @@ -273,6 +371,9 @@ internal class CacheManager: IDisposable private void Clear() { + _steamIdIndex.Clear(); + _ipIndex.Clear(); + _banCache.Clear(); _playerIpsCache.Clear(); _cachedIgnoredIps.Clear(); diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index 95f85fe..7b4c0de 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -313,9 +313,8 @@ public class PlayerManager 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.Config.OtherSettings.CheckMultiAccountsByIp - ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(player.SteamID, player.IpAddress?.Split(":")[0]) - : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress?.Split(":")[0]) + _ => + CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress?.Split(":")[0]) }; }) .ToList(); diff --git a/CS2-SimpleAdmin/Models/BanRecord.cs b/CS2-SimpleAdmin/Models/BanRecord.cs index dd7712a..479a26a 100644 --- a/CS2-SimpleAdmin/Models/BanRecord.cs +++ b/CS2-SimpleAdmin/Models/BanRecord.cs @@ -2,44 +2,17 @@ using System.ComponentModel.DataAnnotations.Schema; namespace CS2_SimpleAdmin.Models; -public record struct BanRecord +public record BanRecord { [Column("id")] public int Id { get; set; } - [Column("player_name")] - public string PlayerName { get; set; } - [Column("player_steamid")] public string? PlayerSteamId { get; set; } [Column("player_ip")] public string? PlayerIp { get; set; } - [Column("admin_steamid")] - public string AdminSteamId { get; set; } - - [Column("admin_name")] - public string AdminName { get; set; } - - [Column("reason")] - public string Reason { get; set; } - - [Column("duration")] - public int Duration { get; set; } - - [Column("ends")] - public DateTime? Ends { get; set; } - - [Column("created")] - public DateTime Created { get; set; } - - [Column("server_id")] - public int? ServerId { get; set; } - [Column("status")] public string Status { get; set; } - - [Column("updated_at")] - public DateTime UpdatedAt { get; set; } }