mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-17 18:39:07 +00:00
Compare commits
6 Commits
build-1.7.
...
build-1.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
665962565e | ||
|
|
c2e8b4a898 | ||
|
|
9723a4faee | ||
|
|
4865b76262 | ||
|
|
0dded66e5d | ||
|
|
038641dbdf |
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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);
|
||||
@@ -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`);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()]);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.8-beta-4
|
||||
1.7.8-beta-8
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user