ist Komplett
C#: BZ_Admin.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>BZ_Admin</AssemblyName>
<RootNamespace>BZ_Admin</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.363">
<PrivateAssets>none</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MySqlConnector" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
Alles anzeigen
C#
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
using Microsoft.Extensions.Logging;
using MySqlConnector;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace BZ_Admin
{
[MinimumApiVersion(80)]
public class BZ_AdminPlugin : BasePlugin
{
public override string ModuleName => "BZ_Admin";
public override string ModuleVersion => "1.6.4";
public override string ModuleAuthor => "ddbmaster";
public override string ModuleDescription => "Admin, Gruppen, Bans und Mapwechsel für CS2 mit MySQL.";
private readonly Dictionary<string, GroupInfo> _groups = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, AdminInfo> _admins = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, string> _lang = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, MapEntry> _maps = new(StringComparer.OrdinalIgnoreCase);
private readonly List<BanTimeEntry> _banTimes = new();
private readonly List<BanReasonEntry> _banReasons = new();
private readonly List<BanInfo> _bans = new();
private string CurrentLanguage = "de";
private DatabaseConfig _databaseConfig = new();
private string PluginDir => ModuleDirectory;
private string LangDir => Path.Combine(PluginDir, "lang");
private string LangDeFilePath => Path.Combine(LangDir, "de.txt");
private string LangEnFilePath => Path.Combine(LangDir, "en.txt");
private string ConfigBaseDir =>
Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp", "configs", "plugins", ModuleName);
private string DatabaseConfigFilePath => Path.Combine(ConfigBaseDir, "database.json");
private string LegacyAdminsFilePath => Path.Combine(ConfigBaseDir, "admins.txt");
private string LegacyGroupsFilePath => Path.Combine(ConfigBaseDir, "gruppen.txt");
private string LegacyBansFilePath => Path.Combine(ConfigBaseDir, "ban-liste.txt");
private string LegacyBanTimesFilePath => Path.Combine(ConfigBaseDir, "ban-zeiten.txt");
private string LegacyBanReasonsFilePath => Path.Combine(ConfigBaseDir, "ban-gruende.txt");
private string ChangeMapFilePath => Path.Combine(ConfigBaseDir, "changemap.txt");
public override void Load(bool hotReload)
{
Directory.CreateDirectory(PluginDir);
Directory.CreateDirectory(LangDir);
Directory.CreateDirectory(ConfigBaseDir);
EnsureDefaultFiles();
LoadDatabaseConfig();
InitializeDatabase();
MigrateDatabase();
SeedOrMigrateDatabaseIfNeeded();
LoadLanguage(CurrentLanguage);
LoadGroups();
LoadAdmins();
LoadBans();
LoadBanTimes();
LoadBanReasons();
LoadMaps();
ExpireElapsedBans();
RegisterListener<Listeners.OnClientPutInServer>(OnClientPutInServer);
Logger.LogInformation($"[BZ_Admin] Loaded (Lang={CurrentLanguage})");
Logger.LogInformation($"[BZ_Admin] PluginDir: {PluginDir}");
Logger.LogInformation($"[BZ_Admin] ConfigDir: {ConfigBaseDir}");
Logger.LogInformation($"[BZ_Admin] LanguageDir: {LangDir}");
Logger.LogInformation($"[BZ_Admin] DatabaseConfig: {DatabaseConfigFilePath}");
Logger.LogInformation($"[BZ_Admin] MySQL: {_databaseConfig.Host}:{_databaseConfig.Port}/{_databaseConfig.Database}");
}
private void EnsureDefaultFiles()
{
if (!File.Exists(ChangeMapFilePath))
{
File.WriteAllLines(ChangeMapFilePath, new[]
{
"# Standard Maps",
"de_dust2",
"de_mirage",
"de_inferno",
"de_nuke",
"de_train",
"de_anubis",
"de_overpass",
"",
"# Workshop Maps",
"# Format: workshopid:mapname",
"3671053668:de_rats_teatre",
"3075012302:de_blackgold_d_prefab",
"3070219079:de_santo",
"3328169568:de_edin",
""
});
}
if (!File.Exists(LangDeFilePath))
File.WriteAllText(LangDeFilePath, GetDefaultGermanLanguageFile());
if (!File.Exists(LangEnFilePath))
File.WriteAllText(LangEnFilePath, GetDefaultEnglishLanguageFile());
if (!File.Exists(DatabaseConfigFilePath))
{
var defaultConfig = new DatabaseConfig
{
Host = "127.0.0.1",
Port = 3306,
Database = "bz_admin",
Username = "bz_admin",
Password = "DEIN_PASSWORT",
Charset = "utf8mb4"
};
File.WriteAllText(
DatabaseConfigFilePath,
JsonSerializer.Serialize(defaultConfig, new JsonSerializerOptions { WriteIndented = true })
);
}
}
private void LoadDatabaseConfig()
{
if (!File.Exists(DatabaseConfigFilePath))
throw new FileNotFoundException($"MySQL config not found: {DatabaseConfigFilePath}");
string json = File.ReadAllText(DatabaseConfigFilePath);
_databaseConfig = JsonSerializer.Deserialize<DatabaseConfig>(json) ?? new DatabaseConfig();
if (string.IsNullOrWhiteSpace(_databaseConfig.Host) ||
string.IsNullOrWhiteSpace(_databaseConfig.Database) ||
string.IsNullOrWhiteSpace(_databaseConfig.Username))
{
throw new InvalidOperationException($"Invalid MySQL config: {DatabaseConfigFilePath}");
}
}
private string BuildConnectionString()
{
var builder = new MySqlConnectionStringBuilder
{
Server = _databaseConfig.Host,
Port = (uint)_databaseConfig.Port,
Database = _databaseConfig.Database,
UserID = _databaseConfig.Username,
Password = _databaseConfig.Password,
CharacterSet = string.IsNullOrWhiteSpace(_databaseConfig.Charset) ? "utf8mb4" : _databaseConfig.Charset,
SslMode = MySqlSslMode.Preferred,
AllowUserVariables = true
};
return builder.ConnectionString;
}
private MySqlConnection GetConnection()
{
var connection = new MySqlConnection(BuildConnectionString());
connection.Open();
return connection;
}
private string GetDefaultGermanLanguageFile()
{
return @"Usage_AddGroup = Benutzung: css_addgroup <#gruppenname> <flag1,flag2,...>
Usage_DelGroup = Benutzung: css_delgroup <#gruppenname>
GroupAdded = Gruppe {0} mit Flags hinzugefügt: {1}
GroupExists = Gruppe {0} existiert bereits.
GroupDeleted = Gruppe {0} wurde gelöscht.
GroupNotExist = Gruppe {0} existiert nicht.
NoGroups = Keine Gruppen vorhanden.
Usage_AddAdmin = Benutzung: css_addadmin <name> <steamid64> <#gruppe> <immunitaet>
Usage_DelAdmin = Benutzung: css_deladmin <steamid64>
Usage_SetGroup = Benutzung: css_setgroup <steamid64> <#gruppe>
Usage_SetImmunity = Benutzung: css_setimmunity <steamid64> <wert>
Usage_SetAdminName = Benutzung: css_setadminname <steamid64> <name>
AdminAdded = Admin {0} hinzugefügt zur Gruppe {1} mit Immunität {2}.
AdminRemoved = Admin {0} wurde entfernt.
AdminNotFound = Kein Admin mit SteamID {0} gefunden.
AdminUpdatedGroup = Admin {0} wurde Gruppe {1} zugewiesen.
AdminUpdatedImmunity = Admin {0} hat jetzt Immunität {1}.
AdminUpdatedName = Admin {0} wurde umbenannt in {1}.
NoAdmins = Keine Admins vorhanden.
AdminSelfGroup = {0} Du bist jetzt {1}
AdminSelfGroupCss = {0} Du bist jetzt CSS-Admin
Usage_Ban = Benutzung: css_ban oder css_ban <steamid64> <minuten> <grund>
Usage_BanId = Benutzung: css_banid <steamid64> <minuten> <grund>
Usage_Unban = Benutzung: css_unban <steamid64>
Usage_EditBan = Benutzung: css_editban <steamid64> <minuten> <grund>
Usage_ReloadBanReasons = Benutzung: css_reloadbanreasons
Usage_ReloadBanTimes = Benutzung: css_reloadbantimes
Usage_ReloadGroups = Benutzung: css_reloadgroups
BanAdded = Spieler {0} ({1}) wurde gebannt. Dauer: {2}. Grund: {3} von {4}
BanEdited = Ban für Spieler {0} ({1}) wurde geändert. Neue Dauer: {2}. Neuer Grund: {3}. Bearbeitet von: {4}
BanRemoved = Ban entfernt: {0}
NoBanFound = Kein aktiver Ban für SteamID {0} gefunden.
NoBans = Keine Bans vorhanden.
NoActiveBans = Keine aktiven Bans vorhanden.
NoBanTimes = Keine Ban-Zeiten vorhanden.
NoBanReasons = Keine Ban-Gründe vorhanden.
BanAlreadyActive = Für SteamID {0} existiert bereits ein aktiver Ban.
BanMenuTitlePlayers = Spieler für Ban auswählen
BanMenuTitleTimes = Ban-Zeit für {0} auswählen
BanMenuTitleReasons = Ban-Grund für {0} auswählen
EditBanMenuTitleBans = Aktiven Ban auswählen
EditBanMenuTitleTimes = Neue Ban-Zeit für {0} auswählen
EditBanMenuTitleReasons = Neuen Ban-Grund für {0} auswählen
EditBanMenuEntry = {0}. {1} ({2})
EditBanCustomReasonHint = Eigener Grund: Benutze css_editban {0} {1} <dein grund>
EditBanUseCommand = Eigenen Grund per Befehl
BanMenuPlayerEntry = {0}. {1}
BanMenuTimeEntry = {0} ({1} Minuten)
BanMenuReasonEntry = {0}
BanMenuNoPlayers = Keine Spieler gefunden.
BanJoinDenied = Du bist gebannt. Grund: {0}. Bitte melde dich bei {1}
BanContact = ddbmaster.de
BanStatusActive = Aktiv
BanStatusRemoved = Entbannt
BanStatusExpired = Abgelaufen
BanDeletedBy = entfernt von {0}
BanEditedBy = bearbeitet von {0}
BanListEntry = {0} | {1} | {2} | {3} | {4}
BanDurationPermanent = Permanent
BanDurationMinutes = {0} Minuten
KickDenied = Du kannst diesen Spieler nicht kicken: Ziel hat gleiche oder höhere Immunität.
KickDone = Spieler {0} wurde gekickt.
Usage_Kick = Benutzung: css_kick oder css_kick <steamid64>
KickMenuTitlePlayers = Spieler für Kick auswählen
KickMenuPlayerEntry = {0}. {1}
KickMenuNoPlayers = Keine Spieler gefunden.
SlayDenied = Du kannst diesen Spieler nicht töten: Ziel hat gleiche oder höhere Immunität.
SlayDone = Spieler {0} wurde getötet.
TargetNotFound = Zielspieler nicht gefunden.
Usage_Slay = Benutzung: css_slay oder css_slay <steamid64>
SlayMenuTitlePlayers = Spieler für Slay auswählen
SlayMenuPlayerEntry = {0}. {1}
SlayMenuNoPlayers = Keine Spieler gefunden.
Usage_Map = Benutzung: css_map oder css_map <mapname|workshopid>
Usage_WsMap = Benutzung: css_wsmap oder css_wsmap <mapname|workshopid>
MapChanged = {0} wechselt die Map nach {1}.
MapNotFound = Die Map oder Workshop-Map ""{0}"" wurde nicht gefunden.
NoMaps = Keine Maps in changemap.txt vorhanden.
MapListHeader = Verfügbare Maps:
MapEntryStandard = {0}
MapEntryWorkshop = {0} ({1})
MapMenuTitle = Standard Map Menü
MapMenuTitleWorkshop = Workshop Map Menü
MapMenuStandardEntry = {0}. {1}
MapMenuWorkshopEntry = {0}. {1} ({2})
MapMenuNoStandardMaps = Keine Standard-Maps gefunden.
MapMenuNoWorkshopMaps = Keine Workshop-Maps gefunden.
Usage_ListBanTimes = Benutzung: css_listbantimes
BanTimeHeader = Verfügbare Ban-Zeiten:
BanTimeEntry = {0} = {1} Minuten
Usage_ListBanReasons = Benutzung: css_listbanreasons
BanReasonHeader = Verfügbare Ban-Gründe:
BanReasonEntry = {0} = {1}
Usage_AdminMenu = Benutzung: css_adminmenu
AdminMenuTitle = BZ Admin Menü
AdminMenuBan = Ban Menü
AdminMenuEditBan = Ban bearbeiten
AdminMenuKick = Kick Menü
AdminMenuSlay = Slay Menü
AdminMenuMap = Standard Map Menü
AdminMenuWsMap = Workshop Map Menü
AdminMenuListMaps = Mapliste anzeigen
AdminMenuListBans = Bannliste anzeigen
AdminMenuListBanTimes = Ban-Zeiten anzeigen
AdminMenuListBanReasons = Ban-Gründe anzeigen
MenuMain = Hauptmenü
ConfigsReloaded = BZ_Admin Konfigurationen wurden neu geladen.
ReloadBanReasonsDone = Ban-Gründe wurden neu geladen: {0}
ReloadBanTimesDone = Ban-Zeiten wurden neu geladen: {0}
ReloadGroupsDone = Gruppen wurden neu geladen: {0}
LanguageLoaded = Sprache geladen: {0}
InvalidSteamId64 = Ungültige SteamID64.
InvalidImmunity = Ungültiger Immunitätswert.
InvalidBanMinutes = Ungültige Ban-Dauer in Minuten.
PlayerOnlyCommand = Dieser Befehl muss von einem Spieler ausgeführt werden.
BanDenied = Du kannst diesen Spieler nicht bannen: Ziel hat gleiche oder höhere Immunität.
NoPermission = Du hast keine Berechtigung für diesen Befehl.";
}
private string GetDefaultEnglishLanguageFile()
{
return @"Usage_AddGroup = Usage: css_addgroup <#groupname> <flag1,flag2,...>
Usage_DelGroup = Usage: css_delgroup <#groupname>
GroupAdded = Group {0} added with flags: {1}
GroupExists = Group {0} already exists.
GroupDeleted = Group {0} has been deleted.
GroupNotExist = Group {0} does not exist.
NoGroups = No groups defined.
Usage_AddAdmin = Usage: css_addadmin <name> <steamid64> <#group> <immunity>
Usage_DelAdmin = Usage: css_deladmin <steamid64>
Usage_SetGroup = Usage: css_setgroup <steamid64> <#group>
Usage_SetImmunity = Usage: css_setimmunity <steamid64> <value>
Usage_SetAdminName = Usage: css_setadminname <steamid64> <name>
AdminAdded = Admin {0} added to {1} with immunity {2}.
AdminRemoved = Admin {0} has been removed.
AdminNotFound = Admin with SteamID {0} not found.
AdminUpdatedGroup = Admin {0} has been assigned to group {1}.
AdminUpdatedImmunity = Admin {0} now has immunity {1}.
AdminUpdatedName = Admin {0} has been renamed to {1}.
NoAdmins = No admins defined.
AdminSelfGroup = {0} You are now {1}
AdminSelfGroupCss = {0} You are now a CSS admin
Usage_Ban = Usage: css_ban or css_ban <steamid64> <minutes> <reason>
Usage_BanId = Usage: css_banid <steamid64> <minutes> <reason>
Usage_Unban = Usage: css_unban <steamid64>
Usage_EditBan = Usage: css_editban <steamid64> <minutes> <reason>
Usage_ReloadBanReasons = Usage: css_reloadbanreasons
Usage_ReloadBanTimes = Usage: css_reloadbantimes
Usage_ReloadGroups = Usage: css_reloadgroups
BanAdded = Player {0} ({1}) has been banned. Duration: {2}. Reason: {3} by {4}
BanEdited = Ban for player {0} ({1}) has been changed. New duration: {2}. New reason: {3}. Edited by: {4}
BanRemoved = Ban removed: {0}
NoBanFound = No active ban found for SteamID {0}.
NoBans = No bans recorded.
NoActiveBans = No active bans found.
NoBanTimes = No ban times found.
NoBanReasons = No ban reasons found.
BanAlreadyActive = There is already an active ban for SteamID {0}.
BanMenuTitlePlayers = Select player to ban
BanMenuTitleTimes = Select ban time for {0}
BanMenuTitleReasons = Select ban reason for {0}
EditBanMenuTitleBans = Select active ban
EditBanMenuTitleTimes = Select new ban time for {0}
EditBanMenuTitleReasons = Select new ban reason for {0}
EditBanMenuEntry = {0}. {1} ({2})
EditBanCustomReasonHint = Custom reason: Use css_editban {0} {1} <your reason>
EditBanUseCommand = Custom reason via command
BanMenuPlayerEntry = {0}. {1}
BanMenuTimeEntry = {0} ({1} minutes)
BanMenuReasonEntry = {0}
BanMenuNoPlayers = No players found.
BanJoinDenied = You are banned. Reason: {0}. Please contact {1}
BanContact = ddbmaster.de
BanStatusActive = Active
BanStatusRemoved = Unbanned
BanStatusExpired = Expired
BanDeletedBy = removed by {0}
BanEditedBy = edited by {0}
BanListEntry = {0} | {1} | {2} | {3} | {4}
BanDurationPermanent = Permanent
BanDurationMinutes = {0} minutes
KickDenied = You cannot kick this player: target has equal or higher immunity.
KickDone = Player {0} has been kicked.
Usage_Kick = Usage: css_kick or css_kick <steamid64>
KickMenuTitlePlayers = Select player to kick
KickMenuPlayerEntry = {0}. {1}
KickMenuNoPlayers = No players found.
SlayDenied = You cannot slay this player: target has equal or higher immunity.
SlayDone = Player {0} has been slain.
TargetNotFound = Target player not found.
Usage_Slay = Usage: css_slay or css_slay <steamid64>
SlayMenuTitlePlayers = Select player to slay
SlayMenuPlayerEntry = {0}. {1}
SlayMenuNoPlayers = No players found.
Usage_Map = Usage: css_map or css_map <mapname|workshopid>
Usage_WsMap = Usage: css_wsmap or css_wsmap <mapname|workshopid>
MapChanged = {0} changed the map to {1}.
MapNotFound = Map or workshop map ""{0}"" not found.
NoMaps = No maps found in changemap.txt.
MapListHeader = Available maps:
MapEntryStandard = {0}
MapEntryWorkshop = {0} ({1})
MapMenuTitle = Standard Map Menu
MapMenuTitleWorkshop = Workshop Map Menu
MapMenuStandardEntry = {0}. {1}
MapMenuWorkshopEntry = {0}. {1} ({2})
MapMenuNoStandardMaps = No standard maps found.
MapMenuNoWorkshopMaps = No workshop maps found.
Usage_ListBanTimes = Usage: css_listbantimes
BanTimeHeader = Available ban times:
BanTimeEntry = {0} = {1} minutes
Usage_ListBanReasons = Usage: css_listbanreasons
BanReasonHeader = Available ban reasons:
BanReasonEntry = {0} = {1}
Usage_AdminMenu = Usage: css_adminmenu
AdminMenuTitle = BZ Admin Menu
AdminMenuBan = Ban Menu
AdminMenuEditBan = Edit Ban
AdminMenuKick = Kick Menu
AdminMenuSlay = Slay Menu
AdminMenuMap = Standard Map Menu
AdminMenuWsMap = Workshop Map Menu
AdminMenuListMaps = Show map list
AdminMenuListBans = Show ban list
AdminMenuListBanTimes = Show ban times
AdminMenuListBanReasons = Show ban reasons
MenuMain = Main menu
ConfigsReloaded = BZ_Admin configurations reloaded.
ReloadBanReasonsDone = Ban reasons reloaded: {0}
ReloadBanTimesDone = Ban times reloaded: {0}
ReloadGroupsDone = Groups reloaded: {0}
LanguageLoaded = Language loaded: {0}
InvalidSteamId64 = Invalid SteamID64.
InvalidImmunity = Invalid immunity value.
InvalidBanMinutes = Invalid ban duration in minutes.
PlayerOnlyCommand = This command must be executed by a player.
BanDenied = You cannot ban this player: target has equal or higher immunity.
NoPermission = You do not have permission for this command.";
}
private void LoadLanguage(string langCode)
{
string langFile = Path.Combine(LangDir, $"{langCode}.txt");
_lang.Clear();
Logger.LogInformation($"[BZ_Admin] Lade Sprache '{langCode}' aus: {langFile}");
if (!File.Exists(langFile))
{
Logger.LogError($"[BZ_Admin] Sprachdatei NICHT gefunden: {langFile}");
return;
}
foreach (var rawLine in File.ReadAllLines(langFile))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
var parts = line.Split('=', 2);
if (parts.Length == 2)
{
string key = parts[0].Trim();
string value = parts[1].Trim();
_lang[key] = value;
}
}
Logger.LogInformation($"[BZ_Admin] Sprachkeys geladen: {_lang.Count}");
}
private string T(string key, params object[] args)
{
if (_lang.TryGetValue(key, out var fmt))
return args.Length > 0 ? string.Format(fmt, args) : fmt;
return key;
}
private string FormatBanDuration(int minutes)
{
return minutes <= 0
? T("BanDurationPermanent")
: T("BanDurationMinutes", minutes);
}
private void OnClientPutInServer(int slot)
{
var player = Utilities.GetPlayerFromSlot(slot);
if (player == null || !player.IsValid || player.IsBot)
return;
string steamId = player.SteamID.ToString();
var activeBan = GetEffectiveActiveBanBySteamId(steamId);
if (activeBan != null)
{
Server.NextFrame(() =>
{
if (player == null || !player.IsValid)
return;
string kickMessage = T("BanJoinDenied", activeBan.Reason, T("BanContact"));
string escaped = EscapeServerCommandArgument(kickMessage);
Server.ExecuteCommand($"kickid {steamId} \"{escaped}\"");
});
return;
}
if (_admins.TryGetValue(steamId, out var localAdmin))
{
Server.NextFrame(() =>
{
if (player == null || !player.IsValid)
return;
player.PrintToChat($" {T("AdminSelfGroup", localAdmin.Name, localAdmin.GroupName)}");
});
return;
}
if (AdminManager.PlayerHasPermissions(player, "@css/root") ||
AdminManager.GetPlayerImmunity(player) > 0)
{
Server.NextFrame(() =>
{
if (player == null || !player.IsValid)
return;
player.PrintToChat($" {T("AdminSelfGroupCss", player.PlayerName)}");
});
}
}
private void InitializeDatabase()
{
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = @"
CREATE TABLE IF NOT EXISTS bz_groups (
group_name VARCHAR(191) NOT NULL,
flags TEXT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (group_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS bz_admins (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
steamid64 VARCHAR(32) NOT NULL,
group_name VARCHAR(191) NOT NULL,
immunity INT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_bz_admins_steamid64 (steamid64),
KEY idx_bz_admins_group_name (group_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS bz_bans (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
steamid64 VARCHAR(32) NOT NULL,
duration_minutes INT NOT NULL,
reason VARCHAR(255) NOT NULL,
admin_name VARCHAR(255) NOT NULL,
admin_steamid64 VARCHAR(32) NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
deleted_at DATETIME NULL,
deleted_by VARCHAR(255) NULL,
edited_by VARCHAR(255) NULL,
edited_by_steamid64 VARCHAR(32) NULL,
edited_at DATETIME NULL,
source VARCHAR(32) NOT NULL DEFAULT 'server',
PRIMARY KEY (id),
KEY idx_bz_bans_steamid64 (steamid64),
KEY idx_bz_bans_active (steamid64, is_active),
KEY idx_bz_bans_updated_at (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS bz_ban_times (
display_name VARCHAR(191) NOT NULL,
minutes INT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (display_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS bz_ban_reasons (
display_name VARCHAR(191) NOT NULL,
reason VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (display_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
";
command.ExecuteNonQuery();
}
private void MigrateDatabase()
{
using var connection = GetConnection();
EnsureColumnExists(connection, "bz_bans", "admin_steamid64", "ALTER TABLE bz_bans ADD COLUMN admin_steamid64 VARCHAR(32) NULL AFTER admin_name;");
EnsureColumnExists(connection, "bz_bans", "edited_by", "ALTER TABLE bz_bans ADD COLUMN edited_by VARCHAR(255) NULL AFTER deleted_by;");
EnsureColumnExists(connection, "bz_bans", "edited_by_steamid64", "ALTER TABLE bz_bans ADD COLUMN edited_by_steamid64 VARCHAR(32) NULL AFTER edited_by;");
EnsureColumnExists(connection, "bz_bans", "edited_at", "ALTER TABLE bz_bans ADD COLUMN edited_at DATETIME NULL AFTER edited_by_steamid64;");
EnsureColumnExists(connection, "bz_bans", "source", "ALTER TABLE bz_bans ADD COLUMN source VARCHAR(32) NOT NULL DEFAULT 'server' AFTER edited_at;");
}
private void EnsureColumnExists(MySqlConnection connection, string tableName, string columnName, string alterSql)
{
using var checkCommand = connection.CreateCommand();
checkCommand.CommandText = @"
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema
AND TABLE_NAME = @table
AND COLUMN_NAME = @column;";
checkCommand.Parameters.AddWithValue("@schema", _databaseConfig.Database);
checkCommand.Parameters.AddWithValue("@table", tableName);
checkCommand.Parameters.AddWithValue("@column", columnName);
int count = Convert.ToInt32(checkCommand.ExecuteScalar());
if (count > 0)
return;
using var alterCommand = connection.CreateCommand();
alterCommand.CommandText = alterSql;
alterCommand.ExecuteNonQuery();
Logger.LogInformation($"[BZ_Admin] Migration ausgeführt: Spalte '{columnName}' zu Tabelle '{tableName}' hinzugefügt.");
}
private string NormalizeDate(string input)
{
if (DateTime.TryParseExact(input, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt))
return dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
if (DateTime.TryParseExact(input, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var d))
return d.ToString("yyyy-MM-dd 00:00:00", CultureInfo.InvariantCulture);
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
}
private void SeedOrMigrateDatabaseIfNeeded()
{
using var connection = GetConnection();
SeedOrMigrateGroups(connection);
SeedOrMigrateAdmins(connection);
SeedOrMigrateBanTimes(connection);
SeedOrMigrateBanReasons(connection);
SeedOrMigrateBans(connection);
}
private void SeedOrMigrateGroups(MySqlConnection connection)
{
if (GetTableCount(connection, "bz_groups") > 0)
return;
using var transaction = connection.BeginTransaction();
bool imported = false;
if (File.Exists(LegacyGroupsFilePath))
{
foreach (var rawLine in File.ReadAllLines(LegacyGroupsFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//"))
continue;
var parts = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
continue;
var groupName = parts[0].Trim();
var flagsPart = parts[1].Trim();
if (!groupName.StartsWith("#"))
continue;
InsertGroup(connection, groupName, flagsPart, transaction);
imported = true;
}
}
if (!imported)
{
InsertGroup(connection, "#root", "@css/root", transaction);
InsertGroup(connection, "#coadmin", "@css/ban,@css/unban,@css/kick,@css/slay,@css/changemap,@css/listmaps,@css/reloadconfigs,@css/adminmenu", transaction);
InsertGroup(connection, "#moderator", "@css/kick,@css/slay,@css/adminmenu", transaction);
InsertGroup(connection, "#supporter", "@css/listmaps", transaction);
}
transaction.Commit();
}
private void SeedOrMigrateAdmins(MySqlConnection connection)
{
if (GetTableCount(connection, "bz_admins") > 0)
return;
using var transaction = connection.BeginTransaction();
bool imported = false;
if (File.Exists(LegacyAdminsFilePath))
{
foreach (var rawLine in File.ReadAllLines(LegacyAdminsFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
var parts = line.Split(':', StringSplitOptions.TrimEntries);
if (parts.Length < 6)
continue;
if (!int.TryParse(parts[0], out var id))
continue;
if (!int.TryParse(parts[4], out var immunity))
continue;
InsertAdmin(connection, new AdminInfo
{
Id = id,
Name = parts[1],
SteamId = parts[2],
GroupName = parts[3],
Immunity = immunity,
CreatedAt = NormalizeDate(parts[5]),
UpdatedAt = NormalizeDate(parts[5])
}, transaction);
imported = true;
}
}
if (!imported)
{
InsertAdmin(connection, new AdminInfo
{
Id = 1,
Name = "ddbmaster",
SteamId = "76561197960398110",
GroupName = "#root",
Immunity = 100,
CreatedAt = "2025-10-29 00:00:00",
UpdatedAt = "2025-10-29 00:00:00"
}, transaction);
InsertAdmin(connection, new AdminInfo
{
Id = 2,
Name = "Nixdorf",
SteamId = "76561197965022135",
GroupName = "#coadmin",
Immunity = 50,
CreatedAt = "2025-10-29 00:00:00",
UpdatedAt = "2025-10-29 00:00:00"
}, transaction);
}
transaction.Commit();
}
private void SeedOrMigrateBanTimes(MySqlConnection connection)
{
if (GetTableCount(connection, "bz_ban_times") > 0)
return;
using var transaction = connection.BeginTransaction();
bool imported = false;
if (File.Exists(LegacyBanTimesFilePath))
{
foreach (var rawLine in File.ReadAllLines(LegacyBanTimesFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
var parts = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
continue;
if (!int.TryParse(parts[1], out var minutes))
continue;
InsertBanTime(connection, parts[0].Trim(), minutes, transaction);
imported = true;
}
}
if (!imported)
{
InsertBanTime(connection, "Permanent", 0, transaction);
InsertBanTime(connection, "30 Minuten", 30, transaction);
InsertBanTime(connection, "1 Stunde", 60, transaction);
InsertBanTime(connection, "6 Stunden", 360, transaction);
InsertBanTime(connection, "12 Stunden", 720, transaction);
InsertBanTime(connection, "1 Tag", 1440, transaction);
InsertBanTime(connection, "3 Tage", 4320, transaction);
InsertBanTime(connection, "1 Woche", 10080, transaction);
InsertBanTime(connection, "2 Wochen", 20160, transaction);
}
transaction.Commit();
}
private void SeedOrMigrateBanReasons(MySqlConnection connection)
{
if (GetTableCount(connection, "bz_ban_reasons") > 0)
return;
using var transaction = connection.BeginTransaction();
bool imported = false;
if (File.Exists(LegacyBanReasonsFilePath))
{
foreach (var rawLine in File.ReadAllLines(LegacyBanReasonsFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
var parts = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
continue;
InsertBanReason(connection, parts[0].Trim(), parts[1].Trim(), transaction);
imported = true;
}
}
if (!imported)
{
InsertBanReason(connection, "Cheating", "Cheating", transaction);
InsertBanReason(connection, "Beleidigung", "Beleidigung", transaction);
InsertBanReason(connection, "Teamkill", "Teamkill", transaction);
InsertBanReason(connection, "Griefing", "Griefing", transaction);
InsertBanReason(connection, "AFK", "AFK", transaction);
InsertBanReason(connection, "Spam", "Spam", transaction);
InsertBanReason(connection, "Sonstiges", "Sonstiges", transaction);
}
transaction.Commit();
}
private void SeedOrMigrateBans(MySqlConnection connection)
{
if (GetTableCount(connection, "bz_bans") > 0)
return;
using var transaction = connection.BeginTransaction();
if (File.Exists(LegacyBansFilePath))
{
foreach (var rawLine in File.ReadAllLines(LegacyBansFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
var parts = line.Split(':', StringSplitOptions.TrimEntries);
if (parts.Length < 7)
continue;
if (!int.TryParse(parts[0], out var id))
continue;
if (!int.TryParse(parts[3], out var minutes))
continue;
string createdAt;
string? adminSteamId = null;
if (parts.Length >= 8)
{
adminSteamId = string.IsNullOrWhiteSpace(parts[6]) ? null : parts[6];
createdAt = NormalizeDate(parts[7]);
}
else
{
createdAt = NormalizeDate(parts[6]);
}
InsertBan(connection, new BanInfo
{
Id = id,
Name = parts[1],
SteamId = parts[2],
DurationMinutes = minutes,
Reason = parts[4],
AdminName = parts[5],
AdminSteamId = adminSteamId,
CreatedAt = createdAt,
UpdatedAt = createdAt,
IsActive = true,
DeletedAt = null,
DeletedBy = null,
EditedBy = null,
EditedBySteamId = null,
EditedAt = null,
Source = "legacy"
}, transaction);
}
}
transaction.Commit();
}
private int GetTableCount(MySqlConnection connection, string tableName)
{
using var command = connection.CreateCommand();
command.CommandText = $"SELECT COUNT(*) FROM {tableName};";
return Convert.ToInt32(command.ExecuteScalar());
}
private void LoadGroups()
{
_groups.Clear();
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = "SELECT group_name, flags FROM bz_groups ORDER BY group_name ASC;";
using var reader = command.ExecuteReader();
while (reader.Read())
{
var groupName = reader.GetString(0);
var flagsPart = reader.GetString(1);
var flags = flagsPart
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToList();
_groups[groupName] = new GroupInfo
{
GroupName = groupName,
Flags = flags
};
}
Logger.LogInformation($"[BZ_Admin] Loaded groups: {_groups.Count}");
}
private void LoadAdmins()
{
_admins.Clear();
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT id, name, steamid64, group_name, immunity, created_at, updated_at
FROM bz_admins
ORDER BY id ASC;";
using var reader = command.ExecuteReader();
while (reader.Read())
{
var admin = new AdminInfo
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
SteamId = reader.GetString(2),
GroupName = reader.GetString(3),
Immunity = reader.GetInt32(4),
CreatedAt = reader.GetDateTime(5).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
UpdatedAt = reader.GetDateTime(6).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
};
_admins[admin.SteamId] = admin;
}
}
private void LoadBans()
{
_bans.Clear();
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT id, name, steamid64, duration_minutes, reason, admin_name, admin_steamid64, created_at, updated_at, is_active, deleted_at, deleted_by, edited_by, edited_by_steamid64, edited_at, source
FROM bz_bans
ORDER BY id ASC;";
using var reader = command.ExecuteReader();
while (reader.Read())
{
_bans.Add(new BanInfo
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
SteamId = reader.GetString(2),
DurationMinutes = reader.GetInt32(3),
Reason = reader.GetString(4),
AdminName = reader.GetString(5),
AdminSteamId = reader.IsDBNull(6) ? null : reader.GetString(6),
CreatedAt = reader.GetDateTime(7).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
UpdatedAt = reader.GetDateTime(8).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
IsActive = reader.GetBoolean(9),
DeletedAt = reader.IsDBNull(10) ? null : reader.GetDateTime(10).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
DeletedBy = reader.IsDBNull(11) ? null : reader.GetString(11),
EditedBy = reader.IsDBNull(12) ? null : reader.GetString(12),
EditedBySteamId = reader.IsDBNull(13) ? null : reader.GetString(13),
EditedAt = reader.IsDBNull(14) ? null : reader.GetDateTime(14).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
Source = reader.IsDBNull(15) ? "server" : reader.GetString(15)
});
}
}
private void LoadBanTimes()
{
_banTimes.Clear();
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = "SELECT display_name, minutes FROM bz_ban_times ORDER BY minutes ASC;";
using var reader = command.ExecuteReader();
while (reader.Read())
{
_banTimes.Add(new BanTimeEntry
{
DisplayName = reader.GetString(0),
Minutes = reader.GetInt32(1)
});
}
}
private void LoadBanReasons()
{
_banReasons.Clear();
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = "SELECT display_name, reason FROM bz_ban_reasons ORDER BY display_name ASC;";
using var reader = command.ExecuteReader();
while (reader.Read())
{
_banReasons.Add(new BanReasonEntry
{
DisplayName = reader.GetString(0),
Reason = reader.GetString(1)
});
}
}
private void LoadMaps()
{
_maps.Clear();
if (!File.Exists(ChangeMapFilePath))
{
Logger.LogWarning($"[BZ_Admin] changemap.txt nicht gefunden: {ChangeMapFilePath}");
return;
}
foreach (var rawLine in File.ReadAllLines(ChangeMapFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
int colonIndex = line.IndexOf(':');
if (colonIndex > 0)
{
string workshopId = line.Substring(0, colonIndex).Trim();
string mapName = line.Substring(colonIndex + 1).Trim().ToLowerInvariant();
if (!ulong.TryParse(workshopId, out _))
{
Logger.LogWarning($"[BZ_Admin] Ungültige Workshop-Zeile in changemap.txt: {line}");
continue;
}
if (string.IsNullOrWhiteSpace(mapName))
{
Logger.LogWarning($"[BZ_Admin] Leerer Workshop-Mapname in changemap.txt: {line}");
continue;
}
var entry = new MapEntry
{
IsWorkshop = true,
WorkshopId = workshopId,
MapName = mapName
};
_maps[workshopId] = entry;
_maps[mapName] = entry;
Logger.LogInformation($"[BZ_Admin] Workshop-Map geladen: {workshopId} -> {mapName}");
}
else
{
string mapName = line.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(mapName))
continue;
_maps[mapName] = new MapEntry
{
IsWorkshop = false,
MapName = mapName
};
Logger.LogInformation($"[BZ_Admin] Standard-Map geladen: {mapName}");
}
}
int standardCount = _maps.Values
.Where(m => !m.IsWorkshop)
.Select(m => m.MapName)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
int workshopCount = _maps.Values
.Where(m => m.IsWorkshop)
.Select(m => m.WorkshopId)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
Logger.LogInformation($"[BZ_Admin] Maps geladen: Standard={standardCount}, Workshop={workshopCount}");
}
private AdminInfo? GetAdminInfo(string steamId)
{
return _admins.TryGetValue(steamId, out var admin) ? admin : null;
}
private string GetConfiguredAdminName(CCSPlayerController? player)
{
if (player == null)
return "Console";
var steamId = player.SteamID.ToString();
var admin = GetAdminInfo(steamId);
return admin?.Name ?? player.PlayerName;
}
private bool HasPermission(CCSPlayerController? player, string permission)
{
if (player == null)
return true;
if (AdminManager.PlayerHasPermissions(player, "@css/root"))
return true;
if (AdminManager.PlayerHasPermissions(player, permission))
return true;
var steamId = player.SteamID.ToString();
if (!_admins.TryGetValue(steamId, out var admin))
return false;
if (!_groups.TryGetValue(admin.GroupName, out var group))
return false;
if (group.Flags.Any(x => x.Equals("@css/root", StringComparison.OrdinalIgnoreCase)))
return true;
return group.Flags.Any(x => x.Equals(permission, StringComparison.OrdinalIgnoreCase));
}
private int GetLocalImmunity(string steamId)
{
var admin = GetAdminInfo(steamId);
return admin?.Immunity ?? 0;
}
private int GetEffectiveImmunity(CCSPlayerController? player)
{
if (player == null)
return 0;
int local = GetLocalImmunity(player.SteamID.ToString());
int css = (int)AdminManager.GetPlayerImmunity(player);
return Math.Max(local, css);
}
private bool CanTarget(CCSPlayerController? executor, CCSPlayerController? target)
{
if (executor == null)
return false;
if (target == null)
return true;
bool cssCanTarget = AdminManager.CanPlayerTarget(executor, target);
int execImmunity = GetEffectiveImmunity(executor);
int targetImmunity = GetEffectiveImmunity(target);
return cssCanTarget || execImmunity > targetImmunity;
}
private bool CanTarget(string executorId, string targetId)
{
if (!ulong.TryParse(executorId, out var executorSteamId))
return false;
if (!ulong.TryParse(targetId, out var targetSteamId))
return false;
var executorPlayer = Utilities.GetPlayerFromSteamId(executorSteamId);
var targetPlayer = Utilities.GetPlayerFromSteamId(targetSteamId);
if (executorPlayer != null)
return CanTarget(executorPlayer, targetPlayer);
var exec = GetAdminInfo(executorId);
var targ = GetAdminInfo(targetId);
if (exec == null)
return false;
if (targ == null)
return true;
return exec.Immunity > targ.Immunity;
}
private bool EnsurePermission(CCSPlayerController? player, CommandInfo commandInfo, string permission)
{
if (HasPermission(player, permission))
return true;
commandInfo.ReplyToCommand(T("NoPermission"));
return false;
}
[ConsoleCommand("css_adminmenu", "Open BZ admin menu")]
public void CmdAdminMenu(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/adminmenu"))
return;
if (player == null)
{
commandInfo.ReplyToCommand(T("PlayerOnlyCommand"));
return;
}
OpenAdminMenu(player);
}
[ConsoleCommand("css_reloadbanreasons", "Reload ban reasons from txt without duplicates")]
public void CmdReloadBanReasons(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/reloadconfigs"))
return;
int count = ReloadBanReasonsFromTxt();
LoadBanReasons();
commandInfo.ReplyToCommand(T("ReloadBanReasonsDone", count));
}
[ConsoleCommand("css_reloadbantimes", "Reload ban times from txt without duplicates")]
public void CmdReloadBanTimes(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/reloadconfigs"))
return;
int count = ReloadBanTimesFromTxt();
LoadBanTimes();
commandInfo.ReplyToCommand(T("ReloadBanTimesDone", count));
}
[ConsoleCommand("css_reloadgroups", "Reload groups from txt without duplicates")]
public void CmdReloadGroups(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/reloadconfigs"))
return;
int count = ReloadGroupsFromTxt();
LoadGroups();
commandInfo.ReplyToCommand(T("ReloadGroupsDone", count));
}
private void OpenAdminMenu(CCSPlayerController admin)
{
var menu = new ChatMenu(T("AdminMenuTitle"));
if (HasPermission(admin, "@css/ban"))
{
menu.AddMenuOption(T("AdminMenuBan"), (player, option) => OpenBanPlayerMenu(player));
menu.AddMenuOption(T("AdminMenuEditBan"), (player, option) => OpenEditBanMenu(player));
}
if (HasPermission(admin, "@css/kick"))
menu.AddMenuOption(T("AdminMenuKick"), (player, option) => OpenKickMenu(player));
if (HasPermission(admin, "@css/slay"))
menu.AddMenuOption(T("AdminMenuSlay"), (player, option) => OpenSlayMenu(player));
if (HasPermission(admin, "@css/changemap"))
{
menu.AddMenuOption(T("AdminMenuMap"), (player, option) => OpenMapMenu(player, false));
menu.AddMenuOption(T("AdminMenuWsMap"), (player, option) => OpenMapMenu(player, true));
}
if (HasPermission(admin, "@css/listmaps"))
menu.AddMenuOption(T("AdminMenuListMaps"), (player, option) => RunListMapsToPlayer(player));
if (HasPermission(admin, "@css/ban"))
{
menu.AddMenuOption(T("AdminMenuListBans"), (player, option) => RunListBansToPlayer(player));
menu.AddMenuOption(T("AdminMenuListBanTimes"), (player, option) => RunListBanTimesToPlayer(player));
menu.AddMenuOption(T("AdminMenuListBanReasons"), (player, option) => RunListBanReasonsToPlayer(player));
}
MenuManager.OpenChatMenu(admin, menu);
}
private void RunListMapsToPlayer(CCSPlayerController player)
{
if (_maps.Count == 0)
{
player.PrintToChat($" {T("NoMaps")}");
return;
}
player.PrintToChat($" {T("MapListHeader")}");
var printedStandard = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var printedWorkshop = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var kvp in _maps.OrderBy(x => x.Key))
{
var map = kvp.Value;
if (map.IsWorkshop)
{
if (printedWorkshop.Contains(map.WorkshopId))
continue;
printedWorkshop.Add(map.WorkshopId);
player.PrintToChat($" {T("MapEntryWorkshop", map.MapName, map.WorkshopId)}");
}
else
{
if (printedStandard.Contains(map.MapName))
continue;
printedStandard.Add(map.MapName);
player.PrintToChat($" {T("MapEntryStandard", map.MapName)}");
}
}
}
private void RunListBansToPlayer(CCSPlayerController player)
{
ExpireElapsedBans();
if (_bans.Count == 0)
{
player.PrintToChat($" {T("NoBans")}");
return;
}
foreach (var ban in _bans.OrderBy(x => x.Id))
player.PrintToChat($" {FormatBanListEntry(ban)}");
}
private void RunListBanTimesToPlayer(CCSPlayerController player)
{
if (_banTimes.Count == 0)
{
player.PrintToChat($" {T("NoBanTimes")}");
return;
}
player.PrintToChat($" {T("BanTimeHeader")}");
foreach (var entry in _banTimes)
player.PrintToChat($" {T("BanTimeEntry", entry.DisplayName, entry.Minutes)}");
}
private void RunListBanReasonsToPlayer(CCSPlayerController player)
{
if (_banReasons.Count == 0)
{
player.PrintToChat($" {T("NoBanReasons")}");
return;
}
player.PrintToChat($" {T("BanReasonHeader")}");
foreach (var entry in _banReasons)
player.PrintToChat($" {T("BanReasonEntry", entry.DisplayName, entry.Reason)}");
}
[ConsoleCommand("css_ban", "Ban menu or direct ban")]
public void CmdBan(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/ban"))
return;
if (commandInfo.ArgCount == 1)
{
if (player == null)
{
commandInfo.ReplyToCommand(T("PlayerOnlyCommand"));
return;
}
OpenBanPlayerMenu(player);
return;
}
if (commandInfo.ArgCount < 4)
{
commandInfo.ReplyToCommand(T("Usage_Ban"));
return;
}
if (player == null)
{
commandInfo.ReplyToCommand(T("PlayerOnlyCommand"));
return;
}
string executorName = GetConfiguredAdminName(player);
string steamIdArg = commandInfo.GetArg(1);
if (!ulong.TryParse(steamIdArg, out ulong targetSteamId))
{
commandInfo.ReplyToCommand(T("InvalidSteamId64"));
return;
}
string minutesArg = commandInfo.GetArg(2);
if (!int.TryParse(minutesArg, out int minutes))
{
commandInfo.ReplyToCommand(T("InvalidBanMinutes"));
return;
}
string reason = string.Join(" ", Enumerable.Range(3, commandInfo.ArgCount - 3).Select(commandInfo.GetArg));
var targetPlayer = Utilities.GetPlayerFromSteamId(targetSteamId);
if (!CanTarget(player, targetPlayer))
{
commandInfo.ReplyToCommand(T("BanDenied"));
return;
}
if (HasActiveBan(targetSteamId.ToString()))
{
commandInfo.ReplyToCommand(T("BanAlreadyActive", targetSteamId.ToString()));
return;
}
string targetName = targetPlayer?.PlayerName ?? "OhneName";
var ban = AddBan(targetName, targetSteamId.ToString(), minutes, reason, executorName, player.SteamID.ToString());
commandInfo.ReplyToCommand(T("BanAdded", ban.Name, ban.SteamId, FormatBanDuration(ban.DurationMinutes), ban.Reason, ban.AdminName));
try { Server.ExecuteCommand($"kickid {ban.SteamId}"); } catch { }
}
[ConsoleCommand("css_banid", "Ban offline or online by steamid64")]
public void CmdBanId(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/ban"))
return;
if (commandInfo.ArgCount < 4)
{
commandInfo.ReplyToCommand(T("Usage_BanId"));
return;
}
string executorName = GetConfiguredAdminName(player);
string? executorSteamId = player?.SteamID.ToString();
string steamIdArg = commandInfo.GetArg(1);
if (!ulong.TryParse(steamIdArg, out ulong targetSteamId))
{
commandInfo.ReplyToCommand(T("InvalidSteamId64"));
return;
}
string minutesArg = commandInfo.GetArg(2);
if (!int.TryParse(minutesArg, out int minutes))
{
commandInfo.ReplyToCommand(T("InvalidBanMinutes"));
return;
}
string reason = string.Join(" ", Enumerable.Range(3, commandInfo.ArgCount - 3).Select(commandInfo.GetArg));
if (player != null)
{
var targetPlayer = Utilities.GetPlayerFromSteamId(targetSteamId);
if (!CanTarget(player, targetPlayer))
{
commandInfo.ReplyToCommand(T("BanDenied"));
return;
}
}
if (HasActiveBan(targetSteamId.ToString()))
{
commandInfo.ReplyToCommand(T("BanAlreadyActive", targetSteamId.ToString()));
return;
}
var maybeTarget = Utilities.GetPlayerFromSteamId(targetSteamId);
string targetName = maybeTarget?.PlayerName ?? "OhneName";
var ban = AddBan(targetName, targetSteamId.ToString(), minutes, reason, executorName, executorSteamId);
commandInfo.ReplyToCommand(T("BanAdded", ban.Name, ban.SteamId, FormatBanDuration(ban.DurationMinutes), ban.Reason, ban.AdminName));
try { Server.ExecuteCommand($"kickid {ban.SteamId}"); } catch { }
}
[ConsoleCommand("css_editban", "Edit an active ban")]
public void CmdEditBan(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/ban"))
return;
if (commandInfo.ArgCount < 4)
{
commandInfo.ReplyToCommand(T("Usage_EditBan"));
return;
}
string steamIdArg = commandInfo.GetArg(1);
if (!ulong.TryParse(steamIdArg, out _))
{
commandInfo.ReplyToCommand(T("InvalidSteamId64"));
return;
}
string minutesArg = commandInfo.GetArg(2);
if (!int.TryParse(minutesArg, out int minutes))
{
commandInfo.ReplyToCommand(T("InvalidBanMinutes"));
return;
}
string reason = string.Join(" ", Enumerable.Range(3, commandInfo.ArgCount - 3).Select(commandInfo.GetArg));
var ban = GetLatestActiveBanBySteamId(steamIdArg);
if (ban == null)
{
commandInfo.ReplyToCommand(T("NoBanFound", steamIdArg));
return;
}
ApplyBanEdit(ban, minutes, reason, player);
commandInfo.ReplyToCommand(T("BanEdited", ban.Name, ban.SteamId, FormatBanDuration(ban.DurationMinutes), ban.Reason, GetConfiguredAdminName(player)));
}
[ConsoleCommand("css_unban", "Unban a player")]
public void CmdUnban(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/unban"))
return;
if (commandInfo.ArgCount < 2)
{
commandInfo.ReplyToCommand(T("Usage_Unban"));
return;
}
string steamIdArg = commandInfo.GetArg(1);
string deletedBy = GetConfiguredAdminName(player);
var ban = GetLatestActiveBanBySteamId(steamIdArg);
if (ban == null)
{
commandInfo.ReplyToCommand(T("NoBanFound", steamIdArg));
return;
}
UnbanInternal(ban, deletedBy);
commandInfo.ReplyToCommand(T("BanRemoved", steamIdArg));
}
[ConsoleCommand("css_kick", "Kick a player")]
public void CmdKick(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/kick"))
return;
if (commandInfo.ArgCount == 1)
{
if (player == null)
{
commandInfo.ReplyToCommand(T("PlayerOnlyCommand"));
return;
}
OpenKickMenu(player);
return;
}
if (commandInfo.ArgCount < 2)
{
commandInfo.ReplyToCommand(T("Usage_Kick"));
return;
}
string targetArg = commandInfo.GetArg(1);
if (!ulong.TryParse(targetArg, out ulong targetSteamId))
{
commandInfo.ReplyToCommand(T("InvalidSteamId64"));
return;
}
var targetPlayer = Utilities.GetPlayerFromSteamId(targetSteamId);
if (player != null && !CanTarget(player, targetPlayer))
{
commandInfo.ReplyToCommand(T("KickDenied"));
return;
}
commandInfo.ReplyToCommand(T("KickDone", targetSteamId));
Server.ExecuteCommand($"kickid {targetSteamId}");
}
[ConsoleCommand("css_slay", "Slay a player")]
public void CmdSlay(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/slay"))
return;
if (commandInfo.ArgCount == 1)
{
if (player == null)
{
commandInfo.ReplyToCommand(T("PlayerOnlyCommand"));
return;
}
OpenSlayMenu(player);
return;
}
if (commandInfo.ArgCount < 2)
{
commandInfo.ReplyToCommand(T("Usage_Slay"));
return;
}
string targetArg = commandInfo.GetArg(1);
if (!ulong.TryParse(targetArg, out ulong targetSteamId))
{
commandInfo.ReplyToCommand(T("InvalidSteamId64"));
return;
}
var targetPlayer = Utilities.GetPlayerFromSteamId(targetSteamId);
if (player != null && !CanTarget(player, targetPlayer))
{
commandInfo.ReplyToCommand(T("SlayDenied"));
return;
}
if (targetPlayer == null || !targetPlayer.IsValid || targetPlayer.Pawn == null || !targetPlayer.Pawn.IsValid)
{
commandInfo.ReplyToCommand(T("TargetNotFound"));
return;
}
targetPlayer.Pawn.Value?.CommitSuicide(false, true);
commandInfo.ReplyToCommand(T("SlayDone", targetSteamId));
}
[ConsoleCommand("css_map", "Change map")]
public void CmdMap(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/changemap"))
return;
if (commandInfo.ArgCount == 1)
{
if (player == null)
{
commandInfo.ReplyToCommand(T("PlayerOnlyCommand"));
return;
}
OpenMapMenu(player, false);
return;
}
string input = commandInfo.GetArg(1).Trim().ToLowerInvariant();
ChangeMap(player, input, commandInfo, false);
}
[ConsoleCommand("css_wsmap", "Change workshop map")]
public void CmdWorkshopMap(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/changemap"))
return;
if (commandInfo.ArgCount == 1)
{
if (player == null)
{
commandInfo.ReplyToCommand(T("PlayerOnlyCommand"));
return;
}
OpenMapMenu(player, true);
return;
}
string input = commandInfo.GetArg(1).Trim().ToLowerInvariant();
ChangeMap(player, input, commandInfo, true);
}
[ConsoleCommand("css_reloadconfigs", "Reload BZ_Admin configs")]
public void CmdReloadConfigs(CCSPlayerController? player, CommandInfo commandInfo)
{
if (!EnsurePermission(player, commandInfo, "@css/reloadconfigs"))
return;
LoadDatabaseConfig();
MigrateDatabase();
LoadLanguage(CurrentLanguage);
LoadGroups();
LoadAdmins();
LoadBans();
LoadBanTimes();
LoadBanReasons();
LoadMaps();
ExpireElapsedBans();
commandInfo.ReplyToCommand(T("ConfigsReloaded"));
}
private void OpenBanPlayerMenu(CCSPlayerController admin)
{
var players = GetConnectedHumanPlayers()
.Where(p => p.SteamID != admin.SteamID)
.OrderBy(p => p.PlayerName)
.ToList();
if (players.Count == 0)
{
admin.PrintToChat($" {T("BanMenuNoPlayers")}");
return;
}
var menu = new ChatMenu(T("BanMenuTitlePlayers"));
int index = 1;
foreach (var target in players)
{
string display = T("BanMenuPlayerEntry", index, target.PlayerName);
menu.AddMenuOption(display, (player, option) =>
{
string executorId = player.SteamID.ToString();
string targetId = target.SteamID.ToString();
if (!CanTarget(executorId, targetId))
{
player.PrintToChat($" {T("BanDenied")}");
return;
}
if (HasActiveBan(targetId))
{
player.PrintToChat($" {T("BanAlreadyActive", targetId)}");
return;
}
OpenBanTimeMenu(player, target);
});
index++;
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenEditBanMenu(CCSPlayerController admin)
{
ExpireElapsedBans();
var activeBans = _bans
.Where(x => x.IsActive)
.OrderByDescending(x => x.Id)
.GroupBy(x => x.SteamId, StringComparer.OrdinalIgnoreCase)
.Select(g => g.First())
.OrderBy(x => x.Name)
.ToList();
if (activeBans.Count == 0)
{
admin.PrintToChat($" {T("NoActiveBans")}");
return;
}
var menu = new ChatMenu(T("EditBanMenuTitleBans"));
int index = 1;
foreach (var ban in activeBans)
{
string display = T("EditBanMenuEntry", index, ban.Name, FormatBanDuration(ban.DurationMinutes));
menu.AddMenuOption(display, (player, option) =>
{
OpenEditBanTimeMenu(player, ban);
});
index++;
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenBanTimeMenu(CCSPlayerController admin, CCSPlayerController target)
{
if (_banTimes.Count == 0)
{
admin.PrintToChat($" {T("NoBanTimes")}");
return;
}
var menu = new ChatMenu(T("BanMenuTitleTimes", target.PlayerName));
foreach (var time in _banTimes)
{
string display = T("BanMenuTimeEntry", time.DisplayName, time.Minutes);
menu.AddMenuOption(display, (player, option) => OpenBanReasonMenu(player, target, time));
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenEditBanTimeMenu(CCSPlayerController admin, BanInfo ban)
{
if (_banTimes.Count == 0)
{
admin.PrintToChat($" {T("NoBanTimes")}");
return;
}
var menu = new ChatMenu(T("EditBanMenuTitleTimes", ban.Name));
foreach (var time in _banTimes)
{
string display = T("BanMenuTimeEntry", time.DisplayName, time.Minutes);
menu.AddMenuOption(display, (player, option) => OpenEditBanReasonMenu(player, ban, time.Minutes));
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenBanReasonMenu(CCSPlayerController admin, CCSPlayerController target, BanTimeEntry time)
{
if (_banReasons.Count == 0)
{
admin.PrintToChat($" {T("NoBanReasons")}");
return;
}
var menu = new ChatMenu(T("BanMenuTitleReasons", target.PlayerName));
foreach (var reason in _banReasons)
{
string display = T("BanMenuReasonEntry", reason.DisplayName);
menu.AddMenuOption(display, (player, option) =>
{
string adminName = GetConfiguredAdminName(player);
string targetName = string.IsNullOrWhiteSpace(target.PlayerName) ? "OhneName" : target.PlayerName;
string targetSteamId = target.SteamID.ToString();
if (HasActiveBan(targetSteamId))
{
player.PrintToChat($" {T("BanAlreadyActive", targetSteamId)}");
return;
}
var ban = AddBan(targetName, targetSteamId, time.Minutes, reason.Reason, adminName, player.SteamID.ToString());
player.PrintToChat($" {T("BanAdded", ban.Name, ban.SteamId, FormatBanDuration(ban.DurationMinutes), ban.Reason, ban.AdminName)}");
try { Server.ExecuteCommand($"kickid {targetSteamId}"); } catch { }
});
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenEditBanReasonMenu(CCSPlayerController admin, BanInfo ban, int minutes)
{
if (_banReasons.Count == 0)
{
admin.PrintToChat($" {T("NoBanReasons")}");
return;
}
var menu = new ChatMenu(T("EditBanMenuTitleReasons", ban.Name));
foreach (var reason in _banReasons)
{
string display = T("BanMenuReasonEntry", reason.DisplayName);
menu.AddMenuOption(display, (player, option) =>
{
ApplyBanEdit(ban, minutes, reason.Reason, player);
player.PrintToChat($" {T("BanEdited", ban.Name, ban.SteamId, FormatBanDuration(ban.DurationMinutes), ban.Reason, GetConfiguredAdminName(player))}");
});
}
menu.AddMenuOption(T("EditBanUseCommand"), (player, option) =>
{
player.PrintToChat($" {T("EditBanCustomReasonHint", ban.SteamId, minutes)}");
});
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenKickMenu(CCSPlayerController admin)
{
var players = GetConnectedHumanPlayers()
.Where(p => p.SteamID != admin.SteamID)
.OrderBy(p => p.PlayerName)
.ToList();
if (players.Count == 0)
{
admin.PrintToChat($" {T("KickMenuNoPlayers")}");
return;
}
var menu = new ChatMenu(T("KickMenuTitlePlayers"));
int index = 1;
foreach (var target in players)
{
string display = T("KickMenuPlayerEntry", index, target.PlayerName);
menu.AddMenuOption(display, (player, option) =>
{
string executorId = player.SteamID.ToString();
string targetId = target.SteamID.ToString();
if (!CanTarget(executorId, targetId))
{
player.PrintToChat($" {T("KickDenied")}");
return;
}
Server.ExecuteCommand($"kickid {target.SteamID}");
player.PrintToChat($" {T("KickDone", target.SteamID)}");
});
index++;
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenSlayMenu(CCSPlayerController admin)
{
var players = GetConnectedHumanPlayers()
.Where(p => p.SteamID != admin.SteamID)
.OrderBy(p => p.PlayerName)
.ToList();
if (players.Count == 0)
{
admin.PrintToChat($" {T("SlayMenuNoPlayers")}");
return;
}
var menu = new ChatMenu(T("SlayMenuTitlePlayers"));
int index = 1;
foreach (var target in players)
{
string display = T("SlayMenuPlayerEntry", index, target.PlayerName);
menu.AddMenuOption(display, (player, option) =>
{
string executorId = player.SteamID.ToString();
string targetId = target.SteamID.ToString();
if (!CanTarget(executorId, targetId))
{
player.PrintToChat($" {T("SlayDenied")}");
return;
}
if (!target.IsValid || target.Pawn == null || !target.Pawn.IsValid)
{
player.PrintToChat($" {T("TargetNotFound")}");
return;
}
target.Pawn.Value?.CommitSuicide(false, true);
player.PrintToChat($" {T("SlayDone", target.SteamID)}");
});
index++;
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
}
private void OpenMapMenu(CCSPlayerController admin, bool workshopOnly)
{
if (workshopOnly)
{
var workshopMaps = _maps.Values
.Where(m => m.IsWorkshop)
.GroupBy(m => m.WorkshopId)
.Select(g => g.First())
.OrderBy(m => m.MapName)
.ToList();
if (workshopMaps.Count == 0)
{
admin.PrintToChat($" {T("MapMenuNoWorkshopMaps")}");
return;
}
var menu = new ChatMenu(T("MapMenuTitleWorkshop"));
int index = 1;
foreach (var map in workshopMaps)
{
string display = T("MapMenuWorkshopEntry", index, map.MapName, map.WorkshopId);
menu.AddMenuOption(display, (player, option) =>
{
Server.ExecuteCommand($"host_workshop_map {map.WorkshopId}");
AnnounceMapChange(player, $"{map.MapName} ({map.WorkshopId})");
});
index++;
}
menu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, menu);
return;
}
var standardMaps = _maps.Values
.Where(m => !m.IsWorkshop)
.GroupBy(m => m.MapName)
.Select(g => g.First())
.OrderBy(m => m.MapName)
.ToList();
if (standardMaps.Count == 0)
{
admin.PrintToChat($" {T("MapMenuNoStandardMaps")}");
return;
}
var standardMenu = new ChatMenu(T("MapMenuTitle"));
int standardIndex = 1;
foreach (var map in standardMaps)
{
string display = T("MapMenuStandardEntry", standardIndex, map.MapName);
standardMenu.AddMenuOption(display, (player, option) =>
{
if (!Server.IsMapValid(map.MapName))
{
player.PrintToChat($" {T("MapNotFound", map.MapName)}");
return;
}
Server.ExecuteCommand($"changelevel {map.MapName}");
AnnounceMapChange(player, map.MapName);
});
standardIndex++;
}
standardMenu.AddMenuOption($"9. {T("MenuMain")}", (player, option) => OpenAdminMenu(player));
MenuManager.OpenChatMenu(admin, standardMenu);
}
private void ChangeMap(CCSPlayerController? caller, string input, CommandInfo commandInfo, bool workshopOnly)
{
if (!_maps.TryGetValue(input, out var map))
{
if (ulong.TryParse(input, out _))
{
Server.ExecuteCommand($"host_workshop_map {input}");
AnnounceMapChange(caller, input);
return;
}
if (workshopOnly)
{
commandInfo.ReplyToCommand(T("MapNotFound", input));
return;
}
if (Server.IsMapValid(input))
{
Server.ExecuteCommand($"changelevel {input}");
AnnounceMapChange(caller, input);
return;
}
commandInfo.ReplyToCommand(T("MapNotFound", input));
return;
}
if (map.IsWorkshop)
{
Server.ExecuteCommand($"host_workshop_map {map.WorkshopId}");
AnnounceMapChange(caller, $"{map.MapName} ({map.WorkshopId})");
return;
}
if (!Server.IsMapValid(map.MapName))
{
commandInfo.ReplyToCommand(T("MapNotFound", map.MapName));
return;
}
Server.ExecuteCommand($"changelevel {map.MapName}");
AnnounceMapChange(caller, map.MapName);
}
private void AnnounceMapChange(CCSPlayerController? caller, string mapDisplayName)
{
string callerName = GetConfiguredAdminName(caller);
Server.PrintToChatAll(T("MapChanged", callerName, mapDisplayName));
Logger.LogInformation($"[BZ_Admin] {callerName} changed map to {mapDisplayName}");
}
private List<CCSPlayerController> GetConnectedHumanPlayers()
{
return Utilities.GetPlayers()
.Where(p => p != null && p.IsValid && !p.IsBot && p.Connected == PlayerConnectedState.PlayerConnected)
.ToList();
}
private string EscapeServerCommandArgument(string value)
{
return value.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
private int GetNextAdminId() => _admins.Count == 0 ? 1 : _admins.Values.Max(x => x.Id) + 1;
private BanInfo AddBan(string targetName, string targetSteamId, int minutes, string reason, string executorName, string? executorSteamId)
{
string now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
var ban = new BanInfo
{
Id = InsertBanPlaceholder(),
Name = string.IsNullOrWhiteSpace(targetName) ? "OhneName" : targetName,
SteamId = targetSteamId,
DurationMinutes = minutes,
Reason = reason,
AdminName = executorName,
AdminSteamId = executorSteamId,
CreatedAt = now,
UpdatedAt = now,
IsActive = true,
DeletedAt = null,
DeletedBy = null,
EditedBy = null,
EditedBySteamId = null,
EditedAt = null,
Source = "server"
};
UpdateBanFull(ban);
_bans.Add(ban);
return ban;
}
private void ApplyBanEdit(BanInfo ban, int minutes, string reason, CCSPlayerController? editor)
{
string editorName = GetConfiguredAdminName(editor);
string? editorSteamId = editor?.SteamID.ToString();
string now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
ban.DurationMinutes = minutes;
ban.Reason = reason;
ban.UpdatedAt = now;
ban.EditedBy = editorName;
ban.EditedBySteamId = editorSteamId;
ban.EditedAt = now;
UpdateBanFull(ban);
}
private int InsertBanPlaceholder()
{
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = @"INSERT INTO bz_bans (name, steamid64, duration_minutes, reason, admin_name, created_at, updated_at, is_active, source)
VALUES ('', '', 0, '', '', NOW(), NOW(), 0, 'server');
SELECT LAST_INSERT_ID();";
return Convert.ToInt32(command.ExecuteScalar());
}
private void UpdateBanFull(BanInfo ban)
{
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = @"
UPDATE bz_bans
SET
name = @name,
steamid64 = @steamid64,
duration_minutes = @duration_minutes,
reason = @reason,
admin_name = @admin_name,
admin_steamid64 = @admin_steamid64,
created_at = @created_at,
updated_at = @updated_at,
is_active = @is_active,
deleted_at = @deleted_at,
deleted_by = @deleted_by,
edited_by = @edited_by,
edited_by_steamid64 = @edited_by_steamid64,
edited_at = @edited_at,
source = @source
WHERE id = @id;";
command.Parameters.AddWithValue("@name", ban.Name);
command.Parameters.AddWithValue("@steamid64", ban.SteamId);
command.Parameters.AddWithValue("@duration_minutes", ban.DurationMinutes);
command.Parameters.AddWithValue("@reason", ban.Reason);
command.Parameters.AddWithValue("@admin_name", ban.AdminName);
command.Parameters.AddWithValue("@admin_steamid64", (object?)ban.AdminSteamId ?? DBNull.Value);
command.Parameters.AddWithValue("@created_at", ban.CreatedAt);
command.Parameters.AddWithValue("@updated_at", ban.UpdatedAt);
command.Parameters.AddWithValue("@is_active", ban.IsActive);
command.Parameters.AddWithValue("@deleted_at", (object?)ban.DeletedAt ?? DBNull.Value);
command.Parameters.AddWithValue("@deleted_by", (object?)ban.DeletedBy ?? DBNull.Value);
command.Parameters.AddWithValue("@edited_by", (object?)ban.EditedBy ?? DBNull.Value);
command.Parameters.AddWithValue("@edited_by_steamid64", (object?)ban.EditedBySteamId ?? DBNull.Value);
command.Parameters.AddWithValue("@edited_at", (object?)ban.EditedAt ?? DBNull.Value);
command.Parameters.AddWithValue("@source", ban.Source);
command.Parameters.AddWithValue("@id", ban.Id);
command.ExecuteNonQuery();
}
private void UnbanInternal(BanInfo ban, string deletedBy)
{
ban.IsActive = false;
ban.DeletedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
ban.DeletedBy = deletedBy;
ban.UpdatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
UpdateBanStatus(ban);
}
private bool HasActiveBan(string steamId) => GetEffectiveActiveBanBySteamId(steamId) != null;
private BanInfo? GetLatestActiveBanBySteamId(string steamId)
{
return _bans
.Where(x => x.SteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase) && x.IsActive)
.OrderByDescending(x => x.Id)
.FirstOrDefault();
}
private BanInfo? GetEffectiveActiveBanBySteamId(string steamId)
{
var activeBan = GetLatestActiveBanBySteamId(steamId);
if (activeBan == null)
return null;
if (IsBanExpired(activeBan))
{
ExpireBan(activeBan);
return null;
}
return activeBan;
}
private bool IsBanExpired(BanInfo ban)
{
if (!ban.IsActive || ban.DurationMinutes <= 0)
return false;
if (!DateTime.TryParseExact(ban.CreatedAt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var createdAt))
return false;
return DateTime.Now >= createdAt.AddMinutes(ban.DurationMinutes);
}
private void ExpireBan(BanInfo ban)
{
ban.IsActive = false;
ban.DeletedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
ban.DeletedBy = "AUTO";
ban.UpdatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
UpdateBanStatus(ban);
}
private void ExpireElapsedBans()
{
foreach (var ban in _bans.Where(x => x.IsActive).ToList())
{
if (!IsBanExpired(ban))
continue;
ban.IsActive = false;
ban.DeletedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
ban.DeletedBy = "AUTO";
ban.UpdatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
UpdateBanStatus(ban);
}
}
private string FormatBanListEntry(BanInfo ban)
{
string status =
ban.IsActive ? T("BanStatusActive") :
string.Equals(ban.DeletedBy, "AUTO", StringComparison.OrdinalIgnoreCase) ? T("BanStatusExpired") :
T("BanStatusRemoved");
return T("BanListEntry", ban.Name, ban.SteamId, ban.Reason, ban.AdminName, status);
}
private int ReloadBanReasonsFromTxt()
{
if (!File.Exists(LegacyBanReasonsFilePath))
return 0;
int count = 0;
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
foreach (var rawLine in File.ReadAllLines(LegacyBanReasonsFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
var parts = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
continue;
using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = @"
INSERT INTO bz_ban_reasons (display_name, reason, created_at, updated_at)
VALUES (@display_name, @reason, NOW(), NOW())
ON DUPLICATE KEY UPDATE
reason = VALUES(reason),
updated_at = NOW();";
command.Parameters.AddWithValue("@display_name", parts[0].Trim());
command.Parameters.AddWithValue("@reason", parts[1].Trim());
count += command.ExecuteNonQuery() > 0 ? 1 : 0;
}
transaction.Commit();
return count;
}
private int ReloadBanTimesFromTxt()
{
if (!File.Exists(LegacyBanTimesFilePath))
return 0;
int count = 0;
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
foreach (var rawLine in File.ReadAllLines(LegacyBanTimesFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
continue;
var parts = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
continue;
if (!int.TryParse(parts[1], out int minutes))
continue;
using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = @"
INSERT INTO bz_ban_times (display_name, minutes, created_at, updated_at)
VALUES (@display_name, @minutes, NOW(), NOW())
ON DUPLICATE KEY UPDATE
minutes = VALUES(minutes),
updated_at = NOW();";
command.Parameters.AddWithValue("@display_name", parts[0].Trim());
command.Parameters.AddWithValue("@minutes", minutes);
count += command.ExecuteNonQuery() > 0 ? 1 : 0;
}
transaction.Commit();
return count;
}
private int ReloadGroupsFromTxt()
{
if (!File.Exists(LegacyGroupsFilePath))
return 0;
int count = 0;
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
foreach (var rawLine in File.ReadAllLines(LegacyGroupsFilePath))
{
var line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//"))
continue;
var parts = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
continue;
var groupName = parts[0].Trim();
var flags = parts[1].Trim();
if (!groupName.StartsWith("#"))
continue;
using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = @"
INSERT INTO bz_groups (group_name, flags, created_at, updated_at)
VALUES (@group_name, @flags, NOW(), NOW())
ON DUPLICATE KEY UPDATE
flags = VALUES(flags),
updated_at = NOW();";
command.Parameters.AddWithValue("@group_name", groupName);
command.Parameters.AddWithValue("@flags", flags);
count += command.ExecuteNonQuery() > 0 ? 1 : 0;
}
transaction.Commit();
return count;
}
private void InsertGroup(MySqlConnection connection, string groupName, string flagsCsv, MySqlTransaction? transaction)
{
using var command = connection.CreateCommand();
if (transaction != null) command.Transaction = transaction;
command.CommandText = @"INSERT INTO bz_groups (group_name, flags, created_at, updated_at) VALUES (@group_name, @flags, NOW(), NOW());";
command.Parameters.AddWithValue("@group_name", groupName);
command.Parameters.AddWithValue("@flags", flagsCsv);
command.ExecuteNonQuery();
}
private void InsertAdmin(MySqlConnection connection, AdminInfo admin, MySqlTransaction? transaction)
{
using var command = connection.CreateCommand();
if (transaction != null) command.Transaction = transaction;
command.CommandText = @"INSERT INTO bz_admins (id, name, steamid64, group_name, immunity, created_at, updated_at)
VALUES (@id, @name, @steamid64, @group_name, @immunity, @created_at, @updated_at);";
command.Parameters.AddWithValue("@id", admin.Id);
command.Parameters.AddWithValue("@name", admin.Name);
command.Parameters.AddWithValue("@steamid64", admin.SteamId);
command.Parameters.AddWithValue("@group_name", admin.GroupName);
command.Parameters.AddWithValue("@immunity", admin.Immunity);
command.Parameters.AddWithValue("@created_at", admin.CreatedAt);
command.Parameters.AddWithValue("@updated_at", admin.UpdatedAt);
command.ExecuteNonQuery();
}
private void InsertBanTime(MySqlConnection connection, string displayName, int minutes, MySqlTransaction? transaction)
{
using var command = connection.CreateCommand();
if (transaction != null) command.Transaction = transaction;
command.CommandText = @"INSERT INTO bz_ban_times (display_name, minutes, created_at, updated_at) VALUES (@display_name, @minutes, NOW(), NOW());";
command.Parameters.AddWithValue("@display_name", displayName);
command.Parameters.AddWithValue("@minutes", minutes);
command.ExecuteNonQuery();
}
private void InsertBanReason(MySqlConnection connection, string displayName, string reason, MySqlTransaction? transaction)
{
using var command = connection.CreateCommand();
if (transaction != null) command.Transaction = transaction;
command.CommandText = @"INSERT INTO bz_ban_reasons (display_name, reason, created_at, updated_at) VALUES (@display_name, @reason, NOW(), NOW());";
command.Parameters.AddWithValue("@display_name", displayName);
command.Parameters.AddWithValue("@reason", reason);
command.ExecuteNonQuery();
}
private void InsertBan(MySqlConnection connection, BanInfo ban, MySqlTransaction? transaction)
{
using var command = connection.CreateCommand();
if (transaction != null) command.Transaction = transaction;
command.CommandText = @"INSERT INTO bz_bans (id, name, steamid64, duration_minutes, reason, admin_name, admin_steamid64, created_at, updated_at, is_active, deleted_at, deleted_by, edited_by, edited_by_steamid64, edited_at, source)
VALUES (@id, @name, @steamid64, @duration_minutes, @reason, @admin_name, @admin_steamid64, @created_at, @updated_at, @is_active, @deleted_at, @deleted_by, @edited_by, @edited_by_steamid64, @edited_at, @source);";
command.Parameters.AddWithValue("@id", ban.Id);
command.Parameters.AddWithValue("@name", ban.Name);
command.Parameters.AddWithValue("@steamid64", ban.SteamId);
command.Parameters.AddWithValue("@duration_minutes", ban.DurationMinutes);
command.Parameters.AddWithValue("@reason", ban.Reason);
command.Parameters.AddWithValue("@admin_name", ban.AdminName);
command.Parameters.AddWithValue("@admin_steamid64", (object?)ban.AdminSteamId ?? DBNull.Value);
command.Parameters.AddWithValue("@created_at", ban.CreatedAt);
command.Parameters.AddWithValue("@updated_at", ban.UpdatedAt);
command.Parameters.AddWithValue("@is_active", ban.IsActive);
command.Parameters.AddWithValue("@deleted_at", (object?)ban.DeletedAt ?? DBNull.Value);
command.Parameters.AddWithValue("@deleted_by", (object?)ban.DeletedBy ?? DBNull.Value);
command.Parameters.AddWithValue("@edited_by", (object?)ban.EditedBy ?? DBNull.Value);
command.Parameters.AddWithValue("@edited_by_steamid64", (object?)ban.EditedBySteamId ?? DBNull.Value);
command.Parameters.AddWithValue("@edited_at", (object?)ban.EditedAt ?? DBNull.Value);
command.Parameters.AddWithValue("@source", ban.Source);
command.ExecuteNonQuery();
}
private void UpdateBanStatus(BanInfo ban)
{
using var connection = GetConnection();
using var command = connection.CreateCommand();
command.CommandText = @"UPDATE bz_bans SET is_active=@is_active, deleted_at=@deleted_at, deleted_by=@deleted_by, updated_at=@updated_at WHERE id=@id;";
command.Parameters.AddWithValue("@is_active", ban.IsActive);
command.Parameters.AddWithValue("@deleted_at", (object?)ban.DeletedAt ?? DBNull.Value);
command.Parameters.AddWithValue("@deleted_by", (object?)ban.DeletedBy ?? DBNull.Value);
command.Parameters.AddWithValue("@updated_at", ban.UpdatedAt);
command.Parameters.AddWithValue("@id", ban.Id);
command.ExecuteNonQuery();
}
private class DatabaseConfig
{
public string Host { get; set; } = "127.0.0.1";
public int Port { get; set; } = 3306;
public string Database { get; set; } = "bz_admin";
public string Username { get; set; } = "bz_admin";
public string Password { get; set; } = "DEIN_PASSWORT";
public string Charset { get; set; } = "utf8mb4";
}
private class GroupInfo
{
public string GroupName { get; set; } = "";
public List<string> Flags { get; set; } = new();
}
private class AdminInfo
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string SteamId { get; set; } = "";
public string GroupName { get; set; } = "";
public int Immunity { get; set; }
public string CreatedAt { get; set; } = "";
public string UpdatedAt { get; set; } = "";
}
private class BanInfo
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string SteamId { get; set; } = "";
public int DurationMinutes { get; set; }
public string Reason { get; set; } = "";
public string AdminName { get; set; } = "";
public string? AdminSteamId { get; set; }
public string CreatedAt { get; set; } = "";
public string UpdatedAt { get; set; } = "";
public bool IsActive { get; set; } = true;
public string? DeletedAt { get; set; }
public string? DeletedBy { get; set; }
public string? EditedBy { get; set; }
public string? EditedBySteamId { get; set; }
public string? EditedAt { get; set; }
public string Source { get; set; } = "server";
}
private class BanTimeEntry
{
public string DisplayName { get; set; } = "";
public int Minutes { get; set; }
}
private class BanReasonEntry
{
public string DisplayName { get; set; } = "";
public string Reason { get; set; } = "";
}
private class MapEntry
{
public bool IsWorkshop { get; set; }
public string MapName { get; set; } = "";
public string WorkshopId { get; set; } = "";
}
}
}
Alles anzeigen