Compare commits

...

7 Commits

Author SHA1 Message Date
Dollan
152d47880d Merge d4e12fadde into f654d6b085 2025-05-21 21:12:52 -04:00
Dawid Bepierszcz
f654d6b085 1.7.7-alpha-connection-fix
- Fixed lag when player connecting
- Added command to force refresh ban cache (css_reloadbans)
2025-05-21 13:11:01 +02:00
Dawid Bepierszcz
676a18d9b4 1.7.7-alpha-associated-accounts (missing migration) 2025-05-21 03:13:05 +02:00
Dawid Bepierszcz
d75a092047 1.7.7-alpha-associated-accounts
- Added possibility to view player associated account (by ip)
2025-05-21 03:08:02 +02:00
Dollan
d4e12fadde Merge branch 'daffyyyy:main' into main 2025-03-27 12:50:43 +01:00
Dollan
bedcfd0358 Merge branch 'daffyyyy:main' into main 2025-03-21 12:39:51 +01:00
Dollan
0c747e3e18 Discord webhook on detection 2025-02-28 20:44:05 +01:00
25 changed files with 206 additions and 66 deletions

View File

@@ -40,10 +40,10 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
var playerManager = new PlayerManager();
Parallel.ForEach(Helper.GetValidPlayers(), player =>
foreach (var player in Helper.GetValidPlayers())
{
playerManager.LoadPlayerData(player);
});
};
});
}
_cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>(GameData.GetSignature("CBasePlayerController_SetPawn"));

View File

@@ -29,6 +29,9 @@
<None Update="Database\Migrations\012_AddUpdatedAtColumnToSaBansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\013_AddNameColumnToSaPlayerIpsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>

View File

@@ -34,6 +34,7 @@ public static class RegisterCommands
new CommandMapping("css_addgroup", CS2_SimpleAdmin.Instance.OnAddGroup),
new CommandMapping("css_delgroup", CS2_SimpleAdmin.Instance.OnDelGroupCommand),
new CommandMapping("css_reloadadmins", CS2_SimpleAdmin.Instance.OnRelAdminCommand),
new CommandMapping("css_reloadbans", CS2_SimpleAdmin.Instance.OnRelBans),
new CommandMapping("css_hide", CS2_SimpleAdmin.Instance.OnHideCommand),
new CommandMapping("css_hidecomms", CS2_SimpleAdmin.Instance.OnHideCommsCommand),
new CommandMapping("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand),
@@ -122,6 +123,7 @@ public static class RegisterCommands
{ "css_addgroup", new Command { Aliases = ["css_addgroup"] } },
{ "css_delgroup", new Command { Aliases = ["css_delgroup"] } },
{ "css_reloadadmins", new Command { Aliases = ["css_reloadadmins"] } },
{ "css_reloadbans", new Command { Aliases = ["css_reloadbans"] } },
{ "css_hide", new Command { Aliases = ["css_hide", "css_stealth"] } },
{ "css_hidecomms", new Command { Aliases = ["css_hidecomms"] } },
{ "css_who", new Command { Aliases = ["css_who"] } },

View File

@@ -386,6 +386,16 @@ public partial class CS2_SimpleAdmin
command.ReplyToCommand("Reloaded sql admins and groups");
}
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
[RequiresPermissions("@css/root")]
public void OnRelBans(CCSPlayerController? caller, CommandInfo command)
{
if (Database == null) return;
_ = Instance.CacheManager.ForceReInitializeCacheAsync();
command.ReplyToCommand("Reloaded bans");
}
public void ReloadAdmins(CCSPlayerController? caller)
{
@@ -509,6 +519,10 @@ public partial class CS2_SimpleAdmin
printMethod($"• Total Mutes: \"{playerInfo.TotalMutes}\"");
printMethod($"• Total Silences: \"{playerInfo.TotalSilences}\"");
printMethod($"• Total Warns: \"{playerInfo.TotalWarns}\"");
var chunkedAccounts = playerInfo.AccountsAssociated.ChunkBy(3).ToList();
foreach (var chunk in chunkedAccounts)
printMethod($"• Associated Accounts: \"{string.Join(", ", chunk.Select(a => $"{a.PlayerName} ({a.SteamId})"))}\"");
}
printMethod($"--------- END INFO ABOUT \"{player.PlayerName}\" ---------");

