Compare commits

...

6 Commits

Author SHA1 Message Date
Dawid Bepierszcz
665962565e Update ban logic and StatusBlocker plugin version
Refactored IP ban checking logic in CacheManager for improved accuracy and maintainability. Replaced StatusBlocker plugin binaries with v1.1.3 for both Linux and Windows.

EOL, no more new features
2025-12-13 16:54:33 +01:00
Dawid Bepierszcz
c2e8b4a898 Refactor player cache and ban checks, update version
Improves player cache handling and ban status checks for race condition safety. Removes unused GodPlayers logic and related event handler. Refactors event handlers for disconnect and team changes, and fixes warn reason field naming. Updates version to 1.7.8-beta-7.
2025-12-02 17:37:14 +01:00
Dawid Bepierszcz
9723a4faee :(
:(
2025-11-13 01:40:50 +01:00
Dawid Bepierszcz
4865b76262 Improve ban cache sync and update dependencies
Enhanced CacheManager to use database time for ban updates, improving multi-server consistency and handling of status changes. Increased PlayerManager's semaphore limit and improved player/bans refresh logic to ensure status changes are detected even when the server is empty. Updated author and version metadata, commented out duplicate event registration, and bumped CounterStrikeSharp.API dependency to 1.0.346.
2025-11-13 01:33:38 +01:00
Dawid Bepierszcz
0dded66e5d Fix closure issues in menus and update dependencies
Captured player and duration variables in menu callbacks to prevent closure-related bugs. Updated package versions in project files and bumped plugin version to 1.7.8-beta-5. Improved player validation and message localization logic.
2025-11-06 02:24:43 +01:00
Dawid Bepierszcz
038641dbdf Comment out MySQL index migration and remove Sqlite optimization
Commented out all index creation statements in the MySQL migration 016 for table and index optimization. Removed the corresponding Sqlite migration 016 entirely. Also replaced TRUNCATE TABLE with DELETE FROM in Sqlite migration 013 for sa_players_ips to improve compatibility.
2025-10-30 18:17:47 +01:00
24 changed files with 289 additions and 261 deletions

View File

@@ -21,13 +21,12 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
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 & Dliix66";
public override string ModuleVersion => "1.7.8-beta-4";
public override string ModuleAuthor => "daffyy";
public override string ModuleVersion => "1.7.8-beta-8";
public override void Load(bool hotReload)
{
Instance = this;
if (hotReload)
{
ServerLoaded = false;
@@ -47,7 +46,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
CachedPlayers.Clear();
BotPlayers.Clear();
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected && !p.IsHLTV).ToArray())
{
if (!player.IsBot)
PlayerManager.LoadPlayerData(player, true);
@@ -261,6 +260,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
CacheManager = null;
PlayersTimer?.Kill();
PlayersTimer = null;
UnregisterEvents();
if (hotReload)

View File

@@ -19,16 +19,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340">
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346">
<PrivateAssets>none</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="MySqlConnector" Version="2.5.0-beta.1" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="ZLinq" Version="1.5.2" />
<PackageReference Include="System.Linq.Async" Version="7.0.0-preview.1.g24680b5469" />
<PackageReference Include="ZLinq" Version="1.5.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,33 +1,33 @@
-- Migration 016: Optimize tables and indexes
-- Add proper indexes for all tables to improve query performance
-- -- Migration 016: Optimize tables and indexes
-- -- Add proper indexes for all tables to improve query performance
-- Optimize sa_players_ips table indexes
-- Add index on used_at for efficient date-based queries
ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
-- -- Optimize sa_players_ips table indexes
-- -- Add index on used_at for efficient date-based queries
-- ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
-- Optimize sa_bans table indexes
-- Add composite indexes for common query patterns
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
-- -- Optimize sa_bans table indexes
-- -- Add composite indexes for common query patterns
-- CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
-- CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
-- CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
-- CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
-- CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
-- Optimize sa_admins table indexes
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
-- -- Optimize sa_admins table indexes
-- CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
-- CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
-- CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
-- Optimize sa_mutes table indexes (in addition to migration 014)
-- Add index for expire queries
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
-- -- Optimize sa_mutes table indexes (in addition to migration 014)
-- -- Add index for expire queries
-- CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
-- CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
-- CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
-- Optimize sa_warns table indexes (if exists)
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
-- -- Optimize sa_warns table indexes (if exists)
-- CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
-- CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
-- CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
-- Add index on sa_servers for faster lookups
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
-- -- Add index on sa_servers for faster lookups
-- CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);

View File

@@ -1,4 +1,4 @@
TRUNCATE TABLE `sa_players_ips`;
DELETE FROM sa_players_ips;
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL;
CREATE INDEX IF NOT EXISTS `idx_sa_players_ips_used_at` ON `sa_players_ips` (`used_at` DESC);

View File

@@ -1,33 +0,0 @@
-- Migration 016: Optimize tables and indexes
-- Add proper indexes for all tables to improve query performance
-- Optimize sa_players_ips table indexes
-- Add index on used_at for efficient date-based queries
CREATE INDEX IF NOT EXISTS `idx_used_at` ON `sa_players_ips` (`used_at` DESC);
-- Optimize sa_bans table indexes
-- Add composite indexes for common query patterns
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
-- Optimize sa_admins table indexes
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
-- Optimize sa_mutes table indexes (in addition to migration 014)
-- Add index for expire queries
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
-- Optimize sa_warns table indexes (if exists)
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
-- Add index on sa_servers for faster lookups
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);

View File

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

View File

@@ -23,7 +23,7 @@ public partial class CS2_SimpleAdmin
{
RegisterListener<Listeners.OnMapStart>(OnMapStart);
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
if (Config.OtherSettings.UserMessageGagChatType)
@@ -77,7 +77,7 @@ public partial class CS2_SimpleAdmin
new ServerManager().LoadServerData();
}
[GameEventHandler(HookMode.Pre)]
[GameEventHandler]
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
{
if (@event.Reason is 149 or 6)
@@ -91,14 +91,15 @@ public partial class CS2_SimpleAdmin
if (player == null || !player.IsValid || player.IsHLTV)
return HookResult.Continue;
BotPlayers.Remove(player);
CachedPlayers.Remove(player);
CachedPlayers.Remove(player);
BotPlayers.Remove(player);
SilentPlayers.Remove(player.Slot);
if (player.IsBot)
{
return HookResult.Continue;
}
#if DEBUG
Logger.LogCritical("[OnClientDisconnect] After Check");
@@ -176,6 +177,9 @@ public partial class CS2_SimpleAdmin
if (player == null || !player.IsValid || player.IsBot)
return;
if (!CachedPlayers.Contains(player))
CachedPlayers.Add(player);
PlayerManager.LoadPlayerData(player);
}
@@ -458,28 +462,11 @@ 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)
{
@@ -512,17 +499,13 @@ public partial class CS2_SimpleAdmin
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
{
var player = @event.Userid;
if (player == null || !player.IsValid || player.IsBot)
if (player == null || !player.IsValid || player.IsBot || !SilentPlayers.Contains(player.Slot))
return HookResult.Continue;
if (!SilentPlayers.Contains(player.Slot))
return HookResult.Continue;
if (@event is { Oldteam: <= 1, Team: >= 1 })
{
SilentPlayers.Remove(player.Slot);
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
}
if (@event is not { Oldteam: <= 1, Team: >= 1 }) return HookResult.Continue;
SilentPlayers.Remove(player.Slot);
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
return HookResult.Continue;
}

View File

@@ -64,7 +64,7 @@ internal static class Helper
public static List<CCSPlayerController> GetValidPlayers()
{
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().ToList();
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected).ToList();
}
public static List<CCSPlayerController> GetValidPlayersWithBots()
@@ -583,27 +583,32 @@ internal static class Helper
}
public static void DisplayCenterMessage(
CCSPlayerController player,
string messageKey,
string? callerName = null,
CCSPlayerController player,
string messageKey,
string? callerName = null,
params object[] messageArgs)
{
if (CS2_SimpleAdmin._localizer == null) return;
// Determine the localized message key
var localizedMessageKey = $"{messageKey}";
var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();
// Replace placeholder based on showActivityType
for (var i = 0; i < formattedMessageArgs.Length; i++)
{
var arg = formattedMessageArgs[i];
var arg = formattedMessageArgs[i]; // Convert argument to string if not null
// Replace "CALLER" placeholder in the argument string
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
{
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
2 => arg.Replace("CALLER", callerName ?? "Console"),
_ => arg
};
}
// Print the localized message to the center of the screen for the player
using (new WithTemporaryCulture(player.GetLanguage()))
{
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);

View File

@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Models;
using Dapper;
using Microsoft.Extensions.Logging;
using ZLinq;
namespace CS2_SimpleAdmin.Managers;
@@ -16,6 +17,7 @@ internal class CacheManager: IDisposable
private HashSet<uint> _cachedIgnoredIps = [];
private DateTime _lastUpdateTime = DateTime.MinValue;
private DateTime? _lastDatabaseTime = null; // Track actual time from database
private bool _isInitialized;
private bool _disposed;
@@ -156,13 +158,20 @@ internal class CacheManager: IDisposable
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
IEnumerable<BanRecord> updatedBans;
// Get current time from database in local timezone (CURRENT_TIMESTAMP uses session timezone, not UTC)
var currentDatabaseTime = await connection.QueryFirstAsync<DateTime>("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<int>? allIds = null;
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
updatedBans = (await connection.QueryAsync<BanRecord>(
// 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<BanRecord>(
"""
SELECT id AS Id,
player_name AS PlayerName,
@@ -171,33 +180,68 @@ internal class CacheManager: IDisposable
status AS Status
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime }
));
new { lastUpdate = lastCheckTime }
)).ToList();
// Detect changes: new bans or status changes
var updatedList = new List<BanRecord>();
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);
}
}
// Optimization: Only fetch all IDs if there were updates
var updatedList = updatedBans.ToList();
if (updatedList.Count > 0)
{
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
}
updatedBans = updatedList;
// Update last check time to current database time
_lastDatabaseTime = currentDatabaseTime;
}
else
{
updatedBans = (await connection.QueryAsync<BanRecord>(
// 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<BanRecord>(
"""
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
FROM `sa_bans` WHERE server_id = @serverId AND (updated_at > @lastUpdate OR created > @lastUpdate) ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
));
new { serverId = CS2_SimpleAdmin.ServerId, lastUpdate = lastCheckTime }
)).ToList();
// Detect changes: new bans or status changes
var updatedList = new List<BanRecord>();
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);
}
}
// Optimization: Only fetch all IDs if there were updates
var updatedList = updatedBans.ToList();
if (updatedList.Count > 0)
{
allIds = (await connection.QueryAsync<int>(
@@ -206,6 +250,9 @@ 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
@@ -276,16 +323,19 @@ internal class CacheManager: IDisposable
}
// Update cache with new/modified bans
var hasUpdates = false;
var needsRebuild = 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;
}
// Always rebuild indexes if there were any updates
// This ensures status changes (ACTIVE -> UNBANNED) are reflected
if (hasUpdates)
// Rebuild indexes if there were updates or status changes
if (updatedBans.Any() || needsRebuild)
{
RebuildIndexes();
}
@@ -436,32 +486,41 @@ internal class CacheManager: IDisposable
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
if (record != null)
{
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
(!record.PlayerSteamId.HasValue))
// Double-check the ban is still active in cache (handle race conditions)
if (_banCache.TryGetValue(record.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
{
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
(!record.PlayerSteamId.HasValue))
{
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
}
return true;
}
return true;
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
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;
}
@@ -547,53 +606,54 @@ internal class CacheManager: IDisposable
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
if (activeBan != null)
{
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp))
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
return true;
// 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)
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
return false;
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
return false;
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
if (_cachedIgnoredIps.Contains(ipUInt))
return false;
if (!_ipIndex.TryGetValue(ipUInt, out var ipBanRecords))
return false;
var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
if (ipBan == null)
return false;
if (!_banCache.TryGetValue(ipBan.Id, out var cachedIpBan) || cachedIpBan.StatusEnum != BanStatus.ACTIVE)
return false;
var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans;
if (expireOldIpBans > 0)
{
var cutoff = Time.ActualDateTime().AddDays(-expireOldIpBans);
if (ipBan.Created < cutoff)
return false;
}
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
{
if (!_cachedIgnoredIps.Contains(ipAsUint))
{
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
}
}
if (string.IsNullOrEmpty(ipBan.PlayerName))
ipBan.PlayerName = playerName;
foreach (var ipRecord in ipData)
{
if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
continue;
ipBan.PlayerSteamId ??= steamId;
if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords))
continue;
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
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;
return true;
}
/// <summary>

