mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-10-18 04:47:04 +02:00
Fix unban delay, admin loading, rename persistence; add css_history (1.9.0)
Fixes for issues reported by server owners, plus one new command.
Unbans took up to a minute to apply, and on SQLite never applied at all:
- css_unban only wrote to the database, but the connect check reads an
in-memory cache that syncs on a 61s timer. BanManager.UnbanPlayer now
marks the bans UNBANNED in CacheManager and rebuilds the indexes, and
logs failures instead of swallowing them.
- SQLite has no ON UPDATE CURRENT_TIMESTAMP and the unban/expire queries
never set updated_at, so the delta refresh never saw the change and
the ban stayed active until a plugin reload. Both queries set it now.
- The delta query uses >= so a change written in the same second as the
refresh snapshot is not skipped.
- css_unban issues removeid when UnlockedCommands is on, clearing the
engine-level banid that css_ban adds.
- RebuildIndexes builds fresh dictionaries and swaps them under a lock,
so the game thread cannot read a half-built index now that unbans can
trigger a rebuild off the timer thread.
Admin permissions were intermittently missing after a restart:
- The startup reload fired at a fixed 3s, usually before ServerManager
had resolved ServerId, so the admin query ran with a null server and
returned only global admins, with nothing reloading afterwards.
ServerManager now reloads admins once ServerId is set, and on both of
its failure paths so global admins still load.
- Loading the plugin mid-map (css_plugins load) fires no map start, so
server loading is kicked off after 5s if it has not happened.
- CreateAdminsJsonFile no longer strips permissions from inside the
ToDictionary lambda, where it ran once per name group and seconds
before the new data was loaded. It returns the new cache and
PermissionManager.ApplyAdminCache applies it on the main thread
immediately before AdminManager.LoadAdminData.
- Reloads are serialized with a semaphore and carry a generation, so
overlapping ones cannot race on the JSON files or apply a stale
snapshot after the hibernation cap.
- GetAllPlayersFlags and GetAllGroupsData rethrow instead of returning
an empty list; ReloadAdmins catches, logs and aborts. A transient
database error previously wrote an empty admins.json and stripped
every admin on the server.
Permanent renames did not survive a restart:
- New sa_renames table (migration 017); css_prename upserts or deletes,
and the table is loaded into memory once the server is ready.
- The rename is re-applied on connect before the DatabaseProvider /
CacheManager early return, which previously skipped it whenever the
DB or cache was not ready yet.
- New EventPlayerChangename handler, and both it and OnPlayerInfo
re-check after a short delay instead of comparing names at event
time; the engine may not have written the client's new name yet, and
Rename itself takes 0.4s to settle.
Smaller fixes:
- css_say printed "ADMIN: <text>" with no name. The caller name is
passed to the localizer and sa_adminsay_prefix carries {1} in all 13
languages.
- css_penalties <player> with @css/kick reassigned only the local user
id while every lookup kept using caller.SteamID, so an admin checking
another player always saw their own penalties. It now uses the
target's SteamID, and replies instead of throwing when that player's
connect data has not finished loading.
- css_rename returned early for an admin in silent mode, which skipped
the rename itself rather than just the announcement.
New: css_history <#userid|name|steamid64> [bans|gags|mutes|silences|warns]
(@css/kick) prints every ban, gag, mute, silence and warn with status,
duration, the admin who issued it, the reason and, for lifted bans and
mutes, who lifted it, when and why. A SteamID64 works for players who
are not online. Also available in the admin menu under Players >
History. The data was always there: unban, unmute and expiry only flip
the status column, rows are never deleted.
Build:
- The csproj listed all 32 migration files by hand, so a new migration
silently never reached the output. Replaced with a glob.
- Commands missing from an existing Commands.json are registered under
their default name, so new commands appear without deleting the file.
An empty alias list still disables a command.
This commit is contained in:
parent
cf284bd121
commit
530f60366d
38 changed files with 698 additions and 251 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.ValveConstants.Protobuf;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -339,8 +340,34 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string
|
|||
var sqlUpdateBan = databaseProvider.GetUpdateBanStatusQuery();
|
||||
await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId });
|
||||
}
|
||||
|
||||
// Apply immediately; the periodic cache refresh would otherwise keep rejecting the player for up to a minute
|
||||
CS2_SimpleAdmin.Instance.CacheManager?.SetBanStatus(bansList.Select(b => (int)b.id), Models.BanStatus.UNBANNED);
|
||||
|
||||
// css_ban also issues a native banid when UnlockedCommands is on; clear it so the engine stops
|
||||
// rejecting the player. Done here, after the rows are updated, and for whichever pattern matched them.
|
||||
if (CS2_SimpleAdmin.UnlockedCommands)
|
||||
{
|
||||
var steamIds = bansList
|
||||
.Select(b => (object?)b.player_steamid)
|
||||
.Where(id => id != null)
|
||||
.Select(id => Convert.ToUInt64(id))
|
||||
.Where(id => id > 0)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (steamIds.Count > 0)
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
foreach (var steamId in steamIds)
|
||||
Server.ExecuteCommand($"removeid {new SteamID(steamId).SteamId3}");
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to unban player: {exception}", ex.Message);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players)
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ namespace CS2_SimpleAdmin.Managers;
|
|||
internal class CacheManager: IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
|
||||
private readonly ConcurrentDictionary<ulong, List<BanRecord>> _steamIdIndex = [];
|
||||
private readonly ConcurrentDictionary<uint, List<BanRecord>> _ipIndex = [];
|
||||
// Rebuilt into fresh instances and swapped, so unlocked readers on the game thread never see a half-built index
|
||||
private ConcurrentDictionary<ulong, List<BanRecord>> _steamIdIndex = [];
|
||||
private ConcurrentDictionary<uint, List<BanRecord>> _ipIndex = [];
|
||||
private readonly object _indexLock = new();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
|
||||
private HashSet<uint> _cachedIgnoredIps = [];
|
||||
|
|
@ -178,7 +180,7 @@ internal class CacheManager: IDisposable
|
|||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
||||
FROM `sa_bans` WHERE updated_at >= @lastUpdate OR created >= @lastUpdate ORDER BY updated_at DESC
|
||||
""",
|
||||
new { lastUpdate = lastCheckTime }
|
||||
)).ToList();
|
||||
|
|
@ -221,7 +223,7 @@ internal class CacheManager: IDisposable
|
|||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
FROM `sa_bans` WHERE server_id = @serverId AND (updated_at > @lastUpdate OR created > @lastUpdate) ORDER BY updated_at DESC
|
||||
FROM `sa_bans` WHERE server_id = @serverId AND (updated_at >= @lastUpdate OR created >= @lastUpdate) ORDER BY updated_at DESC
|
||||
""",
|
||||
new { serverId = CS2_SimpleAdmin.ServerId, lastUpdate = lastCheckTime }
|
||||
)).ToList();
|
||||
|
|
@ -294,7 +296,7 @@ internal class CacheManager: IDisposable
|
|||
.GroupBy(x => x.Address)
|
||||
.Select(g =>
|
||||
{
|
||||
var latest = g.MaxBy(x => x.Used_at);
|
||||
var latest = g.MaxBy(x => x.Used_at)!; // group is never empty
|
||||
return new IpRecord(
|
||||
g.Key,
|
||||
latest.Used_at,
|
||||
|
|
@ -353,8 +355,36 @@ internal class CacheManager: IDisposable
|
|||
/// </summary>
|
||||
private void RebuildIndexes()
|
||||
{
|
||||
_steamIdIndex.Clear();
|
||||
_ipIndex.Clear();
|
||||
// SetBanStatus (unban thread) and RefreshCacheAsync (timer thread) can both get here
|
||||
lock (_indexLock)
|
||||
{
|
||||
RebuildIndexesCore();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the status of the given bans in the cache and rebuilds the indexes,
|
||||
/// so unbans take effect immediately instead of waiting for the next refresh.
|
||||
/// </summary>
|
||||
/// <param name="banIds">Ban ids to update.</param>
|
||||
/// <param name="status">New status.</param>
|
||||
public void SetBanStatus(IEnumerable<int> banIds, BanStatus status)
|
||||
{
|
||||
var changed = false;
|
||||
foreach (var id in banIds)
|
||||
{
|
||||
if (!_banCache.TryGetValue(id, out var ban) || ban.StatusEnum == status) continue;
|
||||
_banCache[id] = ban with { Status = status.ToString() };
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) RebuildIndexes();
|
||||
}
|
||||
|
||||
private void RebuildIndexesCore()
|
||||
{
|
||||
var steamIdIndex = new ConcurrentDictionary<ulong, List<BanRecord>>();
|
||||
var ipIndex = new ConcurrentDictionary<uint, List<BanRecord>>();
|
||||
|
||||
// Optimization: Cache config value to avoid repeated property access
|
||||
var banType = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType;
|
||||
|
|
@ -369,10 +399,10 @@ internal class CacheManager: IDisposable
|
|||
if (ban.PlayerSteamId.HasValue)
|
||||
{
|
||||
var steamId = ban.PlayerSteamId.Value;
|
||||
if (!_steamIdIndex.TryGetValue(steamId, out var steamList))
|
||||
if (!steamIdIndex.TryGetValue(steamId, out var steamList))
|
||||
{
|
||||
steamList = new List<BanRecord>();
|
||||
_steamIdIndex[steamId] = steamList;
|
||||
steamIdIndex[steamId] = steamList;
|
||||
}
|
||||
steamList.Add(ban);
|
||||
}
|
||||
|
|
@ -381,14 +411,18 @@ internal class CacheManager: IDisposable
|
|||
if (checkIpBans && !string.IsNullOrEmpty(ban.PlayerIp) &&
|
||||
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
|
||||
{
|
||||
if (!_ipIndex.TryGetValue(ipUInt, out var ipList))
|
||||
if (!ipIndex.TryGetValue(ipUInt, out var ipList))
|
||||
{
|
||||
ipList = new List<BanRecord>();
|
||||
_ipIndex[ipUInt] = ipList;
|
||||
ipIndex[ipUInt] = ipList;
|
||||
}
|
||||
ipList.Add(ban);
|
||||
}
|
||||
}
|
||||
|
||||
// Reference swap is atomic; readers finish on the old index or start on the complete new one
|
||||
Volatile.Write(ref _steamIdIndex, steamIdIndex);
|
||||
Volatile.Write(ref _ipIndex, ipIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
|
||||
return [];
|
||||
throw; // an empty list would strip every admin; abort the reload and keep the current permissions
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,9 +111,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load groups from database! {exception}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -151,7 +150,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// Creates a JSON file containing admins data asynchronously.
|
||||
/// </summary>
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||
public async Task CreateAdminsJsonFile()
|
||||
public async Task<List<(SteamID steamId, DateTime? ends, List<string> flags)>> CreateAdminsJsonFile()
|
||||
{
|
||||
List<(ulong identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
|
||||
var validPlayers = allPlayers
|
||||
|
|
@ -190,47 +189,11 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
return acc;
|
||||
});
|
||||
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
var keysToRemove = new List<SteamID>();
|
||||
|
||||
foreach (var steamId in AdminCache.Keys.ToList())
|
||||
{
|
||||
var data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data != null)
|
||||
{
|
||||
var flagsArray = AdminCache[steamId].Flags.ToArray();
|
||||
AdminManager.RemovePlayerPermissions(steamId, flagsArray);
|
||||
AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray);
|
||||
}
|
||||
|
||||
keysToRemove.Add(steamId);
|
||||
}
|
||||
|
||||
foreach (var steamId in keysToRemove)
|
||||
{
|
||||
if (!AdminCache.TryRemove(steamId, out _)) continue;
|
||||
|
||||
var data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data == null) continue;
|
||||
if (data.Flags.Count != 0 && data.Groups.Count != 0) continue;
|
||||
|
||||
AdminManager.ClearPlayerPermissions(steamId);
|
||||
AdminManager.RemovePlayerAdminData(steamId);
|
||||
}
|
||||
|
||||
foreach (var player in group)
|
||||
{
|
||||
if (SteamID.TryParse(player.identity.ToString(), out var steamId) && steamId != null)
|
||||
{
|
||||
AdminCache.TryAdd(steamId, (player.ends, player.flags));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return consolidatedData;
|
||||
});
|
||||
|
||||
var newCache = validPlayers.Select(player => (new SteamID(player.identity), player.ends, player.flags)).ToList();
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
|
|
@ -240,6 +203,39 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
var json = JsonSerializer.Serialize(jsonData, options);
|
||||
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
|
||||
await File.WriteAllTextAsync(filePath, json);
|
||||
|
||||
return newCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes permissions of all previously cached admins and replaces the cache with the given admins.
|
||||
/// Must run on the main thread, right before AdminManager.LoadAdminData so admins are not left stripped.
|
||||
/// </summary>
|
||||
/// <param name="admins">Admins loaded from the database.</param>
|
||||
public static void ApplyAdminCache(List<(SteamID steamId, DateTime? ends, List<string> flags)> admins)
|
||||
{
|
||||
foreach (var steamId in AdminCache.Keys.ToList())
|
||||
{
|
||||
if (!AdminCache.TryRemove(steamId, out var cached)) continue;
|
||||
|
||||
var data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data == null) continue;
|
||||
|
||||
var flagsArray = cached.Flags.ToArray();
|
||||
AdminManager.RemovePlayerPermissions(steamId, flagsArray);
|
||||
AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray);
|
||||
|
||||
data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data == null || (data.Flags.Count != 0 && data.Groups.Count != 0)) continue;
|
||||
|
||||
AdminManager.ClearPlayerPermissions(steamId);
|
||||
AdminManager.RemovePlayerAdminData(steamId);
|
||||
}
|
||||
|
||||
foreach (var (steamId, ends, flags) in admins)
|
||||
{
|
||||
AdminCache.TryAdd(steamId, (ends, flags));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Globalization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
|
|
@ -44,6 +45,11 @@ internal class PlayerManager
|
|||
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var ipAddress = player.IpAddress?.Split(":")[0];
|
||||
|
||||
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(steamId, out var renamedTo))
|
||||
{
|
||||
player.Rename(renamedTo);
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null || CS2_SimpleAdmin.Instance.CacheManager == null) return;
|
||||
|
||||
Task.Run(async () =>
|
||||
|
|
@ -219,10 +225,92 @@ internal class PlayerManager
|
|||
_loadPlayerSemaphore.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
|
||||
/// <summary>
|
||||
/// Returns every ban, gag/mute/silence and warn ever recorded for a SteamID, newest first.
|
||||
/// Rows carry: type, status, reason, duration, created, ends, admin_name, lift_reason, lift_date, lift_admin.
|
||||
/// </summary>
|
||||
/// <param name="steamId">SteamID64 of the player, online or not.</param>
|
||||
/// <param name="type">Optional filter: bans, gags, mutes, silences or warns.</param>
|
||||
public async Task<List<dynamic>> GetPenaltyHistory(ulong steamId, string? type = null)
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return [];
|
||||
|
||||
try
|
||||
{
|
||||
player.Rename(name);
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
var sql = CS2_SimpleAdmin.DatabaseProvider.GetPenaltyHistoryQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
var rows = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId })).ToList();
|
||||
|
||||
// Filter word is the plural (bans, gags, mutes, silences, warns); rows carry BAN/GAG/MUTE/SILENCE/WARN
|
||||
if (type != null)
|
||||
rows.RemoveAll(r => !type.Equals((string)r.type + "s", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return rows;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load penalty history: {exception}", ex.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dapper gives DateTime on MySQL but can give a string on SQLite; accept both.
|
||||
/// </summary>
|
||||
internal static DateTime? ToDateTime(object? value) =>
|
||||
value switch
|
||||
{
|
||||
DateTime d => d,
|
||||
string s when DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed) => parsed,
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Loads permanent renames from the database into memory.
|
||||
/// </summary>
|
||||
public async Task LoadRenamedPlayers()
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
var rows = await connection.QueryAsync<(long steamId, string name)>(CS2_SimpleAdmin.DatabaseProvider.GetRenamesQuery());
|
||||
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
foreach (var (steamId, name) in rows)
|
||||
CS2_SimpleAdmin.RenamedPlayers[(ulong)steamId] = name;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load renames: {exception}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists or removes a permanent rename (css_prename) for a player.
|
||||
/// </summary>
|
||||
/// <param name="steamId">SteamID64 of the player.</param>
|
||||
/// <param name="name">Forced name, or null/empty to remove the rename.</param>
|
||||
public async Task SaveRenamedPlayer(ulong steamId, string? name)
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
await connection.ExecuteAsync(CS2_SimpleAdmin.DatabaseProvider.GetDeleteRenameQuery(), new { steamId });
|
||||
else
|
||||
await connection.ExecuteAsync(CS2_SimpleAdmin.DatabaseProvider.GetUpsertRenameQuery(), new { steamId, name });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to save rename: {exception}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ public class ServerManager
|
|||
if (_getIpTryCount > 32 && isInvalidIp)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load server data - can't fetch ip address!");
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null); // still load global admins
|
||||
_ = CS2_SimpleAdmin.PlayerManager.LoadRenamedPlayers();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -103,18 +105,25 @@ public class ServerManager
|
|||
CS2_SimpleAdmin.ServerId = serverId;
|
||||
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
|
||||
|
||||
CS2_SimpleAdmin.ServerLoaded = true;
|
||||
CS2_SimpleAdmin.ServerLoaded = true;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.CacheManager != null)
|
||||
{
|
||||
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
|
||||
}
|
||||
|
||||
// Admins are loaded only now, once ServerId is known, so server-specific admins are included
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogCritical("Unable to create or get server_id: " + ex.Message);
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null); // still load global admins
|
||||
}
|
||||
|
||||
// Renames are server-independent, so load them even when the server row could not be resolved
|
||||
await CS2_SimpleAdmin.PlayerManager.LoadRenamedPlayers();
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.EnableMetrics)
|
||||
{
|
||||
var queryString = $"?address={address}&hostname={hostname}";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue