1.7.7-alpha

- Fixed (@all,@team,@lifestate) spam with discord webhook
- Currently `small` actions for ban optimizations
This commit is contained in:
Dawid Bepierszcz 2025-05-20 23:51:59 +02:00
parent b6c876d709
commit f69f1277f8
20 changed files with 460 additions and 241 deletions

View file

@ -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();

View file

@ -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<int, BanRecord> _banCache = new();
private readonly ConcurrentDictionary<ulong, (HashSet<string> 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<BanRecord>(
"""
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<string>(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<BanRecord>(
"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<string>(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<string>(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<BanRecord> GetAllBans() => _banCache.Values.ToList();
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList();
public List<BanRecord> 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);
}
}

View file

@ -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<object>();
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 });

View file

@ -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<int>(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<int>(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);
}
}

View file

@ -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<bool>())
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)
{