View File

@@ -13,7 +13,7 @@ namespace CS2_SimpleAdmin.Managers;
internal class PlayerManager
{
private readonly SemaphoreSlim _loadPlayerSemaphore = new(5);
private readonly SemaphoreSlim _loadPlayerSemaphore = new(10);
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
/// <summary>
@@ -51,7 +51,6 @@ internal class PlayerManager
try
{
await _loadPlayerSemaphore.WaitAsync();
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
{
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
@@ -81,13 +80,7 @@ internal class PlayerManager
{
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
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)
{
@@ -254,6 +247,7 @@ internal class PlayerManager
_loadPlayerSemaphore.Release();
}
});
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
{
player.Rename(name);
@@ -287,9 +281,6 @@ 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)
@@ -364,7 +355,12 @@ internal class PlayerManager
foreach (var player in bannedPlayers)
{
if (!player.UserId.HasValue) continue;
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
await Server.NextWorldUpdateAsync(() =>
{
if (Helper.GetPlayerFromSteamid64(player.SteamID) != null)
Helper.KickPlayer((int)player.UserId,
NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
});
}
}

View File

@@ -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",
muteReason = reason,
warnReason = reason,
duration = time,
ends = futureTime,
created = now,
@@ -42,7 +42,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
return warnId;
}
catch
catch(Exception e)
{
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",
muteReason = reason,
warnReason = reason,
duration = time,
ends = futureTime,
created = now,

View File

@@ -57,8 +57,9 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, player));
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, capturedPlayer));
}
return slapMenu.WithBackButton();
@@ -112,12 +113,13 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
slayMenu.AddOption(playerName, _ =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Slay(admin, player);
CS2_SimpleAdmin.Slay(admin, capturedPlayer);
}
});
}
@@ -139,13 +141,14 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, capturedPlayer, "Kick", PenaltyType.Kick,
(_, _, reason) =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
CS2_SimpleAdmin.Instance.Kick(admin, capturedPlayer, reason, admin.PlayerName);
}
}));
}
@@ -167,14 +170,15 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Warn",
(_, _, duration) => CreateReasonMenu(admin, player, "Warn", PenaltyType.Warn,
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Warn",
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Warn", PenaltyType.Warn,
(_, _, reason) =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
CS2_SimpleAdmin.Instance.Warn(admin, capturedPlayer, duration, reason, admin.PlayerName);
}
})));
}
@@ -196,14 +200,15 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Ban",
(_, _, duration) => CreateReasonMenu(admin, player, "Ban", PenaltyType.Ban,
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Ban",
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Ban", PenaltyType.Ban,
(_, _, reason) =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
CS2_SimpleAdmin.Instance.Ban(admin, capturedPlayer, duration, reason, admin.PlayerName);
}
})));
}
@@ -225,14 +230,15 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Gag",
(_, _, duration) => CreateReasonMenu(admin, player, "Gag", PenaltyType.Gag,
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Gag",
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Gag", PenaltyType.Gag,
(_, _, reason) =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
CS2_SimpleAdmin.Instance.Gag(admin, capturedPlayer, duration, reason);
}
})));
}
@@ -254,14 +260,15 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Mute",
(_, _, duration) => CreateReasonMenu(admin, player, "Mute", PenaltyType.Mute,
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Mute",
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Mute", PenaltyType.Mute,
(_, _, reason) =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
CS2_SimpleAdmin.Instance.Mute(admin, capturedPlayer, duration, reason);
}
})));
}
@@ -283,14 +290,15 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Silence",
(_, _, duration) => CreateReasonMenu(admin, player, "Silence", PenaltyType.Silence,
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Silence",
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Silence", PenaltyType.Silence,
(_, _, reason) =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
CS2_SimpleAdmin.Instance.Silence(admin, capturedPlayer, duration, reason);
}
})));
}
@@ -312,8 +320,9 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, player));
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, capturedPlayer));
}
return teamMenu.WithBackButton();
@@ -488,8 +497,9 @@ public abstract class BasicMenu
foreach (var player in players)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, player));
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, capturedPlayer));
}
return addAdminMenu.WithBackButton();
@@ -539,19 +549,20 @@ public abstract class BasicMenu
var localizer = CS2_SimpleAdmin._localizer;
var removeAdminMenu = new MenuBuilder("sa_admin_remove", admin, localizer);
var adminPlayers = Helper.GetValidPlayers().Where(p =>
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
p != admin &&
var adminPlayers = Helper.GetValidPlayers().Where(p =>
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
p != admin &&
admin.CanTarget(p));
foreach (var player in adminPlayers)
{
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
var capturedPlayer = player; // Capture to local variable to avoid closure issue
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
removeAdminMenu.AddOption(playerName, _ =>
{
if (player.IsValid)
if (capturedPlayer.IsValid)
{
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, capturedPlayer.SteamID.ToString());
}
});
}
@@ -583,10 +594,10 @@ public abstract class BasicMenu
/// <param name="admin">The admin player selecting duration.</param>
/// <param name="player">The target player for the penalty.</param>
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
/// <param name="onSelectAction">Callback function that returns the next menu when duration is selected.</param>
/// <returns>A MenuBuilder instance for the duration menu.</returns>
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
Func<CCSPlayerController, CCSPlayerController, int, MenuBuilder> onSelectAction)
{
var localizer = CS2_SimpleAdmin._localizer;
@@ -614,10 +625,8 @@ public abstract class BasicMenu
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
{
durationMenu.AddOption(durationItem.Name, _ =>
{
onSelectAction(admin, player, durationItem.Duration);
});
var capturedDuration = durationItem.Duration; // Capture to avoid closure issue
durationMenu.AddSubMenu(durationItem.Name, () => onSelectAction(admin, player, capturedDuration));
}
return durationMenu.WithBackButton();

View File

@@ -8,12 +8,28 @@ public static class DurationMenu
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
{
var menu = AdminMenu.CreateMenu(menuName);
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
if (menu == null)
return;
var durations = CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations;
// Capture admin and player to avoid closure issues
var capturedAdmin = admin;
var capturedPlayer = player;
var capturedAction = onSelectAction;
foreach (var durationItem in durations)
{
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
var duration = durationItem.Duration; // Capture in local variable
var name = durationItem.Name;
menu.AddMenuOption(name, (controller, option) =>
{
capturedAction(capturedAdmin, capturedPlayer, duration);
});
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
AdminMenu.OpenMenu(admin, menu);
}
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)

View File

@@ -49,25 +49,27 @@ public static class ManagePlayersMenu
if (AdminManager.CommandIsOverriden("css_warn")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, WarnMenu))));
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_warn"] ?? "Warn"}: {p.PlayerName}", p, WarnMenu))));
if (hasBan)
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player, BanMenu))));
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () =>
PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (a, p) =>
DurationMenu.OpenMenu(a, $"{localizer?["sa_ban"] ?? "Ban"}: {p.PlayerName}", p, BanMenu))));
if (hasChat)
{
if (AdminManager.CommandIsOverriden("css_gag")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, GagMenu))));
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_gag"] ?? "Gag"}: {p.PlayerName}", p, GagMenu))));
if (AdminManager.CommandIsOverriden("css_mute")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player, MuteMenu))));
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_mute"] ?? "Mute"}: {p.PlayerName}", p, MuteMenu))));
if (AdminManager.CommandIsOverriden("css_silence")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, SilenceMenu))));
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_silence"] ?? "Silence"}: {p.PlayerName}", p, SilenceMenu))));
}
if (AdminManager.CommandIsOverriden("css_team")
@@ -159,22 +161,9 @@ public static class ManagePlayersMenu
{
if (player is { IsValid: true })
Ban(admin, player, duration, reason);
CS2_SimpleAdmin.MenuApi?.CloseMenu(admin);
});
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player?.PlayerName}");
//
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons)
// {
// menu?.AddMenuOption(option, (_, _) =>
// {
// if (player is { IsValid: true })
// Ban(admin, player, duration, option);
// });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void Ban(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)

View File

@@ -41,11 +41,12 @@ public static class PlayersMenu
continue;
var enabled = admin.CanTarget(player);
var capturedPlayer = player; // Capture in local variable to avoid closure issues
if (optionName != null)
menu?.AddMenuOption(optionName, (_, _) =>
menu?.AddMenuOption(optionName, (controller, option) =>
{
if (player != null) onSelectAction.Invoke(admin, player);
if (capturedPlayer != null) onSelectAction.Invoke(admin, capturedPlayer);
},
!enabled);
}

View File

@@ -24,7 +24,10 @@ 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; }

View File

@@ -1 +1 @@
1.7.8-beta-4
1.7.8-beta-8

View File

@@ -40,7 +40,6 @@ public partial class CS2_SimpleAdmin
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
// Player Management
private static readonly HashSet<int> GodPlayers = [];
internal static readonly HashSet<int> SilentPlayers = [];
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
</ItemGroup>
</Project>

View File

@@ -14,7 +14,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
</ItemGroup>
<ItemGroup>