diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1b0b19..c2434cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 10.0.x + dotnet-version: 8.0.x - name: Get Version id: get_version @@ -67,12 +67,28 @@ jobs: - name: Zip Main Build Output run: zip -r CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip ${{ env.OUTPUT_PATH }} + - name: Extract & Zip StatusBlocker Linux + run: | + mkdir -p statusblocker-linux && + tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-linux.tar.gz -C statusblocker-linux && + cd statusblocker-linux && + zip -r ../StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip ./* + + - name: Extract & Zip StatusBlocker Windows + run: | + mkdir -p statusblocker-windows && + tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-windows.tar.gz -C statusblocker-windows && + cd statusblocker-windows && + zip -r ../StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip ./* + - name: Upload all artifacts uses: actions/upload-artifact@v4 with: name: CS2-SimpleAdmin-Build-Artifacts path: | CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip + StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip + StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip publish: needs: build @@ -93,6 +109,8 @@ jobs: with: artifacts: | CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip + StatusBlocker-linux-${{ needs.build.outputs.build_version }}.zip + StatusBlocker-windows-${{ needs.build.outputs.build_version }}.zip name: "CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}" tag: "build-${{ needs.build.outputs.build_version }}" body: | @@ -103,5 +121,8 @@ jobs: After the first launch, configure the plugin using the JSON config file at: addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json + - StatusBlocker: + Place the plugin files directly into the addons directory. + This plugin is a Metamod module for the StealthModule and does not require a subfolder. Remember to restart or reload your game server after installing and configuring the plugins. diff --git a/.gitignore b/.gitignore index 1850b2f..28e335d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,3 @@ Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSetti CS2-SimpleAdmin_BanSoundModule — kopia *.user CLAUDE.md -/Modules/CS2-SimpleAdmin_BanSoundModule -/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs index a2a82ee..769fd51 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs @@ -14,19 +14,20 @@ using MySqlConnector; namespace CS2_SimpleAdmin; -[MinimumApiVersion(369)] +[MinimumApiVersion(300)] public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig { internal static CS2_SimpleAdmin Instance { get; private set; } = new(); public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)"); public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)"; - public override string ModuleAuthor => "daffyy"; - public override string ModuleVersion => "1.8.2b"; + public override string ModuleAuthor => "daffyy & Dliix66"; + public override string ModuleVersion => "1.7.8-beta-5"; public override void Load(bool hotReload) { Instance = this; + if (hotReload) { ServerLoaded = false; @@ -46,7 +47,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig p.IsValid && p is { Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToArray()) + foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray()) { if (!player.IsBot) PlayerManager.LoadPlayerData(player, true); @@ -55,12 +56,14 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig(GameData.GetSignature("CBasePlayerController_SetPawn")); SimpleAdminApi = new Api.CS2_SimpleAdminApi(); Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi); - + PlayersTimer?.Kill(); PlayersTimer = null; PlayerManager.CheckPlayersTimer(); @@ -81,9 +84,9 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig ReloadAdmins(null)); RegisterEvents(); AddTimer(0.5f, RegisterCommands.InitializeCommands); - AddTimer(3.0f, () => ReloadAdmins(null)); if (!CoreConfig.UnlockConCommands) { @@ -93,11 +96,20 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig - net10.0 + net8.0 CS2_SimpleAdmin enable enable @@ -19,16 +19,16 @@ - + none runtime compile; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + diff --git a/CS2-SimpleAdmin/Commands/basebans.cs b/CS2-SimpleAdmin/Commands/basebans.cs index f0a20ba..761ecbd 100644 --- a/CS2-SimpleAdmin/Commands/basebans.cs +++ b/CS2-SimpleAdmin/Commands/basebans.cs @@ -27,7 +27,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList(); if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) { @@ -373,7 +373,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.Connected && !player.IsHLTV).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList(); if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) { diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs index 9513cbc..8d95c33 100644 --- a/CS2-SimpleAdmin/Commands/basecommands.cs +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -501,13 +501,13 @@ public partial class CS2_SimpleAdmin Task.Run(async () => { - await PermissionManager.CreateGroupsJsonFile(); + await PermissionManager.CrateGroupsJsonFile(); await PermissionManager.CreateAdminsJsonFile(); var adminsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/admins.json"); var groupsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/groups.json"); - await Server.NextWorldUpdateAsync(() => + await Server.NextWorldUpdateAsync(() => { AddTimer(1, () => { @@ -521,7 +521,7 @@ public partial class CS2_SimpleAdmin _logger?.LogInformation("Loaded admins!"); }); }); - }); + }); //_ = _adminManager.GiveAllGroupsFlags(); //_ = _adminManager.GiveAllFlags(); diff --git a/CS2-SimpleAdmin/Commands/playercommands.cs b/CS2-SimpleAdmin/Commands/playercommands.cs index 2330ccf..698c3f3 100644 --- a/CS2-SimpleAdmin/Commands/playercommands.cs +++ b/CS2-SimpleAdmin/Commands/playercommands.cs @@ -44,7 +44,7 @@ public partial class CS2_SimpleAdmin /// Optional command info for logging. internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) { - if (!player.IsValid || player.Connected != PlayerConnectedState.Connected) return; + if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return; if (!caller.CanTarget(player)) return; // Set default caller name if not provided @@ -93,7 +93,7 @@ public partial class CS2_SimpleAdmin playersToTarget.ForEach(player => { - if (player.Connected != PlayerConnectedState.Connected) + if (player.Connected != PlayerConnectedState.PlayerConnected) return; if (caller!.CanTarget(player)) @@ -207,7 +207,7 @@ public partial class CS2_SimpleAdmin internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null) { // Check if the player is valid and connected - if (!player.IsValid || player.Connected != PlayerConnectedState.Connected) + if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return; // Ensure the caller can target the player @@ -284,7 +284,7 @@ public partial class CS2_SimpleAdmin playersToTarget.ForEach(player => { // Check if the player is connected and can be targeted - if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player)) + if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player)) return; // Determine message key and arguments for the rename notification @@ -330,7 +330,7 @@ public partial class CS2_SimpleAdmin playersToTarget.ForEach(player => { // Check if the player is connected and can be targeted - if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player)) + if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player)) return; // Determine message key and arguments for the rename notification @@ -379,7 +379,7 @@ public partial class CS2_SimpleAdmin return; destinationPlayer = targets.Players.FirstOrDefault(p => - p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); + p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null) return; @@ -399,7 +399,7 @@ public partial class CS2_SimpleAdmin return; playersToTeleport = targets.Players - .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) + .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) .ToList(); if (!playersToTeleport.Any()) @@ -476,7 +476,7 @@ public partial class CS2_SimpleAdmin destinationPlayer = caller; playersToTeleport = targets.Players - .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) + .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) .ToList(); } else @@ -486,7 +486,7 @@ public partial class CS2_SimpleAdmin return; destinationPlayer = destination.Players.FirstOrDefault(p => - p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); + p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); if (destinationPlayer == null) return; @@ -497,7 +497,7 @@ public partial class CS2_SimpleAdmin return; playersToTeleport = targets.Players - .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p)) + .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p)) .ToList(); } diff --git a/CS2-SimpleAdmin/Database/IDatabaseProvider.cs b/CS2-SimpleAdmin/Database/IDatabaseProvider.cs index e2a4ae4..02ff3e8 100644 --- a/CS2-SimpleAdmin/Database/IDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/IDatabaseProvider.cs @@ -40,7 +40,6 @@ public interface IDatabaseProvider string GetUpdateBanStatusQuery(); string GetExpireBansQuery(bool multiServer); string GetExpireIpBansQuery(bool multiServer); - string GetExpireOldPlayerIpsQuery(); // MuteManager string GetAddMuteQuery(bool includePlayerName); diff --git a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs index fa9a27e..79f0ce2 100644 --- a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs @@ -15,7 +15,7 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';"; await cmd.ExecuteNonQueryAsync(); - // cmd.CommandText = "SET time_zone = '+00:00';"; + cmd.CommandText = "SET time_zone = '+00:00';"; await cmd.ExecuteNonQueryAsync(); return connection; @@ -251,11 +251,6 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider ? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime" : "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid"; } - - public string GetExpireOldPlayerIpsQuery() - { - return "DELETE FROM sa_players_ips WHERE used_at <= @ipBansTime"; - } public string GetAddMuteQuery(bool includePlayerName) => includePlayerName diff --git a/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs b/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs index d6186fb..93772be 100644 --- a/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs @@ -166,9 +166,6 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider multiServer ? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime" : "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid"; - - public string GetExpireOldPlayerIpsQuery() => - "DELETE FROM sa_players_ips WHERE used_at <= @ipBansTime"; public string GetAdminsQuery() => """ diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs index 8d0149a..4a3755a 100644 --- a/CS2-SimpleAdmin/Events.cs +++ b/CS2-SimpleAdmin/Events.cs @@ -23,7 +23,7 @@ public partial class CS2_SimpleAdmin { RegisterListener(OnMapStart); // RegisterListener(OnClientConnect); - // RegisterListener(OnClientConnect); + RegisterListener(OnClientConnect); RegisterListener(OnClientConnected); RegisterListener(OnGameServerSteamAPIActivated); if (Config.OtherSettings.UserMessageGagChatType) @@ -77,7 +77,7 @@ public partial class CS2_SimpleAdmin new ServerManager().LoadServerData(); } - [GameEventHandler] + [GameEventHandler(HookMode.Pre)] public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info) { if (@event.Reason is 149 or 6) @@ -91,15 +91,14 @@ public partial class CS2_SimpleAdmin if (player == null || !player.IsValid || player.IsHLTV) return HookResult.Continue; - - CachedPlayers.Remove(player); + BotPlayers.Remove(player); + CachedPlayers.Remove(player); + SilentPlayers.Remove(player.Slot); if (player.IsBot) - { return HookResult.Continue; - } #if DEBUG Logger.LogCritical("[OnClientDisconnect] After Check"); @@ -177,9 +176,6 @@ public partial class CS2_SimpleAdmin if (player == null || !player.IsValid || player.IsBot) return; - if (!CachedPlayers.Contains(player)) - CachedPlayers.Add(player); - PlayerManager.LoadPlayerData(player); } @@ -253,13 +249,13 @@ public partial class CS2_SimpleAdmin player.DiePosition = null; } - AddTimer(0.5f, () => + AddTimer(0.41f, () => { foreach (var list in RenamedPlayers) { var player = Utilities.GetPlayerFromSteamId(list.Key); - if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.Connected) + if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) continue; if (player.PlayerName.Equals(list.Value)) @@ -328,7 +324,7 @@ public partial class CS2_SimpleAdmin ? Utilities.GetPlayerFromUserid(userId) : null; - if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.Connected) + if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected) return HookResult.Continue; return !player.CanTarget(target) ? HookResult.Stop : HookResult.Continue; @@ -448,13 +444,13 @@ public partial class CS2_SimpleAdmin private void OnMapStart(string mapName) { + if (!ServerLoaded || ServerId == null) + AddTimer(2.0f, OnGameServerSteamAPIActivated); + if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null) - ReloadAdmins(null); + AddTimer(5.0f, () => ReloadAdmins(null)); AddTimer(1.0f, ServerManager.CheckHibernationStatus); - - if (!ServerLoaded || ServerId == null) - AddTimer(1.5f, OnGameServerSteamAPIActivated); // AddTimer(34, () => // { @@ -462,18 +458,35 @@ public partial class CS2_SimpleAdmin // OnGameServerSteamAPIActivated(); // }); + GodPlayers.Clear(); SilentPlayers.Clear(); PlayerPenaltyManager.RemoveAllPenalties(); } + [GameEventHandler] + public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info) + { + var player = @event.Userid; + + 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 (!GodPlayers.Contains(player.Slot)) return HookResult.Continue; + + player.PlayerPawn.Value.Health = player.PlayerPawn.Value.MaxHealth; + player.PlayerPawn.Value.ArmorValue = 100; + + return HookResult.Continue; + } + [GameEventHandler] public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info) { var player = @event.Userid; if (player?.UserId == null || !player.IsValid || player.IsHLTV || - player.Connected != PlayerConnectedState.Connected || !PlayersInfo.ContainsKey(player.SteamID) || + player.Connected != PlayerConnectedState.PlayerConnected || !PlayersInfo.ContainsKey(player.SteamID) || @event.Attacker == null) return HookResult.Continue; @@ -499,13 +512,17 @@ public partial class CS2_SimpleAdmin public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info) { var player = @event.Userid; - if (player == null || !player.IsValid || player.IsBot || !SilentPlayers.Contains(player.Slot)) + if (player == null || !player.IsValid || player.IsBot) return HookResult.Continue; - if (@event is not { Oldteam: <= 1, Team: >= 1 }) return HookResult.Continue; - - SilentPlayers.Remove(player.Slot); - SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false); + if (!SilentPlayers.Contains(player.Slot)) + return HookResult.Continue; + + if (@event is { Oldteam: <= 1, Team: >= 1 }) + { + SilentPlayers.Remove(player.Slot); + SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false); + } return HookResult.Continue; } diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs index aff807d..b7573d4 100644 --- a/CS2-SimpleAdmin/Helper.cs +++ b/CS2-SimpleAdmin/Helper.cs @@ -11,14 +11,12 @@ using CounterStrikeSharp.API.ValveConstants.Protobuf; using CS2_SimpleAdminApi; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.RegularExpressions; using CounterStrikeSharp.API.Core.Plugin.Host; using CounterStrikeSharp.API.Modules.Entities.Constants; @@ -66,7 +64,7 @@ internal static class Helper public static List GetValidPlayers() { - return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.Connected).ToList(); + return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.Connected == PlayerConnectedState.PlayerConnected).ToList(); } public static List GetValidPlayersWithBots() @@ -857,34 +855,26 @@ internal static class Helper } } - public static void UpdateConfig(BasePluginConfig config) + public static void UpdateConfig(T config) where T : BasePluginConfig, new() { // get newest config version - var configType = config.GetType(); - var newCfgVersion = ((BasePluginConfig)Activator.CreateInstance(configType)!).Version; + var newCfgVersion = new T().Version; // loaded config is up to date if (config.Version == newCfgVersion) return; - // Load existing JSON file and update version property - if (!File.Exists(CfgPath)) - return; + // update the version + config.Version = newCfgVersion; - var json = File.ReadAllText(CfgPath); - var node = JsonNode.Parse(json); - - if (node != null) - { - node["Version"] = newCfgVersion; - var updatedJsonContent = node.ToJsonString(new JsonSerializerOptions + // serialize the updated config back to json + var updatedJsonContent = JsonSerializer.Serialize(config, + new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); - - File.WriteAllText(CfgPath, updatedJsonContent); - } + File.WriteAllText(CfgPath, updatedJsonContent); } public static void TryLogCommandOnDiscord(CCSPlayerController? caller, string commandString) @@ -1011,10 +1001,6 @@ public static class PluginInfo Console.WriteLine("|_______||___| |_| |_||___| |_______||_______||__| |__||______| |_| |_||___| |_| |__|"); Console.WriteLine(" >> Version: " + moduleVersion); Console.WriteLine(" >> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin"); - var _m1 = new List { 70,117,99,107,32,121,111,117,32,114,97,118,105,100,44,32,119,104,121,32,121,111,117,32,108,121,105,110,103,32,97,98,111,117,116,32,110,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,63,32,74,117,115,116,32,116,101,108,108,32,116,114,117,116,104,44,32,110,105,99,101,32,116,114,121,32,116,111,32,112,114,111,106,101,99,116,32,116,97,107,101,111,118,101,114 }; - var _m2 = new List { 32,32,58,68,32,40,97,108,108,32,119,104,111,32,116,114,105,101,100,32,116,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,44,32,106,117,115,116,32,119,114,105,116,101,32,109,101,115,115,97,103,101,32,116,111,32,109,101,32,120,68,41 }; - Console.WriteLine(" >> " + new string(_m1.Select(c => (char)c).ToArray())); - Console.WriteLine(" >> " + new string(_m2.Select(c => (char)c).ToArray())); Console.WriteLine(" "); } } diff --git a/CS2-SimpleAdmin/Managers/BanManager.cs b/CS2-SimpleAdmin/Managers/BanManager.cs index 5863b59..2aac526 100644 --- a/CS2-SimpleAdmin/Managers/BanManager.cs +++ b/CS2-SimpleAdmin/Managers/BanManager.cs @@ -431,9 +431,6 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string var ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans); sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode); await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId }); - - sql = databaseProvider.GetExpireOldPlayerIpsQuery(); - await connection.ExecuteAsync(sql, new { ipBansTime }); } } catch (Exception) diff --git a/CS2-SimpleAdmin/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index d02e036..4e580c6 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using CS2_SimpleAdmin.Database; using CS2_SimpleAdmin.Models; using Dapper; -using Microsoft.Extensions.Logging; using ZLinq; namespace CS2_SimpleAdmin.Managers; @@ -17,7 +16,6 @@ internal class CacheManager: IDisposable private HashSet _cachedIgnoredIps = []; private DateTime _lastUpdateTime = DateTime.MinValue; - private DateTime? _lastDatabaseTime = null; // Track actual time from database private bool _isInitialized; private bool _disposed; @@ -73,7 +71,7 @@ internal class CacheManager: IDisposable if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp) { // Optimization: Load IP history and build cache in single pass - var ipHistory = await connection.QueryAsync( + 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 steamid, address, used_at DESC"); var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; @@ -84,24 +82,24 @@ internal class CacheManager: IDisposable foreach (var record in ipHistory) { // When we encounter a new steamid, save the previous one - if ((ulong)record.Steamid != currentSteamId && currentSteamId != 0) + if (record.steamid != currentSteamId && currentSteamId != 0) { _playerIpsCache[currentSteamId] = currentIpSet; currentIpSet = new HashSet(new IpRecordComparer()); latestIpTimestamps.Clear(); } - currentSteamId = (ulong)record.Steamid; + currentSteamId = record.steamid; // Only keep the latest timestamp for each IP - if (!latestIpTimestamps.TryGetValue(record.Address, out var existingTimestamp) || - record.Used_at > existingTimestamp) + if (!latestIpTimestamps.TryGetValue(record.address, out var existingTimestamp) || + record.used_at > existingTimestamp) { - latestIpTimestamps[record.Address] = record.Used_at; + latestIpTimestamps[record.address] = record.used_at; currentIpSet.Add(new IpRecord( - record.Address, - record.Used_at, - string.IsNullOrEmpty(record.Name) ? unknownName : record.Name + record.address, + record.used_at, + string.IsNullOrEmpty(record.name) ? unknownName : record.name )); } } @@ -158,20 +156,13 @@ internal class CacheManager: IDisposable await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); IEnumerable updatedBans; - // Get current time from database in local timezone (CURRENT_TIMESTAMP uses session timezone, not UTC) - var currentDatabaseTime = await connection.QueryFirstAsync("SELECT CURRENT_TIMESTAMP"); - // Optimization: Only get IDs for comparison if we need to check for deletions // Most of the time bans are just added/updated, not deleted HashSet? allIds = null; if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) { - // Use previous database time or start from far past if first run - var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue; - - // Get recently updated bans by timestamp (using database time to avoid timezone issues) - var updatedBans_Query = (await connection.QueryAsync( + updatedBans = (await connection.QueryAsync( """ SELECT id AS Id, player_name AS PlayerName, @@ -180,68 +171,33 @@ internal class CacheManager: IDisposable status AS Status FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC """, - new { lastUpdate = lastCheckTime } - )).ToList(); - - // Detect changes: new bans or status changes - var updatedList = new List(); - foreach (var ban in updatedBans_Query) - { - if (!_banCache.TryGetValue(ban.Id, out var cachedBan)) - { - // New ban - updatedList.Add(ban); - } - else if (cachedBan.Status != ban.Status) - { - // Status changed - updatedList.Add(ban); - } - } + new { lastUpdate = _lastUpdateTime } + )); + // Optimization: Only fetch all IDs if there were updates + var updatedList = updatedBans.ToList(); if (updatedList.Count > 0) { allIds = (await connection.QueryAsync("SELECT id FROM sa_bans")).ToHashSet(); } updatedBans = updatedList; - - // Update last check time to current database time - _lastDatabaseTime = currentDatabaseTime; } else { - // Use previous database time or start from far past if first run - var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue; - - // Get recently updated bans for this server by timestamp (using database time to avoid timezone issues) - var updatedBans_Query = (await connection.QueryAsync( + updatedBans = (await connection.QueryAsync( """ SELECT id AS Id, player_name AS PlayerName, player_steamid AS PlayerSteamId, player_ip AS PlayerIp, status AS Status - FROM `sa_bans` WHERE server_id = @serverId AND (updated_at > @lastUpdate OR created > @lastUpdate) ORDER BY updated_at DESC + FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC """, - new { serverId = CS2_SimpleAdmin.ServerId, lastUpdate = lastCheckTime } - )).ToList(); - - // Detect changes: new bans or status changes - var updatedList = new List(); - foreach (var ban in updatedBans_Query) - { - if (!_banCache.TryGetValue(ban.Id, out var cachedBan)) - { - // New ban - updatedList.Add(ban); - } - else if (cachedBan.Status != ban.Status) - { - // Status changed - updatedList.Add(ban); - } - } + new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId } + )); + // Optimization: Only fetch all IDs if there were updates + var updatedList = updatedBans.ToList(); if (updatedList.Count > 0) { allIds = (await connection.QueryAsync( @@ -250,9 +206,6 @@ internal class CacheManager: IDisposable )).ToHashSet(); } updatedBans = updatedList; - - // Update last check time to current database time - _lastDatabaseTime = currentDatabaseTime; } // Optimization: Only process deletions if we have the full ID list @@ -272,34 +225,34 @@ internal class CacheManager: IDisposable if (string.IsNullOrWhiteSpace(ban.PlayerIp) || !IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) || - !_ipIndex.TryGetValue(ipUInt, out var ipBans)) - continue; - - ipBans.RemoveAll(b => b.Id == id); - if (ipBans.Count == 0) - _ipIndex.TryRemove(ipUInt, out _); + !_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue; + { + ipBans.RemoveAll(b => b.Id == id); + if (ipBans.Count == 0) + _ipIndex.TryRemove(ipUInt, out _); + } } } if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp) { - var ipHistory = (await connection.QueryAsync( + 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 })); - foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.Steamid)) + foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid)) { var ipSet = new HashSet( group - .GroupBy(x => x.Address) + .GroupBy(x => x.address) .Select(g => { - var latest = g.MaxBy(x => x.Used_at); + var latest = g.MaxBy(x => x.used_at); return new IpRecord( g.Key, - latest.Used_at, - !string.IsNullOrEmpty(latest.Name) - ? latest.Name + latest.used_at, + !string.IsNullOrEmpty(latest.name) + ? latest.name : CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown" ); }), @@ -307,7 +260,7 @@ internal class CacheManager: IDisposable ); _playerIpsCache.AddOrUpdate( - (ulong)group.Key, + group.Key, _ => ipSet, (_, existingSet) => { @@ -323,19 +276,16 @@ internal class CacheManager: IDisposable } // Update cache with new/modified bans - var needsRebuild = false; + var hasUpdates = false; foreach (var ban in updatedBans) { - if (_banCache.TryGetValue(ban.Id, out var oldBan) && oldBan.Status != ban.Status) - { - // Ban status changed (e.g., ACTIVE -> EXPIRED/UNBANNED), need to rebuild indexes - needsRebuild = true; - } _banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban); + hasUpdates = true; } - // Rebuild indexes if there were updates or status changes - if (updatedBans.Any() || needsRebuild) + // Always rebuild indexes if there were any updates + // This ensures status changes (ACTIVE -> UNBANNED) are reflected + if (hasUpdates) { RebuildIndexes(); } @@ -486,41 +436,32 @@ internal class CacheManager: IDisposable record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE); if (record != null) { - // Double-check the ban is still active in cache (handle race conditions) - if (_banCache.TryGetValue(record.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE) + if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) || + (!record.PlayerSteamId.HasValue)) { - if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) || - (!record.PlayerSteamId.HasValue)) - { - _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); - } - - return true; + _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); } + + return true; } } - if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress)) + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false; if (string.IsNullOrEmpty(ipAddress) || !IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) || _cachedIgnoredIps.Contains(ipUInt) || !_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false; - + record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE); if (record == null) return false; - - // Double-check the ban is still active in cache (handle race conditions) - if (!_banCache.TryGetValue(record.Id, out var cachedBanIp) || cachedBanIp.StatusEnum != BanStatus.ACTIVE) - return false; - if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) || (!record.PlayerSteamId.HasValue && steamId.HasValue)) { _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); } - + return true; } @@ -606,100 +547,52 @@ internal class CacheManager: IDisposable var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE); if (activeBan != null) { - // Double-check the ban is still active in cache (handle race conditions) - if (_banCache.TryGetValue(activeBan.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE) - { - if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) - _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); - - return true; - } - } - } - - if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress)) - return false; - - if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)) - return false; - - if (_cachedIgnoredIps.Contains(ipUInt)) - return false; - - // Direct ip ban (ban record has player_ip set) - if (_ipIndex.TryGetValue(ipUInt, out var ipBanRecords)) - { - var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE); - if (ipBan != null && _banCache.TryGetValue(ipBan.Id, out var cachedIpBan) && cachedIpBan.StatusEnum == BanStatus.ACTIVE) - { - var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans; - if (expireOldIpBans <= 0 || ipBan.Created >= Time.ActualDateTime().AddDays(-expireOldIpBans)) - { - if (string.IsNullOrEmpty(ipBan.PlayerName)) - ipBan.PlayerName = playerName; - ipBan.PlayerSteamId ??= steamId; + if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp)) _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); - return true; - } - } - } - - // Multiaccount ban - check if other accounts using current ip are banned - if (!_playerIpsCache.IsEmpty) - { - foreach (var (otherSteamId, ipSet) in _playerIpsCache) - { - // Skip current player - if (otherSteamId == steamId) - continue; - - // Check if this ip is in the other accounts ip history - if (ipSet.All(record => record.Ip != ipUInt)) continue; - // Found another account using this ip - check if its banned - if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans)) continue; - var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE); - if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) || - cachedBan.StatusEnum != BanStatus.ACTIVE) continue; - _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); + return true; } } - // Multiaccount ban - check if this player used any ip where other banned accounts are connected - // Search sa_players_ips for all accounts sharing the same ips as current player - if (!CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp) + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false; - if (!_playerIpsCache.TryGetValue(steamId, out var playerIps)) + if (!_playerIpsCache.TryGetValue(steamId, out var ipData)) return false; - // For each ip the player used (current or historical) - foreach (var playerIpRecord in playerIps) + var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans); + var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; + + if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint)) { - // Search sa_players_ips for other accounts using this same ip (as uint) - foreach (var (otherSteamId, otherIpSet) in _playerIpsCache) + if (!_cachedIgnoredIps.Contains(ipAsUint)) { - if (otherSteamId == steamId) - continue; - - // Check if this other account used the player ip - if (otherIpSet.All(record => record.Ip != playerIpRecord.Ip)) - continue; - - // Check if this other account is banned - if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans)) - continue; - - var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE); - if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) || - cachedBan.StatusEnum != BanStatus.ACTIVE) - continue; - - _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); - return true; + ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName)); } } + foreach (var ipRecord in ipData) + { + if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip)) + continue; + + if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords)) + continue; + + var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE); + if (activeBan == null) + continue; + + if (string.IsNullOrEmpty(activeBan.PlayerName)) + activeBan.PlayerName = unknownName; + + activeBan.PlayerSteamId ??= steamId; + + _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); + + return true; + } + return false; } diff --git a/CS2-SimpleAdmin/Managers/PermissionManager.cs b/CS2-SimpleAdmin/Managers/PermissionManager.cs index 5826e74..c9f2b41 100644 --- a/CS2-SimpleAdmin/Managers/PermissionManager.cs +++ b/CS2-SimpleAdmin/Managers/PermissionManager.cs @@ -1,4 +1,4 @@ -using CounterStrikeSharp.API; +using CounterStrikeSharp.API; using CounterStrikeSharp.API.Modules.Entities; using Dapper; using Microsoft.Extensions.Logging; @@ -12,8 +12,54 @@ namespace CS2_SimpleAdmin.Managers; public class PermissionManager(IDatabaseProvider? databaseProvider) { + // Unused for now + //public static readonly ConcurrentDictionary> _adminCache = new ConcurrentDictionary>(); + // public static readonly ConcurrentDictionary AdminCache = new(); public static readonly ConcurrentDictionary Flags)> AdminCache = new(); + /* + public async Task, int)>> GetAdminFlags(string steamId) + { + DateTime now = Time.ActualDateTime(); + + await using MySqlConnection connection = await _database.GetConnectionAsync(); + + string sql = "SELECT flags, immunity, ends FROM sa_admins WHERE player_steamid = @PlayerSteamID AND (ends IS NULL OR ends > @CurrentTime) AND (server_id IS NULL OR server_id = @serverid)"; + List? activeFlags = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId }))?.ToList(); + + if (activeFlags == null) + { + return new List<(List, int)>(); + } + + List<(List, int)> filteredFlagsWithImmunity = []; + + foreach (dynamic flags in activeFlags) + { + if (flags is not IDictionary flagsDict) + { + continue; + } + + if (!flagsDict.TryGetValue("flags", out var flagsValueObj) || !flagsDict.TryGetValue("immunity", out var immunityValueObj)) + { + continue; + } + + if (!(flagsValueObj is string flagsValue) || !int.TryParse(immunityValueObj.ToString(), out var immunityValue)) + { + continue; + } + + //Console.WriteLine($"Flags: {flagsValue}, Immunity: {immunityValue}"); + + filteredFlagsWithImmunity.Add((flagsValue.Split(',').ToList(), immunityValue)); + } + + return filteredFlagsWithImmunity; + } + */ + /// /// Retrieves all players' flags and associated data asynchronously. /// @@ -54,7 +100,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) string playerName = g.Key.playerName as string ?? string.Empty; - // Dapper returns string here, not dynamic + // tutaj zakładamy, że Dapper zwraca już string (nie dynamic) var flags = g.Select(r => r.flag as string ?? string.Empty) .Distinct() .ToList(); @@ -72,6 +118,72 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) } } + + /* + public async Task, List>, int>>> GetAllGroupsFlags() + { + try + { + await using MySqlConnection connection = await _database.GetConnectionAsync(); + + string sql = "SELECT group_id FROM sa_groups_servers WHERE server_id = @serverid"; + var groupIds = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); + + sql = @" + SELECT g.group_id, f.flag + FROM sa_groups_flags f + JOIN sa_groups_servers g ON f.group_id = g.group_id + WHERE g.server_id = @serverid"; + + var groupFlagData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); + + if (groupIds.Count == 0 || groupFlagData.Count == 0) + { + return []; + } + + var groupInfoDictionary = new Dictionary, List>, int>>(); + + foreach (var groupId in groupIds) + { + groupInfoDictionary[groupId] = new Tuple, List>, int>([], [], 0); + } + + foreach (var row in groupFlagData) + { + var groupId = (int)row.group_id; + var flag = (string)row.flag; + + groupInfoDictionary[groupId].Item1.Add(flag); + } + + sql = @" + SELECT a.group_id, a.player_steamid, a.ends, g.immunity, g.name + FROM sa_admins a + JOIN sa_groups g ON a.group_id = g.id + WHERE a.group_id IN @groupIds"; + + var playerData = (await connection.QueryAsync(sql, new { groupIds })).ToList(); + + foreach (var row in playerData) + { + var groupId = (int)row.group_id; + var playerSteamid = (string)row.player_steamid; + var ends = row.ends as DateTime?; + var immunity = (int)row.immunity; + + groupInfoDictionary[groupId].Item2.Add(new Tuple(playerSteamid, ends)); + groupInfoDictionary[groupId] = new Tuple, List>, int>(groupInfoDictionary[groupId].Item1, groupInfoDictionary[groupId].Item2, immunity); + } + + return groupInfoDictionary; + } + catch { } + + return []; + } + */ + /// /// Retrieves all groups' data including flags and immunity asynchronously. /// @@ -80,9 +192,13 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) { if (databaseProvider == null) return []; + await using var connection = await databaseProvider.CreateConnectionAsync(); + ; try { - await using var connection = await databaseProvider.CreateConnectionAsync(); + // var sql = "SELECT group_id FROM sa_groups_servers WHERE (server_id = @serverid OR server_id IS NULL)"; + // var groupDataSql = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); + var sql = databaseProvider.GetGroupsQuery(); var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); if (groupData.Count == 0) @@ -97,9 +213,11 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) var flag = (string)row.flag; var immunity = (int)row.immunity; + // Check if the group name already exists in the dictionary if (!groupInfoDictionary.TryGetValue(groupName, out (List, int) value)) { value = ([], immunity); + // If it doesn't exist, add a new entry with an empty list of flags and immunity groupInfoDictionary[groupName] = value; } @@ -120,7 +238,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) /// Creates a JSON file containing groups data asynchronously. /// [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - public async Task CreateGroupsJsonFile() + public async Task CrateGroupsJsonFile() { var groupsData = await GetAllGroupsData(); var jsonData = new Dictionary(); @@ -139,7 +257,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) var options = new JsonSerializerOptions { WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase + PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var json = JsonSerializer.Serialize(jsonData, options); @@ -147,6 +265,66 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) await File.WriteAllTextAsync(filePath, json); } + /* + public async Task GiveAllGroupsFlags() + { + Dictionary, List>, int>> groupFlags = await GetAllGroupsFlags(); + + foreach (var kvp in groupFlags) + { + var flags = kvp.Value.Item1; + var players = kvp.Value.Item2; + int immunity = kvp.Value.Item3; + + foreach (var playerTuple in players) + { + var steamIdStr = playerTuple.Item1; + var ends = playerTuple.Item2; + + if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null) + { + if (!_adminCache.ContainsKey(steamId)) + { + _adminCache.TryAdd(steamId, ends); + } + + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + // Often need to call 2 times + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + } + } + } + } + */ + /* + public async Task GiveAllFlags() + { + List<(string, string, List, int, DateTime?)> allPlayers = await GetAllPlayersFlags(); + + foreach (var record in allPlayers) + { + string steamIdStr = record.Item1; + List flags = record.Item2; + int immunity = record.Item3; + + DateTime? ends = record.Item4; + + if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null) + { + if (!_adminCache.ContainsKey(steamId)) + { + _adminCache.TryAdd(steamId, ends); + //_adminCacheTimestamps.Add(steamId, ends); + } + + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + // Often need to call 2 times + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + } + } + } + */ + /// /// Creates a JSON file containing admins data asynchronously. /// @@ -158,12 +336,25 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) .Where(player => SteamID.TryParse(player.identity.ToString(), out _)) .ToList(); + // foreach (var player in allPlayers) + // { + // var (steamId, name, flags, immunity, ends) = player; + // + // Console.WriteLine($"Player SteamID: {steamId}"); + // Console.WriteLine($"Player Name: {name}"); + // Console.WriteLine($"Flags: {string.Join(", ", flags)}"); + // Console.WriteLine($"Immunity: {immunity}"); + // Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}"); + // Console.WriteLine(); + // } + var jsonData = validPlayers - .GroupBy(player => player.name) + .GroupBy(player => player.name) // Group by player name .ToDictionary( - group => group.Key, + group => group.Key, // Use the player name as key object (group) => { + // Consolidate data for players with same name var consolidatedData = group.Aggregate( new { @@ -174,13 +365,16 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) }, (acc, player) => { + // Merge identities if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString())) { acc = acc with { identity = player.identity.ToString() }; } + // Combine immunities by maximum value acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) }; + // Combine flags and groups acc = acc with { flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(), @@ -189,12 +383,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) return acc; }); - + Server.NextWorldUpdate(() => { var keysToRemove = new List(); - foreach (var steamId in AdminCache.Keys.ToList()) + foreach (var steamId in AdminCache.Keys.ToList()) { var data = AdminManager.GetPlayerAdminData(steamId); if (data != null) @@ -228,9 +422,40 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) } }); + // 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 options = new JsonSerializerOptions { WriteIndented = true, @@ -239,7 +464,10 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) var json = JsonSerializer.Serialize(jsonData, options); var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json"); + await File.WriteAllTextAsync(filePath, json); + + //await File.WriteAllTextAsync(CS2_SimpleAdmin.Instance.ModuleDirectory + "/data/admins.json", json); } /// @@ -252,6 +480,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) if (databaseProvider == null) return; if (string.IsNullOrEmpty(playerSteamId)) return; + //_adminCache.TryRemove(playerSteamId, out _); + try { await using var connection = await databaseProvider.CreateConnectionAsync(); @@ -280,12 +510,18 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return; var now = Time.ActualDateTime(); - DateTime? futureTime = time != 0 ? now.AddMinutes(time) : null; + DateTime? futureTime; + + if (time != 0) + futureTime = now.AddMinutes(time); + else + futureTime = null; try { await using var connection = await databaseProvider.CreateConnectionAsync(); + // Insert admin into sa_admins table var insertAdminSql = databaseProvider.GetAddAdminQuery(); var adminId = await connection.ExecuteScalarAsync(insertAdminSql, new { @@ -297,8 +533,28 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) serverid = globalAdmin ? null : CS2_SimpleAdmin.ServerId }); + // Insert flags into sa_admins_flags table foreach (var flag in flagsList) { + // if (flag.StartsWith($"#")) + // { + // // const string sql = "SELECT id FROM `sa_groups` WHERE name = @groupName"; + // // var groupId = await connection.QuerySingleOrDefaultAsync(sql, new { groupName = flag }); + // + // var sql = databaseProvider.GetGroupIdByNameQuery(); + // var groupId = await connection.QuerySingleOrDefaultAsync(sql, new { groupName = flag, CS2_SimpleAdmin.ServerId }); + // + // if (groupId != null) + // { + // var updateAdminGroup = "UPDATE `sa_admins` SET group_id = @groupId WHERE id = @adminId"; + // await connection.ExecuteAsync(updateAdminGroup, new + // { + // groupId, + // adminId + // }); + // } + // } + var insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery(); await connection.ExecuteAsync(insertFlagsSql, new { @@ -331,10 +587,10 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return; + await using var connection = await databaseProvider.CreateConnectionAsync(); try { - await using var connection = await databaseProvider.CreateConnectionAsync(); - + // Insert group into sa_groups table var insertGroup = databaseProvider.GetAddGroupQuery(); var groupId = await connection.ExecuteScalarAsync(insertGroup, new { @@ -342,9 +598,11 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) immunity }); + // Insert flags into sa_groups_flags table foreach (var flag in flagsList) { var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery(); + await connection.ExecuteAsync(insertFlagsSql, new { groupId, @@ -358,6 +616,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) { CS2_SimpleAdmin.Instance.ReloadAdmins(null); }); + } catch (Exception ex) { @@ -375,9 +634,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) if (string.IsNullOrEmpty(groupName)) return; + await using var connection = await databaseProvider.CreateConnectionAsync(); try { - await using var connection = await databaseProvider.CreateConnectionAsync(); var sql = databaseProvider.GetDeleteGroupQuery(); await connection.ExecuteAsync(sql, new { groupName }); } @@ -406,4 +665,4 @@ public class PermissionManager(IDatabaseProvider? databaseProvider) CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired admins"); } } -} +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index 1736112..1378962 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -13,7 +13,7 @@ namespace CS2_SimpleAdmin.Managers; internal class PlayerManager { - private readonly SemaphoreSlim _loadPlayerSemaphore = new(6); + private readonly SemaphoreSlim _loadPlayerSemaphore = new(5); private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config; /// @@ -52,40 +52,75 @@ internal class PlayerManager { await _loadPlayerSemaphore.WaitAsync(); - // Save ip address before ban check - await SavePlayerIpAddress(steamId, playerName, ipAddress); - - // Always check bans first, regardless of PlayersInfo state - var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch + if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId)) { - 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null), - _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp - ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, - ipAddress) - : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress) - }; - - CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] Player {playerName} ({steamId}) IP: {ipAddress} - BanType: {CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType} - CheckMultiAccounts: {CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp} - isBanned: {isBanned}"); - - if (isBanned) - { - CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] KICKING {playerName} ({steamId})"); - await Server.NextWorldUpdateAsync(() => + var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch { - CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] Executing kick for {playerName}"); - Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); - }); + 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null), + _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp + ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, + ipAddress) + : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress) + }; - return; + // CS2_SimpleAdmin._logger?.LogInformation($"Player {playerName} ({steamId} - {ipAddress}) is banned? {isBanned.ToString()}"); + + if (isBanned) + { + await Server.NextWorldUpdateAsync(() => + { + // CS2_SimpleAdmin._logger?.LogInformation($"Kicking {playerName}"); + Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + }); + + return; + } } - if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId)) + if (fullConnect) { var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress); CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo; - if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null) + await Server.NextWorldUpdateAsync(() => { + if (!CS2_SimpleAdmin.CachedPlayers.Contains(player)) + CS2_SimpleAdmin.CachedPlayers.Add(player); + }); + + if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null && + CS2_SimpleAdmin.PlayersInfo[steamId] != null) + { + try + { + await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); + + // Eliminates the need for SELECT COUNT and duplicate UPDATE queries + var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64; + var ipUint = IpHelper.IpToUint(ipAddress); + + // Use database-specific UPSERT query (handles MySQL vs SQLite syntax differences) + var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery(); + + await connection.ExecuteAsync(upsertQuery, new + { + SteamID = steamId64, + playerName, + IPAddress = ipUint + }); + + // // Cache will be updated on next refresh cycle + // if (!CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(steamId, ipAddress)) + // { + // // IP association will be reflected after cache refresh + // } + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError( + $"Unable to save ip address for {playerInfo.Name} ({ipAddress}): {ex.Message}"); + } + playerInfo.AccountsAssociated = CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable() .Select(x => (x.SteamId, x.PlayerName)).ToList() ?? []; @@ -172,7 +207,7 @@ internal class PlayerManager AdminManager.PlayerHasPermissions( new SteamID(p.SteamID), "@css/ban")) && - p.Connected == PlayerConnectedState.Connected && + p.Connected == PlayerConnectedState.PlayerConnected && !CS2_SimpleAdmin.AdminDisabledJoinComms .Contains(p.SteamID))) { @@ -219,45 +254,12 @@ internal class PlayerManager _loadPlayerSemaphore.Release(); } }); - if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name)) { player.Rename(name); } } - /// - /// Saves player's IP address to the database for multi-account detection. - /// This is called before ban checks to ensure IP is recorded even if player is banned. - /// - private async Task SavePlayerIpAddress(ulong steamId, string playerName, string? ipAddress) - { - if (!_config.OtherSettings.CheckMultiAccountsByIp || ipAddress == null || CS2_SimpleAdmin.DatabaseProvider == null) - return; - - try - { - await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); - - var steamId64 = steamId; - var ipUint = IpHelper.IpToUint(ipAddress); - - var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery(); - - await connection.ExecuteAsync(upsertQuery, new - { - SteamID = steamId64, - playerName, - IPAddress = ipUint - }); - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError( - $"Unable to save ip address for {playerName} ({ipAddress}): {ex.Message}"); - } - } - /// /// Periodically checks the status of online players and applies timers for speed, gravity, /// and penalty expiration validation. @@ -275,16 +277,6 @@ internal class PlayerManager /// public void CheckPlayersTimer() { - CS2_SimpleAdmin.Instance.AddTimer(5f, () => - { - foreach (var (steamid, name) in CS2_SimpleAdmin.RenamedPlayers) - { - var player = Helper.GetPlayerFromSteamid64(steamid); - if (player == null || !player.IsValid || player.PlayerName == name) continue; - player.Rename(name); - } - }, TimerFlags.REPEAT); - CS2_SimpleAdmin.Instance.PlayersTimer = CS2_SimpleAdmin.Instance.AddTimer(61.0f, () => { #if DEBUG @@ -295,6 +287,9 @@ internal class PlayerManager // Optimization: Get players once and avoid allocating anonymous types var validPlayers = Helper.GetValidPlayers(); + if (validPlayers.Count == 0) + return; + // Use ValueTuple instead of anonymous type - better performance and less allocations var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count); foreach (var p in validPlayers) diff --git a/CS2-SimpleAdmin/Managers/ServerManager.cs b/CS2-SimpleAdmin/Managers/ServerManager.cs index ec80aaa..e2401bc 100644 --- a/CS2-SimpleAdmin/Managers/ServerManager.cs +++ b/CS2-SimpleAdmin/Managers/ServerManager.cs @@ -31,7 +31,7 @@ public class ServerManager /// public void LoadServerData() { - CS2_SimpleAdmin.Instance.AddTimer(1.0f, () => + CS2_SimpleAdmin.Instance.AddTimer(2.0f, () => { if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return; @@ -103,12 +103,14 @@ public class ServerManager CS2_SimpleAdmin.ServerId = serverId; CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress); -CS2_SimpleAdmin.ServerLoaded = true; - - if (CS2_SimpleAdmin.Instance.CacheManager != null) + if (CS2_SimpleAdmin.ServerId != null) { - await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync(); + await Server.NextWorldUpdateAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null)); } + + CS2_SimpleAdmin.ServerLoaded = true; + if (CS2_SimpleAdmin.Instance.CacheManager != null) + await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync(); } catch (Exception ex) { diff --git a/CS2-SimpleAdmin/Managers/WarnManager.cs b/CS2-SimpleAdmin/Managers/WarnManager.cs index 7db3d1d..0f58b84 100644 --- a/CS2-SimpleAdmin/Managers/WarnManager.cs +++ b/CS2-SimpleAdmin/Managers/WarnManager.cs @@ -33,7 +33,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider) playerName = player.Name, adminSteamid = issuer?.SteamId.SteamId64 ?? 0, adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console", - warnReason = reason, + muteReason = reason, duration = time, ends = futureTime, created = now, @@ -42,7 +42,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider) return warnId; } - catch(Exception) + catch { return null; } @@ -73,7 +73,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider) playerSteamid = playerSteamId, adminSteamid = issuer?.SteamId.SteamId64 ?? 0, adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console", - warnReason = reason, + muteReason = reason, duration = time, ends = futureTime, created = now, diff --git a/CS2-SimpleAdmin/Models/BanRecord.cs b/CS2-SimpleAdmin/Models/BanRecord.cs index 996e423..026ffd6 100644 --- a/CS2-SimpleAdmin/Models/BanRecord.cs +++ b/CS2-SimpleAdmin/Models/BanRecord.cs @@ -24,10 +24,7 @@ public record BanRecord [Column("player_ip")] public string? PlayerIp { get; set; } - - [Column("created")] - public DateTime Created { get; init; } - + [Column("status")] public required string Status { get; init; } diff --git a/CS2-SimpleAdmin/Models/IpHistoryRow.cs b/CS2-SimpleAdmin/Models/IpHistoryRow.cs deleted file mode 100644 index 5c794a7..0000000 --- a/CS2-SimpleAdmin/Models/IpHistoryRow.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CS2_SimpleAdmin.Models; - -public record IpHistoryRow -{ - public long Steamid { get; init; } - public string? Name { get; init; } - public uint Address { get; init; } - public DateTime Used_at { get; init; } -} diff --git a/CS2-SimpleAdmin/VERSION b/CS2-SimpleAdmin/VERSION index c7a73ab..9403fa9 100644 --- a/CS2-SimpleAdmin/VERSION +++ b/CS2-SimpleAdmin/VERSION @@ -1 +1 @@ -1.8.2b \ No newline at end of file +1.7.8-beta-5 \ No newline at end of file diff --git a/CS2-SimpleAdmin/Variables.cs b/CS2-SimpleAdmin/Variables.cs index 35424a9..a35cc75 100644 --- a/CS2-SimpleAdmin/Variables.cs +++ b/CS2-SimpleAdmin/Variables.cs @@ -40,6 +40,7 @@ public partial class CS2_SimpleAdmin internal static readonly HashSet AdminDisabledJoinComms = []; // Player Management + private static readonly HashSet GodPlayers = []; internal static readonly HashSet SilentPlayers = []; internal static readonly Dictionary RenamedPlayers = []; internal static readonly ConcurrentDictionary PlayersInfo = []; diff --git a/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj index bbd5d85..d12aa88 100644 --- a/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj +++ b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj @@ -1,7 +1,7 @@  - net10.0 + net8.0 CS2_SimpleAdminApi enable enable @@ -9,7 +9,7 @@ - + diff --git a/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs b/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs index 03b2495..9379f33 100644 --- a/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs +++ b/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs @@ -45,7 +45,7 @@ public class CS2_SimpleAdmin_BanSoundModule: BasePlugin foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot)) { var filter = new RecipientFilter(player); - player?.EmitSound("bansound", volume: 0.75f, recipients: filter); + player?.EmitSound("bansound", volume: 0.9f, recipients: filter); } } } \ No newline at end of file diff --git a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj index 85c6be5..3990512 100644 --- a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj +++ b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj @@ -14,7 +14,7 @@ - + diff --git a/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.1.0-linux.tar.gz b/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.1.0-linux.tar.gz new file mode 100644 index 0000000..ae1a40d Binary files /dev/null and b/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.1.0-linux.tar.gz differ diff --git a/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.1.0-windows.tar.gz b/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.1.0-windows.tar.gz new file mode 100644 index 0000000..1ec754a Binary files /dev/null and b/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.1.0-windows.tar.gz differ