View File

@@ -89,6 +89,17 @@ public class Discord
new DiscordPenaltySetting { Name = "Footer", Value = "" },
new DiscordPenaltySetting { Name = "Time", Value = "{relative}" },
];
[JsonPropertyName("DiscordAssociatedAccountsSettings")]
public DiscordPenaltySetting[] DiscordAssociatedAccountsSettings { get; set; } =
[
new DiscordPenaltySetting { Name = "Color", Value = "" },
new DiscordPenaltySetting { Name = "Webhook", Value = "" },
new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" },
new DiscordPenaltySetting { Name = "ImageUrl", Value = "" },
new DiscordPenaltySetting { Name = "Footer", Value = "" },
new DiscordPenaltySetting { Name = "Time", Value = "{relative}" },
];
}
public class CustomServerCommandData

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`;

View File

@@ -0,0 +1,12 @@
namespace CS2_SimpleAdmin;
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> ChunkBy<T>(this IEnumerable<T> source, int chunkSize)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / chunkSize)
.Select(x => x.Select(v => v.Value));
}
}

View File

@@ -669,12 +669,11 @@ internal static class Helper
}
catch (Exception ex)
{
// Log or handle the exception
Console.WriteLine(ex);
}
});
}
private static string GenerateMessageDiscord(string message)
{
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";

View File

@@ -1,26 +1,27 @@
using System.Collections.Concurrent;
using CS2_SimpleAdmin.Models;
using Dapper;
using Microsoft.Extensions.Logging;
using ZLinq;
namespace CS2_SimpleAdmin.Managers;
internal class CacheManager
{
private readonly ConcurrentDictionary<int, BanRecord> _banCache = new();
private readonly ConcurrentDictionary<ulong, (HashSet<string> ips, DateTime used_at)> _playerIpsCache = new();
private readonly HashSet<string> _cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps];
private readonly ConcurrentDictionary<ulong, (HashSet<string> ips, DateTime usedAt, string playerName)> _playerIpsCache = new();
private HashSet<string> _cachedIgnoredIps;
private DateTime _lastUpdateTime = DateTime.MinValue;
private bool _isInitialized;
public async Task InitializeCacheAsync()
{
if (CS2_SimpleAdmin.Database == null) return;
if (!CS2_SimpleAdmin.ServerLoaded)
return;
if (!CS2_SimpleAdmin.ServerLoaded) return;
if (_isInitialized) return;
_cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps];
try
{
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
@@ -41,21 +42,25 @@ internal class CacheManager
status AS Status,
updated_at AS UpdatedAt
FROM sa_bans
""");
var ipHistory = await connection.QueryAsync<(ulong steamid, string address, DateTime used_at)>(
"SELECT steamid, address, used_at FROM sa_players_ips");
""");
var ipHistory =
await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC");
foreach (var ban in bans)
{
_banCache.TryAdd(ban.Id, ban);
}
foreach (var group in ipHistory.GroupBy(x => x.steamid))
{
var ips = new HashSet<string>(group.Select(x => x.address));
var lastUsed = group.Max(x => x.used_at);
_playerIpsCache[group.Key] = (ips, lastUsed);
var playerName = group.FirstOrDefault(x => !string.IsNullOrEmpty(x.name)).name
?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
_playerIpsCache[group.Key] = (ips, lastUsed, playerName);
}
_lastUpdateTime = DateTime.Now;
@@ -66,6 +71,18 @@ internal class CacheManager
Console.WriteLine(e.ToString());
}
}
public async Task ForceReInitializeCacheAsync()
{
_isInitialized = false;
_banCache.Clear();
_playerIpsCache.Clear();
_cachedIgnoredIps.Clear();
_lastUpdateTime = DateTime.MinValue;
await InitializeCacheAsync();
}
public async Task RefreshCacheAsync()
{
@@ -78,10 +95,9 @@ internal class CacheManager
var updatedBans = (await connection.QueryAsync<BanRecord>(
"SELECT * FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC",
new { lastUpdate = _lastUpdateTime }
)).ToList();
var ipHistory = await connection.QueryAsync<(ulong steamid, string address, DateTime used_at)>(
"SELECT steamid, address, used_at FROM sa_players_ips");
)).ToList().AsValueEnumerable();
var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC LIMIT 500")).ToList();
// foreach (var group in ipHistory.GroupBy(x => x.steamid))
// {
@@ -89,25 +105,43 @@ internal class CacheManager
// var lastUsed = group.Max(x => x.used_at);
// _playerIpsCache[group.Key] = (ips, lastUsed);
// }
var groupedData = ipHistory.GroupBy(x => x.steamid).ToList();
Parallel.ForEach(groupedData, group =>
var groupedData = ipHistory.AsValueEnumerable()
.GroupBy(x => x.steamid)
.ToList();
groupedData.ForEach(group =>
{
var ips = new HashSet<string>(group.Select(x => x.address));
var lastUsed = group.Max(x => x.used_at);
var ips = new HashSet<string>(
group.Select(x => x.address),
StringComparer.OrdinalIgnoreCase
);
var lastUsed = group.Max(x => x.used_at);
var playerName = group
.OrderByDescending(x => x.used_at) // Prefer newer records
.Select(x => x.name)
.FirstOrDefault(name => !string.IsNullOrEmpty(name))
?? CS2_SimpleAdmin._localizer?["sa_unknown"]
?? "Unknown";
_playerIpsCache.AddOrUpdate(
group.Key,
_ => (ips, lastUsed),
// Add new entry
_ => (ips, lastUsed, playerName),
// Update existing
(_, existing) =>
{
existing.ips.UnionWith(ips);
return (existing.ips,
lastUsed > existing.used_at ? lastUsed : existing.used_at);
existing.ips.UnionWith(ips);
return (
existing.ips,
lastUsed > existing.usedAt ? lastUsed : existing.usedAt,
string.IsNullOrEmpty(existing.playerName) ? playerName : existing.playerName
);
});
});
if (updatedBans.Count == 0)
if (updatedBans.Count() == 0)
return;
foreach (var ban in updatedBans)
@@ -126,7 +160,13 @@ internal class CacheManager
public List<BanRecord> GetAllBans() => _banCache.Values.ToList();
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList();
public List<BanRecord> GetPlayerBansBySteamId(string steamId) => _banCache.Values.Where(b => b.PlayerSteamId == steamId).ToList();
public List<(ulong SteamId, string PlayerName)> GetAccountsByIp(string ipAddress)
{
return _playerIpsCache.AsValueEnumerable()
.Where(kvp => kvp.Value.ips.Contains(ipAddress))
.Select(kvp => (kvp.Key, kvp.Value.playerName))
.ToList();
}
private bool IsIpBanned(string ipAddress)
{
@@ -168,5 +208,4 @@ internal class CacheManager
return _playerIpsCache.TryGetValue(steamId, out var ipList)
&& ipList.ips.Contains(ipAddress);
}
}

View File

@@ -46,33 +46,32 @@ public class PlayerManager
var steamId64 = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64;
var steamId = steamId64.ToString();
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
{
0 => // SteamID only check
CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, null),
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp // SteamID and IP address check
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(steamId64) // All associated IP addresses
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, ipAddress) // Only current IP address
};
if (isBanned)
{
// Kick the player if banned
Server.NextFrame(() =>
{
if (!player.UserId.HasValue) return;
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
});
return;
}
if (CS2_SimpleAdmin.Database == null) return;
// Perform asynchronous database operations within a single method
Task.Run(async () =>
{
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
{
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, null),
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(steamId64)
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, ipAddress)
};
if (isBanned)
{
// Kick the player if banned
await Server.NextFrameAsync(() =>
{
if (!player.UserId.HasValue) return;
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
});
return;
}
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null)
{
try
@@ -137,6 +136,10 @@ public class PlayerManager
CS2_SimpleAdmin._logger?.LogError(
$"Unable to save ip address for {CS2_SimpleAdmin.PlayersInfo[userId].Name} ({ipAddress}) {ex.Message}");
}
// Get all accounts associated to the player (ip address)
CS2_SimpleAdmin.PlayersInfo[userId].AccountsAssociated =
CS2_SimpleAdmin.Instance.CacheManager.GetAccountsByIp(ipAddress);
}
try
@@ -204,6 +207,8 @@ public class PlayerManager
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.NotifyPenaltiesToAdminOnConnect && fullConnect)
{
var associatedAcccountsChunks = CS2_SimpleAdmin.PlayersInfo[userId].AccountsAssociated.ChunkBy(5).ToList();
await Server.NextFrameAsync(() =>
{
foreach (var admin in Helper.GetValidPlayers()
@@ -212,6 +217,7 @@ public class PlayerManager
p.Connected == PlayerConnectedState.PlayerConnected && !CS2_SimpleAdmin.AdminDisabledJoinComms.Contains(p.SteamID)))
{
if (CS2_SimpleAdmin._localizer != null && admin != player)
{
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_penalty_info",
player.PlayerName,
CS2_SimpleAdmin.PlayersInfo[userId].TotalBans,
@@ -220,6 +226,16 @@ public class PlayerManager
CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences,
CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns
);
foreach (var chunk in associatedAcccountsChunks)
{
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_associated_accounts",
player.PlayerName,
string.Join(", ",
chunk.Select(a => $"{a.PlayerName} ({a.SteamId})"))
);
}
}
}
});
}

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nعقوبات اللاعبين لـ {lightred}{0}{default},\nعدد الحظر: {lightred}{1}{default}, عدد الصمت: {lightred}{2}{default}, عدد الكتم: {lightred}{3}{default}, عدد السكوت: {lightred}{4}{default}, عدد التحذيرات: {lightred}{5}{default}\nالعقوبات النشطة:\n{6}\nالتحذيرات النشطة:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}عقوبات اللاعبين لـ {lightred}{0}{grey}, حظر: {lightred}{1}{grey}, صمت: {lightred}{2}{grey}, كتم: {lightred}{3}{grey}, سكوت: {lightred}{4}{grey}, تحذيرات: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}الحسابات المرتبطة باللاعب {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "تم حظرك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!",
"sa_player_ban_message_perm": "تم حظرك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
"sa_player_kick_message": "تم طردك لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nSpielerstrafe für {lightred}{0}{default},\nAnzahl der Sperren: {lightred}{1}{default}, Anzahl der Mundtot: {lightred}{2}{default}, Anzahl der Stummschaltungen: {lightred}{3}{default}, Anzahl der Stille: {lightred}{4}{default}, Anzahl der Warnungen: {lightred}{5}{default}\nAktive Strafen:\n{6}\nAktive Warnungen:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Spielerstrafe für {lightred}{0}{grey}, Sperren: {lightred}{1}{grey}, Mundtot: {lightred}{2}{grey}, Stummschaltungen: {lightred}{3}{grey}, Stille: {lightred}{4}{grey}, Warnungen: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Verknüpfte Konten des Spielers {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} gebannt!",
"sa_player_ban_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent gebannt!",
"sa_player_kick_message": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} gekickt!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nPlayer penalties for {lightred}{0}{default},\nNumber of bans: {lightred}{1}{default}, Number of gags: {lightred}{2}{default}, Number of mutes: {lightred}{3}{default}, Number of silences: {lightred}{4}{default}, Number of warnings: {lightred}{5}{default}\nActive penalties:\n{6}\nActive warnings:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Player penalties for {lightred}{0}{grey}, Bans: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Warns: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Associated accounts of player {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "You have been banned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!",
"sa_player_ban_message_perm": "You have been banned permanently for {lightred}{0}{default} by {lightred}{1}{default}!",
"sa_player_kick_message": "You have been kicked for {lightred}{0}{default} by {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nPenalizaciones del jugador para {lightred}{0}{default},\nNúmero de prohibiciones: {lightred}{1}{default}, Número de boqueos: {lightred}{2}{default}, Número de silenciamientos: {lightred}{3}{default}, Número de silencios: {lightred}{4}{default}, Número de advertencias: {lightred}{5}{default}\nPenalizaciones activas:\n{6}\nAdvertencias activas:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Penalizaciones del jugador para {lightred}{0}{grey}, Prohibiciones: {lightred}{1}{grey}, Boqueos: {lightred}{2}{grey}, Silenciamientos: {lightred}{3}{grey}, Silencios: {lightred}{4}{grey}, Advertencias: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Cuentas asociadas del jugador {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Has sido baneado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Has sido baneado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_kick_message": "Has sido expulsado por {lightred}{0}{default} durante {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nتنبیهات بازیکن برای {lightred}{0}{default},\nتعداد مسدودیت‌ها: {lightred}{1}{default}, تعداد سکوت‌ها: {lightred}{2}{default}, تعداد بی‌صدا کردن‌ها: {lightred}{3}{default}, تعداد سکوت‌ها: {lightred}{4}{default}, تعداد هشدارها: {lightred}{5}{default}\nتنبیهات فعال:\n{6}\nهشدارهای فعال:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}تنبیهات بازیکن برای {lightred}{0}{grey}, مسدودیت‌ها: {lightred}{1}{grey}, سکوت‌ها: {lightred}{2}{grey}, بی‌صدا کردن‌ها: {lightred}{3}{grey}, سکوت‌ها: {lightred}{4}{grey}, هشدارها: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}حساب‌های مرتبط با بازیکن {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} مسدود شده‌اید!",
"sa_player_ban_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه مسدود شده‌اید!",
"sa_player_kick_message": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} اخراج شده‌اید!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nPénalités du joueur pour {lightred}{0}{default},\nNombre de bannissements: {lightred}{1}{default}, Nombre de gag: {lightred}{2}{default}, Nombre de mutes: {lightred}{3}{default}, Nombre de silences: {lightred}{4}{default}, Nombre davertissements: {lightred}{5}{default}\nPénalités actives:\n{6}\nAvertissements actifs:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Pénalités du joueur pour {lightred}{0}{grey}, Bannissements: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Avertissements: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Comptes associés du joueur {lightred}{0}{grey} : {1}",
"sa_player_ban_message_time": "Vous avez été banni pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Vous avez été banni définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!",
"sa_player_kick_message": "Vous avez été expulsé pour {lightred}{0}{default} par {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nSpēlētāja sods priekš {lightred}{0}{default},\nAizliegumu skaits: {lightred}{1}{default}, Klusumu skaits: {lightred}{2}{default}, Izslēgšanas skaits: {lightred}{3}{default}, Klusēšanas skaits: {lightred}{4}{default}, Brīdinājumu skaits: {lightred}{5}{default}\nAktīvie sodi:\n{6}\nAktīvie brīdinājumi:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Spēlētāja sods priekš {lightred}{0}{grey}, Aizliegumi: {lightred}{1}{grey}, Klusumi: {lightred}{2}{grey}, Izslēgšana: {lightred}{3}{grey}, Klusēšana: {lightred}{4}{grey}, Brīdinājumi: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Spēlētāja {lightred}{0}{grey} saistītie konti: {1}",
"sa_player_ban_message_time": "Tu esi nobanots uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Tevis bans ir uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
"sa_player_kick_message": "Tu esi izmests, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nBlokady gracza {lightred}{0}{default},\nIlość banów: {lightred}{1}{default}, Ilość zakneblowań: {lightred}{2}{default}, Ilość wyciszeń: {lightred}{3}{default}, Ilość uciszeń: {lightred}{4}{default}Ilość ostrzeżeń: {lightred}{5}{default}\nAktywne blokady:\n{6}\nAktywne ostrzeżenia:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Blokady gracza {lightred}{0}{grey} - bany: {lightred}{1}{grey}, zakneblowania: {lightred}{2}{grey}, wyciszenia: {lightred}{3}{grey}, uciszenia: {lightred}{4}{grey}, ostrzeżenia: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Powiązane konta gracza {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Zostałeś zbanowany za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Zostałeś zbanowany na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!",
"sa_player_kick_message": "Zostałeś wyrzucony za {lightred}{0}{default} przez {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Você foi banido por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Você foi banido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_kick_message": "Você foi expulso por {lightred}{0}{default} por {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Foste banido pelo administrador {lightred}{0}{default} durante {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Foste banido permanentemente pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
"sa_player_kick_message": "Foste expulso pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nШтрафы игрока для {lightred}{0}{default},\nКоличество банов: {lightred}{1}{default}, Количество гэгов: {lightred}{2}{default}, Количество мутов: {lightred}{3}{default}, Количество тишин: {lightred}{4}{default}, Количество предупреждений: {lightred}{5}{default}\nАктивные штрафы:\n{6}\nАктивные предупреждения:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Штрафы игрока для {lightred}{0}{grey}, Баны: {lightred}{1}{grey}, Гэги: {lightred}{2}{grey}, Муты: {lightred}{3}{grey}, Тишины: {lightred}{4}{grey}, Предупреждения: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Связанные аккаунты игрока {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Вы были забанены по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Вас забанили навсегда по причине {lightred}{0}{default} администратором {lightred}{1}{default}!",
"sa_player_kick_message": "Вы были выгнаны {lightred}{0}{default} администратором {lightred}{1}{default}!",

View File

@@ -78,6 +78,7 @@
"sa_player_penalty_info": "===========================\nOyuncunun cezaları {lightred}{0}{default} için,\nBan sayısı: {lightred}{1}{default}, Gag sayısı: {lightred}{2}{default}, Mute sayısı: {lightred}{3}{default}, Sessizlik sayısı: {lightred}{4}{default}, Uyarı sayısı: {lightred}{5}{default}\nAktif cezalar:\n{6}\nAktif uyarılar:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Oyuncunun cezaları {lightred}{0}{grey}, Banlar: {lightred}{1}{grey}, Gaglar: {lightred}{2}{grey}, Mute'lar: {lightred}{3}{grey}, Sessizlikler: {lightred}{4}{grey}, Uyarılar: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}{lightred}{0}{grey} oyuncusunun bağlı hesapları: {1}",
"sa_player_ban_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından yasaklandınız!",
"sa_player_ban_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından yasaklandınız!",
"sa_player_kick_message": "Senaryo nedeniyle {lightred}{0}{default} tarafından atıldınız!",

View File

@@ -76,6 +76,7 @@
"sa_player_penalty_info": "===========================\n玩家 {lightred}{0}{default} 的处罚信息,\n禁止次数: {lightred}{1}{default}, 禁言次数: {lightred}{2}{default}, 静音次数: {lightred}{3}{default}, 沉默次数: {lightred}{4}{default}, 警告次数: {lightred}{5}{default}\n活跃的处罚:\n{6}\n活跃的警告:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}玩家 {lightred}{0}{grey} 的处罚信息, 禁止: {lightred}{1}{grey}, 禁言: {lightred}{2}{grey}, 静音: {lightred}{3}{grey}, 沉默: {lightred}{4}{grey}, 警告: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}玩家 {lightred}{0}{grey} 的关联账户:{1}",
"sa_player_ban_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 禁止 {lightred}{1}{default} 分钟!",
"sa_player_ban_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久禁止!",
"sa_player_kick_message": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 踢出!",

View File

@@ -26,6 +26,7 @@ public class PlayerInfo(
public int TotalSilences { get; set; } = totalSilences;
public int TotalWarns { get; set; } = totalWarns;
public bool WaitingForKick { get; set; } = false;
public List<(ulong SteamId, string PlayerName)> AccountsAssociated { get; set; } = [];
public DiePosition? DiePosition { get; set; }
}

View File

@@ -10,14 +10,17 @@ using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using Microsoft.Extensions.Logging;
using AntiDLL.API;
using System.Text.Json;
using System.Net.Http.Headers;
public class PluginConfig : IBasePluginConfig
{
[JsonPropertyName("ConfigVersion")] public int Version { get; set; } = 1;
[JsonPropertyName("ConfigVersion")] public int Version { get; set; } = 2;
[JsonPropertyName("Reason")] public string Reason { get; set; } = "Invalid event detected!";
[JsonPropertyName("Duration")] public int Duration { get; set; } = 0;
[JsonPropertyName("CommandToExecute")] public string CommandToExecute { get; set; } = "css_addban {steamid64} {duration} {reason}";
[JsonPropertyName("BanType")] public string BanType { get; set; } = "auto";
[JsonPropertyName("WebhookUrl")] public string WebhookUrl { get; set; } = "";
}
public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginConfig>
@@ -29,7 +32,7 @@ public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginCo
private static PluginCapability<IAntiDLL> AntiDll { get; } = new("AntiDLL");
private static PluginCapability<ICS2_SimpleAdminApi> SimpleAdminApi { get; } = new("simpleadmin:api");
private static ICS2_SimpleAdminApi? _simpleAdminApi;
public override string ModuleName => "AntiDLL [CS2-SimpleAdmin Module]";
public override string ModuleDescription => "AntiDLL module for CS2-SimpleAdmin integration";
public override string ModuleVersion => "1.0.1";
@@ -39,12 +42,12 @@ public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginCo
{
RegisterListener<Listeners.OnClientDisconnect>(OnClientDisconnect);
}
public void OnConfigParsed(PluginConfig config)
{
Config = config;
}
public override void OnAllPluginsLoaded(bool hotReload)
{
try
@@ -56,7 +59,7 @@ public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginCo
Unload(false);
return;
}
antidll.OnDetection += OnDetection;
}
catch (Exception)
@@ -94,9 +97,9 @@ public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginCo
public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo _)
{
var player = @event.Userid;
if (player == null || !player.IsValid || player.IsBot || !_detections.Contains(player.Slot))
if (player == null || !player.IsValid || player.IsBot || !_detections.Contains(player.Slot))
return HookResult.Continue;
if (!_bannedPlayers.Contains(player.Slot) && player.Connected == PlayerConnectedState.PlayerConnected && player.TeamNum != 0)
PunishPlayer(player);
@@ -108,22 +111,23 @@ public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginCo
if (player == null || !player.IsValid || player.IsBot) return;
if (!_detections.Add(player.Slot))
return;
// if (player.Connected != PlayerConnectedState.PlayerConnected)
// {
// _detections.Add(player.Slot);
// // AddTimer(3.0f, () => OnDetection(player, eventName));
// return;
// }
Logger.LogInformation("Detected \"{eventName}\" for \"{player}({steamid})\"", eventName, player.PlayerName, player.SteamID.ToString());
_ = SendWebhook(eventName, player.PlayerName, player.SteamID.ToString());
}
private void PunishPlayer(CCSPlayerController player)
{
if (!_bannedPlayers.Add(player.Slot))
return;
if (_banType == 1 && _simpleAdminApi != null)
{
_simpleAdminApi.IssuePenalty(new SteamID(player.SteamID), null, PenaltyType.Ban, Config.Reason, Config.Duration);
@@ -143,11 +147,36 @@ public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginCo
public override void Unload(bool hotReload)
{
RemoveListener<Listeners.OnClientDisconnect>(OnClientDisconnect);
var antidll = AntiDll.Get();
if (antidll != null)
{
antidll.OnDetection -= OnDetection;
}
}
// Discord Webhook
private async Task SendWebhook(string eventName, string playerName, string steamid)
{
var webhookUrl = Config.WebhookUrl;
if (string.IsNullOrEmpty(webhookUrl))
return;
var payload = new
{
content = $"Detected \"{eventName}\" for \"{playerName}({steamid})\""
};
var json = JsonSerializer.Serialize(payload);
using var client = new HttpClient();
using var content = new StringContent(json, System.Text.Encoding.UTF8, new MediaTypeHeaderValue("application/json"));
var response = await client.PostAsync(webhookUrl, content);
if (!response.IsSuccessStatusCode)
{
Logger.LogError("Failed to forward event to webhook: {response}", response.ReasonPhrase);
}
}
}