diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a20dea..403e2db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,9 +22,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - name: Restore @@ -38,9 +38,9 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - name: Restore @@ -55,13 +55,13 @@ jobs: ${{ env.OUTPUT_PATH }}/Microsoft.DotNet.PlatformAbstractions.dll \ ${{ env.OUTPUT_PATH }}/Microsoft.Extensions.DependencyModel.dll \ - name: Zip - uses: thedoctor0/zip-release@0.7.5 + uses: thedoctor0/zip-release@0.7.6 with: type: 'zip' filename: '${{ env.PROJECT_NAME }}.zip' path: ${{ env.OUTPUT_PATH }} - name: CS2-SimpleAdmin - uses: ncipollo/release-action@v1.12.0 + uses: ncipollo/release-action@v1.14.0 with: artifacts: "${{ env.PROJECT_NAME }}.zip" name: "Build ${{ env.BUILD_NUMBER }}" diff --git a/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin.cs index 53bf02a..c656057 100644 --- a/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin.cs @@ -11,7 +11,7 @@ namespace CS2_SimpleAdmin; -[MinimumApiVersion(220)] +[MinimumApiVersion(225)] public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig { public static CS2_SimpleAdmin Instance { get; private set; } = new(); @@ -37,7 +37,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.4.3d"; + public override string ModuleVersion => "1.4.4a"; public CS2_SimpleAdminConfig Config { get; set; } = new(); diff --git a/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin.csproj index 7db2dde..af84250 100644 --- a/CS2-SimpleAdmin.csproj +++ b/CS2-SimpleAdmin.csproj @@ -10,7 +10,7 @@ - + diff --git a/Config.cs b/Config.cs index 1c0bb68..06da46b 100644 --- a/Config.cs +++ b/Config.cs @@ -142,6 +142,9 @@ public class CS2_SimpleAdminConfig : BasePluginConfig [JsonPropertyName("BanType")] public int BanType { get; set; } = 1; + [JsonPropertyName("TimeMode")] + public int TimeMode { get; set; } = 1; + [JsonPropertyName("MaxBanDuration")] public int MaxBanDuration { get; set; } = 60 * 24 * 7; // 7 days [JsonPropertyName("MultiServerMode")] diff --git a/Events.cs b/Events.cs index ba84539..fcef94d 100644 --- a/Events.cs +++ b/Events.cs @@ -148,7 +148,6 @@ await Server.NextFrameAsync(() => string muteType = mute.type; DateTime ends = mute.ends; int duration = mute.duration; - switch (muteType) { // Apply mute penalty based on mute type @@ -275,7 +274,7 @@ public void OnMapStart(string mapName) { var ipAddress = ConVar.Find("ip")?.StringValue; - if (string.IsNullOrEmpty(ipAddress)) + if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")) { Logger.LogError("Unable to get server ip, Check that you have added the correct start parameter \"-ip \""); } @@ -352,8 +351,8 @@ await Server.NextFrameAsync(() => var players = Helper.GetValidPlayers(); var onlinePlayers = players - .Where(player => player.IpAddress != null && player.SteamID.ToString().Length == 17) - .Select(player => (player.IpAddress, player.SteamID, player.UserId)) + .Where(player => player.IpAddress != null) + .Select(player => (player.IpAddress, player.SteamID, player.UserId, player.Slot)) .ToList(); Task.Run(async () => @@ -363,18 +362,25 @@ await Server.NextFrameAsync(() => MuteManager muteManager = new(_database); await banManager.ExpireOldBans(); - await muteManager.ExpireOldMutes(); await adminManager.DeleteOldAdmins(); + BannedPlayers.Clear(); + if (onlinePlayers.Count > 0) { try { await banManager.CheckOnlinePlayers(onlinePlayers); + if (Config.TimeMode == 0) + { + await muteManager.CheckOnlineModeMutes(onlinePlayers); + } } catch { } } + await muteManager.ExpireOldMutes(); + await Server.NextFrameAsync(() => { try diff --git a/Managers/BanManager.cs b/Managers/BanManager.cs index 435c83a..cc24ef8 100644 --- a/Managers/BanManager.cs +++ b/Managers/BanManager.cs @@ -165,8 +165,8 @@ public async Task GetPlayerBans(PlayerInfo player) var sql = ""; sql = config.MultiServerMode - ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid" - : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)"; + ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)" + : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid"; int banCount; @@ -210,18 +210,12 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string { await using var connection = await database.GetConnectionAsync(); - string sqlRetrieveBans; - if (config.MultiServerMode) - { - sqlRetrieveBans = "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' " + - "AND server_id = @serverid"; - } - else - { - sqlRetrieveBans = "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'"; - } + var sqlRetrieveBans = config.MultiServerMode + ? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'" + : "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid"; + var bans = await connection.QueryAsync(sqlRetrieveBans, new { pattern = playerPattern, serverid = CS2_SimpleAdmin.ServerId }); - + var bansList = bans as dynamic[] ?? bans.ToArray(); if (bansList.Length == 0) return; @@ -262,25 +256,18 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string catch { } } - public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId)> players) + public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players) { try { await using var connection = await database.GetConnectionAsync(); - string sql; bool checkIpBans = config.BanType > 0; - if (config.MultiServerMode) - { - sql = "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND status = 'ACTIVE' AND" + - " server_id = @serverid"; - } - else - { - sql = "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND status = 'ACTIVE'"; - } + var sql = config.MultiServerMode + ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND status = 'ACTIVE'" + : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND status = 'ACTIVE' AND server_id = @serverid"; - foreach (var (IpAddress, SteamID, UserId) in players) + foreach (var (IpAddress, SteamID, UserId, Slot) in players) { if (!UserId.HasValue) continue; @@ -336,7 +323,6 @@ UPDATE sa_bans `duration` > 0 AND ends <= @currentTime - AND server_id = @serverid """ : """ UPDATE sa_bans @@ -348,6 +334,7 @@ UPDATE sa_bans `duration` > 0 AND ends <= @currentTime + AND server_id = @serverid """; await connection.ExecuteAsync(sql, new { currentTime, serverid = CS2_SimpleAdmin.ServerId }); @@ -364,7 +351,6 @@ UPDATE sa_bans status = 'ACTIVE' AND ends <= @ipBansTime - AND server_id = @serverid """ : """ UPDATE sa_bans @@ -374,6 +360,7 @@ UPDATE sa_bans status = 'ACTIVE' AND ends <= @ipBansTime + AND server_id = @serverid """; await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId }); diff --git a/Managers/MuteManager.cs b/Managers/MuteManager.cs index dcb91ba..411f105 100644 --- a/Managers/MuteManager.cs +++ b/Managers/MuteManager.cs @@ -1,4 +1,5 @@ -using Dapper; +using CounterStrikeSharp.API.Core; +using Dapper; using Microsoft.Extensions.Logging; namespace CS2_SimpleAdmin; @@ -96,16 +97,20 @@ public async Task> IsPlayerMuted(string steamId) { await using var connection = await database.GetConnectionAsync(); var currentTime = DateTime.UtcNow.ToLocalTime(); - string sql; - + var sql = ""; + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) { - sql = "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) " + - "AND server_id = @serverid"; + sql = CS2_SimpleAdmin.Instance.Config.TimeMode == 1 + ? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)" + : "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0))"; } else { - sql = "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"; + sql = CS2_SimpleAdmin.Instance.Config.TimeMode == 1 + ? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid" + : "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0)) AND server_id = @serverid"; + } var parameters = new { PlayerSteamID = steamId, CurrentTime = currentTime, serverid = CS2_SimpleAdmin.ServerId }; @@ -125,8 +130,8 @@ public async Task GetPlayerMutes(string steamId) await using var connection = await database.GetConnectionAsync(); var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode - ? "SELECT COUNT(*) FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND server_id = @serverid" - : "SELECT COUNT(*) FROM sa_mutes WHERE player_steamid = @PlayerSteamID"; + ? "SELECT COUNT(*) FROM sa_mutes WHERE player_steamid = @PlayerSteamID" + : "SELECT COUNT(*) FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND server_id = @serverid"; var muteCount = await connection.ExecuteScalarAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId }); return muteCount; @@ -136,6 +141,56 @@ public async Task GetPlayerMutes(string steamId) return 0; } } + + public async Task CheckOnlineModeMutes(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players) + { + try + { + int batchSize = 10; + await using var connection = await database.GetConnectionAsync(); + + var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE'" + : "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid"; + /* + foreach (var (IpAddress, SteamID, UserId, Slot) in players) + { + await connection.ExecuteAsync(sql, + new { PlayerSteamID = SteamID, serverid = CS2_SimpleAdmin.ServerId }); + }*/ + + for (var i = 0; i < players.Count; i += batchSize) + { + var batch = players.Skip(i).Take(batchSize); + var parametersList = new List(); + + foreach (var (IpAddress, SteamID, UserId, Slot) in batch) + { + parametersList.Add(new { PlayerSteamID = SteamID, serverid = CS2_SimpleAdmin.ServerId }); + } + + await connection.ExecuteAsync(sql, parametersList); + } + + sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE'" + : "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid"; + + + foreach (var (IpAddress, SteamID, UserId, Slot) in players) + { + var muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = SteamID, serverid = CS2_SimpleAdmin.ServerId }); + + foreach (var muteRecord in muteRecords) + { + DateTime endDateTime = muteRecord.ends; + PlayerPenaltyManager.RemovePenaltiesByDateTime(Slot, endDateTime); + } + + } + } + catch { } + } public async Task UnmutePlayer(string playerPattern, string adminSteamId, string reason, int type = 0) { @@ -160,12 +215,12 @@ public async Task UnmutePlayer(string playerPattern, string adminSteamId, string if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) { sqlRetrieveMutes = "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND " + - "type = @muteType AND status = 'ACTIVE' AND server_id = @serverid"; + "type = @muteType AND status = 'ACTIVE'"; } else { sqlRetrieveMutes = "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND " + - "type = @muteType AND status = 'ACTIVE'"; + "type = @muteType AND status = 'ACTIVE' AND server_id = @serverid"; } var mutes = await connection.QueryAsync(sqlRetrieveMutes, new { pattern = playerPattern, muteType, serverid = CS2_SimpleAdmin.ServerId }); @@ -212,10 +267,20 @@ public async Task ExpireOldMutes() try { await using var connection = await database.GetConnectionAsync(); - - var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode - ? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid" - : "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"; + var sql = ""; + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + sql = CS2_SimpleAdmin.Instance.Config.TimeMode == 1 + ? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime" + : "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration`"; + } + else + { + sql = CS2_SimpleAdmin.Instance.Config.TimeMode == 1 + ? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid" + : "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration` AND server_id = @serverid"; + } await connection.ExecuteAsync(sql, new { CurrentTime = DateTime.UtcNow.ToLocalTime(), serverid = CS2_SimpleAdmin.ServerId }); } diff --git a/Managers/PlayerPenaltyManager.cs b/Managers/PlayerPenaltyManager.cs index 0008342..0c65257 100644 --- a/Managers/PlayerPenaltyManager.cs +++ b/Managers/PlayerPenaltyManager.cs @@ -11,18 +11,18 @@ public enum PenaltyType public class PlayerPenaltyManager { - private static readonly ConcurrentDictionary>> Penalties = + private static readonly ConcurrentDictionary>> Penalties = new(); // Add a penalty for a player - public static void AddPenalty(int slot, PenaltyType penaltyType, DateTime endDateTime, int durationSeconds) + public static void AddPenalty(int slot, PenaltyType penaltyType, DateTime endDateTime, int durationInMinutes) { Penalties.AddOrUpdate(slot, (_) => { - var dict = new Dictionary> + var dict = new Dictionary> { - [penaltyType] = [(endDateTime, durationSeconds)] + [penaltyType] = [(endDateTime, durationInMinutes, false)] }; return dict; }, @@ -30,11 +30,11 @@ public static void AddPenalty(int slot, PenaltyType penaltyType, DateTime endDat { if (!existingDict.TryGetValue(penaltyType, out var value)) { - value = new List<(DateTime, int)>(); + value = new List<(DateTime, int, bool)>(); existingDict[penaltyType] = value; } - value.Add((endDateTime, durationSeconds)); + value.Add((endDateTime, durationInMinutes, false)); return existingDict; }); } @@ -46,6 +46,9 @@ public static bool IsPenalized(int slot, PenaltyType penaltyType) if (!Penalties.TryGetValue(slot, out var penaltyDict) || !penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) return false; //Console.WriteLine($"Found penalties for player with slot {slot} and penalty type {penaltyType}"); + + if (CS2_SimpleAdmin.Instance.Config.TimeMode == 0) + return penaltiesList.Count != 0; var now = DateTime.UtcNow.ToLocalTime(); @@ -53,7 +56,7 @@ public static bool IsPenalized(int slot, PenaltyType penaltyType) foreach (var penalty in penaltiesList.ToList()) { // Check if the penalty is still active - if (penalty.Duration > 0 && now >= penalty.EndDateTime.AddSeconds(penalty.Duration)) + if (penalty.Duration > 0 && now >= penalty.EndDateTime) { //Console.WriteLine($"Removing expired penalty for player with slot {slot} and penalty type {penaltyType}"); penaltiesList.Remove(penalty); // Remove expired penalty @@ -80,7 +83,7 @@ public static bool IsPenalized(int slot, PenaltyType penaltyType) } // Get the end datetime and duration of penalties for a player and penalty type - public static List<(DateTime EndDateTime, int Duration)> GetPlayerPenalties(int slot, PenaltyType penaltyType) + public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, PenaltyType penaltyType) { if (Penalties.TryGetValue(slot, out var penaltyDict) && penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) @@ -113,26 +116,64 @@ public static void RemoveAllPenalties() // Remove all penalties of a selected type from a specific player public static void RemovePenaltiesByType(int slot, PenaltyType penaltyType) { - if (Penalties.TryGetValue(slot, out Dictionary>? penaltyDict) && + if (Penalties.TryGetValue(slot, out var penaltyDict) && penaltyDict.ContainsKey(penaltyType)) { penaltyDict.Remove(penaltyType); } } + + public static void RemovePenaltiesByDateTime(int slot, DateTime dateTime) + { + if (!Penalties.TryGetValue(slot, out var penaltyDict)) return; + + foreach (var penaltiesList in penaltyDict.Values) + { + for (var i = 0; i < penaltiesList.Count; i++) + { + if (penaltiesList[i].EndDateTime != dateTime) continue; + // Create a copy of the penalty + var penalty = penaltiesList[i]; + + // Update the end datetime of the copied penalty to the current datetime + penalty.Passed = true; + + // Replace the original penalty with the modified one + penaltiesList[i] = penalty; + } + } + } // Remove all expired penalties for all players and penalty types public static void RemoveExpiredPenalties() { - var now = DateTime.UtcNow.ToLocalTime(); - foreach (var kvp in Penalties.ToList()) // Use ToList to avoid modification while iterating + if (CS2_SimpleAdmin.Instance.Config.TimeMode == 0) { - var playerSlot = kvp.Key; - var penaltyDict = kvp.Value; + foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating + { + // Remove expired penalties for the player + foreach (var penaltiesList in penaltyDict.Values) + { + penaltiesList.RemoveAll(p => p is { Duration: > 0, Passed: true }); + } + // Remove player slot if no penalties left + if (penaltyDict.Count == 0) + { + Penalties.TryRemove(playerSlot, out _); + } + } + + return; + } + + var now = DateTime.UtcNow.ToLocalTime(); + foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating + { // Remove expired penalties for the player foreach (var penaltiesList in penaltyDict.Values) { - penaltiesList.RemoveAll(p => p.Duration > 0 && now >= p.EndDateTime.AddSeconds(p.Duration).ToLocalTime()); + penaltiesList.RemoveAll(p => p.Duration > 0 && now >= p.EndDateTime); } // Remove player slot if no penalties left diff --git a/VERSION b/VERSION index fb39751..b1109f7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.3d \ No newline at end of file +1.4.4a \ No newline at end of file