Compare commits

..

3 Commits

Author SHA1 Message Date
Yuriy Petleshkov
cf6d58812f Merge 84646e4451 into 7154843d1d 2024-11-01 05:37:21 +03:00
Yuriy Petleshkov
84646e4451 chore: fix duplicate messages 2024-10-13 12:27:50 +03:00
Yuriy Petleshkov
94e0013cf9 chore: add vip chat 2024-10-13 09:58:29 +03:00
138 changed files with 2134 additions and 9556 deletions

View File

@@ -1,7 +1,6 @@
name: Build and Publish
name: Build
on:
workflow_dispatch:
push:
branches: [ "main" ]
paths-ignore:
@@ -12,111 +11,85 @@ on:
- '**/README.md'
env:
BUILD_NUMBER: ${{ github.run_number }}
PROJECT_PATH_CS2_SIMPLEADMIN: "CS2-SimpleAdmin/CS2-SimpleAdmin.csproj"
PROJECT_NAME_CS2_SIMPLEADMIN: "CS2-SimpleAdmin"
PROJECT_PATH_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj"
PROJECT_NAME_CS2_SIMPLEADMIN: "CS2-SimpleAdmin"
PROJECT_NAME_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi"
PROJECT_PATH_STEALTHMODULE: "Modules/CS2-SimpleAdmin_StealthModule/CS2-SimpleAdmin_StealthModule.csproj"
PROJECT_NAME_STEALTHMODULE: "CS2-SimpleAdmin_StealthModule"
OUTPUT_PATH: "./counterstrikesharp"
TMP_PATH: "./tmp"
jobs:
build:
runs-on: ubuntu-latest
permissions: write-all
outputs:
build_version: ${{ steps.get_version.outputs.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Get Version
id: get_version
run: echo "VERSION=$(cat CS2-SimpleAdmin/VERSION)" >> $GITHUB_OUTPUT
- name: Restore & Build All Projects
run: |
dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }}
dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}
dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }}
dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}
dotnet restore ${{ env.PROJECT_PATH_STEALTHMODULE }}
dotnet build ${{ env.PROJECT_PATH_STEALTHMODULE }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_STEALTHMODULE }}
- name: Combine projects
run: |
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_StealthModule
mkdir -p ${{ env.OUTPUT_PATH }}/shared/CS2-SimpleAdminApi
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin/
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_STEALTHMODULE }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_StealthModule
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/* ${{ env.OUTPUT_PATH }}/shared/CS2-SimpleAdminApi/
- name: Zip Main Build Output
run: zip -r CS2-SimpleAdmin${{ steps.get_version.outputs.VERSION }}.zip ${{ env.OUTPUT_PATH }}
- name: Extract & Zip StatusBlocker Linux
run: |
mkdir -p statusblocker-linux &&
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-linux.tar.gz -C statusblocker-linux &&
cd statusblocker-linux &&
zip -r ../StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip ./*
- name: Extract & Zip StatusBlocker Windows
run: |
mkdir -p statusblocker-windows &&
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-windows.tar.gz -C statusblocker-windows &&
cd statusblocker-windows &&
zip -r ../StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip ./*
- name: Upload all artifacts
uses: actions/upload-artifact@v4
with:
name: CS2-SimpleAdmin-Build-Artifacts
path: |
CS2-SimpleAdmin${{ steps.get_version.outputs.VERSION }}.zip
StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip
StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore CS2-SimpleAdmin
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }}
- name: Build CS2-SimpleAdmin
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}
- name: Restore CS2-SimpleAdminApi
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }}
- name: Build CS2-SimpleAdminApi
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}
publish:
needs: build
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions: write-all
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: CS2-SimpleAdmin-Build-Artifacts
path: .
- name: Unzip main build artifact
run: unzip CS2-SimpleAdmin${{ needs.build.outputs.build_version }}.zip -d ./counterstrikesharp
- name: Publish combined release
uses: ncipollo/release-action@v1.14.0
with:
artifacts: |
CS2-SimpleAdmin${{ needs.build.outputs.build_version }}.zip
StatusBlocker-linux-${{ needs.build.outputs.build_version }}.zip
StatusBlocker-windows-${{ needs.build.outputs.build_version }}.zip
name: "CS2-SimpleAdmin Build #${{ needs.build.outputs.build_version }}"
tag: "build-${{ needs.build.outputs.build_version }}"
body: |
Place the files in your server as follows:
- CS2-SimpleAdmin:
Place the files inside the addons/counterstrikesharp directory.
After the first launch, configure the plugin using the JSON config file at:
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
- StatusBlocker:
Place the plugin files directly into the addons directory.
This plugin is a Metamod module for the StealthModule and does not require a subfolder.
Remember to restart or reload your game server after installing and configuring the plugins.
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore CS2-SimpleAdmin
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }}
- name: Build CS2-SimpleAdmin
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}
- name: Restore CS2-SimpleAdminApi
run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }}
- name: Build CS2-SimpleAdminApi
run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}
- name: Clean files
run: |
rm -f \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/CounterStrikeSharp.API.dll \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/McMaster.NETCore.Plugins.dll \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.DotNet.PlatformAbstractions.dll \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.Extensions.DependencyModel.dll \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/CS2-SimpleAdminApi.* \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.* \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/CounterStrikeSharp.API.dll \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/McMaster.NETCore.Plugins.dll \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/Microsoft.DotNet.PlatformAbstractions.dll \
${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/Microsoft.Extensions.DependencyModel.dll
- name: Combine projects
run: |
mkdir -p ${{ env.OUTPUT_PATH }}/plugins
mkdir -p ${{ env.OUTPUT_PATH }}/shared
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }} ${{ env.OUTPUT_PATH }}/plugins/
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }} ${{ env.OUTPUT_PATH }}/shared/
- name: Zip combined
uses: thedoctor0/zip-release@0.7.6
with:
type: 'zip'
filename: '${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}-${{ env.BUILD_NUMBER }}.zip'
path: ${{ env.OUTPUT_PATH }}
- name: Publish combined release
uses: ncipollo/release-action@v1.14.0
with:
artifacts: "${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}-${{ env.BUILD_NUMBER }}.zip"
name: "CS2-SimpleAdmin - Build ${{ env.BUILD_NUMBER }}"
tag: "build-${{ env.BUILD_NUMBER }}"
body: |
Place files in addons/counterstrikesharp
After first launch, configure the plugins in the respective configs:
- CS2-SimpleAdmin: addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json

7
.gitignore vendored
View File

@@ -3,9 +3,4 @@ obj/
.vs/
.git
.vscode/
.idea/
Modules/CS2-SimpleAdmin_PlayTimeModule
CS2-SimpleAdmin.sln.DotSettings.user
Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user
CS2-SimpleAdmin_BanSoundModule — kopia
*.user
.idea/

Binary file not shown.

View File

@@ -1,5 +1,4 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdmin.Managers;
@@ -14,7 +13,7 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
if (!player.UserId.HasValue)
throw new KeyNotFoundException("Player with specific UserId not found");
return CS2_SimpleAdmin.PlayersInfo[player.SteamID];
return CS2_SimpleAdmin.PlayersInfo[player.UserId.Value];
}
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
@@ -26,20 +25,14 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot);
}
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied;
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded;
public event Action<string, string?, bool, object>? OnAdminShowActivity;
public event Action<int, bool>? OnAdminToggleSilent;
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltied;
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltiedAdded;
public void OnPlayerPenaltiedEvent(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
int duration = -1) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, CS2_SimpleAdmin.ServerId);
public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
public void OnAdminToggleSilentEvent(int slot, bool status) => OnAdminToggleSilent?.Invoke(slot, status);
int duration) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration, CS2_SimpleAdmin.ServerId);
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
{
@@ -79,40 +72,6 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
}
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
{
switch (penaltyType)
{
case PenaltyType.Ban:
{
CS2_SimpleAdmin.Instance.AddBan(admin, steamid, duration, reason);
break;
}
case PenaltyType.Gag:
{
CS2_SimpleAdmin.Instance.AddGag(admin, steamid, duration, reason);
break;
}
case PenaltyType.Mute:
{
CS2_SimpleAdmin.Instance.AddMute(admin, steamid, duration, reason);
break;
}
case PenaltyType.Silence:
{
CS2_SimpleAdmin.Instance.AddSilence(admin, steamid, duration, reason);
break;
}
case PenaltyType.Warn:
{
CS2_SimpleAdmin.Instance.AddWarn(admin, steamid, duration, reason);
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
}
public void LogCommand(CCSPlayerController? caller, string command)
{
@@ -123,48 +82,4 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
{
Helper.LogCommand(caller, command);
}
public bool IsAdminSilent(CCSPlayerController player)
{
return CS2_SimpleAdmin.SilentPlayers.Contains(player.Slot);
}
public HashSet<int> ListSilentAdminsSlots()
{
return CS2_SimpleAdmin.SilentPlayers;
}
public void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Command name cannot be null or empty.", nameof(name));
ArgumentNullException.ThrowIfNull(callback);
var definition = new CommandDefinition(name, description ?? "No description", callback);
if (!RegisterCommands._commandDefinitions.TryGetValue(name, out var list))
{
list = new List<CommandDefinition>();
RegisterCommands._commandDefinitions[name] = list;
}
list.Add(definition);
}
public void UnRegisterCommand(string commandName)
{
var definitions = RegisterCommands._commandDefinitions[commandName];
if (definitions.Count == 0)
return;
foreach (var definition in definitions)
{
CS2_SimpleAdmin.Instance.RemoveCommand(commandName, definition.Callback);
}
}
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
{
Helper.ShowAdminActivity(messageKey, callerName, dontPublish, messageArgs);
}
}

View File

@@ -1,12 +1,9 @@
using System.Reflection;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
@@ -14,7 +11,7 @@ using MySqlConnector;
namespace CS2_SimpleAdmin;
[MinimumApiVersion(300)]
[MinimumApiVersion(279)]
public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdminConfig>
{
internal static CS2_SimpleAdmin Instance { get; private set; } = new();
@@ -22,197 +19,87 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
public override string ModuleAuthor => "daffyy & Dliix66";
public override string ModuleVersion => "1.7.7-alpha-9";
public override string ModuleVersion => "1.6.7a";
public override void Load(bool hotReload)
{
Instance = this;
RegisterEvents();
if (hotReload)
{
ServerLoaded = false;
_serverLoading = false;
CacheManager?.Dispose();
CacheManager = new CacheManager();
// OnGameServerSteamAPIActivated();
OnGameServerSteamAPIActivated();
OnMapStart(string.Empty);
AddTimer(6.0f, () =>
AddTimer(2.0f, () =>
{
if (DatabaseProvider == null) return;
PlayersInfo.Clear();
CachedPlayers.Clear();
BotPlayers.Clear();
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
if (Database == null) return;
Helper.GetValidPlayers().ForEach(player =>
{
if (!player.IsBot)
PlayerManager.LoadPlayerData(player, true);
else
BotPlayers.Add(player);
};
var playerManager = new PlayerManager();
playerManager.LoadPlayerData(player);
});
});
PlayersTimer?.Kill();
PlayersTimer = null;
}
_cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>(GameData.GetSignature("CBasePlayerController_SetPawn"));
SimpleAdminApi = new Api.CS2_SimpleAdminApi();
Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi);
PlayersTimer?.Kill();
PlayersTimer = null;
PlayerManager.CheckPlayersTimer();
new PlayerManager().CheckPlayersTimer();
}
public override void OnAllPluginsLoaded(bool hotReload)
{
try
{
MenuApi = MenuCapability.Get();
}
catch (Exception ex)
{
Logger.LogError("Unable to load required plugins ... \n{exception}", ex.Message);
Unload(false);
}
AddTimer(3.0f, () => ReloadAdmins(null));
AddTimer(6.0f, () => ReloadAdmins(null));
RegisterEvents();
MenuApi = MenuCapability.Get();
if (MenuApi == null)
Logger.LogError("MenuManager Core not found...");
RegisterCommands.InitializeCommands();
if (!CoreConfig.UnlockConCommands)
{
_logger?.LogError(
$"⚠️ Warning: 'UnlockConCommands' is disabled in core.json. " +
$"Players will not be automatically banned when kicked and will be able " +
$"to rejoin the server for 60 seconds. " +
$"To enable instant banning, set 'UnlockConCommands': true"
);
_logger?.LogError(
$"⚠️ Warning: 'UnlockConCommands' is disabled in core.json. " +
$"Players will not be automatically banned when kicked and will be able " +
$"to rejoin the server for 60 seconds. " +
$"To enable instant banning, set 'UnlockConCommands': true"
);
}
}
public void OnConfigParsed(CS2_SimpleAdminConfig config)
{
if (System.Diagnostics.Debugger.IsAttached)
Environment.FailFast(":(!");
Helper.UpdateConfig(config);
_logger = Logger;
Config = config;
bool missing = false;
var cssPath = Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp");
var pluginsPath = Path.Combine(cssPath, "plugins");
var sharedPath = Path.Combine(cssPath, "shared");
foreach (var plugin in _requiredPlugins)
{
var pluginDirPath = Path.Combine(pluginsPath, plugin);
var pluginDllPath = Path.Combine(pluginDirPath, $"{plugin}.dll");
if (!Directory.Exists(pluginDirPath))
{
_logger?.LogError(
$"❌ Plugin directory '{plugin}' missing at: {pluginDirPath}"
);
missing = true;
}
if (!File.Exists(pluginDllPath))
{
_logger?.LogError(
$"❌ Plugin DLL '{plugin}.dll' missing at: {pluginDllPath}"
);
missing = true;
}
}
foreach (var shared in _requiredShared)
{
var sharedDirPath = Path.Combine(sharedPath, shared);
var sharedDllPath = Path.Combine(sharedDirPath, $"{shared}.dll");
if (!Directory.Exists(sharedDirPath))
{
_logger?.LogError(
$"❌ Shared library directory '{shared}' missing at: {sharedDirPath}"
);
missing = true;
}
if (!File.Exists(sharedDllPath))
{
_logger?.LogError(
$"❌ Shared library DLL '{shared}.dll' missing at: {sharedDllPath}"
);
missing = true;
}
}
if (missing)
Server.ExecuteCommand($"css_plugins unload {ModuleName}");
Instance = this;
_logger = Logger;
if (Config.DatabaseConfig.DatabaseType.Contains("mysql", StringComparison.CurrentCultureIgnoreCase))
{
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseHost) ||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseName) ||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseUser))
if (config.DatabaseHost.Length < 1 || config.DatabaseName.Length < 1 || config.DatabaseUser.Length < 1)
{
throw new Exception("[CS2-SimpleAdmin] You need to setup MySQL credentials in config!");
throw new Exception("[CS2-SimpleAdmin] You need to setup Database credentials in config!");
}
var builder = new MySqlConnectionStringBuilder()
MySqlConnectionStringBuilder builder = new()
{
Server = config.DatabaseConfig.DatabaseHost,
Database = config.DatabaseConfig.DatabaseName,
UserID = config.DatabaseConfig.DatabaseUser,
Password = config.DatabaseConfig.DatabasePassword,
Port = (uint)config.DatabaseConfig.DatabasePort,
SslMode = Enum.TryParse(config.DatabaseConfig.DatabaseSSlMode, true, out MySqlSslMode sslMode)
? sslMode
: MySqlSslMode.Preferred,
Server = config.DatabaseHost,
Database = config.DatabaseName,
UserID = config.DatabaseUser,
Password = config.DatabasePassword,
Port = (uint)config.DatabasePort,
Pooling = true,
MinimumPoolSize = 0,
MaximumPoolSize = 640,
};
DbConnectionString = builder.ConnectionString;
DatabaseProvider = new MySqlDatabaseProvider(DbConnectionString);
}
else
{
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.SqliteFilePath))
Database = new Database.Database(DbConnectionString);
if (!Database.CheckDatabaseConnection())
{
throw new Exception("[CS2-SimpleAdmin] You need to specify SQLite file path in config!");
Logger.LogError("Unable connect to database!");
Unload(false);
return;
}
Task.Run(() => Database.DatabaseMigration());
DatabaseProvider = new SqliteDatabaseProvider(ModuleDirectory + "/" + config.DatabaseConfig.SqliteFilePath);
}
Config = config;
Helper.UpdateConfig(config);
var (success, exception) = Task.Run(() => DatabaseProvider.CheckConnectionAsync()).GetAwaiter().GetResult();
if (!success)
{
if (exception != null)
Logger.LogError("Problem with database connection! \n{exception}", exception);
Unload(false);
return;
}
Task.Run(() => DatabaseProvider.DatabaseMigrationAsync());
if (!Directory.Exists(ModuleDirectory + "/data"))
{
Directory.CreateDirectory(ModuleDirectory + "/data");
@@ -223,46 +110,33 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
if (!string.IsNullOrEmpty(Config.Discord.DiscordLogWebhook))
DiscordWebhookClientLog = new DiscordManager(Config.Discord.DiscordLogWebhook);
PluginInfo.ShowAd(ModuleVersion);
if (Config.EnableUpdateCheck)
Task.Run(async () => await PluginInfo.CheckVersion(ModuleVersion, Logger));
Task.Run(async () => await PluginInfo.CheckVersion(ModuleVersion, _logger));
PermissionManager = new PermissionManager(DatabaseProvider);
BanManager = new BanManager(DatabaseProvider);
MuteManager = new MuteManager(DatabaseProvider);
WarnManager = new WarnManager(DatabaseProvider);
PermissionManager = new PermissionManager(Database);
BanManager = new BanManager(Database);
MuteManager = new MuteManager(Database);
WarnManager = new WarnManager(Database);
}
private static TargetResult? GetTarget(CommandInfo command, int argument = 1)
private static TargetResult? GetTarget(CommandInfo command)
{
var matches = command.GetArgTargetResult(argument);
var matches = command.GetArgTargetResult(1);
if (!matches.Any())
{
command.ReplyToCommand($"Target {command.GetArg(argument)} not found.");
command.ReplyToCommand($"Target {command.GetArg(1)} not found.");
return null;
}
if (command.GetArg(argument).StartsWith('@'))
if (command.GetArg(1).StartsWith('@'))
return matches;
if (matches.Count() == 1)
return matches;
command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(argument)}\".");
command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(1)}\".");
return null;
}
public override void Unload(bool hotReload)
{
CacheManager?.Dispose();
CacheManager = null;
PlayersTimer?.Kill();
PlayersTimer = null;
UnregisterEvents();
if (hotReload)
PlayersInfo.Clear();
else
Server.ExecuteCommand($"css_plugins unload {ModuleDirectory}");
}
}

View File

@@ -7,136 +7,23 @@
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<PublishTrimmed>true</PublishTrimmed>
<DebuggerSupport>false</DebuggerSupport>
<GenerateDependencyFile>false</GenerateDependencyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340">
<PrivateAssets>none</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="ZLinq" Version="1.5.2" />
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.284" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="MySqlConnector" Version="2.3.7" />
<PackageReference Include="Newtonsoft.Json" Version="*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj" />
</ItemGroup>
<Target Name="ClearBuildFiles" AfterTargets="PostBuildEvent">
<ItemGroup>
<FilesToDelete Include="$(OutDir)Tomlyn.dll" />
<FilesToDelete Include="$(OutDir)Serilog*.dll" />
<FilesToDelete Include="$(OutDir)CS2-SimpleAdminApi.*" />
<FilesToDelete Include="$(OutDir)McMaster*" />
<FilesToDelete Include="$(OutDir)Scrutor.dll" />
</ItemGroup>
<Delete Files="@(FilesToDelete)" />
</Target>
<ItemGroup>
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
<None Update="Database\Migrations\Mysql\001_CreateTables.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\002_CreateFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\003_ChangeColumnsPosition.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\004_MoveOldFlagsToFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\005_CreateUnbansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\006_ServerGroupsFeature.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\007_ServerGroupsGlobal.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\008_OnlineTimeInPenalties.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\009_BanAllUsedIpAddress.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\010_CreateWarnsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\011_AddRconColumnToServersTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\012_AddUpdatedAtColumnToSaBansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\013_AddNameColumnToSaPlayerIpsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\014_AddIndexesToMutesTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\015_steamidToBigInt.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\001_CreateTables.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\002_CreateFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\003_ChangeColumnsPosition.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\004_MoveOldFlagsToFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\005_CreateUnbansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\006_ServerGroupsFeature.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\007_ServerGroupsGlobal.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\008_OnlineTimeInPenalties.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\009_BanAllUsedIpAddress.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\010_CreateWarnsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\011_AddRconColumnToServersTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\012_AddUpdatedAtColumnToSaBansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\013_AddNameColumnToSaPlayerIpsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\014_AddIndexesToMutesTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\015_steamidToBigInt.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<None Update="Database\Migrations\010_CreateWarnsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
@@ -151,8 +38,7 @@
<ItemGroup>
<Reference Include="MenuManagerApi">
<HintPath>3rd_party\MenuManagerApi.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>

View File

@@ -1,99 +1,86 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace CS2_SimpleAdmin;
public static class RegisterCommands
{
internal static readonly Dictionary<string, IList<CommandDefinition>> _commandDefinitions =
new(StringComparer.InvariantCultureIgnoreCase);
private delegate void CommandCallback(CCSPlayerController? caller, CommandInfo.CommandCallback callback);
private static readonly string CommandsPath = Path.Combine(CS2_SimpleAdmin.ConfigDirectory, "Commands.json");
private static readonly List<CommandMapping> CommandMappings =
[
new("css_ban", CS2_SimpleAdmin.Instance.OnBanCommand),
new("css_addban", CS2_SimpleAdmin.Instance.OnAddBanCommand),
new("css_banip", CS2_SimpleAdmin.Instance.OnBanIpCommand),
new("css_unban", CS2_SimpleAdmin.Instance.OnUnbanCommand),
new("css_warn", CS2_SimpleAdmin.Instance.OnWarnCommand),
new("css_unwarn", CS2_SimpleAdmin.Instance.OnUnwarnCommand),
new CommandMapping("css_ban", CS2_SimpleAdmin.Instance.OnBanCommand),
new CommandMapping("css_addban", CS2_SimpleAdmin.Instance.OnAddBanCommand),
new CommandMapping("css_banip", CS2_SimpleAdmin.Instance.OnBanIpCommand),
new CommandMapping("css_unban", CS2_SimpleAdmin.Instance.OnUnbanCommand),
new CommandMapping("css_warn", CS2_SimpleAdmin.Instance.OnWarnCommand),
new CommandMapping("css_unwarn", CS2_SimpleAdmin.Instance.OnUnwarnCommand),
new("css_asay", CS2_SimpleAdmin.Instance.OnAdminToAdminSayCommand),
new("css_cssay", CS2_SimpleAdmin.Instance.OnAdminCustomSayCommand),
new("css_say", CS2_SimpleAdmin.Instance.OnAdminSayCommand),
new("css_psay", CS2_SimpleAdmin.Instance.OnAdminPrivateSayCommand),
new("css_csay", CS2_SimpleAdmin.Instance.OnAdminCenterSayCommand),
new("css_hsay", CS2_SimpleAdmin.Instance.OnAdminHudSayCommand),
new CommandMapping("css_asay", CS2_SimpleAdmin.Instance.OnAdminToAdminSayCommand),
new CommandMapping("css_cssay", CS2_SimpleAdmin.Instance.OnAdminCustomSayCommand),
new CommandMapping("css_say", CS2_SimpleAdmin.Instance.OnAdminSayCommand),
new CommandMapping("css_psay", CS2_SimpleAdmin.Instance.OnAdminPrivateSayCommand),
new CommandMapping("css_csay", CS2_SimpleAdmin.Instance.OnAdminCenterSayCommand),
new CommandMapping("css_hsay", CS2_SimpleAdmin.Instance.OnAdminHudSayCommand),
new("css_penalties", CS2_SimpleAdmin.Instance.OnPenaltiesCommand),
new("css_admin", CS2_SimpleAdmin.Instance.OnAdminCommand),
new("css_adminhelp", CS2_SimpleAdmin.Instance.OnAdminHelpCommand),
new("css_addadmin", CS2_SimpleAdmin.Instance.OnAddAdminCommand),
new("css_deladmin", CS2_SimpleAdmin.Instance.OnDelAdminCommand),
new("css_addgroup", CS2_SimpleAdmin.Instance.OnAddGroup),
new("css_delgroup", CS2_SimpleAdmin.Instance.OnDelGroupCommand),
new("css_reloadadmins", CS2_SimpleAdmin.Instance.OnRelAdminCommand),
new("css_reloadbans", CS2_SimpleAdmin.Instance.OnRelBans),
new("css_hide", CS2_SimpleAdmin.Instance.OnHideCommand),
new("css_hidecomms", CS2_SimpleAdmin.Instance.OnHideCommsCommand),
new("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand),
new("css_disconnected", CS2_SimpleAdmin.Instance.OnDisconnectedCommand),
new("css_warns", CS2_SimpleAdmin.Instance.OnWarnsCommand),
new("css_players", CS2_SimpleAdmin.Instance.OnPlayersCommand),
new("css_kick", CS2_SimpleAdmin.Instance.OnKickCommand),
new("css_map", CS2_SimpleAdmin.Instance.OnMapCommand),
new("css_wsmap", CS2_SimpleAdmin.Instance.OnWorkshopMapCommand),
new("css_cvar", CS2_SimpleAdmin.Instance.OnCvarCommand),
new("css_rcon", CS2_SimpleAdmin.Instance.OnRconCommand),
new("css_rr", CS2_SimpleAdmin.Instance.OnRestartCommand),
new CommandMapping("css_penalties", CS2_SimpleAdmin.Instance.OnPenaltiesCommand),
new CommandMapping("css_admin", CS2_SimpleAdmin.Instance.OnAdminCommand),
new CommandMapping("css_adminhelp", CS2_SimpleAdmin.Instance.OnAdminHelpCommand),
new CommandMapping("css_addadmin", CS2_SimpleAdmin.Instance.OnAddAdminCommand),
new CommandMapping("css_deladmin", CS2_SimpleAdmin.Instance.OnDelAdminCommand),
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_hide", CS2_SimpleAdmin.Instance.OnHideCommand),
new CommandMapping("css_hidecomms", CS2_SimpleAdmin.Instance.OnHideCommsCommand),
new CommandMapping("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand),
new CommandMapping("css_disconnected", CS2_SimpleAdmin.Instance.OnDisconnectedCommand),
new CommandMapping("css_warns", CS2_SimpleAdmin.Instance.OnWarnsCommand),
new CommandMapping("css_players", CS2_SimpleAdmin.Instance.OnPlayersCommand),
new CommandMapping("css_kick", CS2_SimpleAdmin.Instance.OnKickCommand),
new CommandMapping("css_map", CS2_SimpleAdmin.Instance.OnMapCommand),
new CommandMapping("css_wsmap", CS2_SimpleAdmin.Instance.OnWorkshopMapCommand),
new CommandMapping("css_cvar", CS2_SimpleAdmin.Instance.OnCvarCommand),
new CommandMapping("css_rcon", CS2_SimpleAdmin.Instance.OnRconCommand),
new CommandMapping("css_rr", CS2_SimpleAdmin.Instance.OnRestartCommand),
new("css_gag", CS2_SimpleAdmin.Instance.OnGagCommand),
new("css_addgag", CS2_SimpleAdmin.Instance.OnAddGagCommand),
new("css_ungag", CS2_SimpleAdmin.Instance.OnUngagCommand),
new("css_mute", CS2_SimpleAdmin.Instance.OnMuteCommand),
new("css_addmute", CS2_SimpleAdmin.Instance.OnAddMuteCommand),
new("css_unmute", CS2_SimpleAdmin.Instance.OnUnmuteCommand),
new("css_silence", CS2_SimpleAdmin.Instance.OnSilenceCommand),
new("css_addsilence", CS2_SimpleAdmin.Instance.OnAddSilenceCommand),
new("css_unsilence", CS2_SimpleAdmin.Instance.OnUnsilenceCommand),
new CommandMapping("css_gag", CS2_SimpleAdmin.Instance.OnGagCommand),
new CommandMapping("css_addgag", CS2_SimpleAdmin.Instance.OnAddGagCommand),
new CommandMapping("css_ungag", CS2_SimpleAdmin.Instance.OnUngagCommand),
new CommandMapping("css_mute", CS2_SimpleAdmin.Instance.OnMuteCommand),
new CommandMapping("css_addmute", CS2_SimpleAdmin.Instance.OnAddMuteCommand),
new CommandMapping("css_unmute", CS2_SimpleAdmin.Instance.OnUnmuteCommand),
new CommandMapping("css_silence", CS2_SimpleAdmin.Instance.OnSilenceCommand),
new CommandMapping("css_addsilence", CS2_SimpleAdmin.Instance.OnAddSilenceCommand),
new CommandMapping("css_unsilence", CS2_SimpleAdmin.Instance.OnUnsilenceCommand),
new("css_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
new CommandMapping("css_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
new("css_noclip", CS2_SimpleAdmin.Instance.OnNoclipCommand),
new("css_freeze", CS2_SimpleAdmin.Instance.OnFreezeCommand),
new("css_unfreeze", CS2_SimpleAdmin.Instance.OnUnfreezeCommand),
new("css_godmode", CS2_SimpleAdmin.Instance.OnGodCommand),
new CommandMapping("css_noclip", CS2_SimpleAdmin.Instance.OnNoclipCommand),
new CommandMapping("css_freeze", CS2_SimpleAdmin.Instance.OnFreezeCommand),
new CommandMapping("css_unfreeze", CS2_SimpleAdmin.Instance.OnUnfreezeCommand),
new CommandMapping("css_godmode", CS2_SimpleAdmin.Instance.OnGodCommand),
new("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
new("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
new("css_give", CS2_SimpleAdmin.Instance.OnGiveCommand),
new("css_strip", CS2_SimpleAdmin.Instance.OnStripCommand),
new("css_hp", CS2_SimpleAdmin.Instance.OnHpCommand),
new("css_speed", CS2_SimpleAdmin.Instance.OnSpeedCommand),
new("css_gravity", CS2_SimpleAdmin.Instance.OnGravityCommand),
new("css_resize", CS2_SimpleAdmin.Instance.OnResizeCommand),
new("css_money", CS2_SimpleAdmin.Instance.OnMoneyCommand),
new("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
new("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
new("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
new("css_respawn", CS2_SimpleAdmin.Instance.OnRespawnCommand),
new("css_tp", CS2_SimpleAdmin.Instance.OnGotoCommand),
new("css_bring", CS2_SimpleAdmin.Instance.OnBringCommand),
new("css_pluginsmanager", CS2_SimpleAdmin.Instance.OnPluginManagerCommand),
new("css_adminvoice", CS2_SimpleAdmin.Instance.OnAdminVoiceCommand)
new CommandMapping("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
new CommandMapping("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
new CommandMapping("css_give", CS2_SimpleAdmin.Instance.OnGiveCommand),
new CommandMapping("css_strip", CS2_SimpleAdmin.Instance.OnStripCommand),
new CommandMapping("css_hp", CS2_SimpleAdmin.Instance.OnHpCommand),
new CommandMapping("css_speed", CS2_SimpleAdmin.Instance.OnSpeedCommand),
new CommandMapping("css_gravity", CS2_SimpleAdmin.Instance.OnGravityCommand),
new CommandMapping("css_money", CS2_SimpleAdmin.Instance.OnMoneyCommand),
new CommandMapping("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
new CommandMapping("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
new CommandMapping("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
new CommandMapping("css_respawn", CS2_SimpleAdmin.Instance.OnRespawnCommand),
new CommandMapping("css_tp", CS2_SimpleAdmin.Instance.OnGotoCommand),
new CommandMapping("css_bring", CS2_SimpleAdmin.Instance.OnBringCommand),
new CommandMapping("css_pluginsmanager", CS2_SimpleAdmin.Instance.OnPluginManagerCommand)
];
/// <summary>
/// Initializes command registration.
/// If the commands config file does not exist, creates it and then recurses to register commands.
/// Otherwise, directly registers commands from the configuration.
/// </summary>
public static void InitializeCommands()
{
if (!File.Exists(CommandsPath))
@@ -107,10 +94,6 @@ public static class RegisterCommands
}
}
/// <summary>
/// Creates the default commands configuration JSON file with built-in commands and aliases.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void CreateConfig()
{
var commands = new CommandsConfig
@@ -137,7 +120,6 @@ 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"] } },
@@ -171,7 +153,6 @@ public static class RegisterCommands
{ "css_hp", new Command { Aliases = ["css_hp"] } },
{ "css_speed", new Command { Aliases = ["css_speed"] } },
{ "css_gravity", new Command { Aliases = ["css_gravity"] } },
{ "css_resize", new Command { Aliases = ["css_resize", "css_size"] } },
{ "css_money", new Command { Aliases = ["css_money"] } },
{ "css_team", new Command { Aliases = ["css_team"] } },
{ "css_rename", new Command { Aliases = ["css_rename"] } },
@@ -179,31 +160,18 @@ public static class RegisterCommands
{ "css_respawn", new Command { Aliases = ["css_respawn"] } },
{ "css_tp", new Command { Aliases = ["css_tp", "css_tpto", "css_goto"] } },
{ "css_bring", new Command { Aliases = ["css_bring", "css_tphere"] } },
{ "css_pluginsmanager", new Command { Aliases = ["css_pluginsmanager", "css_pluginmanager"] } },
{ "css_adminvoice", new Command { Aliases = ["css_adminvoice", "css_listenall"] } }
{ "css_pluginsmanager", new Command { Aliases = ["css_pluginsmanager", "css_pluginmanager"] } }
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(commands, options);
var json = JsonConvert.SerializeObject(commands, Formatting.Indented);
File.WriteAllText(CommandsPath, json);
}
/// <summary>
/// Reads the command configuration JSON file and registers all commands and their aliases with their callbacks.
/// Also registers any custom commands previously stored.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void Register()
{
var json = File.ReadAllText(CommandsPath);
var commandsConfig = JsonSerializer.Deserialize<CommandsConfig>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
var commandsConfig = JsonConvert.DeserializeObject<CommandsConfig>(json);
if (commandsConfig?.Commands == null) return;
@@ -221,37 +189,19 @@ public static class RegisterCommands
{
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
}
}
foreach (var (name, definitions) in RegisterCommands._commandDefinitions)
{
foreach (var definition in definitions)
{
CS2_SimpleAdmin._logger?.LogInformation($"Registering custom command: `{name}`");
CS2_SimpleAdmin.Instance.AddCommand(name, definition.Description, definition.Callback);
}
}
}
}
/// <summary>
/// Represents the JSON configuration structure for commands.
/// </summary>
private class CommandsConfig
{
public Dictionary<string, Command>? Commands { get; init; }
}
/// <summary>
/// Represents a command definition containing a list of aliases.
/// </summary>
private class Command
{
public string[]? Aliases { get; init; }
}
/// <summary>
/// Maps a command key to its respective command callback handler.
/// </summary>
private class CommandMapping(string commandKey, CommandInfo.CommandCallback callback)
{
public string CommandKey { get; } = commandKey;

View File

@@ -12,11 +12,6 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Handles the 'ban' command, allowing admins to ban one or more valid connected players.
/// </summary>
/// <param name="caller">The player issuing the ban command, or null for console.</param>
/// <param name="command">The command information including arguments.</param>
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
@@ -24,7 +19,9 @@ public partial class CS2_SimpleAdmin
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
if (command.ArgCount < 2)
return;
var reason = _localizer?["sa_unknown"] ?? "Unknown";
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList();
@@ -34,18 +31,14 @@ public partial class CS2_SimpleAdmin
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
reason = command.GetArg(3);
playersToTarget.ForEach(player =>
{
if (!caller.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player,
ManagePlayersMenu.BanMenu);
@@ -56,42 +49,37 @@ public partial class CS2_SimpleAdmin
});
}
/// <summary>
/// Core logic to ban a specific player, scheduling database updates, notifications, and kicks.
/// </summary>
/// <param name="caller">The player issuing the ban, or null for console.</param>
/// <param name="player">The player to be banned.</param>
/// <param name="time">Ban duration in minutes; 0 means permanent.</param>
/// <param name="reason">Reason for the ban.</param>
/// <param name="callerName">Optional caller name string. If null, defaults to player name or console.</param>
/// <param name="banManager">Optional BanManager to handle ban persistence.</param>
/// <param name="command">Optional command info object for logging.</param>
/// <param name="silent">If true, suppresses command logging.</param>
internal void Ban(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, BanManager? banManager = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (Database == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
callerName ??= _localizer?["sa_console"] ?? "Console";
// Freeze player pawn if alive
if (player.PawnIsAlive)
{
player.Pawn.Value?.Freeze();
}
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var playerInfo = PlayersInfo[player.UserId.Value];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Asynchronously handle banning logic
Task.Run(async () =>
{
int? penaltyId = await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
});
await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
});
// Update banned players list
if (playerInfo.IpAddress != null && !BannedPlayers.Contains(playerInfo.IpAddress))
BannedPlayers.Add(playerInfo.IpAddress);
if (!BannedPlayers.Contains(player.SteamID.ToString()))
BannedPlayers.Add(player.SteamID.ToString());
// Determine message keys and arguments based on ban time
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm",
@@ -107,13 +95,19 @@ public partial class CS2_SimpleAdmin
// Display admin activity message if necessary
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Schedule a kick timer
if (player.UserId.HasValue)
{
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED, Config.OtherSettings.KickTime);
AddTimer(Config.OtherSettings.KickTime, () =>
{
if (player is { IsValid: true, UserId: not null })
{
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED);
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
// Execute ban command if necessary
@@ -131,61 +125,14 @@ public partial class CS2_SimpleAdmin
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer);
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time);
}
/// <summary>
/// Adds a ban for a player by their SteamID, including offline bans.
/// </summary>
/// <param name="caller">The player issuing the ban command.</param>
/// <param name="steamid">SteamID of the player to ban.</param>
/// <param name="time">Ban duration in minutes (0 means permanent).</param>
/// <param name="reason">Reason for banning.</param>
/// <param name="banManager">Optional ban manager for database operations.</param>
internal void AddBan(CCSPlayerController? caller, SteamID steamid, int time, string reason, BanManager? banManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await BanManager.AddBanBySteamid(steamid.SteamId64, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Ban, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Ban, _localizer);
}
}
/// <summary>
/// Handles banning a player by specifying their SteamID via command.
/// </summary>
/// <param name="caller">The player issuing the command, or null if console.</param>
/// <param name="command">Command information including arguments (SteamID, time, reason).</param>
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
@@ -194,21 +141,21 @@ public partial class CS2_SimpleAdmin
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
var steamid = steamId.SteamId64.ToString();
var reason = command.ArgCount >= 3 && !string.IsNullOrEmpty(command.GetArg(3))
? command.GetArg(3)
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
int.TryParse(command.GetArg(2), out var time);
if (!CheckValidBan(caller, time)) return;
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
? PlayersInfo[caller.UserId.Value]
: null;
var player = Helper.GetPlayerFromSteamid64(steamid);
var matches = Helper.GetPlayerFromSteamid64(steamid);
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
@@ -219,21 +166,11 @@ public partial class CS2_SimpleAdmin
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time,
penaltyId);
});
await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Ban, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline.");
}
@@ -242,18 +179,15 @@ public partial class CS2_SimpleAdmin
if (UnlockedCommands)
Server.ExecuteCommand($"banid 1 {steamId.SteamId3}");
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time);
}
/// <summary>
/// Handles banning a player by their IP address, supporting offline banning if player is not online.
/// </summary>
/// <param name="caller">The player issuing the ban command.</param>
/// <param name="command">The command containing the IP, time, and reason arguments.</param>
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: "<ip> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanIpCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
var ipAddress = command.GetArg(1);
@@ -264,31 +198,26 @@ public partial class CS2_SimpleAdmin
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
var reason = command.ArgCount >= 3 && !string.IsNullOrEmpty(command.GetArg(3))
? command.GetArg(3)
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
int.TryParse(command.GetArg(2), out var time);
if (!CheckValidBan(caller, time)) return;
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
? PlayersInfo[caller.UserId.Value]
: null;
var players = Helper.GetPlayerFromIp(ipAddress);
if (players.Count >= 1)
{
foreach (var player in players)
{
if (player == null || !player.IsValid) continue;
if (!caller.CanTarget(player))
return;
var matches = Helper.GetPlayerFromIp(ipAddress);
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
Ban(caller, player, time, reason, callerName, silent: true);
}
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
}
else
{
@@ -304,17 +233,11 @@ public partial class CS2_SimpleAdmin
Helper.LogCommand(caller, command);
}
/// <summary>
/// Checks whether the ban duration is valid based on the caller's permissions and configured limits.
/// </summary>
/// <param name="caller">The player issuing the ban command.</param>
/// <param name="duration">Requested ban duration in minutes.</param>
/// <returns>True if ban duration is valid; otherwise, false.</returns>
private bool CheckValidBan(CCSPlayerController? caller, int duration)
{
if (caller == null) return true;
var canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
var canPermBan = AdminManager.PlayerHasPermissions(caller, "@css/permban");
if (duration <= 0 && canPermBan == false)
{
@@ -328,17 +251,14 @@ public partial class CS2_SimpleAdmin
return false;
}
/// <summary>
/// Handles unbanning players by pattern (steamid, name, or IP).
/// </summary>
/// <param name="caller">The player issuing the unban command.</param>
/// <param name="command">Command containing target pattern and optional reason.</param>
[RequiresPermissions("@css/unban")]
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnbanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
if (command.GetArg(1).Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
@@ -346,31 +266,27 @@ public partial class CS2_SimpleAdmin
}
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var reason = command.GetArg(2);
Task.Run(async () => await BanManager.UnbanPlayer(pattern, callerSteamId, reason));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unbanned player with pattern {pattern}.");
}
/// <summary>
/// Handles warning players, supporting multiple targets and warning durations.
/// </summary>
/// <param name="caller">The player issuing the warn command.</param>
/// <param name="command">The command containing target, time, and reason parameters.</param>
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnWarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null)
if (Database == null)
return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
if (command.ArgCount < 2)
return;
var reason = _localizer?["sa_unknown"] ?? "Unknown";
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList();
@@ -380,65 +296,49 @@ public partial class CS2_SimpleAdmin
return;
}
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
WarnManager warnManager = new(Database);
int.TryParse(command.GetArg(2), out var time);
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
reason = command.GetArg(3);
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
Warn(caller, player, time, reason, callerName, command);
Warn(caller, player, time, reason, callerName, warnManager, command);
}
});
}
/// <summary>
/// Issues a warning penalty to a specific player with optional duration and reason.
/// </summary>
/// <param name="caller">The player issuing the warning.</param>
/// <param name="player">The player to warn.</param>
/// <param name="time">Duration of the warning in minutes.</param>
/// <param name="reason">Reason for the warning.</param>
/// <param name="callerName">Optional display name of the caller.</param>
/// <param name="command">Optional command info for logging.</param>
internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null)
internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, WarnManager? warnManager = null, CommandInfo? command = null)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (Database == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
callerName ??= _localizer?["sa_console"] ?? "Console";
// Freeze player pawn if alive
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
if (player.PawnIsAlive)
{
player.PlayerPawn?.Value?.Freeze();
AddTimer(5.0f, () => player.PlayerPawn?.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
player.Pawn.Value?.Freeze();
}
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var playerInfo = PlayersInfo[player.UserId.Value];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Asynchronously handle warning logic
Task.Run(async () =>
{
int? penaltyId = await WarnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time,
penaltyId);
});
warnManager ??= new WarnManager(Database);
await warnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(player.SteamID);
var totalWarns = await warnManager.GetPlayerWarnsCount(player.SteamID.ToString());
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
@@ -451,7 +351,7 @@ public partial class CS2_SimpleAdmin
if (!string.IsNullOrEmpty(punishCommand))
{
await Server.NextWorldUpdateAsync(() =>
await Server.NextFrameAsync(() =>
{
Server.ExecuteCommand(punishCommand.Replace("USERID", playerInfo.UserId.ToString()).Replace("STEAMID64", playerInfo.SteamId?.ToString()));
});
@@ -474,7 +374,7 @@ public partial class CS2_SimpleAdmin
// Display admin activity message if necessary
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Log the warning command
@@ -485,86 +385,14 @@ public partial class CS2_SimpleAdmin
// Send Discord notification for the warning
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer);
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time);
}
/// <summary>
/// Adds a warning to a player by their SteamID, including support for offline players.
/// </summary>
/// <param name="caller">The player issuing the warn command.</param>
/// <param name="steamid">SteamID of the player to warn.</param>
/// <param name="time">Warning duration in minutes.</param>
/// <param name="reason">Reason for the warning.</param>
/// <param name="warnManager">Optional warn manager instance.</param>
internal void AddWarn(CCSPlayerController? caller, SteamID steamid, int time, string reason, WarnManager? warnManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Warn(caller, player, time, reason, callerName);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await WarnManager.AddWarnBySteamid(steamid.SteamId64, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Warn, reason, time,
penaltyId);
});
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
var lastKey = Config.WarnThreshold.Keys.Max();
if (totalWarns >= lastKey)
punishCommand = Config.WarnThreshold[lastKey];
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
punishCommand = value;
if (!string.IsNullOrEmpty(punishCommand))
{
await Server.NextWorldUpdateAsync(() =>
{
Server.ExecuteCommand(punishCommand.Replace("STEAMID64", steamid.SteamId64.ToString()));
});
}
}
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Warn, _localizer);
}
}
/// <summary>
/// Handles removing a warning (unwarn) by a pattern string.
/// </summary>
/// <param name="caller">The player issuing the unwarn command.</param>
/// <param name="command">The command containing target pattern.</param>
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnwarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
if (command.GetArg(1).Length <= 1)
{
@@ -573,7 +401,9 @@ public partial class CS2_SimpleAdmin
}
var pattern = command.GetArg(1);
Task.Run(async () => await WarnManager.UnwarnPlayer(pattern));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unwarned player with pattern {pattern}.");
}

View File

@@ -5,18 +5,11 @@ using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using System.Text;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Sends a chat message only to admins that have chat permission.
/// The message is encoded properly to handle UTF-8 characters.
/// </summary>
/// <param name="caller">The admin player sending the message, or null for console.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminToAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
@@ -27,7 +20,7 @@ public partial class CS2_SimpleAdmin
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
foreach (var player in Helper.GetValidPlayers()
.Where(p => AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
.Where(p => AdminManager.PlayerHasPermissions(p, "@css/chat")))
{
if (_localizer != null)
player.PrintToChat(_localizer["sa_adminchat_template_admin",
@@ -36,11 +29,6 @@ public partial class CS2_SimpleAdmin
}
}
/// <summary>
/// Sends a custom chat message to all players with color tags processed.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminCustomSayCommand(CCSPlayerController? caller, CommandInfo command)
@@ -58,11 +46,6 @@ public partial class CS2_SimpleAdmin
}
}
/// <summary>
/// Sends a chat message to all players with localization prefix and color tags handled.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
@@ -73,6 +56,7 @@ public partial class CS2_SimpleAdmin
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
foreach (var player in Helper.GetValidPlayers())
{
player.SendLocalizedMessage(_localizer,
@@ -81,11 +65,6 @@ public partial class CS2_SimpleAdmin
}
}
/// <summary>
/// Sends a private chat message from the caller to the specified target player(s).
/// </summary>
/// <param name="caller">The admin or console sending the private message.</param>
/// <param name="command">The command input containing target and message.</param>
[CommandHelper(2, "<#userid or name> <message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminPrivateSayCommand(CCSPlayerController? caller, CommandInfo command)
@@ -112,11 +91,6 @@ public partial class CS2_SimpleAdmin
command.ReplyToCommand($" Private message sent!");
}
/// <summary>
/// Broadcasts a center-screen message to all players.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminCenterSayCommand(CCSPlayerController? caller, CommandInfo command)
@@ -125,14 +99,10 @@ public partial class CS2_SimpleAdmin
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
Helper.PrintToCenterAll(utf8String.ReplaceColorTags());
}
/// <summary>
/// Sends a HUD alert message to all players.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminHudSayCommand(CCSPlayerController? caller, CommandInfo command)
@@ -145,6 +115,6 @@ public partial class CS2_SimpleAdmin
VirtualFunctions.ClientPrintAll(
HudDestination.Alert,
utf8String.ReplaceColorTags(),
0, 0, 0, 0, 0);
0, 0, 0, 0);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Menus;
using CS2_SimpleAdminApi;
@@ -11,18 +10,15 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Processes the 'gag' command, applying a muted penalty to target players with optional time and reason.
/// </summary>
/// <param name="caller">The player issuing the gag command or null for console.</param>
/// <param name="command">The command input containing targets, time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGagCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var reason = _localizer?["sa_unknown"] ?? "Unknown";
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
@@ -32,18 +28,13 @@ public partial class CS2_SimpleAdmin
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
reason = command.GetArg(3);
var time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player,
ManagePlayersMenu.GagMenu);
@@ -54,19 +45,9 @@ public partial class CS2_SimpleAdmin
});
}
/// <summary>
/// Applies the gag penalty logic to an individual player, performing permission checks, notification, and logging.
/// </summary>
/// <param name="caller">The player issuing the gag.</param>
/// <param name="player">The player to gag.</param>
/// <param name="time">Duration of the gag in minutes, 0 is permanent.</param>
/// <param name="reason">Reason for the gag.</param>
/// <param name="callerName">Optional caller name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
/// <param name="silent">If true, suppresses logging notifications.</param>
internal void Gag(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (Database == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidMute(caller, time)) return;
@@ -74,18 +55,13 @@ public partial class CS2_SimpleAdmin
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var playerInfo = PlayersInfo[player.UserId.Value];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Asynchronously handle gag logic
Task.Run(async () =>
{
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time,
penaltyId);
});
await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time);
});
// Add penalty to the player's penalty manager
@@ -106,11 +82,11 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Increment the player's total gags count
PlayersInfo[player.SteamID].TotalGags++;
PlayersInfo[player.UserId.Value].TotalGags++;
// Log the gag command and send Discord notification
if (!silent)
@@ -122,63 +98,14 @@ public partial class CS2_SimpleAdmin
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Gag, _localizer);
}
/// <summary>
/// Adds a gag penalty to a player identified by SteamID, supporting offline players.
/// </summary>
/// <param name="caller">The player issuing the command or null for console.</param>
/// <param name="steamid">SteamID of the target player.</param>
/// <param name="time">Duration in minutes (0 for permanent).</param>
/// <param name="reason">Reason for the gag.</param>
internal void AddGag(CCSPlayerController? caller, SteamID steamid, int time, string reason)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Gag(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 3);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Gag, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Gag, _localizer);
}
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time);
}
/// <summary>
/// Handles the 'addgag' command, which adds a gag penalty to a player specified by SteamID.
/// </summary>
/// <param name="caller">The player issuing the command or null for console.</param>
/// <param name="command">Command input that includes SteamID, optional time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddGagCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
// Set caller name
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
@@ -193,21 +120,20 @@ public partial class CS2_SimpleAdmin
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var steamid = steamId.SteamId64.ToString();
var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0
? command.GetArg(3)
: (_localizer?["sa_unknown"] ?? "Unknown");
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
int.TryParse(command.GetArg(2), out var time);
if (!CheckValidMute(caller, time)) return;
// Get player and admin info
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Attempt to match player based on SteamID
var player = Helper.GetPlayerFromSteamid64(steamid);
var matches = Helper.GetPlayerFromSteamid64(steamid);
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
if (player != null && player.IsValid)
{
@@ -219,48 +145,30 @@ public partial class CS2_SimpleAdmin
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous gag operation for offline players
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time,
penaltyId);
});
await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time);
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Gag, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Gag has been added offline.");
}
// Log the gag command and respond to the command
Helper.LogCommand(caller, command);
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time);
}
/// <summary>
/// Handles removing a gag penalty ('ungag') of a player, either by SteamID or pattern match.
/// </summary>
/// <param name="caller">The player issuing the ungag command or null for console.</param>
/// <param name="command">Command input containing SteamID or player name and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUngagCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
var reason = command.GetArg(2);
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (pattern.Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
@@ -272,7 +180,8 @@ public partial class CS2_SimpleAdmin
// Check if pattern is a valid SteamID64
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
{
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString());
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
if (player != null && player.IsValid)
{
@@ -296,8 +205,8 @@ public partial class CS2_SimpleAdmin
{
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Gag);
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalGags > 0)
PlayersInfo[namePlayer.SteamID].TotalGags--;
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalGags > 0)
PlayersInfo[namePlayer.UserId.Value].TotalGags--;
Task.Run(async () =>
{
@@ -317,18 +226,15 @@ public partial class CS2_SimpleAdmin
}
}
/// <summary>
/// Processes the 'mute' command, applying a voice mute penalty to target players with optional time and reason.
/// </summary>
/// <param name="caller">The player issuing the mute command or null for console.</param>
/// <param name="command">The command input containing targets, time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnMuteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var reason = _localizer?["sa_unknown"] ?? "Unknown";
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
@@ -338,18 +244,13 @@ public partial class CS2_SimpleAdmin
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
reason = command.GetArg(3);
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player,
ManagePlayersMenu.MuteMenu);
@@ -360,19 +261,9 @@ public partial class CS2_SimpleAdmin
});
}
/// <summary>
/// Applies the mute penalty logic to an individual player, handling permissions, notifications, logging, and countdown timers.
/// </summary>
/// <param name="caller">The player issuing the mute.</param>
/// <param name="player">The player to mute.</param>
/// <param name="time">Duration in minutes, 0 indicates permanent mute.</param>
/// <param name="reason">Reason for the mute.</param>
/// <param name="callerName">Optional caller name for notification messages.</param>
/// <param name="command">Optional command info for logging.</param>
/// <param name="silent">If true, suppresses some logging.</param>
internal void Mute(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (Database == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidMute(caller, time)) return;
@@ -380,8 +271,8 @@ public partial class CS2_SimpleAdmin
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var playerInfo = PlayersInfo[player.UserId.Value];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Set player's voice flags to muted
player.VoiceFlags = VoiceFlags.Muted;
@@ -389,12 +280,7 @@ public partial class CS2_SimpleAdmin
// Asynchronously handle mute logic
Task.Run(async () =>
{
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time,
penaltyId);
});
await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1);
});
// Add penalty to the player's penalty manager
@@ -415,11 +301,11 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Increment the player's total mutes count
PlayersInfo[player.SteamID].TotalMutes++;
PlayersInfo[player.UserId.Value].TotalMutes++;
// Log the mute command and send Discord notification
if (!silent)
@@ -431,18 +317,14 @@ public partial class CS2_SimpleAdmin
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Mute, _localizer);
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time);
}
/// <summary>
/// Handles the 'addmute' command that adds a mute penalty to a player specified by SteamID.
/// </summary>
/// <param name="caller">The player issuing the command or null for console.</param>
/// <param name="command">Command input includes SteamID, optional time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddMuteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
// Set caller name
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
@@ -457,21 +339,20 @@ public partial class CS2_SimpleAdmin
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var steamid = steamId.SteamId64.ToString();
var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0
? command.GetArg(3)
: (_localizer?["sa_unknown"] ?? "Unknown");
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
int.TryParse(command.GetArg(2), out var time);
if (!CheckValidMute(caller, time)) return;
// Get player and admin info
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Attempt to match player based on SteamID
var player = Helper.GetPlayerFromSteamid64(steamid);
var matches = Helper.GetPlayerFromSteamid64(steamid);
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
if (player != null && player.IsValid)
{
@@ -483,95 +364,30 @@ public partial class CS2_SimpleAdmin
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous mute operation for offline players
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time,
penaltyId);
});
await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1);
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Mute, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Mute has been added offline.");
}
// Log the mute command and respond to the command
Helper.LogCommand(caller, command);
}
/// <summary>
/// Asynchronously adds a mute penalty to a player by Steam ID. Handles both online and offline players.
/// </summary>
/// <param name="caller">The admin/player issuing the mute.</param>
/// <param name="steamid">The Steam ID of the player to mute.</param>
/// <param name="time">Duration of the mute in minutes.</param>
/// <param name="reason">Reason for the mute.</param>
/// <param name="muteManager">Optional mute manager instance for handling database ops.</param>
internal void AddMute(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Mute(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 1);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Mute, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Mute, _localizer);
}
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time);
}
/// <summary>
/// Handles the unmute command - removes mute penalty from player identified by SteamID or name.
/// Can target both online and offline players.
/// </summary>
/// <param name="caller">The admin/player issuing the unmute.</param>
/// <param name="command">The command arguments including target identifier and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnmuteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
var reason = command.GetArg(2);
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (pattern.Length <= 1)
{
command.ReplyToCommand("Too short pattern to search.");
@@ -583,7 +399,8 @@ public partial class CS2_SimpleAdmin
// Check if pattern is a valid SteamID64
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
{
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString());
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
if (player != null && player.IsValid)
{
@@ -609,8 +426,8 @@ public partial class CS2_SimpleAdmin
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Mute);
namePlayer.VoiceFlags = VoiceFlags.Normal;
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalMutes > 0)
PlayersInfo[namePlayer.SteamID].TotalMutes--;
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalMutes > 0)
PlayersInfo[namePlayer.UserId.Value].TotalMutes--;
Task.Run(async () =>
{
@@ -630,19 +447,15 @@ public partial class CS2_SimpleAdmin
}
}
/// <summary>
/// Issue a 'silence' penalty to a player - disables voice communication.
/// Handles online and offline players, with duration and reason specified.
/// </summary>
/// <param name="caller">The admin/player issuing the silence.</param>
/// <param name="command">Command containing target, duration, and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSilenceCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var reason = _localizer?["sa_unknown"] ?? "Unknown";
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
@@ -652,18 +465,13 @@ public partial class CS2_SimpleAdmin
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
reason = command.GetArg(3);
var time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player,
ManagePlayersMenu.SilenceMenu);
@@ -674,19 +482,9 @@ public partial class CS2_SimpleAdmin
});
}
/// <summary>
/// Applies silence logical processing for a player - updates database and notifies.
/// </summary>
/// <param name="caller">Admin/player applying the silence.</param>
/// <param name="player">Target player.</param>
/// <param name="time">Duration of silence.</param>
/// <param name="reason">Reason for silence.</param>
/// <param name="callerName">Optional name of silent admin or console.</param>
/// <param name="command">Optional command details for logging.</param>
/// <param name="silent">If true, suppresses logging notifications.</param>
internal void Silence(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (Database == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidMute(caller, time)) return;
@@ -694,18 +492,13 @@ public partial class CS2_SimpleAdmin
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var playerInfo = PlayersInfo[player.UserId.Value];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Asynchronously handle silence logic
Task.Run(async () =>
{
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time,
penaltyId);
});
await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2); // Assuming 2 is the type for silence
});
// Add penalty to the player's penalty manager
@@ -727,11 +520,11 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Increment the player's total silences count
PlayersInfo[player.SteamID].TotalSilences++;
PlayersInfo[player.UserId.Value].TotalSilences++;
// Log the silence command and send Discord notification
if (!silent)
@@ -743,19 +536,14 @@ public partial class CS2_SimpleAdmin
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Silence, _localizer);
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time);
}
/// <summary>
/// Handles the 'AddSilence' command, applying a silence penalty to a player specified by SteamID,
/// with support for offline player penalties.
/// </summary>
/// <param name="caller">The player/admin issuing the command.</param>
/// <param name="command">The command input containing SteamID, optional time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddSilenceCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
// Set caller name
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
@@ -770,21 +558,20 @@ public partial class CS2_SimpleAdmin
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var steamid = steamId.SteamId64.ToString();
var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0
? command.GetArg(3)
: (_localizer?["sa_unknown"] ?? "Unknown");
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
int.TryParse(command.GetArg(2), out var time);
if (!CheckValidMute(caller, time)) return;
// Get player and admin info
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
// Attempt to match player based on SteamID
var player = Helper.GetPlayerFromSteamid64(steamid);
var matches = Helper.GetPlayerFromSteamid64(steamid);
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
if (player != null && player.IsValid)
{
@@ -796,107 +583,43 @@ public partial class CS2_SimpleAdmin
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous silence operation for offline players
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason,
time, penaltyId);
});
await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2);
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Silence, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Silence has been added offline.");
}
// Log the silence command and respond to the command
Helper.LogCommand(caller, command);
}
/// <summary>
/// Adds a silence penalty to a player by Steam ID. Manages both online and offline player cases.
/// </summary>
/// <param name="caller">Admin/player initiating the silence.</param>
/// <param name="steamid">Steam ID of player.</param>
/// <param name="time">Duration of silence.</param>
/// <param name="reason">Reason for the penalty.</param>
/// <param name="muteManager">Optional mute manager for DB operations.</param>
internal void AddSilence(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Silence(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 2);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Silence, reason,
time, penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Silence, _localizer);
}
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason, time);
}
/// <summary>
/// Removes the silence penalty from a player, either by SteamID, name, or offline pattern.
/// Resets voice settings and updates notices accordingly.
/// </summary>
/// <param name="caller">Admin/player issuing the unsilence.</param>
/// <param name="command">Command arguments with target pattern and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnsilenceCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (Database == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
var reason = command.GetArg(2);
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (pattern.Length <= 1)
{
command.ReplyToCommand("Too short pattern to search.");
return;
}
Helper.LogCommand(caller, command);
// Check if pattern is a valid SteamID64
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
{
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString());
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
if (player != null && player.IsValid)
{
@@ -926,8 +649,8 @@ public partial class CS2_SimpleAdmin
// Reset voice flags to normal
namePlayer.VoiceFlags = VoiceFlags.Normal;
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalSilences > 0)
PlayersInfo[namePlayer.SteamID].TotalSilences--;
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalSilences > 0)
PlayersInfo[namePlayer.UserId.Value].TotalSilences--;
Task.Run(async () =>
{
@@ -947,17 +670,11 @@ public partial class CS2_SimpleAdmin
}
}
/// <summary>
/// Validates mute penalty duration based on admin privileges and configured max duration.
/// </summary>
/// <param name="caller">Admin/player issuing the mute.</param>
/// <param name="duration">Requested duration in minutes.</param>
/// <returns>True if mute penalty duration is allowed; false otherwise.</returns>
private bool CheckValidMute(CCSPlayerController? caller, int duration)
{
if (caller == null) return true;
var canPermMute = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permmute");
var canPermMute = AdminManager.PlayerHasPermissions(caller, "@css/permmute");
if (duration <= 0 && canPermMute == false)
{

View File

@@ -8,12 +8,6 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Handles the vote command, creates voting menu for players, and collects answers.
/// Displays results after timeout and resets voting state.
/// </summary>
/// <param name="caller">The player/admin who initiated the vote, or null for console.</param>
/// <param name="command">Command object containing question and options.</param>
[RequiresPermissions("@css/generic")]
[CommandHelper(minArgs: 2, usage: "<question> [... options ...]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnVoteCommand(CCSPlayerController? caller, CommandInfo command)

View File

@@ -1,5 +1,3 @@
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
@@ -8,11 +6,6 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Enables or disables no-clip mode for specified player(s).
/// </summary>
/// <param name="caller">The player issuing the command.</param>
/// <param name="command">The command input containing targets.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
@@ -23,7 +16,7 @@ public partial class CS2_SimpleAdmin
if (targets == null) return;
var playersToTarget = targets.Players.Where(player =>
player.IsValid &&
player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
player is { PawnIsAlive: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList();
playersToTarget.ForEach(player =>
{
@@ -32,17 +25,8 @@ public partial class CS2_SimpleAdmin
NoClip(caller, player, callerName);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Toggles no-clip mode for a player and shows admin activity messages.
/// </summary>
/// <param name="caller">The player/admin toggling no-clip.</param>
/// <param name="player">The target player whose no-clip state changes.</param>
/// <param name="callerName">Optional caller name for messages.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
@@ -62,20 +46,20 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Log the command
if (command == null)
{
Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
else
{
Helper.LogCommand(caller, command);
}
}
/// <summary>
/// Enables or disables god mode for specified player(s).
/// </summary>
/// <param name="caller">The player issuing the command.</param>
/// <param name="command">The command input containing targets.</param>
[RequiresPermissions("@css/cheats")]
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
@@ -84,7 +68,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
@@ -96,16 +80,8 @@ public partial class CS2_SimpleAdmin
God(caller, player, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Toggles god mode for a player and notifies admins.
/// </summary>
/// <param name="caller">The player/admin toggling god mode.</param>
/// <param name="player">The target player whose god mode changes.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -122,6 +98,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
else
Helper.LogCommand(caller, command);
// Determine message key and arguments for the god mode notification
var (activityMessageKey, adminActivityArgs) =
@@ -131,15 +109,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Freezes target player(s) for an optional specified duration.
/// </summary>
/// <param name="caller">The player issuing the freeze command.</param>
/// <param name="command">The command input containing targets and duration.</param>
[CommandHelper(1, "<#userid or name> [duration]")]
[RequiresPermissions("@css/slay")]
public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
@@ -149,7 +122,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
@@ -158,63 +131,8 @@ public partial class CS2_SimpleAdmin
Freeze(caller, player, time, callerName, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Resizes the target player(s) models to a specified scale.
/// </summary>
/// <param name="caller">The player issuing the resize command.</param>
/// <param name="command">The command input containing targets and scale factor.</param>
[CommandHelper(1, "<#userid or name> [size]")]
[RequiresPermissions("@css/slay")]
public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
if (sceneNode == null) return;
sceneNode.GetSkeletonInstance().Scale = size;
player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
Server.NextWorldUpdate(() =>
{
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
});
var (activityMessageKey, adminActivityArgs) =
("sa_admin_resize_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Freezes a single player and optionally schedules automatic unfreeze after a duration.
/// </summary>
/// <param name="caller">The player/admin freezing the player.</param>
/// <param name="player">The player to freeze.</param>
/// <param name="time">Duration of freeze in seconds.</param>
/// <param name="callerName">Optional name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
@@ -234,7 +152,7 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Schedule unfreeze for the player if time is specified
@@ -246,13 +164,10 @@ public partial class CS2_SimpleAdmin
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
else
Helper.LogCommand(caller, command);
}
/// <summary>
/// Unfreezes target player(s).
/// </summary>
/// <param name="caller">The player issuing the unfreeze command.</param>
/// <param name="command">The command input with targets.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/slay")]
public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
@@ -261,23 +176,14 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
Unfreeze(caller, player, callerName, command);
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Unfreezes a single player and notifies admins.
/// </summary>
/// <param name="caller">The player/admin unfreezing the player.</param>
/// <param name="player">The player to unfreeze.</param>
/// <param name="callerName">Optional name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
@@ -297,11 +203,13 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
else
Helper.LogCommand(caller, command);
}
}

View File

@@ -1,5 +1,4 @@
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
@@ -11,15 +10,9 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
internal static readonly Dictionary<CCSPlayerController, float> SpeedPlayers = [];
internal static readonly Dictionary<int, float> SpeedPlayers = [];
internal static readonly Dictionary<CCSPlayerController, float> GravityPlayers = [];
/// <summary>
/// Executes the 'slay' command, forcing the targeted players to commit suicide.
/// Checks player validity and permissions.
/// </summary>
/// <param name="caller">Player or console issuing the command.</param>
/// <param name="command">Command details, including targets.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
@@ -28,23 +21,14 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
Slay(caller, player, callerName, command);
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Performs the actual slay action on a player, with notification and logging.
/// </summary>
/// <param name="caller">Admin or console issuing the slay.</param>
/// <param name="player">Target player to slay.</param>
/// <param name="callerName">Optional name to display as the slayer.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return;
@@ -64,20 +48,16 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
else
Helper.LogCommand(caller, command);
}
/// <summary>
/// Executes the 'give' command to provide a specified weapon to targeted players.
/// Enforces server rules for prohibited weapons.
/// </summary>
/// <param name="caller">Player or console issuing the command.</param>
/// <param name="command">Command details, including targets and weapon name.</param>
[RequiresPermissions("@css/cheats")]
[CommandHelper(minArgs: 2, usage: "<#userid or name> <weapon>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGiveCommand(CCSPlayerController? caller, CommandInfo command)
@@ -86,9 +66,16 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
var weaponName = command.GetArg(2);
// check if item is typed
// if (weaponName.Length < 2)
// {
// command.ReplyToCommand($"No weapon typed.");
// return;
// }
// check if weapon is knife
if (weaponName.Contains("_knife") || weaponName.Contains("bayonet"))
{
@@ -106,19 +93,8 @@ public partial class CS2_SimpleAdmin
GiveWeapon(caller, player, weaponName, callerName, command);
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Gives a weapon identified by name to a player, handling ambiguous matches and logging.
/// </summary>
/// <param name="caller">Admin issuing the command.</param>
/// <param name="player">Target player to receive the weapon.</param>
/// <param name="weaponName">Weapon name or partial name.</param>
/// <param name="callerName">Optional name to display in notifications.</param>
/// <param name="command">Optional command info for logging.</param>
private static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, string weaponName, string? callerName = null, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -145,6 +121,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weaponName}");
else
Helper.LogCommand(caller, command);
// Determine message keys and arguments for the weapon give notification
var (activityMessageKey, adminActivityArgs) =
@@ -154,18 +132,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Gives a specific weapon to a player, with notifications and logging.
/// </summary>
/// <param name="caller">Admin issuing the command.</param>
/// <param name="player">Target player.</param>
/// <param name="weapon">Weapon item object.</param>
/// <param name="callerName">Optional caller name for notifications.</param>
/// <param name="command">Optional command info.</param>
internal static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, CsItem weapon, string? callerName = null, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -179,6 +149,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weapon.ToString()}");
else
Helper.LogCommand(caller, command);
// Determine message keys and arguments for the weapon give notification
var (activityMessageKey, adminActivityArgs) =
@@ -188,16 +160,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Executes the 'strip' command, removing all weapons from targeted players.
/// Checks player validity and permissions.
/// </summary>
/// <param name="caller">Player or console issuing the command.</param>
/// <param name="command">Command details including targets.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnStripCommand(CCSPlayerController? caller, CommandInfo command)
@@ -206,7 +172,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
@@ -215,17 +181,8 @@ public partial class CS2_SimpleAdmin
StripWeapons(caller, player, callerName, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Removes all weapons from a player, with notifications and logging.
/// </summary>
/// <param name="caller">Admin or console issuing the strip command.</param>
/// <param name="player">Target player.</param>
/// <param name="callerName">Optional caller name.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void StripWeapons(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -234,7 +191,7 @@ public partial class CS2_SimpleAdmin
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Check if player is valid, alive, and connected
if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.Connected != PlayerConnectedState.PlayerConnected)
if (!player.IsValid || !player.PawnIsAlive || player.Connected != PlayerConnectedState.PlayerConnected)
return;
// Strip weapons from the player
@@ -243,6 +200,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_strip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
else
Helper.LogCommand(caller, command);
// Determine message keys and arguments for the weapon strip notification
var (activityMessageKey, adminActivityArgs) =
@@ -252,15 +211,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Sets health value on targeted players.
/// </summary>
/// <param name="caller">Admin or console issuing the command.</param>
/// <param name="command">Command details including targets and health value.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <health>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnHpCommand(CCSPlayerController? caller, CommandInfo command)
@@ -270,7 +224,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
@@ -279,17 +233,8 @@ public partial class CS2_SimpleAdmin
SetHp(caller, player, health, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Changes health of a player and logs the action.
/// </summary>
/// <param name="caller">Admin or console calling the method.</param>
/// <param name="player">Target player.</param>
/// <param name="health">Health value to set.</param>
/// <param name="command">Optional command info.</param>
internal static void SetHp(CCSPlayerController? caller, CCSPlayerController player, int health, CommandInfo? command = null)
{
if (!player.IsValid || player.IsHLTV) return;
@@ -304,6 +249,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_hp {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {health}");
else
Helper.LogCommand(caller, command);
// Determine message keys and arguments for the HP set notification
var (activityMessageKey, adminActivityArgs) =
@@ -313,15 +260,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Sets movement speed on targeted players.
/// </summary>
/// <param name="caller">Admin or console issuing the command.</param>
/// <param name="command">Command details including targets and speed.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <speed>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSpeedCommand(CCSPlayerController? caller, CommandInfo command)
@@ -331,7 +273,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
@@ -343,17 +285,8 @@ public partial class CS2_SimpleAdmin
SetSpeed(caller, player, speed, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Changes speed of a player and logs the action.
/// </summary>
/// <param name="caller">Admin or console calling the method.</param>
/// <param name="player">Target player.</param>
/// <param name="speed">Speed value to set.</param>
/// <param name="command">Optional command info.</param>
internal static void SetSpeed(CCSPlayerController? caller, CCSPlayerController player, float speed, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -365,13 +298,15 @@ public partial class CS2_SimpleAdmin
player.SetSpeed(speed);
if (speed == 1f)
SpeedPlayers.Remove(player);
SpeedPlayers.Remove(player.Slot);
else
SpeedPlayers[player] = speed;
SpeedPlayers[player.Slot] = speed;
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_speed {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {speed}");
else
Helper.LogCommand(caller, command);
// Determine message keys and arguments for the speed set notification
var (activityMessageKey, adminActivityArgs) =
@@ -381,15 +316,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Sets gravity on targeted players.
/// </summary>
/// <param name="caller">Admin or console issuing the command.</param>
/// <param name="command">Command details including targets and gravity value.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <gravity>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGravityCommand(CCSPlayerController? caller, CommandInfo command)
@@ -399,7 +329,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
@@ -411,17 +341,8 @@ public partial class CS2_SimpleAdmin
SetGravity(caller, player, gravity, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Changes gravity of a player and logs the action.
/// </summary>
/// <param name="caller">Admin or console calling the method.</param>
/// <param name="player">Target player.</param>
/// <param name="gravity">Gravity value to set.</param>
/// <param name="command">Optional command info.</param>
internal static void SetGravity(CCSPlayerController? caller, CCSPlayerController player, float gravity, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -440,6 +361,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_gravity {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {gravity}");
else
Helper.LogCommand(caller, command);
// Determine message keys and arguments for the gravity set notification
var (activityMessageKey, adminActivityArgs) =
@@ -449,15 +372,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Sets the money amount for the targeted players.
/// </summary>
/// <param name="caller">The player/admin executing the command.</param>
/// <param name="command">The command containing target player and money value.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> <money>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnMoneyCommand(CCSPlayerController? caller, CommandInfo command)
@@ -468,7 +386,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
playersToTarget.ForEach(player =>
{
@@ -480,17 +398,8 @@ public partial class CS2_SimpleAdmin
SetMoney(caller, player, money, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Applies money value to a single targeted player and logs the operation.
/// </summary>
/// <param name="caller">The player/admin setting the money.</param>
/// <param name="player">The player whose money will be set.</param>
/// <param name="money">The value of money to set.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void SetMoney(CCSPlayerController? caller, CCSPlayerController player, int money, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -504,6 +413,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_money {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {money}");
else
Helper.LogCommand(caller, command);
// Determine message keys and arguments for the money set notification
var (activityMessageKey, adminActivityArgs) =
@@ -513,15 +424,10 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Applies damage as a slap effect to the targeted players.
/// </summary>
/// <param name="caller">The player/admin executing the slap command.</param>
/// <param name="command">The command including targets and optional damage value.</param>
[RequiresPermissions("@css/slay")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [damage]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSlapCommand(CCSPlayerController? caller, CommandInfo command)
@@ -531,7 +437,7 @@ public partial class CS2_SimpleAdmin
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
if (command.ArgCount >= 2)
{
@@ -548,17 +454,8 @@ public partial class CS2_SimpleAdmin
Slap(caller, player, damage, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Applies slap damage to a specific player with notifications and logging.
/// </summary>
/// <param name="caller">The player/admin applying the slap effect.</param>
/// <param name="player">The target player to slap.</param>
/// <param name="damage">The damage amount to apply.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
@@ -572,7 +469,9 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}");
else
Helper.LogCommand(caller, command);
// Determine message key and arguments for the slap notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_slap_message",
@@ -583,15 +482,10 @@ public partial class CS2_SimpleAdmin
if (_localizer != null)
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
}
/// <summary>
/// Changes the team of targeted players with optional kill on switch.
/// </summary>
/// <param name="caller">The player/admin issuing the command.</param>
/// <param name="command">The command containing targets, team info, and optional kill flag.</param>
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 2, usage: "<#userid or name> [<ct/tt/spec>] [-k]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnTeamCommand(CCSPlayerController? caller, CommandInfo command)
@@ -637,19 +531,8 @@ public partial class CS2_SimpleAdmin
{
ChangeTeam(caller, player, _teamName, teamNum, kill, command);
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Changes the team of a player with various conditions and logs the operation.
/// </summary>
/// <param name="caller">The player/admin issuing the change.</param>
/// <param name="player">The target player.</param>
/// <param name="teamName">Team name string.</param>
/// <param name="teamNum">Team enumeration value.</param>
/// <param name="kill">If true, kills player on team change.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null)
{
// Check if the player is valid and connected
@@ -665,7 +548,7 @@ public partial class CS2_SimpleAdmin
// Change team based on the provided teamName and conditions
if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase))
{
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
if (player.PawnIsAlive && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
player.SwitchTeam(teamNum);
else
player.ChangeTeam(teamNum);
@@ -676,7 +559,7 @@ public partial class CS2_SimpleAdmin
{
var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist;
teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT";
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
if (player.PawnIsAlive && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
player.SwitchTeam(_teamNum);
else
player.ChangeTeam(_teamNum);
@@ -686,6 +569,8 @@ public partial class CS2_SimpleAdmin
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}");
else
Helper.LogCommand(caller, command);
// Determine message key and arguments for the team change notification
var activityMessageKey = "sa_admin_team_message";
@@ -694,14 +579,9 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
/// <summary>
/// Renames targeted players to a new name.
/// </summary>
/// <param name="caller">The admin issuing the rename command.</param>
/// <param name="command">The command including targets and new name.</param>
[CommandHelper(1, "<#userid or name> <new name>")]
[RequiresPermissions("@css/kick")]
public void OnRenameCommand(CCSPlayerController? caller, CommandInfo command)
@@ -740,18 +620,13 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
// Rename the player
player.Rename(newName);
});
}
/// <summary>
/// Renames permamently targeted players to a new name.
/// </summary>
/// <param name="caller">The admin issuing the pre-rename command.</param>
/// <param name="command">The command containing targets and new alias.</param>
[CommandHelper(1, "<#userid or name> <new name>")]
[RequiresPermissions("@css/ban")]
public void OnPrenameCommand(CCSPlayerController? caller, CommandInfo command)
@@ -786,7 +661,7 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller != null && !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
// Determine if the new name is valid and update the renamed players list
@@ -802,11 +677,6 @@ public partial class CS2_SimpleAdmin
});
}
/// <summary>
/// Respawns targeted players, restoring their state.
/// </summary>
/// <param name="caller">The admin or player issuing respawn.</param>
/// <param name="command">The command including target players.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
public void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command)
@@ -827,17 +697,8 @@ public partial class CS2_SimpleAdmin
Respawn(caller, player, callerName, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Respawns a specified player and updates admin notifications.
/// </summary>
/// <param name="caller">Admin or player executing respawn.</param>
/// <param name="player">Player to respawn.</param>
/// <param name="callerName">Optional admin name.</param>
/// <param name="command">Optional command info.</param>
internal static void Respawn(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
// Check if the caller can target the player
@@ -853,13 +714,15 @@ public partial class CS2_SimpleAdmin
var playerPawn = player.PlayerPawn.Value;
_cBasePlayerControllerSetPawnFunc.Invoke(player, playerPawn, true, false);
VirtualFunction.CreateVoid<CCSPlayerController>(player.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(player);
if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.SteamID, out var value) && value.DiePosition != null)
if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.UserId.Value, out var value) && value.DiePosition != null)
playerPawn.Teleport(value.DiePosition?.Position, value.DiePosition?.Angle);
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
else
Helper.LogCommand(caller, command);
// Determine message key and arguments for the respawn notification
var activityMessageKey = "sa_admin_respawn_message";
@@ -868,273 +731,92 @@ public partial class CS2_SimpleAdmin
// Display admin activity message to other players
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
}
/// <summary>
/// Teleports targeted player(s) to another player's location.
/// </summary>
/// <param name="caller">Admin issuing teleport command.</param>
/// <param name="command">Command containing teleport targets and destination.</param>
[CommandHelper(1, "<#userid or name> [#userid or name]")]
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/kick")]
public void OnGotoCommand(CCSPlayerController? caller, CommandInfo command)
{
IEnumerable<CCSPlayerController> playersToTeleport;
CCSPlayerController? destinationPlayer;
// Check if the caller is valid and has a live pawn
if (caller == null || !caller.PawnIsAlive) return;
// Get the target players
var targets = GetTarget(command);
if (targets == null || targets.Count() > 1) return;
if (command.ArgCount < 3)
{
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
return;
var playersToTarget = targets.Players
.Where(player => player is { IsValid: true, IsHLTV: false })
.ToList();
if (targets == null || targets.Count() != 1)
return;
destinationPlayer = targets.Players.FirstOrDefault(p =>
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null)
return;
playersToTeleport = [caller];
}
else
{
var destination = GetTarget(command, 2);
if (targets == null || destination == null || destination.Count() != 1)
return;
destinationPlayer = destination.Players.FirstOrDefault(p =>
p is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
if (destinationPlayer == null)
return;
playersToTeleport = targets.Players
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
.ToList();
if (!playersToTeleport.Any())
return;
}
// Log command
// Log the command
Helper.LogCommand(caller, command);
foreach (var player in playersToTeleport)
// Process each player to teleport
foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }).Where(caller.CanTarget))
{
if (player.PlayerPawn?.Value == null || destinationPlayer?.PlayerPawn?.Value == null)
if (caller.PlayerPawn.Value == null)
continue;
player.TeleportPlayer(destinationPlayer);
// Teleport the caller to the player and toggle noclip
caller.TeleportPlayer(player);
caller.PlayerPawn.Value.ToggleNoclip();
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
// Set a timer to toggle noclip back after 3 seconds
AddTimer(3, () => caller.PlayerPawn.Value.ToggleNoclip());
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
// Prepare message key and arguments for the teleport notification
var activityMessageKey = "sa_admin_tp_message";
var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
AddTimer(4, () =>
// Show admin activity
if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
{
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
}
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
}
});
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
{
Helper.ShowAdminActivity("sa_admin_tp_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, adminActivityArgs);
}
}
}
/// <summary>
/// Brings targeted player(s) to the caller or specified destination player's location.
/// </summary>
/// <param name="caller">Player issuing the bring command.</param>
/// <param name="command">Command containing the destination and targets.</param>
[CommandHelper(1, "<#destination or name> [#userid or name...]")]
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/kick")]
public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
{
IEnumerable<CCSPlayerController> playersToTeleport;
CCSPlayerController? destinationPlayer;
// Check if the caller is valid and has a live pawn
if (caller == null || !caller.PawnIsAlive) return;
if (command.ArgCount < 3)
{
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
return;
// Get the target players
var targets = GetTarget(command);
if (targets == null || targets.Count() > 1) return;
var targets = GetTarget(command);
if (targets == null || !targets.Any())
return;
var playersToTarget = targets.Players
.Where(player => player is { IsValid: true, IsHLTV: false })
.ToList();
destinationPlayer = caller;
playersToTeleport = targets.Players
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
.ToList();
}
else
{
var destination = GetTarget(command);
if (destination == null || destination.Count() != 1)
return;
destinationPlayer = destination.Players.FirstOrDefault(p =>
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
if (destinationPlayer == null)
return;
// Rest args = targets to teleport
var targets = GetTarget(command, 2);
if (targets == null || !targets.Any())
return;
playersToTeleport = targets.Players
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
.ToList();
}
if (destinationPlayer == null || !playersToTeleport.Any())
return;
// Log command
// Log the command
Helper.LogCommand(caller, command);
foreach (var player in playersToTeleport)
// Process each player to teleport
foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }).Where(caller.CanTarget))
{
if (player.PlayerPawn?.Value == null || destinationPlayer.PlayerPawn?.Value == null)
if (caller.PlayerPawn.Value == null)
continue;
// Teleport
player.TeleportPlayer(destinationPlayer);
// Teleport the player to the caller and toggle noclip
player.TeleportPlayer(caller);
caller.PlayerPawn.Value.ToggleNoclip();
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
// Set a timer to toggle noclip back after 3 seconds
AddTimer(3, () => caller.PlayerPawn.Value.ToggleNoclip());
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
// Prepare message key and arguments for the bring notification
var activityMessageKey = "sa_admin_bring_message";
var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
AddTimer(4, () =>
// Show admin activity
if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
{
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
}
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
}
});
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
{
Helper.ShowAdminActivity("sa_admin_bring_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, adminActivityArgs);
}
}
}
// [CommandHelper(1, "<#userid or name> [#userid or name]")]
// [RequiresPermissions("@css/kick")]
// public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
// {
// // Check if the caller is valid and has a live pawn
// if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
// return;
//
// // Get the target players
// var targets = GetTarget(command);
// if (targets == null || targets.Count() > 1) return;
//
// var playersToTarget = targets.Players
// .Where(player => player is { IsValid: true, IsHLTV: false })
// .ToList();
//
// // Log the command
// Helper.LogCommand(caller, command);
//
// // Process each player to teleport
// foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).Where(caller.CanTarget))
// {
// if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null)
// continue;
//
// // Teleport the player to the caller and toggle noclip
// player.TeleportPlayer(caller);
// // caller.PlayerPawn.Value.ToggleNoclip();
//
// caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
// caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
//
// Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
// Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
//
// player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
// player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
//
// Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
// Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
//
// // Set a timer to toggle collision back after 4 seconds
// AddTimer(4, () =>
// {
// if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
// return;
//
// caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
// caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
//
// Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
// Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
//
// player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
// player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
//
// Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
// Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
// });
//
// // Prepare message key and arguments for the bring notification
// var activityMessageKey = "sa_admin_bring_message";
// var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
//
// // Show admin activity
// if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
// {
// Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, false, adminActivityArgs);
// }
// }
// }
}

View File

@@ -38,67 +38,56 @@ public class Discord
[JsonPropertyName("DiscordPenaltyBanSettings")]
public DiscordPenaltySetting[] DiscordPenaltyBanSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
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}" },
];
[JsonPropertyName("DiscordPenaltyMuteSettings")]
public DiscordPenaltySetting[] DiscordPenaltyMuteSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
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}" },
];
[JsonPropertyName("DiscordPenaltyGagSettings")]
public DiscordPenaltySetting[] DiscordPenaltyGagSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
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}" },
];
[JsonPropertyName("DiscordPenaltySilenceSettings")]
public DiscordPenaltySetting[] DiscordPenaltySilenceSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
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}" },
];
[JsonPropertyName("DiscordPenaltyWarnSettings")]
public DiscordPenaltySetting[] DiscordPenaltyWarnSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
];
[JsonPropertyName("DiscordAssociatedAccountsSettings")]
public DiscordPenaltySetting[] DiscordAssociatedAccountsSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
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}" },
];
}
@@ -124,15 +113,15 @@ public class MenuConfig
[JsonPropertyName("Durations")]
public DurationItem[] Durations { get; set; } =
[
new() { Name = "1 minute", Duration = 1 },
new() { Name = "5 minutes", Duration = 5 },
new() { Name = "15 minutes", Duration = 15 },
new() { Name = "1 hour", Duration = 60 },
new() { Name = "1 day", Duration = 60 * 24 },
new() { Name = "7 days", Duration = 60 * 24 * 7 },
new() { Name = "14 days", Duration = 60 * 24 * 14 },
new() { Name = "30 days", Duration = 60 * 24 * 30 },
new() { Name = "Permanent", Duration = 0 }
new DurationItem { Name = "1 minute", Duration = 1 },
new DurationItem { Name = "5 minutes", Duration = 5 },
new DurationItem { Name = "15 minutes", Duration = 15 },
new DurationItem { Name = "1 hour", Duration = 60 },
new DurationItem { Name = "1 day", Duration = 60 * 24 },
new DurationItem { Name = "7 days", Duration = 60 * 24 * 7 },
new DurationItem { Name = "14 days", Duration = 60 * 24 * 14 },
new DurationItem { Name = "30 days", Duration = 60 * 24 * 30 },
new DurationItem { Name = "Permanent", Duration = 0 }
];
[JsonPropertyName("BanReasons")]
@@ -177,18 +166,18 @@ public class MenuConfig
[JsonPropertyName("AdminFlags")]
public AdminFlag[] AdminFlags { get; set; } =
[
new() { Name = "Generic", Flag = "@css/generic" },
new() { Name = "Chat", Flag = "@css/chat" },
new() { Name = "Change Map", Flag = "@css/changemap" },
new() { Name = "Slay", Flag = "@css/slay" },
new() { Name = "Kick", Flag = "@css/kick" },
new() { Name = "Ban", Flag = "@css/ban" },
new() { Name = "Perm Ban", Flag = "@css/permban" },
new() { Name = "Unban", Flag = "@css/unban" },
new() { Name = "Show IP", Flag = "@css/showip" },
new() { Name = "Cvar", Flag = "@css/cvar" },
new() { Name = "Rcon", Flag = "@css/rcon" },
new() { Name = "Root (all flags)", Flag = "@css/root" }
new AdminFlag { Name = "Generic", Flag = "@css/generic" },
new AdminFlag { Name = "Chat", Flag = "@css/chat" },
new AdminFlag { Name = "Change Map", Flag = "@css/changemap" },
new AdminFlag { Name = "Slay", Flag = "@css/slay" },
new AdminFlag { Name = "Kick", Flag = "@css/kick" },
new AdminFlag { Name = "Ban", Flag = "@css/ban" },
new AdminFlag { Name = "Perm Ban", Flag = "@css/permban" },
new AdminFlag { Name = "Unban", Flag = "@css/unban" },
new AdminFlag { Name = "Show IP", Flag = "@css/showip" },
new AdminFlag { Name = "Cvar", Flag = "@css/cvar" },
new AdminFlag { Name = "Rcon", Flag = "@css/rcon" },
new AdminFlag { Name = "Root (all flags)", Flag = "@css/root" }
];
}
@@ -235,23 +224,27 @@ public class OtherSettings
[JsonPropertyName("UserMessageGagChatType")]
public bool UserMessageGagChatType { get; set; } = false;
[JsonPropertyName("CheckMultiAccountsByIp")]
public bool CheckMultiAccountsByIp { get; set; } = true;
[JsonPropertyName("AdditionalCommandsToLog")]
public List<string> AdditionalCommandsToLog { get; set; } = new();
[JsonPropertyName("IgnoredIps")]
public List<string> IgnoredIps { get; set; } = new();
}
public class CS2_SimpleAdminConfig : BasePluginConfig
{
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 25;
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 23;
[JsonPropertyName("DatabaseHost")]
public string DatabaseHost { get; set; } = "";
[JsonPropertyName("DatabasePort")]
public int DatabasePort { get; set; } = 3306;
[JsonPropertyName("DatabaseUser")]
public string DatabaseUser { get; set; } = "";
[JsonPropertyName("DatabasePassword")]
public string DatabasePassword { get; set; } = "";
[JsonPropertyName("DatabaseName")]
public string DatabaseName { get; set; } = "";
[JsonPropertyName("DatabaseConfig")]
public DatabaseConfig DatabaseConfig { get; set; } = new();
[JsonPropertyName("OtherSettings")]
public OtherSettings OtherSettings { get; set; } = new();
@@ -288,38 +281,4 @@ public class CS2_SimpleAdminConfig : BasePluginConfig
[JsonPropertyName("MenuConfig")]
public MenuConfig MenuConfigs { get; set; } = new();
}
public class DatabaseConfig
{
[JsonPropertyName("DatabaseType")]
public string DatabaseType { get; set; } = "SQLite";
[JsonPropertyName("SqliteFilePath")]
public string SqliteFilePath { get; set; } = "cs2-simpleadmin.sqlite";
[JsonPropertyName("DatabaseHost")]
public string DatabaseHost { get; set; } = "";
[JsonPropertyName("DatabasePort")]
public int DatabasePort { get; set; } = 3306;
[JsonPropertyName("DatabaseUser")]
public string DatabaseUser { get; set; } = "";
[JsonPropertyName("DatabasePassword")]
public string DatabasePassword { get; set; } = "";
[JsonPropertyName("DatabaseName")]
public string DatabaseName { get; set; } = "";
[JsonPropertyName("DatabaseSSlMode")]
public string DatabaseSSlMode { get; set; } = "preferred";
}
public enum DatabaseType
{
MySQL,
SQLite
}
}

View File

@@ -11,11 +11,6 @@ public class Database(string dbConnectionString)
{
var connection = new MySqlConnection(dbConnectionString);
connection.Open();
// using var cmd = connection.CreateCommand();
// cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
// cmd.ExecuteNonQueryAsync();
return connection;
}
catch (Exception ex)
@@ -31,11 +26,6 @@ public class Database(string dbConnectionString)
{
var connection = new MySqlConnection(dbConnectionString);
await connection.OpenAsync();
// await using var cmd = connection.CreateCommand();
// cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
// await cmd.ExecuteNonQueryAsync();
return connection;
}
catch (Exception ex)
@@ -45,24 +35,22 @@ public class Database(string dbConnectionString)
}
}
// public async Task DatabaseMigration()
// {
// Migration migrator = new(this);
// await migrator.ExecuteMigrationsAsync();
// }
public void DatabaseMigration()
{
Migration migrator = new(this);
migrator.ExecuteMigrations();
}
public bool CheckDatabaseConnection(out string? exception)
public bool CheckDatabaseConnection()
{
using var connection = GetConnection();
exception = null;
try
{
return connection.Ping();
}
catch (Exception ex)
catch
{
exception = ex.Message;
return false;
}
}

View File

@@ -1,60 +0,0 @@
using System.Data.Common;
namespace CS2_SimpleAdmin.Database;
public interface IDatabaseProvider
{
Task<DbConnection> CreateConnectionAsync();
Task<(bool Success, string? Exception)> CheckConnectionAsync();
Task DatabaseMigrationAsync();
// CacheManager
string GetBanSelectQuery(bool multiServer);
string GetIpHistoryQuery();
string GetBanUpdateQuery(bool multiServer);
// PermissionManager
string GetAdminsQuery();
string GetDeleteAdminQuery(bool globalDelete);
string GetAddAdminQuery();
string GetAddAdminFlagsQuery();
string GetUpdateAdminGroupQuery();
string GetGroupsQuery();
string GetGroupIdByNameQuery();
string GetAddGroupQuery();
string GetAddGroupFlagsQuery();
string GetAddGroupServerQuery();
string GetDeleteGroupQuery();
string GetDeleteOldAdminsQuery();
// BanManager
string GetAddBanQuery();
string GetAddBanBySteamIdQuery();
string GetAddBanByIpQuery();
string GetUnbanRetrieveBansQuery(bool multiServer);
string GetUnbanAdminIdQuery();
string GetInsertUnbanQuery(bool includeReason);
string GetUpdateBanStatusQuery();
string GetExpireBansQuery(bool multiServer);
string GetExpireIpBansQuery(bool multiServer);
// MuteManager
string GetAddMuteQuery(bool includePlayerName);
string GetIsMutedQuery(bool multiServer, int timeMode);
string GetMuteStatsQuery(bool multiServer);
string GetUpdateMutePassedQuery(bool multiServer);
string GetCheckExpiredMutesQuery(bool multiServer);
string GetRetrieveMutesQuery(bool multiServer);
string GetUnmuteAdminIdQuery();
string GetInsertUnmuteQuery(bool includeReason);
string GetUpdateMuteStatusQuery();
string GetExpireMutesQuery(bool multiServer, int timeMode);
// WarnManager
string GetAddWarnQuery(bool includePlayerName);
string GetPlayerWarnsQuery(bool multiServer, bool active);
string GetPlayerWarnsCountQuery(bool multiServer, bool active);
string GetUnwarnByIdQuery(bool multiServer);
string GetUnwarnLastQuery(bool multiServer);
string GetExpireWarnsQuery(bool multiServer);
}

View File

@@ -1,94 +1,61 @@
using System.Data.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using MySqlConnector;
namespace CS2_SimpleAdmin.Database;
public class Migration(string migrationsPath)
public class Migration(Database database)
{
/// <summary>
/// Executes all migration scripts found in the configured migrations path that have not been applied yet.
/// Creates a migration tracking table if it does not exist.
/// Applies migration scripts in filename order and logs successes or failures.
/// </summary>
public async Task ExecuteMigrationsAsync()
public void ExecuteMigrations()
{
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
var files = Directory.GetFiles(migrationsPath, "*.sql").OrderBy(f => f).ToList();
if (files.Count == 0) return;
var migrationsDirectory = CS2_SimpleAdmin.Instance.ModuleDirectory + "/Database/Migrations";
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
var files = Directory.GetFiles(migrationsDirectory, "*.sql")
.OrderBy(f => f);
await using (var cmd = connection.CreateCommand())
{
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS sa_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version TEXT NOT NULL
);
""";
using var connection = database.GetConnection();
await cmd.ExecuteNonQueryAsync();
}
// Create sa_migrations table if not exists
using var cmd = new MySqlCommand("""
CREATE TABLE IF NOT EXISTS `sa_migrations` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`version` VARCHAR(255) NOT NULL
);
""", connection);
var lastAppliedVersion = await GetLastAppliedVersionAsync(connection);
cmd.ExecuteNonQuery();
// Get the last applied migration version
var lastAppliedVersion = GetLastAppliedVersion(connection);
foreach (var file in files)
{
var version = Path.GetFileNameWithoutExtension(file);
if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0)
continue;
try
{
var sqlScript = await File.ReadAllTextAsync(file);
// Check if the migration has already been applied
if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0) continue;
var sqlScript = File.ReadAllText(file);
await using (var cmdMigration = connection.CreateCommand())
{
cmdMigration.CommandText = sqlScript;
await cmdMigration.ExecuteNonQueryAsync();
}
using var cmdMigration = new MySqlCommand(sqlScript, connection);
cmdMigration.ExecuteNonQuery();
await UpdateLastAppliedVersionAsync(connection, version);
// Update the last applied migration version
UpdateLastAppliedVersion(connection, version);
CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied.");
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex, $"Error applying migration \"{version}\".");
break;
}
CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied.");
}
}
/// <summary>
/// Retrieves the version string of the last applied migration from the database.
/// </summary>
/// <param name="connection">The open database connection.</param>
/// <returns>The version string of the last applied migration, or empty string if none.</returns>
private static async Task<string> GetLastAppliedVersionAsync(DbConnection connection)
private static string GetLastAppliedVersion(MySqlConnection connection)
{
await using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT version FROM sa_migrations ORDER BY id DESC LIMIT 1;";
var result = await cmd.ExecuteScalarAsync();
using var cmd = new MySqlCommand("SELECT `version` FROM `sa_migrations` ORDER BY `id` DESC LIMIT 1;", connection);
var result = cmd.ExecuteScalar();
return result?.ToString() ?? string.Empty;
}
/// <summary>
/// Inserts a record tracking the successful application of a migration version.
/// </summary>
/// <param name="connection">The open database connection.</param>
/// <param name="version">The version string of the migration applied.</param>
private static async Task UpdateLastAppliedVersionAsync(DbConnection connection, string version)
private static void UpdateLastAppliedVersion(MySqlConnection connection, string version)
{
await using var cmd = connection.CreateCommand();
cmd.CommandText = "INSERT INTO sa_migrations (version) VALUES (@Version);";
var param = cmd.CreateParameter();
param.ParameterName = "@Version";
param.Value = version;
cmd.Parameters.Add(param);
await cmd.ExecuteNonQueryAsync();
using var cmd = new MySqlCommand("INSERT INTO `sa_migrations` (`version`) VALUES (@Version);", connection);
cmd.Parameters.AddWithValue("@Version", version);
cmd.ExecuteNonQuery();
}
}

View File

@@ -16,8 +16,8 @@ CREATE TABLE IF NOT EXISTS `sa_unmutes` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT IGNORE INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
VALUES (0, 'Console', 'Console', '', '0', NULL, NULL, NOW());
INSERT INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
VALUES (-1, 'Console', 'Console', '', '0', NULL, NULL, NOW());
UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1;

View File

@@ -1 +0,0 @@
ALTER TABLE `sa_servers` ADD `rcon_password` varchar(128) NULL AFTER `hostname`;

View File

@@ -1 +0,0 @@
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `status`;

View File

@@ -1,13 +0,0 @@
DELETE FROM `sa_players_ips`
WHERE `id` NOT IN (
SELECT * FROM (
SELECT MIN(`id`)
FROM `sa_players_ips`
GROUP BY `steamid`
) AS `keep_ids`
);
DELETE FROM sa_players_ips WHERE INET_ATON(address) IS NULL AND address IS NOT NULL;
UPDATE `sa_players_ips` SET `address` = INET_ATON(address);
ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL;
ALTER TABLE `sa_players_ips` ADD INDEX (used_at DESC);
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`;

View File

@@ -1,3 +0,0 @@
ALTER TABLE sa_mutes ADD INDEX (player_steamid, status, ends);
ALTER TABLE sa_mutes ADD INDEX(player_steamid, status, server_id, duration);
ALTER TABLE sa_mutes ADD INDEX(player_steamid, type);

View File

@@ -1,23 +0,0 @@
ALTER TABLE `sa_bans` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
UPDATE `sa_bans`
SET admin_steamid = '0'
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_bans` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
ALTER TABLE `sa_mutes` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
UPDATE `sa_mutes`
SET admin_steamid = '0'
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_mutes` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
ALTER TABLE `sa_warns` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
UPDATE `sa_warns`
SET admin_steamid = '0'
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_warns` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
UPDATE `sa_admins`
SET player_steamid = '0'
WHERE player_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_admins` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;

View File

@@ -1,47 +0,0 @@
CREATE TABLE IF NOT EXISTS `sa_bans` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128),
`player_steamid` VARCHAR(64),
`player_ip` VARCHAR(128),
`admin_steamid` VARCHAR(64) NOT NULL,
`admin_name` VARCHAR(128) NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`duration` INTEGER NOT NULL,
`ends` TIMESTAMP NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`server_id` INTEGER NULL,
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
);
CREATE TABLE IF NOT EXISTS `sa_mutes` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128) NULL,
`player_steamid` VARCHAR(64) NOT NULL,
`admin_steamid` VARCHAR(64) NOT NULL,
`admin_name` VARCHAR(128) NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`duration` INTEGER NOT NULL,
`ends` TIMESTAMP NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`type` TEXT NOT NULL DEFAULT 'GAG',
`server_id` INTEGER NULL,
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
);
CREATE TABLE IF NOT EXISTS `sa_admins` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128) NOT NULL,
`player_steamid` VARCHAR(64) NOT NULL,
`flags` TEXT NULL,
`immunity` INTEGER NOT NULL DEFAULT 0,
`server_id` INTEGER NULL,
`ends` TIMESTAMP NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `sa_servers` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`hostname` VARCHAR(128) NOT NULL,
`address` VARCHAR(64) NOT NULL,
UNIQUE (`address`)
);

View File

@@ -1,6 +0,0 @@
CREATE TABLE IF NOT EXISTS `sa_admins_flags` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`admin_id` INTEGER NOT NULL,
`flag` VARCHAR(64) NOT NULL,
FOREIGN KEY (`admin_id`) REFERENCES `sa_admins` (`id`) ON DELETE CASCADE
);

View File

@@ -1,46 +0,0 @@
INSERT INTO sa_admins_flags (admin_id, flag)
WITH RECURSIVE
min_admins AS (
SELECT MIN(id) AS admin_id, player_steamid, server_id
FROM sa_admins
WHERE player_steamid != 'Console'
GROUP BY player_steamid, server_id
),
split_flags AS (
SELECT
ma.admin_id,
sa.flags,
1 AS pos,
CASE
WHEN INSTR(sa.flags || ',', ',') = 0 THEN sa.flags
ELSE SUBSTR(sa.flags, 1, INSTR(sa.flags || ',', ',') - 1)
END AS flag,
CASE
WHEN INSTR(sa.flags || ',', ',') = 0 THEN ''
ELSE SUBSTR(sa.flags, INSTR(sa.flags || ',', ',') + 1)
END AS remaining
FROM min_admins ma
JOIN sa_admins sa ON ma.player_steamid = sa.player_steamid
AND (ma.server_id = sa.server_id OR (ma.server_id IS NULL AND sa.server_id IS NULL))
WHERE sa.flags IS NOT NULL AND sa.flags != ''
UNION ALL
SELECT
admin_id,
flags,
pos + 1,
CASE
WHEN INSTR(remaining || ',', ',') = 0 THEN remaining
ELSE SUBSTR(remaining, 1, INSTR(remaining || ',', ',') - 1)
END AS flag,
CASE
WHEN INSTR(remaining || ',', ',') = 0 THEN ''
ELSE SUBSTR(remaining, INSTR(remaining || ',', ',') + 1)
END AS remaining
FROM split_flags
WHERE remaining != ''
)
SELECT admin_id, TRIM(flag)
FROM split_flags
WHERE flag IS NOT NULL AND TRIM(flag) != '';

View File

@@ -1,23 +0,0 @@
CREATE TABLE IF NOT EXISTS `sa_unbans` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`ban_id` INTEGER NOT NULL,
`admin_id` INTEGER NOT NULL DEFAULT 0,
`reason` VARCHAR(255) NOT NULL DEFAULT 'Unknown',
`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `sa_unmutes` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`mute_id` INTEGER NOT NULL,
`admin_id` INTEGER NOT NULL DEFAULT 0,
`reason` VARCHAR(255) NOT NULL DEFAULT 'Unknown',
`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT OR IGNORE INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
VALUES (0, 'Console', 'Console', '', '0', NULL, NULL, CURRENT_TIMESTAMP);
UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1;
ALTER TABLE `sa_bans` ADD `unban_id` INTEGER NULL;
ALTER TABLE `sa_mutes` ADD `unmute_id` INTEGER NULL;

View File

@@ -1,21 +0,0 @@
CREATE TABLE IF NOT EXISTS `sa_groups` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL,
`immunity` INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS `sa_groups_flags` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`group_id` INTEGER NOT NULL,
`flag` VARCHAR(64) NOT NULL,
FOREIGN KEY (`group_id`) REFERENCES `sa_groups` (`id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`group_id` INTEGER NOT NULL,
`server_id` INTEGER NULL,
FOREIGN KEY (`group_id`) REFERENCES `sa_groups` (`id`) ON DELETE CASCADE
);
ALTER TABLE `sa_admins` ADD `group_id` INTEGER NULL;

View File

@@ -1 +0,0 @@
ALTER TABLE `sa_mutes` ADD `passed` INTEGER NULL;

View File

@@ -1,7 +0,0 @@
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`steamid` INTEGER NOT NULL,
`address` VARCHAR(64) NOT NULL,
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (`steamid`, `address`)
);

View File

@@ -1,13 +0,0 @@
CREATE TABLE IF NOT EXISTS `sa_warns` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128) DEFAULT NULL,
`player_steamid` VARCHAR(64) NOT NULL,
`admin_steamid` VARCHAR(64) NOT NULL,
`admin_name` VARCHAR(128) NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`duration` INTEGER NOT NULL,
`ends` TIMESTAMP NOT NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`server_id` INTEGER DEFAULT NULL,
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
);

View File

@@ -1 +0,0 @@
ALTER TABLE `sa_servers` ADD `rcon_password` VARCHAR(128) NULL;

View File

@@ -1 +0,0 @@
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;

View File

@@ -1,9 +0,0 @@
DELETE FROM `sa_players_ips`
WHERE `id` NOT IN (
SELECT MIN(`id`)
FROM `sa_players_ips`
GROUP BY `steamid`
);
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL;
CREATE INDEX IF NOT EXISTS `idx_sa_players_ips_used_at` ON `sa_players_ips` (`used_at` DESC);

View File

@@ -1,3 +0,0 @@
CREATE INDEX IF NOT EXISTS `idx_sa_mutes_steamid_status_ends` ON `sa_mutes` (`player_steamid`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_sa_mutes_steamid_status_server_duration` ON `sa_mutes` (`player_steamid`, `status`, `server_id`, `duration`);
CREATE INDEX IF NOT EXISTS `idx_sa_mutes_steamid_type` ON `sa_mutes` (`player_steamid`, `type`);

View File

@@ -1,15 +0,0 @@
UPDATE `sa_bans`
SET admin_steamid = '0'
WHERE admin_steamid NOT GLOB '[0-9]*';
UPDATE `sa_mutes`
SET admin_steamid = '0'
WHERE admin_steamid NOT GLOB '[0-9]*';
UPDATE `sa_warns`
SET admin_steamid = '0'
WHERE admin_steamid NOT GLOB '[0-9]*';
UPDATE `sa_admins`
SET player_steamid = '0'
WHERE player_steamid NOT GLOB '[0-9]*';

View File

@@ -1,384 +0,0 @@
using System.Data.Common;
using MySqlConnector;
namespace CS2_SimpleAdmin.Database;
public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
{
public async Task<DbConnection> CreateConnectionAsync()
{
var connection = new MySqlConnection(connectionString);
await connection.OpenAsync();
return connection;
}
public async Task<(bool Success, string? Exception)> CheckConnectionAsync()
{
try
{
await using var conn = await CreateConnectionAsync();
return (true, null);
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
public Task DatabaseMigrationAsync()
{
var migration = new Migration(CS2_SimpleAdmin.Instance.ModuleDirectory + "/Database/Migrations/Mysql");
return migration.ExecuteMigrationsAsync();
}
public string GetBanSelectQuery(bool multiServer)
{
return multiServer ? """
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
""" : """
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE server_id = @serverId
""";
}
public static string GetBanUpdatedSelectQuery(bool multiServer)
{
return multiServer ? """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
""" : """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
""";
}
public string GetIpHistoryQuery()
{
return "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
}
public string GetBanUpdateQuery(bool multiServer)
{
return multiServer ? """
UPDATE sa_bans
SET
player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE
(player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
""" : """
UPDATE sa_bans
SET
player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE
(player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId
""";
}
public string GetAdminsQuery()
{
return """
SELECT sa_admins.player_steamid, sa_admins.player_name, sa_admins_flags.flag, sa_admins.immunity, sa_admins.ends
FROM sa_admins_flags
JOIN sa_admins ON sa_admins_flags.admin_id = sa_admins.id
WHERE (sa_admins.ends IS NULL OR sa_admins.ends > @CurrentTime)
AND (sa_admins.server_id IS NULL OR sa_admins.server_id = @serverid)
ORDER BY sa_admins.player_steamid
""";
}
public string GetDeleteAdminQuery(bool globalDelete) =>
globalDelete
? "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID"
: "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId";
public string GetAddAdminQuery() =>
"INSERT INTO sa_admins (player_steamid, player_name, immunity, ends, created, server_id) " +
"VALUES (@playerSteamId, @playerName, @immunity, @ends, @created, @serverid); SELECT LAST_INSERT_ID();";
public string GetGroupsQuery()
{
return """
SELECT g.group_id, sg.name AS group_name, sg.immunity, f.flag
FROM sa_groups_flags f
JOIN sa_groups_servers g ON f.group_id = g.group_id
JOIN sa_groups sg ON sg.id = g.group_id
WHERE (g.server_id = @serverid OR server_id IS NULL)
""";
}
public string GetAddAdminFlagsQuery() =>
"INSERT INTO sa_admins_flags (admin_id, flag) VALUES (@adminId, @flag);";
public string GetUpdateAdminGroupQuery() =>
"UPDATE sa_admins SET group_id = @groupId WHERE id = @adminId;";
public string GetAddGroupQuery() =>
"INSERT INTO sa_groups (name, immunity) VALUES (@groupName, @immunity); SELECT LAST_INSERT_ID();";
public string GetGroupIdByNameQuery() =>
"""
SELECT sgs.group_id
FROM sa_groups_servers sgs
JOIN sa_groups sg ON sgs.group_id = sg.id
WHERE sg.name = @groupName
ORDER BY (sgs.server_id = @serverId) DESC, sgs.server_id ASC
LIMIT 1;
""";
public string GetAddGroupFlagsQuery() =>
"INSERT INTO sa_groups_flags (group_id, flag) VALUES (@groupId, @flag);";
public string GetAddGroupServerQuery() =>
"INSERT INTO sa_groups_servers (group_id, server_id) VALUES (@groupId, @server_id);";
public string GetDeleteGroupQuery() =>
"DELETE FROM sa_groups WHERE name = @groupName;";
public string GetDeleteOldAdminsQuery() =>
"DELETE FROM sa_admins WHERE ends IS NOT NULL AND ends <= @CurrentTime;";
public string GetAddBanQuery()
{
return """
INSERT INTO `sa_bans`
(`player_steamid`, `player_name`, `player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @playerName, @playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
""";
}
public string GetAddBanBySteamIdQuery()
{
return """
INSERT INTO `sa_bans`
(`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
""";
}
public string GetAddBanByIpQuery()
{
return """
INSERT INTO `sa_bans`
(`player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
""";
}
public string GetUnbanRetrieveBansQuery(bool multiServer)
{
return multiServer
? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'"
: "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid";
}
public string GetUnbanAdminIdQuery()
{
return "SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
}
public string GetInsertUnbanQuery(bool includeReason)
{
return includeReason
? "INSERT INTO sa_unbans (ban_id, admin_id, reason) VALUES (@banId, @adminId, @reason); SELECT LAST_INSERT_ID();"
: "INSERT INTO sa_unbans (ban_id, admin_id) VALUES (@banId, @adminId); SELECT LAST_INSERT_ID();";
}
public string GetUpdateBanStatusQuery()
{
return "UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId";
}
public string GetExpireBansQuery(bool multiServer)
{
return multiServer
? "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime"
: "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime AND server_id = @serverid";
}
public string GetExpireIpBansQuery(bool multiServer)
{
return multiServer
? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime"
: "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid";
}
public string GetAddMuteQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO `sa_mutes`
(`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`)
VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT LAST_INSERT_ID();
"""
: """
INSERT INTO `sa_mutes`
(`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`)
VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT LAST_INSERT_ID();
""";
public string GetIsMutedQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0))")
: (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0)) AND server_id = @serverid");
public string GetMuteStatsQuery(bool multiServer) =>
multiServer
? """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID;
"""
: """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId;
""";
public string GetUpdateMutePassedQuery(bool multiServer) =>
multiServer
? "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE'"
: "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetCheckExpiredMutesQuery(bool multiServer) =>
multiServer
? "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE'"
: "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetRetrieveMutesQuery(bool multiServer) =>
multiServer
? "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE'"
: "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE' AND server_id = @serverid";
public string GetUnmuteAdminIdQuery() =>
"SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
public string GetInsertUnmuteQuery(bool includeReason) =>
includeReason
? "INSERT INTO sa_unmutes (mute_id, admin_id, reason) VALUES (@muteId, @adminId, @reason); SELECT LAST_INSERT_ID();"
: "INSERT INTO sa_unmutes (mute_id, admin_id) VALUES (@muteId, @adminId); SELECT LAST_INSERT_ID();";
public string GetUpdateMuteStatusQuery() =>
"UPDATE sa_mutes SET status = 'UNMUTED', unmute_id = @unmuteId WHERE id = @muteId";
public string GetExpireMutesQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration`")
: (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration` AND server_id = @serverid");
public string GetAddWarnQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO `sa_warns`
(`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @playerName, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
"""
: """
INSERT INTO `sa_warns`
(`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
""";
public string GetPlayerWarnsQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID ORDER BY id DESC"
: active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid ORDER BY id DESC";
public string GetPlayerWarnsCountQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID"
: active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid";
public string GetUnwarnByIdQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId AND server_id = @serverid";
public string GetUnwarnLastQuery(bool multiServer) =>
multiServer
? """
UPDATE sa_warns
JOIN (
SELECT MAX(id) AS max_id
FROM sa_warns
WHERE player_steamid = @steamid AND status = 'ACTIVE'
) AS subquery ON sa_warns.id = subquery.max_id
SET sa_warns.status = 'EXPIRED'
WHERE sa_warns.status = 'ACTIVE' AND sa_warns.player_steamid = @steamid;
"""
: """
UPDATE sa_warns
JOIN (
SELECT MAX(id) AS max_id
FROM sa_warns
WHERE player_steamid = @steamid AND status = 'ACTIVE' AND server_id = @serverid
) AS subquery ON sa_warns.id = subquery.max_id
SET sa_warns.status = 'EXPIRED'
WHERE sa_warns.status = 'ACTIVE' AND sa_warns.player_steamid = @steamid AND sa_warns.server_id = @serverid;
""";
public string GetExpireWarnsQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid";
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

View File

@@ -1,366 +0,0 @@
using System.Data.Common;
using System.Data.SQLite;
namespace CS2_SimpleAdmin.Database;
public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider
{
private readonly string _connectionString = $"Data Source={filePath}";
public async Task<DbConnection> CreateConnectionAsync()
{
var conn = new SQLiteConnection(_connectionString);
await conn.OpenAsync();
return conn;
}
public async Task<(bool Success, string? Exception)> CheckConnectionAsync()
{
try
{
await using var conn = await CreateConnectionAsync();
await using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT 1";
await cmd.ExecuteScalarAsync();
return (true, null);
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
public Task DatabaseMigrationAsync()
{
var migration = new Migration(CS2_SimpleAdmin.Instance.ModuleDirectory + "/Database/Migrations/Sqlite");
return migration.ExecuteMigrationsAsync();
}
public string GetBanSelectQuery(bool multiServer) =>
multiServer
? """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
"""
: """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE server_id = @serverId
""";
public static string GetBanUpdatedSelectQuery(bool multiServer) =>
multiServer
? """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE updated_at > @lastUpdate OR created > @lastUpdate
ORDER BY updated_at DESC
"""
: """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE (updated_at > @lastUpdate OR created > @lastUpdate)
AND server_id = @serverId
ORDER BY updated_at DESC
""";
public string GetIpHistoryQuery() =>
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
public string GetBanUpdateQuery(bool multiServer) =>
multiServer
? """
UPDATE sa_bans
SET player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
"""
: """
UPDATE sa_bans
SET player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
AND server_id = @ServerId
""";
public string GetAddBanQuery() =>
"""
INSERT INTO sa_bans
(player_steamid, player_name, player_ip, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @playerName, @playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetAddBanBySteamIdQuery() =>
"""
INSERT INTO sa_bans
(player_steamid, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetAddBanByIpQuery() =>
"""
INSERT INTO sa_bans
(player_ip, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetUnbanRetrieveBansQuery(bool multiServer) =>
multiServer
? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'"
: "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid";
public string GetUnbanAdminIdQuery() =>
"SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
public string GetInsertUnbanQuery(bool includeReason) =>
includeReason
? "INSERT INTO sa_unbans (ban_id, admin_id, reason) VALUES (@banId, @adminId, @reason); SELECT last_insert_rowid();"
: "INSERT INTO sa_unbans (ban_id, admin_id) VALUES (@banId, @adminId); SELECT last_insert_rowid();";
public string GetUpdateBanStatusQuery() =>
"UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId";
public string GetExpireBansQuery(bool multiServer) =>
multiServer
? "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime"
: "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime AND server_id = @serverid";
public string GetExpireIpBansQuery(bool multiServer) =>
multiServer
? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime"
: "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid";
public string GetAdminsQuery() =>
"""
SELECT sa_admins.player_steamid, sa_admins.player_name, sa_admins_flags.flag, sa_admins.immunity, sa_admins.ends
FROM sa_admins_flags
JOIN sa_admins ON sa_admins_flags.admin_id = sa_admins.id
WHERE (sa_admins.ends IS NULL OR sa_admins.ends > @CurrentTime)
AND (sa_admins.server_id IS NULL OR sa_admins.server_id = @serverid)
ORDER BY sa_admins.player_steamid
""";
public string GetDeleteAdminQuery(bool globalDelete) =>
globalDelete
? "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID"
: "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId";
public string GetAddAdminQuery() =>
"""
INSERT INTO sa_admins (player_steamid, player_name, immunity, ends, created, server_id)
VALUES (@playerSteamId, @playerName, @immunity, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetGroupsQuery() =>
"""
SELECT g.group_id, sg.name AS group_name, sg.immunity, f.flag
FROM sa_groups_flags f
JOIN sa_groups_servers g ON f.group_id = g.group_id
JOIN sa_groups sg ON sg.id = g.group_id
WHERE (g.server_id = @serverid OR server_id IS NULL)
""";
public string GetAddAdminFlagsQuery() =>
"INSERT INTO sa_admins_flags (admin_id, flag) VALUES (@adminId, @flag);";
public string GetUpdateAdminGroupQuery() =>
"UPDATE sa_admins SET group_id = @groupId WHERE id = @adminId;";
public string GetAddGroupQuery() =>
"""
INSERT INTO sa_groups (name, immunity) VALUES (@groupName, @immunity);
SELECT last_insert_rowid();
""";
public string GetGroupIdByNameQuery() =>
"""
SELECT sgs.group_id
FROM sa_groups_servers sgs
JOIN sa_groups sg ON sgs.group_id = sg.id
WHERE sg.name = @groupName
ORDER BY (sgs.server_id = @serverId) DESC, sgs.server_id ASC
LIMIT 1;
""";
public string GetAddGroupFlagsQuery() =>
"INSERT INTO sa_groups_flags (group_id, flag) VALUES (@groupId, @flag);";
public string GetAddGroupServerQuery() =>
"INSERT INTO sa_groups_servers (group_id, server_id) VALUES (@groupId, @server_id);";
public string GetDeleteGroupQuery() =>
"DELETE FROM sa_groups WHERE name = @groupName;";
public string GetDeleteOldAdminsQuery() =>
"DELETE FROM sa_admins WHERE ends IS NOT NULL AND ends <= @CurrentTime;";
public string GetAddMuteQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO sa_mutes
(player_steamid, player_name, admin_steamid, admin_name, reason, duration, ends, created, type, server_id)
VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT last_insert_rowid();
"""
: """
INSERT INTO sa_mutes
(player_steamid, admin_steamid, admin_name, reason, duration, ends, created, type, server_id)
VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT last_insert_rowid();
""";
public string GetIsMutedQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0))")
: (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0)) AND server_id = @serverid");
public string GetMuteStatsQuery(bool multiServer) =>
multiServer
? """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID;
"""
: """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId;
""";
public string GetUpdateMutePassedQuery(bool multiServer) =>
multiServer
? "UPDATE sa_mutes SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE'"
: "UPDATE sa_mutes SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetCheckExpiredMutesQuery(bool multiServer) =>
multiServer
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE'"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetRetrieveMutesQuery(bool multiServer) =>
multiServer
? "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE'"
: "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE' AND server_id = @serverid";
public string GetUnmuteAdminIdQuery() =>
"SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
public string GetInsertUnmuteQuery(bool includeReason) =>
includeReason
? "INSERT INTO sa_unmutes (mute_id, admin_id, reason) VALUES (@muteId, @adminId, @reason); SELECT last_insert_rowid();"
: "INSERT INTO sa_unmutes (mute_id, admin_id) VALUES (@muteId, @adminId); SELECT last_insert_rowid();";
public string GetUpdateMuteStatusQuery() =>
"UPDATE sa_mutes SET status = 'UNMUTED', unmute_id = @unmuteId WHERE id = @muteId";
public string GetExpireMutesQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND passed >= duration")
: (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime AND server_id = @serverid"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND passed >= duration AND server_id = @serverid");
public string GetAddWarnQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO sa_warns
(player_steamid, player_name, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @playerName, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
"""
: """
INSERT INTO sa_warns
(player_steamid, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetPlayerWarnsQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID ORDER BY id DESC"
: active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid ORDER BY id DESC";
public string GetPlayerWarnsCountQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID"
: active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid";
public string GetUnwarnByIdQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId AND server_id = @serverid";
public string GetUnwarnLastQuery(bool multiServer) =>
multiServer
? """
UPDATE sa_warns
SET status = 'EXPIRED'
WHERE status = 'ACTIVE'
AND player_steamid = @steamid
ORDER BY id DESC
LIMIT 1
"""
: """
UPDATE sa_warns
SET status = 'EXPIRED'
WHERE status = 'ACTIVE'
AND player_steamid = @steamid
AND server_id = @serverid
ORDER BY id DESC
LIMIT 1
""";
public string GetExpireWarnsQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime AND server_id = @serverid";
}

View File

@@ -1,15 +1,14 @@
using System.Numerics;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Utils;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Models;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
using System.Text;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.UserMessages;
@@ -17,14 +16,9 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
private bool _serverLoading;
private void RegisterEvents()
{
RegisterListener<Listeners.OnMapStart>(OnMapStart);
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
if (Config.OtherSettings.UserMessageGagChatType)
HookUserMessage(118, HookUmChat);
@@ -35,22 +29,6 @@ public partial class CS2_SimpleAdmin
// AddCommandListener("say_team", OnCommandTeamSay);
}
private void UnregisterEvents()
{
RemoveListener<Listeners.OnMapStart>(OnMapStart);
RemoveListener<Listeners.OnClientConnect>(OnClientConnect);
RemoveListener<Listeners.OnClientConnected>(OnClientConnected);
RemoveListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
if (Config.OtherSettings.UserMessageGagChatType)
UnhookUserMessage(118, HookUmChat);
RemoveCommandListener(null!, ComamndListenerHandler, HookMode.Pre);
// AddCommandListener("callvote", OnCommandCallVote);
// AddCommandListener("say", OnCommandSay);
// AddCommandListener("say_team", OnCommandTeamSay);
}
// private HookResult OnCommandCallVote(CCSPlayerController? caller, CommandInfo info)
// {
// var voteType = info.GetArg(1).ToLower();
@@ -70,38 +48,22 @@ public partial class CS2_SimpleAdmin
private void OnGameServerSteamAPIActivated()
{
if (ServerLoaded || _serverLoading)
return;
_serverLoading = true;
new ServerManager().LoadServerData();
}
[GameEventHandler(HookMode.Pre)]
[GameEventHandler]
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
{
if (@event.Reason is 149 or 6)
info.DontBroadcast = true;
var player = @event.Userid;
#if DEBUG
Logger.LogCritical("[OnClientDisconnect] Before");
#endif
if (player == null || !player.IsValid || player.IsHLTV)
return HookResult.Continue;
BotPlayers.Remove(player);
CachedPlayers.Remove(player);
SilentPlayers.Remove(player.Slot);
GodPlayers.Remove(player.Slot);
SpeedPlayers.Remove(player);
GravityPlayers.Remove(player);
if (player.IsBot)
if (player == null || !player.IsValid || player.IsBot)
{
return HookResult.Continue;
}
#if DEBUG
Logger.LogCritical("[OnClientDisconnect] After Check");
@@ -122,30 +84,26 @@ public partial class CS2_SimpleAdmin
}
else
{
DisconnectedPlayers.Add(new DisconnectedPlayer(steamId, player.PlayerName,
player.IpAddress?.Split(":")[0], Time.ActualDateTime()));
DisconnectedPlayers.Add(new DisconnectedPlayer(steamId, player.PlayerName, player.IpAddress?.Split(":")[0], Time.ActualDateTime()));
}
PlayerPenaltyManager.RemoveAllPenalties(player.Slot);
SilentPlayers.Remove(player.Slot);
GodPlayers.Remove(player.Slot);
SpeedPlayers.Remove(player.Slot);
GravityPlayers.Remove(player);
if (player.UserId.HasValue)
PlayersInfo.TryRemove(player.SteamID, out _);
PlayersInfo.Remove(player.UserId.Value);
if (!PermissionManager.AdminCache.TryGetValue(steamId, out var data)
|| !(data.ExpirationTime <= Time.ActualDateTime()))
{
return HookResult.Continue;
}
var authorizedSteamId = player.AuthorizedSteamID;
if (authorizedSteamId == null || !PermissionManager.AdminCache.TryGetValue(authorizedSteamId,
out var expirationTime)
|| !(expirationTime <= Time.ActualDateTime())) return HookResult.Continue;
AdminManager.RemovePlayerPermissions(steamId, PermissionManager.AdminCache[steamId].Flags.ToArray());
AdminManager.RemovePlayerFromGroup(steamId, true, PermissionManager.AdminCache[steamId].Flags.ToArray());
var adminData = AdminManager.GetPlayerAdminData(steamId);
if (adminData == null || data.Flags.ToList().Count != 0 && adminData.Groups.ToList().Count != 0)
return HookResult.Continue;
AdminManager.ClearPlayerPermissions(steamId);
AdminManager.RemovePlayerAdminData(steamId);
AdminManager.ClearPlayerPermissions(authorizedSteamId);
AdminManager.RemovePlayerAdminData(authorizedSteamId);
return HookResult.Continue;
}
@@ -156,87 +114,16 @@ public partial class CS2_SimpleAdmin
}
}
private void OnClientConnect(int playerslot, string name, string ipAddress)
{
#if DEBUG
Logger.LogCritical("[OnClientConnect]");
#endif
var player = Utilities.GetPlayerFromSlot(playerslot);
if (player == null || !player.IsValid || player.IsBot)
return;
PlayerManager.LoadPlayerData(player);
}
private void OnClientConnected(int playerslot)
{
#if DEBUG
Logger.LogCritical("[OnClientConnected]");
#endif
var player = Utilities.GetPlayerFromSlot(playerslot);
if (player == null || !player.IsValid || player.IsBot)
return;
PlayerManager.LoadPlayerData(player);
}
// private void OnClientConnect(int playerslot, string name, string ipaddress)
// {
// #if DEBUG
// Logger.LogCritical("[OnClientConnect]");
// #endif
// if (Config.OtherSettings.BanType == 0)
// return;
//
// if (Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0]))
// return;
//
// var testPlayer = Utilities.GetPlayerFromSlot(playerslot);
// if (testPlayer == null)
// return;
// Logger.LogInformation($"Gracz {testPlayer.PlayerName} ({testPlayer.SteamID.ToString()}) Czas: {DateTime.Now}");
//
// Server.NextFrame((() =>
// {
// var player = Utilities.GetPlayerFromSlot(playerslot);
// if (player == null || !player.IsValid || player.IsBot)
// return;
//
// Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
// }));
//
// // Server.NextFrame(() =>
// // {
// // var player = Utilities.GetPlayerFromSlot(playerslot);
// //
// // if (player == null || !player.IsValid || player.IsBot)
// // return;
// //
// // new PlayerManager().LoadPlayerData(player);
// // });
// }
[GameEventHandler]
public HookResult OnPlayerFullConnect(EventPlayerConnectFull @event, GameEventInfo info)
{
#if DEBUG
Logger.LogCritical("[OnPlayerFullConnect]");
#endif
var player = @event.Userid;
if (player == null || !player.IsValid)
if (player == null || !player.IsValid || player.IsBot)
return HookResult.Continue;
if (player is { IsBot: true, IsHLTV: false })
{
BotPlayers.Add(player);
return HookResult.Continue;
}
new PlayerManager().LoadPlayerData(player);
PlayerManager.LoadPlayerData(player, true);
return HookResult.Continue;
}
@@ -244,7 +131,7 @@ public partial class CS2_SimpleAdmin
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
{
#if DEBUG
Logger.LogCritical("[OnRoundStart]");
Logger.LogCritical("[OnRoundEnd]");
#endif
GodPlayers.Clear();
@@ -280,29 +167,13 @@ public partial class CS2_SimpleAdmin
var author = Utilities.GetPlayerFromIndex(um.ReadInt("entityindex"));
if (author == null || !author.IsValid || author.IsBot)
return HookResult.Continue;
if (!PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Gag, out DateTime? endDateTime) &&
!PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Silence, out endDateTime))
return HookResult.Continue;
if (_localizer == null || endDateTime == null)
return HookResult.Continue;
var message = um.ReadString("param2");
var triggers = CoreConfig.PublicChatTrigger.Concat(CoreConfig.SilentChatTrigger);
if (!triggers.Any(trigger => message.StartsWith(trigger))) return HookResult.Stop;
for (var i = um.Recipients.Count - 1; i >= 0; i--)
{
if (um.Recipients[i] != author)
{
um.Recipients.RemoveAt(i);
}
}
if (PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Gag) || PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Silence))
return HookResult.Stop;
// um.Recipients.Clear();
return HookResult.Continue;
// author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage()));
}
private HookResult ComamndListenerHandler(CCSPlayerController? player, CommandInfo info)
@@ -312,9 +183,6 @@ public partial class CS2_SimpleAdmin
var command = info.GetArg(0).ToLower();
if (Config.OtherSettings.AdditionalCommandsToLog.Contains(command))
Helper.LogCommand(player, info);
switch (command)
{
case "css_admins_reload":
@@ -333,47 +201,43 @@ public partial class CS2_SimpleAdmin
if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected)
return HookResult.Continue;
return !player.CanTarget(target) ? HookResult.Stop : HookResult.Continue;
return !AdminManager.CanPlayerTarget(player, target) ? HookResult.Stop : HookResult.Continue;
}
}
if (!command.Contains("say"))
return HookResult.Continue;
if (!Config.OtherSettings.UserMessageGagChatType)
if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence))
return HookResult.Stop;
if (info.GetArg(1).StartsWith($"/")
|| info.GetArg(1).StartsWith($"!"))
return HookResult.Continue;
if (info.GetArg(1).Length == 0)
return HookResult.Stop;
var triggers = CoreConfig.PublicChatTrigger.Concat(CoreConfig.SilentChatTrigger);
if (triggers.Any(trigger => info.GetArg(1).StartsWith(trigger)))
if (command == "say" && info.GetArg(1).StartsWith($"@") &&
AdminManager.PlayerHasPermissions(player, "@vip/chat"))
{
return HookResult.Continue;
}
// if (!Config.OtherSettings.UserMessageGagChatType)
// {
if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out DateTime? endDateTime) ||
PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out endDateTime))
sb.Append(_localizer!["sa_vipchat_template", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@vip/chat")))
{
if (_localizer != null && endDateTime is not null)
player.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", player.GetLanguage()));
return HookResult.Stop;
p.PrintToChat(sb.ToString());
}
// }
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat") && command == "say" && info.GetArg(1).StartsWith($"@"))
{
player.ExecuteClientCommandFromServer($"css_say {info.GetArg(1).Remove(0, 1)}");
return HookResult.Stop;
}
if (command != "say_team" || !info.GetArg(1).StartsWith($"@")) return HookResult.Continue;
StringBuilder sb = new();
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat"))
if (AdminManager.PlayerHasPermissions(player, "@css/chat"))
{
sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
@@ -382,7 +246,7 @@ public partial class CS2_SimpleAdmin
{
sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
player.PrintToChat(sb.ToString());
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
@@ -421,17 +285,17 @@ public partial class CS2_SimpleAdmin
if (info.GetArg(1).Length == 0)
return HookResult.Handled;
if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _))
return HookResult.Stop;
if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence))
return HookResult.Handled;
if (!info.GetArg(1).StartsWith($"@")) return HookResult.Continue;
StringBuilder sb = new();
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat"))
if (AdminManager.PlayerHasPermissions(player, "@css/chat"))
{
sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
@@ -440,7 +304,7 @@ public partial class CS2_SimpleAdmin
{
sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
player.PrintToChat(sb.ToString());
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
@@ -451,19 +315,14 @@ public partial class CS2_SimpleAdmin
private void OnMapStart(string mapName)
{
if (!ServerLoaded || ServerId == null)
AddTimer(2.0f, OnGameServerSteamAPIActivated);
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
AddTimer(5.0f, () => ReloadAdmins(null));
AddTimer(3.0f, () => ReloadAdmins(null));
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
// AddTimer(34, () =>
// {
// if (!ServerLoaded)
// OnGameServerSteamAPIActivated();
// });
AddTimer(34, () =>
{
if (!ServerLoaded)
OnGameServerSteamAPIActivated();
});
GodPlayers.Clear();
SilentPlayers.Clear();
@@ -478,8 +337,11 @@ public partial class CS2_SimpleAdmin
{
var player = @event.Userid;
if (player is null || @event.Attacker is null || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.PlayerPawn.Value == null)
if (player is null || @event.Attacker is null || !player.PawnIsAlive || player.PlayerPawn.Value == null)
return HookResult.Continue;
if (SpeedPlayers.TryGetValue(player.Slot, out var speedPlayer))
player.SetSpeed(speedPlayer);
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
@@ -493,30 +355,19 @@ public partial class CS2_SimpleAdmin
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
{
var player = @event.Userid;
if (player?.UserId == null || !player.IsValid || player.IsHLTV || player.Connected != PlayerConnectedState.PlayerConnected)
if (player?.UserId == null || player.IsBot || player.Connected != PlayerConnectedState.PlayerConnected)
return HookResult.Continue;
SpeedPlayers.Remove(player);
SpeedPlayers.Remove(player.Slot);
GravityPlayers.Remove(player);
if (!PlayersInfo.ContainsKey(player.SteamID) || @event.Attacker == null)
return HookResult.Continue;
var playerPosition = player.PlayerPawn.Value?.AbsOrigin;
var playerRotation = player.PlayerPawn.Value?.AbsRotation;
PlayersInfo[player.SteamID].DiePosition = new DiePosition(
new Vector3(
playerPosition?.X ?? 0,
playerPosition?.Y ?? 0,
playerPosition?.Z ?? 0
),
new Vector3(
playerRotation?.X ?? 0,
playerRotation?.Y ?? 0,
playerRotation?.Z ?? 0
)
);
PlayersInfo[player.UserId.Value].DiePosition =
new DiePosition(
new Vector(player.PlayerPawn.Value?.AbsOrigin?.X, player.PlayerPawn.Value?.AbsOrigin?.Y,
player.PlayerPawn.Value?.AbsOrigin?.Z),
new QAngle(player.PlayerPawn.Value?.AbsRotation?.X, player.PlayerPawn.Value?.AbsRotation?.Y,
player.PlayerPawn.Value?.AbsRotation?.Z));
return HookResult.Continue;
}
@@ -525,17 +376,17 @@ public partial class CS2_SimpleAdmin
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
{
var player = @event.Userid;
if (player == null || !player.IsValid || player.IsBot)
return HookResult.Continue;
if (!SilentPlayers.Contains(player.Slot))
return HookResult.Continue;
if (@event is { Oldteam: <= 1, Team: >= 1 })
{
info.DontBroadcast = true;
if (@event.Team > 1)
SilentPlayers.Remove(player.Slot);
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
}
return HookResult.Continue;
}

View File

@@ -1,12 +0,0 @@
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

@@ -1,6 +1,4 @@
using System.Drawing;
using System.Numerics;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
@@ -15,21 +13,11 @@ namespace CS2_SimpleAdmin;
public static class PlayerExtensions
{
/// <summary>
/// Slaps the player pawn by applying optional damage and adding a random velocity knockback.
/// </summary>
/// <param name="pawn">The player pawn to slap.</param>
/// <param name="damage">The amount of damage to apply (default is 0).</param>
public static void Slap(this CBasePlayerPawn pawn, int damage = 0)
{
PerformSlap(pawn, damage);
}
/// <summary>
/// Prints a localized chat message to the player with a prefix.
/// </summary>
/// <param name="controller">The player controller to send the message to.</param>
/// <param name="message">The message string.</param>
public static void Print(this CCSPlayerController controller, string message = "")
{
StringBuilder _message = new(CS2_SimpleAdmin._localizer!["sa_prefix"]);
@@ -37,12 +25,6 @@ public static class PlayerExtensions
controller.PrintToChat(_message.ToString());
}
/// <summary>
/// Determines if the player controller can target another player controller, respecting admin permissions and immunity.
/// </summary>
/// <param name="controller">The player controller who wants to target.</param>
/// <param name="target">The player controller being targeted.</param>
/// <returns>True if targeting is allowed, false otherwise.</returns>
public static bool CanTarget(this CCSPlayerController? controller, CCSPlayerController? target)
{
if (controller is null || target is null) return true;
@@ -50,29 +32,9 @@ public static class PlayerExtensions
return AdminManager.CanPlayerTarget(controller, target) ||
AdminManager.CanPlayerTarget(new SteamID(controller.SteamID),
new SteamID(target.SteamID)) ||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(target);
new SteamID(target.SteamID));
}
/// <summary>
/// Checks if the controller can target a player by SteamID, considering targeting permissions and immunities.
/// </summary>
/// <param name="controller">The attacker player controller.</param>
/// <param name="steamId">The SteamID of the target player.</param>
/// <returns>True if targeting is permitted, false otherwise.</returns>
public static bool CanTarget(this CCSPlayerController? controller, SteamID steamId)
{
if (controller is null) return true;
return AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), steamId) ||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(steamId);
}
/// <summary>
/// Sets the movement speed modifier of the player controller.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="speed">The speed modifier value.</param>
public static void SetSpeed(this CCSPlayerController? controller, float speed)
{
var playerPawnValue = controller?.PlayerPawn.Value;
@@ -81,24 +43,14 @@ public static class PlayerExtensions
playerPawnValue.VelocityModifier = speed;
}
/// <summary>
/// Sets the gravity scale for the player controller.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="gravity">The gravity scale.</param>
public static void SetGravity(this CCSPlayerController? controller, float gravity)
{
var playerPawnValue = controller?.PlayerPawn.Value;
if (playerPawnValue == null) return;
playerPawnValue.ActualGravityScale = gravity;
playerPawnValue.GravityScale = gravity;
}
/// <summary>
/// Sets the player's in-game money amount.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="money">The amount of money to set.</param>
public static void SetMoney(this CCSPlayerController? controller, int money)
{
var moneyServices = controller?.InGameMoneyServices;
@@ -109,15 +61,10 @@ public static class PlayerExtensions
if (controller != null) Utilities.SetStateChanged(controller, "CCSPlayerController", "m_pInGameMoneyServices");
}
/// <summary>
/// Sets the player's health points.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="health">The health value, default is 100.</param>
public static void SetHp(this CCSPlayerController? controller, int health = 100)
{
if (controller == null) return;
if (health <= 0 || controller.PlayerPawn.Value == null || controller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return;
if ((health <= 0 || !controller.PawnIsAlive || controller.PlayerPawn.Value == null)) return;
controller.PlayerPawn.Value.Health = health;
@@ -129,76 +76,36 @@ public static class PlayerExtensions
Utilities.SetStateChanged(controller.PlayerPawn.Value, "CBaseEntity", "m_iHealth");
}
/// <summary>
/// Buries the player pawn by moving it down by a depth offset.
/// </summary>
/// <param name="pawn">The player pawn to bury.</param>
/// <param name="depth">The depth offset (default 10 units).</param>
public static void Bury(this CBasePlayerPawn pawn, float depth = 10f)
{
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
var newPos = new Vector(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
pawn.AbsOrigin!.Z - depth);
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
pawn.Teleport(newPos, newRotation, newVelocity);
pawn.Teleport(newPos, pawn.AbsRotation!, pawn.AbsVelocity);
}
/// <summary>
/// Unburies the player pawn by moving it up by a depth offset.
/// </summary>
/// <param name="pawn">The player pawn to unbury.</param>
/// <param name="depth">The depth offset (default 15 units).</param>
public static void Unbury(this CBasePlayerPawn pawn, float depth = 15f)
{
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
var newPos = new Vector(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
pawn.AbsOrigin!.Z + depth);
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
pawn.Teleport(newPos, newRotation, newVelocity);
pawn.Teleport(newPos, pawn.AbsRotation!, pawn.AbsVelocity);
}
/// <summary>
/// Freezes the player pawn, disabling movement.
/// </summary>
/// <param name="pawn">The player pawn to freeze.</param>
public static void Freeze(this CBasePlayerPawn pawn)
{
pawn.MoveType = MoveType_t.MOVETYPE_INVALID;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 11); // invalid
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
/// <summary>
/// Unfreezes the player pawn, enabling movement.
/// </summary>
/// <param name="pawn">The player pawn to unfreeze.</param>
public static void Unfreeze(this CBasePlayerPawn pawn)
{
pawn.MoveType = MoveType_t.MOVETYPE_WALK;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
/// <summary>
/// Changes the player's color tint to specified RGBA values.
/// </summary>
/// <param name="pawn">The pawn to colorize.</param>
/// <param name="r">Red component (0-255).</param>
/// <param name="g">Green component (0-255).</param>
/// <param name="b">Blue component (0-255).</param>
/// <param name="a">Alpha (transparency) component (0-255).</param>
public static void Colorize(this CBasePlayerPawn pawn, int r = 255, int g = 255, int b = 255, int a = 255)
{
pawn.Render = Color.FromArgb(a, r, g, b);
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
}
/// <summary>
/// Toggles noclip mode for the player pawn.
/// </summary>
/// <param name="pawn">The player pawn.</param>
public static void ToggleNoclip(this CBasePlayerPawn pawn)
{
if (pawn.MoveType == MoveType_t.MOVETYPE_NOCLIP)
@@ -215,11 +122,6 @@ public static class PlayerExtensions
}
}
/// <summary>
/// Renames the player controller to a new name, with fallback to a localized "Unknown".
/// </summary>
/// <param name="controller">The player controller to rename.</param>
/// <param name="newName">The new name to assign.</param>
public static void Rename(this CCSPlayerController? controller, string newName = "Unknown")
{
newName ??= CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
@@ -247,11 +149,6 @@ public static class PlayerExtensions
});
}
/// <summary>
/// Teleports a player controller to the position, rotation, and velocity of another player controller.
/// </summary>
/// <param name="controller">The controller to teleport.</param>
/// <param name="target">The target controller whose position to copy.</param>
public static void TeleportPlayer(this CCSPlayerController? controller, CCSPlayerController? target)
{
if (controller?.PlayerPawn.Value == null && target?.PlayerPawn.Value == null)
@@ -270,11 +167,6 @@ public static class PlayerExtensions
}
}
/// <summary>
/// Applies a slap effect to the given player pawn, optionally inflicting damage and adding velocity knockback.
/// </summary>
/// <param name="pawn">The player pawn to slap.</param>
/// <param name="damage">The amount of damage to deal (default is 0).</param>
private static void PerformSlap(CBasePlayerPawn pawn, int damage = 0)
{
if (pawn.LifeState != (int)LifeState_t.LIFE_ALIVE)
@@ -285,7 +177,7 @@ public static class PlayerExtensions
/* Teleport in a random direction - thank you, Mani!*/
/* Thank you AM & al!*/
var random = new Random();
var vel = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
var vel = new Vector(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
vel.X += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
vel.Y += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
@@ -316,16 +208,6 @@ public static class PlayerExtensions
pawn.CommitSuicide(true, true);
}
/// <summary>
/// Sends a localized chat message to the player controller.
/// The message is retrieved from the specified localizer using the given message key and optional formatting arguments.
/// Each line of the message is prefixed with a localized prefix string.
/// The message respects the player's configured language for proper localization.
/// </summary>
/// <param name="controller">The target player controller to receive the message.</param>
/// <param name="localizer">The string localizer used for localization.</param>
/// <param name="messageKey">The key identifying the localized message.</param>
/// <param name="messageArgs">Optional arguments to format the localized message.</param>
public static void SendLocalizedMessage(this CCSPlayerController? controller, IStringLocalizer? localizer,
string messageKey, params object[] messageArgs)
{
@@ -344,16 +226,6 @@ public static class PlayerExtensions
}
}
/// <summary>
/// Sends a localized chat message to the player controller, centered horizontally on the player's screen.
/// The message is retrieved from the specified localizer using the given message key and optional formatting arguments.
/// Each line of the message is centered and prefixed with a localized prefix string.
/// The message respects the player's configured language for localization.
/// </summary>
/// <param name="controller">The target player controller to receive the message.</param>
/// <param name="localizer">The string localizer used for localization.</param>
/// <param name="messageKey">The key identifying the localized message.</param>
/// <param name="messageArgs">Optional arguments to format the localized message.</param>
public static void SendLocalizedMessageCenter(this CCSPlayerController? controller, IStringLocalizer? localizer,
string messageKey, params object[] messageArgs)
{

View File

@@ -22,7 +22,6 @@ using CounterStrikeSharp.API.Core.Plugin.Host;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CS2_SimpleAdmin.Managers;
using MenuManager;
using ZLinq;
namespace CS2_SimpleAdmin;
@@ -49,34 +48,42 @@ internal static class Helper
public static List<CCSPlayerController> GetPlayerFromName(string name)
{
return GetValidPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase));
return Utilities.GetPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
public static CCSPlayerController? GetPlayerFromSteamid64(ulong steamid)
public static List<CCSPlayerController> GetPlayerFromSteamid64(string steamid)
{
return GetValidPlayers().FirstOrDefault(x => x.SteamID == steamid);
return GetValidPlayers().FindAll(x =>
x.SteamID.ToString().Equals(steamid, StringComparison.OrdinalIgnoreCase)
);
}
public static List<CCSPlayerController> GetPlayerFromIp(string ipAddress)
{
return CS2_SimpleAdmin.CachedPlayers.FindAll(x => x.IpAddress != null && x.IpAddress.Split(":")[0].Equals(ipAddress));
return GetValidPlayers().FindAll(x =>
x.IpAddress != null &&
x.IpAddress.Split(":")[0].Equals(ipAddress)
);
}
public static List<CCSPlayerController> GetValidPlayers()
{
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().ToList();
}
public static List<CCSPlayerController> GetValidPlayersWithBots()
{
return CS2_SimpleAdmin.CachedPlayers.Concat(CS2_SimpleAdmin.BotPlayers).AsValueEnumerable().ToList();
return Utilities.GetPlayers().FindAll(p => p is
{ IsValid: true, IsBot: false, Connected: PlayerConnectedState.PlayerConnected });
}
// public static bool IsValidSteamId64(string input)
// {
// const string pattern = @"^\d{17}$";
// return Regex.IsMatch(input, pattern);
// }
public static IEnumerable<CCSPlayerController?> GetValidPlayersWithBots()
{
return Utilities.GetPlayers().FindAll(p =>
p is { IsValid: true, IsBot: false, IsHLTV: false } or { IsValid: true, IsBot: true, IsHLTV: false }
);
}
public static bool IsValidSteamId64(string input)
{
const string pattern = @"^\d{17}$";
return Regex.IsMatch(input, pattern);
}
public static bool ValidateSteamId(string input, out SteamID? steamId)
{
@@ -86,7 +93,7 @@ internal static class Helper
{
return false;
}
if (!SteamID.TryParse(input, out var parsedSteamId)) return false;
steamId = parsedSteamId;
@@ -130,67 +137,14 @@ internal static class Helper
}
}
public static void KickPlayer(int userId, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0)
public static void KickPlayer(int userId, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED)
{
var player = Utilities.GetPlayerFromUserid(userId);
if (player == null || !player.IsValid || player.IsHLTV)
return;
if (player.UserId.HasValue && CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
value.WaitingForKick = true;
// player.CommitSuicide(true, true);
player.VoiceFlags = VoiceFlags.Muted;
var playerPawn = player.PlayerPawn.Value;
if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
playerPawn.Freeze();
playerPawn.Colorize(255, 0, 0);
var weaponServices = playerPawn.WeaponServices;
if (weaponServices == null)
return;
foreach (var _weap in weaponServices.MyWeapons)
{
var weapon = _weap.Value;;
if (weapon == null || !weapon.IsValid)
continue;
if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot"))
continue;
weapon.NextPrimaryAttackTick = Server.TickCount + 999;
weapon.NextSecondaryAttackTick = Server.TickCount + 999;
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick");
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick");
}
}
if (delay > 0)
{
CS2_SimpleAdmin.Instance.AddTimer(delay, () =>
{
if (!player.IsValid || player.IsHLTV)
return;
// Server.ExecuteCommand($"kickid {player.UserId}");
playerPawn?.Colorize();
player.Disconnect(reason);
});
}
else
{
// Server.ExecuteCommand($"kickid {player.UserId}");
playerPawn?.Colorize();
player.Disconnect(reason);
}
if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED)
Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");
player.Disconnect(reason);
// if (!string.IsNullOrEmpty(reason))
// {
@@ -204,141 +158,6 @@ internal static class Helper
//
// Server.ExecuteCommand($"kickid {userId} {reason}");
}
public static void KickPlayer(CCSPlayerController player, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0)
{
if (!player.IsValid || player.IsHLTV)
return;
if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
{
if (value.WaitingForKick)
return;
value.WaitingForKick = true;
}
player.VoiceFlags = VoiceFlags.Muted;
var playerPawn = player.PlayerPawn.Value;
if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
playerPawn.Freeze();
playerPawn.Colorize(255, 0, 0);
var weaponServices = playerPawn.WeaponServices;
if (weaponServices == null)
return;
foreach (var _weap in weaponServices.MyWeapons)
{
var weapon = _weap.Value;
;
if (weapon == null || !weapon.IsValid)
continue;
if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot"))
continue;
weapon.NextPrimaryAttackTick = Server.TickCount + 999;
weapon.NextSecondaryAttackTick = Server.TickCount + 999;
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick");
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick");
}
}
if (delay > 0)
{
CS2_SimpleAdmin.Instance.AddTimer(delay, () =>
{
if (!player.IsValid || player.IsHLTV)
return;
// if (!string.IsNullOrEmpty(reason))
// {
// var escapeChars = reason.IndexOfAny([';', '|']);
//
// if (escapeChars != -1)
// {
// reason = reason[..escapeChars];
// }
// }
//
// Server.ExecuteCommand($"kickid {player.UserId}");
player.Disconnect(reason);
});
}
else
{
// Server.ExecuteCommand($"kickid {player.UserId}");
player.Disconnect(reason);
}
if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED)
Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");
// if (!string.IsNullOrEmpty(reason))
// {
// var escapeChars = reason.IndexOfAny([';', '|']);
//
// if (escapeChars != -1)
// {
// reason = reason[..escapeChars];
// }
// }
//
// Server.ExecuteCommand($"kickid {userId} {reason}");
}
public static int ParsePenaltyTime(string time)
{
time = time.ToLower();
if (string.IsNullOrWhiteSpace(time) || !time.Any(char.IsDigit))
{
// CS2_SimpleAdmin._logger?.LogError("Time string cannot be null or empty.");
return -1;
}
if (time.Equals($"0"))
return 0;
var timeUnits = new Dictionary<string, int>
{
{ "m", 1 }, // Minute
{ "h", 60 }, // Hour
{ "d", 1440 }, // Day (24 * 60)
{ "w", 10080 }, // Week (7 * 24 * 60)
{ "mo", 43200 }, // Month (30 * 24 * 60)
{ "y", 525600 } // Year (365 * 24 * 60)
};
// Check if the input is purely numeric (e.g., "10" for 10 minutes)
if (int.TryParse(time, out var numericMinutes))
{
return numericMinutes;
}
int totalMinutes = 0;
var regex = new Regex(@"(\d+)([a-z]+)");
var matches = regex.Matches(time);
foreach (Match match in matches)
{
var value = int.Parse(match.Groups[1].Value); // Numeric part
var unit = match.Groups[2].Value; // Unit part
if (timeUnits.TryGetValue(unit, out var minutesPerUnit))
{
totalMinutes += value * minutesPerUnit;
}
else
{
throw new ArgumentException($"Invalid time unit '{unit}' in time string.", nameof(time));
}
}
return totalMinutes > 0 ? totalMinutes : -1;
}
public static void PrintToCenterAll(string message)
{
@@ -363,6 +182,7 @@ internal static class Helper
return;
var playerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer["sa_console"];
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer["sa_unknown"];
CS2_SimpleAdmin.Instance.Logger.LogInformation($"{CS2_SimpleAdmin._localizer[
@@ -438,23 +258,15 @@ internal static class Helper
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command]));
}
public static void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
public static void ShowAdminActivity(string messageKey, string? callerName = null, params object[] messageArgs)
{
string[] publishActions = ["ban", "gag", "silence", "mute"];
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
if (CS2_SimpleAdmin._localizer == null) return;
if (string.IsNullOrWhiteSpace(callerName))
callerName = CS2_SimpleAdmin._localizer["sa_console"];
// Determine the localized message key
var localizedMessageKey = $"{messageKey}";
var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();
if (dontPublish == false && publishActions.Any(messageKey.Contains))
{
CS2_SimpleAdmin.SimpleAdminApi?.OnAdminShowActivityEvent(messageKey, callerName, dontPublish, messageArgs);
}
// // Replace placeholder based on showActivityType
// for (var i = 0; i < formattedMessageArgs.Length; i++)
// {
@@ -467,19 +279,8 @@ internal static class Helper
// _ => arg
// };
// }
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
if (!validPlayers.Any())
return;
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
{
validPlayers = validPlayers.Where(c =>
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
}
foreach (var controller in validPlayers.ToList())
foreach (var controller in GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false }))
{
var currentMessageArgs = (string[])formattedMessageArgs.Clone();
@@ -489,13 +290,14 @@ internal static class Helper
var arg = currentMessageArgs[i];
currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
{
1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
_ => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(controller, "@css/kick") || AdminManager.PlayerHasPermissions(controller, "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
2 => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
_ => arg
};
}
// Send the localized message to each player
controller.SendLocalizedMessage(CS2_SimpleAdmin._localizer, messageKey, currentMessageArgs.Cast<object>().ToArray());
controller.SendLocalizedMessage(CS2_SimpleAdmin._localizer, localizedMessageKey, currentMessageArgs.Cast<object>().ToArray());
}
}
@@ -507,20 +309,25 @@ internal static class Helper
{
if (CS2_SimpleAdmin._localizer == null) return;
// Determine the localized message key
var localizedMessageKey = $"{messageKey}";
var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();
// Replace placeholder based on showActivityType
for (var i = 0; i < formattedMessageArgs.Length; i++)
{
var arg = formattedMessageArgs[i];
var arg = formattedMessageArgs[i]; // Convert argument to string if not null
// Replace "CALLER" placeholder in the argument string
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
{
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
2 => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
_ => arg
};
}
// Print the localized message to the center of the screen for the player
using (new WithTemporaryCulture(player.GetLanguage()))
{
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
@@ -537,7 +344,7 @@ internal static class Helper
public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, CCSPlayerController? target, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer)
{
if (localizer == null) return;
var penaltySetting = penalty switch
{
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings,
@@ -551,7 +358,7 @@ internal static class Helper
var webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value;
if (string.IsNullOrEmpty(webhookUrl)) return;
const string defaultCommunityUrl = "<https://steamcommunity.com/profiles/0>";
var callerCommunityUrl = caller != null ? $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
var targetCommunityUrl = target != null ? $"<{new SteamID(target.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
@@ -584,8 +391,7 @@ internal static class Helper
bool[] inlineFlags = [true, true, true, false, false];
var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"];
var colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value;
var colorHex = string.IsNullOrWhiteSpace(colorValue) ? "#FFFFFF" : colorValue.Trim();
var colorHex = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value ?? "#FFFFFF";
var embed = new Embed
{
@@ -624,104 +430,11 @@ internal static class Helper
catch (Exception ex)
{
// Log or handle the exception
CS2_SimpleAdmin._logger?.LogError("Unable to send discord webhook: {exception}", ex.Message);
}
});
}
public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, string steamId, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer)
{
if (localizer == null) return;
var penaltySetting = penalty switch
{
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings,
PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyMuteSettings,
PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyGagSettings,
PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltySilenceSettings,
PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyWarnSettings,
_ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
};
var webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value;
if (string.IsNullOrEmpty(webhookUrl)) return;
const string defaultCommunityUrl = "<https://steamcommunity.com/profiles/0>";
var callerCommunityUrl = caller != null ? $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
var targetCommunityUrl = $"<{new SteamID(ulong.Parse(steamId)).ToCommunityUrl()}>";
var callerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
var targetName = steamId;
var targetSteamId = steamId;
var futureTime = Time.ActualDateTime().AddMinutes(duration);
var futureUnixTimestamp = new DateTimeOffset(futureTime).ToUnixTimeSeconds();
string time;
if (penaltySetting.FirstOrDefault(s => s.Name.Equals("Time"))?.Value == "{relative}")
time = duration != 0 ? $"<t:{futureUnixTimestamp}:R>" : localizer["sa_permanent"];
else
time = duration != 0 ? ConvertMinutesToTime(duration) : localizer["sa_permanent"];
string[] fieldNames = [
localizer["sa_player"],
localizer["sa_steamid"],
localizer["sa_duration"],
localizer["sa_reason"],
localizer["sa_admin"]];
string[] fieldValues =
[
$"[{targetName}]({targetCommunityUrl})", $"||{targetSteamId}||", time, reason,
$"[{callerName}]({callerCommunityUrl})"
];
bool[] inlineFlags = [true, true, true, false, false];
var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"];
var colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value;
var colorHex = string.IsNullOrWhiteSpace(colorValue) ? "#FFFFFF" : colorValue.Trim();
var embed = new Embed
{
Color = DiscordManager.ColorFromHex(colorHex),
Title = penalty switch
{
PenaltyType.Ban => localizer["sa_discord_penalty_ban"],
PenaltyType.Mute => localizer["sa_discord_penalty_mute"],
PenaltyType.Gag => localizer["sa_discord_penalty_gag"],
PenaltyType.Silence => localizer["sa_discord_penalty_silence"],
PenaltyType.Warn => localizer["sa_discord_penalty_warn"],
_ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
},
Description = $"{hostname}",
ThumbnailUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ThumbnailUrl"))?.Value,
ImageUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ImageUrl"))?.Value,
Footer = new Footer
{
Text = penaltySetting.FirstOrDefault(s => s.Name.Equals("Footer"))?.Value
},
Timestamp = Time.ActualDateTime().ToUniversalTime().ToString("o"),
};
for (var i = 0; i < fieldNames.Length; i++)
{
embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]);
}
Task.Run(async () =>
{
try
{
await new DiscordManager(webhookUrl).SendEmbedAsync(embed);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
});
}
private static string GenerateMessageDiscord(string message)
{
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
@@ -806,28 +519,28 @@ internal static class Helper
commandString]));
}
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
public static IMenu? CreateMenu(string title)
{
var menuType = CS2_SimpleAdmin.Instance.Config.MenuConfigs.MenuType.ToLower();
var menu = menuType switch
{
_ when menuType.Equals("selectable", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi?.GetMenu(title),
CS2_SimpleAdmin.MenuApi?.NewMenu(title),
_ when menuType.Equals("dynamic", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ButtonMenu),
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.ButtonMenu),
_ when menuType.Equals("center", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.CenterMenu),
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.CenterMenu),
_ when menuType.Equals("chat", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ChatMenu),
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.ChatMenu),
_ when menuType.Equals("console", StringComparison.CurrentCultureIgnoreCase) =>
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ConsoleMenu),
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.ConsoleMenu),
_ => CS2_SimpleAdmin.MenuApi?.GetMenu(title)
_ => CS2_SimpleAdmin.MenuApi?.NewMenu(title)
};
return menu;
@@ -844,18 +557,15 @@ internal static class Helper
return pluginManager;
}
}
public static class PluginInfo
{
internal static async Task CheckVersion(string localVersion, ILogger logger)
{
ShowAd(localVersion);
const string versionUrl = "https://raw.githubusercontent.com/daffyyyy/CS2-SimpleAdmin/main/CS2-SimpleAdmin/VERSION";
var client = CS2_SimpleAdmin.HttpClient;
client.Timeout = TimeSpan.FromSeconds(3);
try
{
var response = await client.GetAsync(versionUrl);
@@ -894,8 +604,8 @@ public static class PluginInfo
logger.LogError(ex, "An error occurred while checking version.");
}
}
private static void ShowAd(string moduleVersion)
internal static void ShowAd(string moduleVersion)
{
Console.WriteLine(" ");
Console.WriteLine(" _______ ___ __ __ _______ ___ _______ _______ ______ __ __ ___ __ _ ");
@@ -1020,48 +730,3 @@ public static class WeaponHelper
return filteredWeapons; // Return all relevant matches for the partial input
}
}
public static class IpHelper
{
public static uint IpToUint(string ipAddress)
{
if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP address cannot be null or empty.", nameof(ipAddress));
if (!System.Net.IPAddress.TryParse(ipAddress, out var ip))
throw new FormatException($"Invalid IP address format: {ipAddress}");
// Ensure it's IPv4 (IPv6 will throw)
if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
throw new FormatException("Only IPv4 addresses are supported.");
byte[] bytes = ip.GetAddressBytes();
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes); // Ensure big-endian (network order)
return BitConverter.ToUInt32(bytes, 0);
}
public static bool TryConvertIpToUint(string ipString, out uint ipUint)
{
ipUint = 0;
if (string.IsNullOrWhiteSpace(ipString))
return false;
if (!System.Net.IPAddress.TryParse(ipString, out var ipAddress))
return false;
var bytes = ipAddress.GetAddressBytes();
if (bytes.Length != 4)
return false;
ipUint = IpToUint(ipString);
return true;
}
public static string UintToIp(uint ipAddress)
{
var bytes = BitConverter.GetBytes(ipAddress).Reverse().ToArray();
return new System.Net.IPAddress(bytes).ToString();
}
}

View File

@@ -5,119 +5,31 @@ using Dapper;
using Microsoft.Extensions.Logging;
using MySqlConnector;
using System.Text;
using CS2_SimpleAdmin.Database;
namespace CS2_SimpleAdmin.Managers;
internal class BanManager(IDatabaseProvider? databaseProvider)
internal class BanManager(Database.Database? database)
{
/// <summary>
/// Bans an online player and inserts the ban record into the database.
/// </summary>
/// <param name="player">The player to be banned (must be currently online).</param>
/// <param name="issuer">The admin issuing the ban. Can be null if issued from console.</param>
/// <param name="reason">The reason for the ban.</param>
/// <param name="time">Ban duration in minutes. If 0, the ban is permanent.</param>
/// <returns>The newly created ban ID if successful, otherwise null.</returns>
public async Task<int?> BanPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
public async Task BanPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
if (database == null) return;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
await using var connection = await databaseProvider.CreateConnectionAsync();
await using MySqlConnection connection = await database.GetConnectionAsync();
try
{
var sql = databaseProvider.GetAddBanQuery();
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = player.SteamId.SteamId64,
playerName = player.Name,
playerIp = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 1 ? player.IpAddress : null,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
const string sql =
"INSERT INTO `sa_bans` (`player_steamid`, `player_name`, `player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " +
"VALUES (@playerSteamid, @playerName, @playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid)";
return banId;
}
catch(Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
return null;
}
}
/// <summary>
/// Adds a ban for an offline player identified by their SteamID.
/// </summary>
/// <param name="playerSteamId">The SteamID64 of the player to ban.</param>
/// <param name="issuer">The admin issuing the ban. Can be null if issued from console.</param>
/// <param name="reason">The reason for the ban.</param>
/// <param name="time">Ban duration in minutes. If 0, the ban is permanent.</param>
/// <returns>The ID of the newly created ban if successful, otherwise null.</returns>
public async Task<int?> AddBanBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddBanBySteamIdQuery();
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = playerSteamId,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
return banId;
}
catch(Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
return null;
}
}
/// <summary>
/// Adds a ban for an offline player identified by their IP address.
/// </summary>
/// <param name="playerIp">The IP address of the player to ban.</param>
/// <param name="issuer">The admin issuing the ban. Can be null if issued from console.</param>
/// <param name="reason">The reason for the ban.</param>
/// <param name="time">Ban duration in minutes. If 0, the ban is permanent.</param>
public async Task AddBanByIp(string playerIp, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(playerIp)) return;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddBanByIpQuery();
await connection.ExecuteAsync(sql, new
{
playerIp,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
playerSteamid = player.SteamId.SteamId64.ToString(),
playerName = player.Name,
playerIp = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 1 ? player.IpAddress : null,
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
@@ -129,308 +41,398 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
catch { }
}
// public async Task<bool> IsPlayerBanned(PlayerInfo player)
// {
// if (database == null) return false;
//
// if (player.IpAddress == null)
// {
// return false;
// }
//
// #if DEBUG
// if (CS2_SimpleAdmin._logger != null)
// CS2_SimpleAdmin._logger.LogCritical($"IsPlayerBanned for {player.Name}");
// #endif
//
// int banCount;
//
// DateTime currentTime = Time.ActualDateTime();
//
// try
// {
// string sql;
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp && !CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress))
// {
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
// SELECT COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// ), 0)
// +
// COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
// WHERE sa_bans.status = 'ACTIVE'
// AND sa_players_ips.address = @PlayerIP
// AND NOT EXISTS (
// SELECT 1
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// )
// ), 0) AS TotalBanCount;
// """ : """
// SELECT COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// AND server_id = @ServerId
// ), 0)
// +
// COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
// WHERE sa_bans.status = 'ACTIVE'
// AND sa_players_ips.address = @PlayerIP
// AND NOT EXISTS (
// SELECT 1
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// AND server_id = @ServerId
// )
// ), 0) AS TotalBanCount;
// """;
// }
// else
// {
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
// UPDATE sa_bans
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime);
//
// SELECT COUNT(*) FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime);
// """ : """
// UPDATE sa_bans
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
//
// SELECT COUNT(*) FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
// """;
// }
//
// await using var connection = await database.GetConnectionAsync();
//
// var parameters = new
// {
// PlayerSteamID = player.SteamId.SteamId64,
// PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 ||
// string.IsNullOrEmpty(player.IpAddress) ||
// CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress)
// ? null
// : player.IpAddress,
// PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty,
// CurrentTime = currentTime,
// CS2_SimpleAdmin.ServerId
// };
//
// banCount = await connection.ExecuteScalarAsync<int>(sql, parameters);
// }
// catch (Exception ex)
// {
// CS2_SimpleAdmin._logger?.LogError("Unable to check ban status for {PlayerName} ({ExceptionMessage})",
// player.Name, ex.Message);
// return false;
// }
//
// return banCount > 0;
// }
//
// public async Task<int> GetPlayerBans(PlayerInfo player)
// {
// if (database == null) return 0;
//
// try
// {
// string sql;
//
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
// ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)"
// : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid";
//
// int banCount;
//
// await using var connection = await database.GetConnectionAsync();
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0 && !string.IsNullOrEmpty(player.IpAddress))
// {
// banCount = await connection.ExecuteScalarAsync<int>(sql,
// new
// {
// PlayerSteamID = player.SteamId.SteamId64,
// PlayerIP = player.IpAddress,
// serverid = CS2_SimpleAdmin.ServerId
// });
// }
// else
// {
// banCount = await connection.ExecuteScalarAsync<int>(sql,
// new
// {
// PlayerSteamID = player.SteamId.SteamId64,
// PlayerIP = DBNull.Value,
// serverid = CS2_SimpleAdmin.ServerId
// });
// }
//
// return banCount;
// }
// catch { }
//
// return 0;
// }
/// <summary>
/// Unbans a player based on a pattern match of SteamID or IP address.
/// </summary>
/// <param name="playerPattern">Pattern to match against player identifiers (e.g., partial SteamID).</param>
/// <param name="adminSteamId">SteamID64 of the admin performing the unban.</param>
/// <param name="reason">Optional reason for the unban. If null or empty, the unban reason is not stored.</param>
public async Task UnbanPlayer(string playerPattern, string adminSteamId, string reason)
{
if (databaseProvider == null) return;
if (playerPattern is not { Length: > 1 })
public async Task AddBanBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
{
return;
}
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sqlRetrieveBans = databaseProvider.GetUnbanRetrieveBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
if (database == null) return;
var bans = await connection.QueryAsync(sqlRetrieveBans, new { pattern = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
var bansList = bans as dynamic[] ?? bans.ToArray();
if (bansList.Length == 0)
return;
if (string.IsNullOrEmpty(playerSteamId)) return;
var sqlAdminId = databaseProvider.GetUnbanAdminIdQuery();
var adminId = await connection.ExecuteScalarAsync<int?>(sqlAdminId, new { adminSteamId }) ?? 0;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
foreach (var ban in bansList)
try
{
int banId = ban.id;
await using MySqlConnection connection = await database.GetConnectionAsync();
var sqlInsertUnban = databaseProvider.GetInsertUnbanQuery(reason != null);
var unbanId = await connection.ExecuteScalarAsync<int>(sqlInsertUnban, new { banId, adminId, reason });
var sql = "INSERT INTO `sa_bans` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " +
"VALUES (@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid)";
var sqlUpdateBan = databaseProvider.GetUpdateBanStatusQuery();
await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId });
await connection.ExecuteAsync(sql, new
{
playerSteamid = playerSteamId,
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
}
catch { }
}
public async Task AddBanByIp(string playerIp, PlayerInfo? issuer, string reason, int time = 0)
{
if (database == null) return;
if (string.IsNullOrEmpty(playerIp)) return;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
try
{
await using MySqlConnection connection = await database.GetConnectionAsync();
var sql = "INSERT INTO `sa_bans` (`player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " +
"VALUES (@playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid)";
await connection.ExecuteAsync(sql, new
{
playerIp,
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
}
catch { }
}
public async Task<bool> IsPlayerBanned(PlayerInfo player)
{
if (database == null) return false;
if (player.IpAddress == null)
{
return false;
}
#if DEBUG
if (CS2_SimpleAdmin._logger != null)
CS2_SimpleAdmin._logger.LogCritical($"IsPlayerBanned for {player.Name}");
#endif
int banCount;
DateTime currentTime = Time.ActualDateTime();
try
{
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
SELECT COALESCE((
SELECT COUNT(*)
FROM sa_bans
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
), 0)
+
COALESCE((
SELECT COUNT(*)
FROM sa_bans
JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
WHERE sa_bans.status = 'ACTIVE'
AND sa_players_ips.address = @PlayerIP
AND NOT EXISTS (
SELECT 1
FROM sa_bans
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
)
), 0) AS TotalBanCount;
""" : """
SELECT COALESCE((
SELECT COUNT(*)
FROM sa_bans
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
AND server_id = @ServerId
), 0)
+
COALESCE((
SELECT COUNT(*)
FROM sa_bans
JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
WHERE sa_bans.status = 'ACTIVE'
AND sa_players_ips.address = @PlayerIP
AND NOT EXISTS (
SELECT 1
FROM sa_bans
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
AND server_id = @ServerId
)
), 0) AS TotalBanCount;
""";
await using var connection = await database.GetConnectionAsync();
var parameters = new
{
PlayerSteamID = player.SteamId.SteamId64.ToString(),
PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 ||
string.IsNullOrEmpty(player.IpAddress)
? null
: player.IpAddress,
PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty,
CurrentTime = currentTime,
CS2_SimpleAdmin.ServerId
};
banCount = await connection.ExecuteScalarAsync<int>(sql, parameters);
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Unable to check ban status for {PlayerName} ({ExceptionMessage})",
player.Name, ex.Message);
return false;
}
return banCount > 0;
}
public async Task<int> GetPlayerBans(PlayerInfo player)
{
if (database == null) return 0;
try
{
string sql;
sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)"
: "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid";
int banCount;
await using var connection = await database.GetConnectionAsync();
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0 && !string.IsNullOrEmpty(player.IpAddress))
{
banCount = await connection.ExecuteScalarAsync<int>(sql,
new
{
PlayerSteamID = player.SteamId.SteamId64.ToString(),
PlayerIP = player.IpAddress,
serverid = CS2_SimpleAdmin.ServerId
});
}
else
{
banCount = await connection.ExecuteScalarAsync<int>(sql,
new
{
PlayerSteamID = player.SteamId.SteamId64.ToString(),
PlayerIP = DBNull.Value,
serverid = CS2_SimpleAdmin.ServerId
});
}
return banCount;
}
catch { }
return 0;
}
public async Task UnbanPlayer(string playerPattern, string adminSteamId, string reason)
{
if (database == null) return;
if (playerPattern is not { Length: > 1 })
{
return;
}
try
{
await using var connection = await database.GetConnectionAsync();
var sqlRetrieveBans = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'"
: "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid";
var bans = await connection.QueryAsync(sqlRetrieveBans, new { pattern = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
var bansList = bans as dynamic[] ?? bans.ToArray();
if (bansList.Length == 0)
return;
const string sqlAdmin = "SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
var sqlInsertUnban = "INSERT INTO sa_unbans (ban_id, admin_id, reason) VALUES (@banId, @adminId, @reason); SELECT LAST_INSERT_ID();";
var sqlAdminId = await connection.ExecuteScalarAsync<int?>(sqlAdmin, new { adminSteamId });
var adminId = sqlAdminId ?? 0;
foreach (var ban in bansList)
{
int banId = ban.id;
int? unbanId;
if (reason != null)
{
unbanId = await connection.ExecuteScalarAsync<int>(sqlInsertUnban, new { banId, adminId, reason });
}
else
{
sqlInsertUnban = "INSERT INTO sa_unbans (ban_id, admin_id) VALUES (@banId, @adminId); SELECT LAST_INSERT_ID();";
unbanId = await connection.ExecuteScalarAsync<int>(sqlInsertUnban, new { banId, adminId });
}
const string sqlUpdateBan = "UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId";
await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId });
}
}
catch { }
}
public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players)
{
if (database == null) return;
try
{
await using var connection = await database.GetConnectionAsync();
bool checkIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0;
var filteredPlayers = players.Where(p => p.UserId.HasValue).ToList();
var steamIds = filteredPlayers.Select(p => p.SteamID).Distinct().ToList();
var ipAddresses = filteredPlayers
.Where(p => !string.IsNullOrEmpty(p.IpAddress))
.Select(p => p.IpAddress)
.Distinct()
.ToList();
var sql = new StringBuilder();
sql.Append("SELECT `player_steamid`, `player_ip` FROM `sa_bans` WHERE `status` = 'ACTIVE' ");
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
sql.Append("AND (player_steamid IN @SteamIDs ");
if (checkIpBans && ipAddresses.Count != 0)
{
sql.Append("OR player_ip IN @IpAddresses");
}
sql.Append(')');
}
else
{
sql.Append("AND server_id = @ServerId AND (player_steamid IN @SteamIDs ");
if (checkIpBans && ipAddresses.Count != 0)
{
sql.Append("OR player_ip IN @IpAddresses");
}
sql.Append(')');
}
var bannedPlayers = await connection.QueryAsync<(ulong PlayerSteamID, string PlayerIP)>(
sql.ToString(),
new
{
SteamIDs = steamIds,
IpAddresses = checkIpBans ? ipAddresses : [],
ServerId = CS2_SimpleAdmin.ServerId
});
var valueTuples = bannedPlayers.ToList();
var bannedSteamIds = valueTuples.Select(b => b.PlayerSteamID).ToHashSet();
var bannedIps = valueTuples.Select(b => b.PlayerIP).ToHashSet();
foreach (var player in filteredPlayers.Where(player => bannedSteamIds.Contains(player.SteamID) ||
(checkIpBans && bannedIps.Contains(player.IpAddress ?? ""))))
{
if (!player.UserId.HasValue) continue;
await Server.NextFrameAsync(() =>
{
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED);
});
}
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}");
}
}
catch { }
}
// public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players)
// {
// if (database == null) return;
//
// try
// {
// await using var connection = await database.GetConnectionAsync();
// bool checkIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0;
//
// var filteredPlayers = players.Where(p => p.UserId.HasValue).ToList();
//
// var steamIds = filteredPlayers.Select(p => p.SteamID).Distinct().ToList();
// var ipAddresses = filteredPlayers
// .Where(p => !string.IsNullOrEmpty(p.IpAddress))
// .Select(p => p.IpAddress)
// .Distinct()
// .ToList();
//
// var sql = new StringBuilder();
// sql.Append("SELECT `player_steamid`, `player_ip` FROM `sa_bans` WHERE `status` = 'ACTIVE' ");
//
// if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
// {
// sql.Append("AND (player_steamid IN @SteamIDs ");
// if (checkIpBans && ipAddresses.Count != 0)
// {
// sql.Append("OR player_ip IN @IpAddresses");
// }
// sql.Append(')');
// }
// else
// {
// sql.Append("AND server_id = @ServerId AND (player_steamid IN @SteamIDs ");
// if (checkIpBans && ipAddresses.Count != 0)
// {
// sql.Append("OR player_ip IN @IpAddresses");
// }
// sql.Append(')');
// }
//
// var bannedPlayers = await connection.QueryAsync<(ulong PlayerSteamID, string PlayerIP)>(
// sql.ToString(),
// new
// {
// SteamIDs = steamIds,
// IpAddresses = checkIpBans ? ipAddresses : [],
// CS2_SimpleAdmin.ServerId
// });
//
// var valueTuples = bannedPlayers.ToList();
// var bannedSteamIds = valueTuples.Select(b => b.PlayerSteamID).ToHashSet();
// var bannedIps = valueTuples.Select(b => b.PlayerIP).ToHashSet();
//
// foreach (var player in filteredPlayers.Where(player => bannedSteamIds.Contains(player.SteamID) ||
// (checkIpBans && bannedIps.Contains(player.IpAddress ?? ""))))
// {
// if (!player.UserId.HasValue || CS2_SimpleAdmin.PlayersInfo[player.SteamID].WaitingForKick) continue;
//
// await Server.NextWorldUpdateAsync(() =>
// {
// Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED);
// });
// }
// }
// catch (Exception ex)
// {
// CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}");
// }
// }
/// <summary>
/// Expires all bans that have passed their end time, including optional cleanup of old IP bans.
/// </summary>
public async Task ExpireOldBans()
{
if (databaseProvider == null) return;
if (database == null) return;
var currentTime = Time.ActualDateTime();
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetExpireBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await using var connection = await database.GetConnectionAsync();
/*
string sql = "";
await using MySqlConnection connection = await _database.GetConnectionAsync();
sql = "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime";
await connection.ExecuteAsync(sql, new { CurrentTime = DateTime.UtcNow });
*/
string sql;
sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
UPDATE sa_bans
SET
status = 'EXPIRED'
WHERE
status = 'ACTIVE'
AND
`duration` > 0
AND
ends <= @currentTime
""" : """
UPDATE sa_bans
SET
status = 'EXPIRED'
WHERE
status = 'ACTIVE'
AND
`duration` > 0
AND
ends <= @currentTime
AND server_id = @serverid
""";
await connection.ExecuteAsync(sql, new { currentTime, serverid = CS2_SimpleAdmin.ServerId });
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans > 0)
{
var ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
UPDATE sa_bans
SET
player_ip = NULL
WHERE
status = 'ACTIVE'
AND
ends <= @ipBansTime
""" : """
UPDATE sa_bans
SET
player_ip = NULL
WHERE
status = 'ACTIVE'
AND
ends <= @ipBansTime
AND server_id = @serverid
""";
await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId });
}
}

View File

@@ -1,686 +0,0 @@
using System.Collections.Concurrent;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Models;
using Dapper;
using ZLinq;
namespace CS2_SimpleAdmin.Managers;
internal class CacheManager: IDisposable
{
private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
private readonly ConcurrentDictionary<ulong, List<BanRecord>> _steamIdIndex = [];
private readonly ConcurrentDictionary<uint, List<BanRecord>> _ipIndex = [];
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
private HashSet<uint> _cachedIgnoredIps = [];
private DateTime _lastUpdateTime = DateTime.MinValue;
private bool _isInitialized;
private bool _disposed;
/// <summary>
/// Initializes and builds the ban and IP cache from the database. Loads bans, player IP history, and config settings.
/// </summary>
/// <returns>Asynchronous task representing the initialization process.</returns>
public async Task InitializeCacheAsync()
{
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
if (!CS2_SimpleAdmin.ServerLoaded) return;
if (_isInitialized) return;
try
{
Clear();
_cachedIgnoredIps = CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps
.AsValueEnumerable()
.Select(IpHelper.IpToUint)
.ToHashSet();
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
List<BanRecord> bans;
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
bans = (await connection.QueryAsync<BanRecord>(
"""
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
""")).ToList();
}
else
{
bans = (await connection.QueryAsync<BanRecord>(
"""
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE server_id = @serverId
""", new {serverId = CS2_SimpleAdmin.ServerId})).ToList();
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
{
var ipHistory =
(await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC")).ToList();
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
{
var ipSet = group
.GroupBy(x => x.address)
.Select(g =>
{
var latest = g.MaxBy(x => x.used_at);
return new IpRecord(
g.Key,
latest.used_at,
string.IsNullOrEmpty(latest.name)
? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
: latest.name
);
})
.ToHashSet(new IpRecordComparer());
_playerIpsCache.AddOrUpdate(
group.Key,
_ => ipSet,
(_, existingSet) =>
{
foreach (var ip in ipSet)
{
existingSet.Remove(ip);
existingSet.Add(ip);
}
return existingSet;
});
}
}
foreach (var ban in bans.AsValueEnumerable())
_banCache.TryAdd(ban.Id, ban);
RebuildIndexes();
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
_isInitialized = true;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
/// <summary>
/// Clears all cached data and reinitializes the cache from the database.
/// </summary>
/// <returns>Asynchronous task representing the reinitialization process.</returns>
public async Task ForceReInitializeCacheAsync()
{
_isInitialized = false;
_banCache.Clear();
_playerIpsCache.Clear();
_cachedIgnoredIps = [];
_lastUpdateTime = DateTime.MinValue;
await InitializeCacheAsync();
}
/// <summary>
/// Refreshes the in-memory cache with updated or new data from the database since the last update time.
/// Also updates multi-account IP history if enabled.
/// </summary>
/// <returns>Asynchronous task representing the refresh operation.</returns>
public async Task RefreshCacheAsync()
{
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
if (!_isInitialized) return;
try
{
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
IEnumerable<BanRecord> updatedBans;
var allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
updatedBans = (await connection.QueryAsync<BanRecord>(
"""
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime }
));
}
else
{
updatedBans = (await connection.QueryAsync<BanRecord>(
"""
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
));
}
foreach (var id in _banCache.Keys)
{
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
if (ban.PlayerSteamId != null &&
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
{
steamBans.RemoveAll(b => b.Id == id);
if (steamBans.Count == 0)
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
}
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
{
ipBans.RemoveAll(b => b.Id == id);
if (ipBans.Count == 0)
_ipIndex.TryRemove(ipUInt, out _);
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
{
var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300",
new { lastUpdate = _lastUpdateTime }));
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
{
var ipSet = new HashSet<IpRecord>(
group
.GroupBy(x => x.address)
.Select(g =>
{
var latest = g.MaxBy(x => x.used_at);
return new IpRecord(
g.Key,
latest.used_at,
!string.IsNullOrEmpty(latest.name)
? latest.name
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
);
}),
new IpRecordComparer()
);
_playerIpsCache.AddOrUpdate(
group.Key,
_ => ipSet,
(_, existingSet) =>
{
foreach (var newEntry in ipSet)
{
existingSet.Remove(newEntry);
existingSet.Add(newEntry);
}
return existingSet;
});
}
}
foreach (var ban in updatedBans)
{
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
}
RebuildIndexes();
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
}
catch (Exception)
{
}
}
/// <summary>
/// Rebuilds the internal indexes for fast lookup of active bans by Steam ID and IP address.
/// Clears and repopulates both indexes based on the current in-memory ban cache.
/// </summary>
private void RebuildIndexes()
{
_steamIdIndex.Clear();
_ipIndex.Clear();
foreach (var ban in _banCache.Values)
{
if (ban.StatusEnum != BanStatus.ACTIVE)
continue;
if (ban.PlayerSteamId != null)
{
var steamId = ban.PlayerSteamId;
_steamIdIndex.AddOrUpdate(
steamId.Value,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) continue;
if (ban.PlayerIp != null &&
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
{
_ipIndex.AddOrUpdate(
ipUInt,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
}
}
/// <summary>
/// Retrieves all ban records currently stored in the cache.
/// </summary>
/// <returns>List of all <see cref="BanRecord"/> objects.</returns>
public List<BanRecord> GetAllBans() => _banCache.Values.ToList();
/// <summary>
/// Retrieves only active ban records from the cache.
/// </summary>
/// <returns>List of active <see cref="BanRecord"/> objects.</returns>
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE).ToList();
/// <summary>
/// Retrieves all ban records for a specific player by their Steam ID.
/// </summary>
/// <param name="steamId">64-bit Steam ID of the player.</param>
/// <returns>List of <see cref="BanRecord"/> objects associated with the Steam ID.</returns>
public List<BanRecord> GetPlayerBansBySteamId(ulong steamId) => _steamIdIndex.TryGetValue(steamId, out var bans) ? bans : [];
/// <summary>
/// Gets all known Steam accounts that have used the specified IP address.
/// </summary>
/// <param name="ipAddress">The IP address to search for, in string format.</param>
/// <returns>
/// List of tuples containing the Steam ID, last used time, and player name for each matching entry.
/// </returns>
public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
{
var ipAsUint = IpHelper.IpToUint(ipAddress);
var results = new List<(ulong, DateTime, string)>();
var comparer = _playerIpsCache.Comparer;
foreach (var (steamId, ipSet) in _playerIpsCache)
{
if (!ipSet.TryGetValue(new IpRecord(ipAsUint, Time.ActualDateTime(), "Unknown"), out var actualEntry)) continue;
results.Add((steamId, actualEntry.UsedAt, actualEntry.PlayerName));
foreach (var entry in ipSet)
{
if (entry.Ip == ipAsUint && !Equals(entry, actualEntry))
{
results.Add((steamId, entry.UsedAt, entry.PlayerName));
}
}
}
return results;
}
// public IEnumerable<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
// {
// var ipAsUint = IpHelper.IpToUint(ipAddress);
//
// return _playerIpsCache.SelectMany(kvp => kvp.Value
// .Where(entry => entry.Ip == ipAsUint)
// .Select(entry => (kvp.Key, entry.UsedAt, entry.PlayerName)));
// }
private bool IsIpBanned(string ipAddress)
{
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
var ipUInt = IpHelper.IpToUint(ipAddress);
return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
}
// public bool IsPlayerBanned(ulong? steamId, string? ipAddress)
// {
// if (steamId != null && _steamIdIndex.ContainsKey(steamId.Value))
// return true;
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
// return false;
//
// if (string.IsNullOrEmpty(ipAddress) || !IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
// return false;
//
// return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
// }
/// <summary>
/// Checks if a player is currently banned by Steam ID or IP address.
/// If a partial ban record is found, updates it with the latest player information.
/// </summary>
/// <param name="playerName">Name of the player attempting to connect.</param>
/// <param name="steamId">Optional 64-bit Steam ID of the player.</param>
/// <param name="ipAddress">Optional IP address of the player.</param>
/// <returns>True if the player is banned, otherwise false.</returns>
public bool IsPlayerBanned(string playerName, ulong? steamId, string? ipAddress)
{
BanRecord? record;
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
{
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
if (record != null)
{
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
(!record.PlayerSteamId.HasValue))
{
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
}
return true;
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
return false;
if (string.IsNullOrEmpty(ipAddress) ||
!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) ||
_cachedIgnoredIps.Contains(ipUInt) ||
!_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false;
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
if (record == null) return false;
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
(!record.PlayerSteamId.HasValue && steamId.HasValue))
{
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
}
return true;
}
// public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress)
// {
// if (_steamIdIndex.ContainsKey(steamId))
// return true;
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
// return false;
//
// if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
// return false;
//
// // var now = Time.ActualDateTime();
// var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
// var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
//
// if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
// {
// if (!_cachedIgnoredIps.Contains(ipAsUint))
// {
// ipData.Add(new IpRecord(
// ipAsUint,
// Time.ActualDateTime().AddSeconds(-2),
// unknownName
// ));
// }
// }
//
// // foreach (var ipRecord in ipData)
// // {
// // // Skip if too old or in ignored list
// // if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
// // continue;
// //
// // // Check if IP is banned
// // if (_ipIndex.ContainsKey(ipRecord.Ip))
// // return true;
// // }
//
// foreach (var ipRecord in ipData)
// {
// if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
// continue;
//
// if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords)) continue;
//
// var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
// if (activeBan == null) continue;
//
// if (!string.IsNullOrEmpty(activeBan.PlayerName) && activeBan.PlayerSteamId.HasValue) return true;
//
// _ = Task.Run(() => UpdatePlayerData(
// activeBan.PlayerName,
// steamId,
// ipAddress
// ));
//
// if (string.IsNullOrEmpty(activeBan.PlayerName) && !string.IsNullOrEmpty(unknownName))
// activeBan.PlayerName = unknownName;
//
// activeBan.PlayerSteamId ??= steamId;
//
// return true;
// }
//
// return false;
// }
/// <summary>
/// Checks if the player or any IP previously associated with them is currently banned.
/// Also updates ban records with missing player info if found.
/// </summary>
/// <param name="playerName">Current player name.</param>
/// <param name="steamId">64-bit Steam ID of the player.</param>
/// <param name="ipAddress">Current IP address of the player (optional).</param>
/// <returns>True if the player or their known IPs are banned, otherwise false.</returns>
public bool IsPlayerOrAnyIpBanned(string playerName, ulong steamId, string? ipAddress)
{
if (_steamIdIndex.TryGetValue(steamId, out var steamBans))
{
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
if (activeBan != null)
{
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp))
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
return true;
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
return false;
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
return false;
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
{
if (!_cachedIgnoredIps.Contains(ipAsUint))
{
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
}
}
foreach (var ipRecord in ipData)
{
if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
continue;
if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords))
continue;
var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
if (activeBan == null)
continue;
if (string.IsNullOrEmpty(activeBan.PlayerName))
activeBan.PlayerName = unknownName;
activeBan.PlayerSteamId ??= steamId;
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
return true;
}
return false;
}
/// <summary>
/// Checks if the given IP address is known (previously recorded) for the specified Steam ID.
/// </summary>
/// <param name="steamId">64-bit Steam ID of the player.</param>
/// <param name="ipAddress">IP address to check.</param>
/// <returns>True if the IP is recorded for the player, otherwise false.</returns>
public bool HasIpForPlayer(ulong steamId, string ipAddress)
{
if (string.IsNullOrWhiteSpace(ipAddress))
return false;
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUint))
return false;
return _playerIpsCache.TryGetValue(steamId, out var ipData) &&
ipData.Contains(new IpRecord(ipUint, default, null!));
}
// public bool HasIpForPlayer(ulong steamId, string ipAddress)
// {
// if (string.IsNullOrWhiteSpace(ipAddress))
// return false;
//
// return _playerIpsCache.TryGetValue(steamId, out var ipData)
// && ipData.Any(x => x.Ip == IpHelper.IpToUint(ipAddress));
// }
/// <summary>
/// Updates existing active ban records in the database with the latest known player name and IP address.
/// Also updates in-memory cache to reflect these changes.
/// </summary>
/// <param name="playerName">Current player name.</param>
/// <param name="steamId">Optional Steam ID of the player.</param>
/// <param name="ipAddress">Optional IP address of the player.</param>
/// <returns>Asynchronous task representing the update operation.</returns>
private async Task UpdatePlayerData(string? playerName, ulong? steamId, string? ipAddress)
{
if (CS2_SimpleAdmin.DatabaseProvider == null)
return;
var baseSql = """
UPDATE sa_bans
SET
player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE
(player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
""";
if (!CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
baseSql += " AND server_id = @ServerId;";
}
var parameters = new
{
PlayerSteamID = steamId,
PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0
|| string.IsNullOrEmpty(ipAddress)
|| CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(ipAddress)
? null
: ipAddress,
PlayerName = string.IsNullOrEmpty(playerName) ? string.Empty : playerName,
CurrentTime = Time.ActualDateTime(),
CS2_SimpleAdmin.ServerId
};
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
await connection.ExecuteAsync(baseSql, parameters);
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
{
foreach (var rec in steamRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
{
if (string.IsNullOrEmpty(rec.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
rec.PlayerIp = ipAddress;
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
rec.PlayerName = playerName;
}
}
if (!string.IsNullOrEmpty(ipAddress) && IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)
&& _ipIndex.TryGetValue(ipUInt, out var ipRecords))
{
foreach (var rec in ipRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
{
if (!rec.PlayerSteamId.HasValue && steamId.HasValue)
rec.PlayerSteamId = steamId;
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
rec.PlayerName = playerName;
}
}
}
private void Clear()
{
_steamIdIndex.Clear();
_ipIndex.Clear();
_banCache.Clear();
_playerIpsCache.Clear();
_cachedIgnoredIps.Clear();
}
/// <summary>
/// Clears and disposes of all cached data and marks the object as disposed.
/// </summary>
public void Dispose()
{
if (_disposed) return;
Clear();
_disposed = true;
}
}
public class IpRecordComparer : IEqualityComparer<IpRecord>
{
public bool Equals(IpRecord x, IpRecord y)
=> x.Ip == y.Ip;
public int GetHashCode(IpRecord obj)
=> obj.Ip.GetHashCode();
}

View File

@@ -1,33 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace CS2_SimpleAdmin.Managers;
public class DiscordManager(string webhookUrl)
{
/// <summary>
/// Sends a plain text message asynchronously to the configured Discord webhook URL.
/// </summary>
/// <param name="message">The text message to send to Discord.</param>
/// <returns>A task representing the asynchronous operation.</returns>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task SendMessageAsync(string message)
{
var client = CS2_SimpleAdmin.HttpClient;
var payload = new
{
content = message
};
var options = new JsonSerializerOptions
{
WriteIndented = false
};
var json = JsonSerializer.Serialize(payload, options);
var json = JsonConvert.SerializeObject(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
@@ -46,12 +34,6 @@ public class DiscordManager(string webhookUrl)
}
}
/// <summary>
/// Sends an embed message asynchronously to the configured Discord webhook URL.
/// </summary>
/// <param name="embed">The embed object containing rich content to send.</param>
/// <returns>A task representing the asynchronous operation.</returns>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task SendEmbedAsync(Embed embed)
{
var httpClient = CS2_SimpleAdmin.HttpClient;
@@ -79,12 +61,7 @@ public class DiscordManager(string webhookUrl)
}
};
var options = new JsonSerializerOptions
{
WriteIndented = false
};
var jsonPayload = JsonSerializer.Serialize(payload, options);
var jsonPayload = JsonConvert.SerializeObject(payload);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(webhookUrl, content);
@@ -96,11 +73,6 @@ public class DiscordManager(string webhookUrl)
}
}
/// <summary>
/// Converts a hexadecimal color string (e.g. "#FF0000") to its integer representation.
/// </summary>
/// <param name="hex">The hexadecimal color string, optionally starting with '#'.</param>
/// <returns>An integer representing the color.</returns>
public static int ColorFromHex(string hex)
{
if (hex.StartsWith($"#"))
@@ -112,9 +84,6 @@ public class DiscordManager(string webhookUrl)
}
}
/// <summary>
/// Represents a Discord embed message containing rich content such as title, description, fields, and images.
/// </summary>
public class Embed
{
public int Color { get; init; }
@@ -127,12 +96,6 @@ public class Embed
public List<EmbedField> Fields { get; } = [];
/// <summary>
/// Adds a field to the embed message.
/// </summary>
/// <param name="name">The name of the field.</param>
/// <param name="value">The value or content of the field.</param>
/// <param name="inline">Whether the field should be displayed inline with other fields.</param>
public void AddField(string name, string value, bool inline)
{
var field = new EmbedField
@@ -146,18 +109,12 @@ public class Embed
}
}
/// <summary>
/// Represents the footer section of a Discord embed message, including optional text and icon URL.
/// </summary>
public class Footer
{
public string? Text { get; init; }
public string? IconUrl { get; set; }
}
/// <summary>
/// Represents a field inside a Discord embed message.
/// </summary>
public class EmbedField
{
public string? Name { get; init; }

View File

@@ -1,24 +1,14 @@
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdminApi;
using CS2_SimpleAdminApi;
using Dapper;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin.Managers;
internal class MuteManager(IDatabaseProvider? databaseProvider)
internal class MuteManager(Database.Database? database)
{
/// <summary>
/// Adds a mute entry for a specified player with detailed information.
/// </summary>
/// <param name="player">Player to be muted.</param>
/// <param name="issuer">Admin issuing the mute; null if issued from console.</param>
/// <param name="reason">Reason for muting the player.</param>
/// <param name="time">Duration of the mute in minutes. Zero means permanent mute.</param>
/// <param name="type">Mute type: 0 = GAG, 1 = MUTE, 2 = SILENCE.</param>
/// <returns>Mute ID if successfully added, otherwise null.</returns>
public async Task<int?> MutePlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
public async Task MutePlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
{
if (databaseProvider == null) return null;
if (database == null) return;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
@@ -32,14 +22,16 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddMuteQuery(true);
await using var connection = await database.GetConnectionAsync();
const string sql =
"INSERT INTO `sa_mutes` (`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`) " +
"VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid)";
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
await connection.ExecuteAsync(sql, new
{
playerSteamid = player.SteamId.SteamId64,
playerSteamid = player.SteamId.SteamId64.ToString(),
playerName = player.Name,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
@@ -48,28 +40,19 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
type = muteType,
serverid = CS2_SimpleAdmin.ServerId
});
return muteId;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex.Message);
return null;
}
};
}
/// <summary>
/// Adds a mute entry for a offline player identified by their SteamID.
/// </summary>
/// <param name="playerSteamId">SteamID64 of the player to mute.</param>
/// <param name="issuer">Admin issuing the mute; can be null if from console.</param>
/// <param name="reason">Reason for the mute.</param>
/// <param name="time">Mute duration in minutes; 0 for permanent.</param>
/// <param name="type">Mute type: 0 = GAG, 1 = MUTE, 2 = SILENCE.</param>
/// <returns>Mute ID if successful, otherwise null.</returns>
public async Task<int?> AddMuteBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
public async Task AddMuteBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
{
if (databaseProvider == null) return null;
if (database == null) return;
if (string.IsNullOrEmpty(playerSteamId)) return;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
@@ -83,13 +66,14 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddMuteQuery(false);
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
await using var connection = await database.GetConnectionAsync();
const string sql = "INSERT INTO `sa_mutes` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`) " +
"VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid)";
await connection.ExecuteAsync(sql, new
{
playerSteamid = playerSteamId,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
@@ -98,23 +82,13 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
type = muteType,
serverid = CS2_SimpleAdmin.ServerId
});
return muteId;
}
catch
{
return null;
}
catch { };
}
/// <summary>
/// Checks if a player with the given SteamID currently has any active mutes.
/// </summary>
/// <param name="steamId">SteamID64 of the player to check.</param>
/// <returns>List of active mute records; empty list if none or on error.</returns>
public async Task<List<dynamic>> IsPlayerMuted(string steamId)
{
if (databaseProvider == null) return [];
if (database == null) return [];
if (string.IsNullOrEmpty(steamId))
{
@@ -128,11 +102,24 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var currentTime = Time.ActualDateTime();
var sql = databaseProvider.GetIsMutedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
var sql = "";
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0))";
}
else
{
sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0)) AND server_id = @serverid";
}
var parameters = new { PlayerSteamID = steamId, CurrentTime = currentTime, serverid = CS2_SimpleAdmin.ServerId };
var activeMutes = (await connection.QueryAsync(sql, parameters)).ToList();
return activeMutes;
@@ -143,26 +130,35 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Retrieves counts of total mutes, gags, and silences for a given player.
/// </summary>
/// <param name="playerInfo">Information about the player.</param>
/// <returns>
/// Tuple containing total mutes, total gags, and total silences respectively.
/// Returns zeros if no data or on error.
/// </returns>
public async Task<(int TotalMutes, int TotalGags, int TotalSilences)> GetPlayerMutes(PlayerInfo playerInfo)
{
if (databaseProvider == null) return (0,0,0);
if (database == null) return (0,0,0);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID;
"""
: """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId;
""";
var sql = databaseProvider.GetRetrieveMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
var result = await connection.QuerySingleAsync<(int TotalMutes, int TotalGags, int TotalSilences)>(sql, new
{
PlayerSteamID = playerInfo.SteamId.SteamId64,
PlayerSteamID = playerInfo.SteamId.SteamId64.ToString(),
CS2_SimpleAdmin.ServerId
});
@@ -174,28 +170,25 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Processes a batch of online players to update their mute status and remove expired penalties.
/// </summary>
/// <param name="players">List of tuples containing player SteamID, optional UserID, and slot index.</param>
/// <returns>Task representing the asynchronous operation.</returns>
public async Task CheckOnlineModeMutes(List<(ulong SteamID, int? UserId, int Slot)> players)
public async Task CheckOnlineModeMutes(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players)
{
if (databaseProvider == null) return;
if (database == null) return;
try
{
const int batchSize = 20;
await using var connection = await databaseProvider.CreateConnectionAsync();
var batchSize = 10;
await using var connection = await database.GetConnectionAsync();
var sql = databaseProvider.GetUpdateMutePassedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE'"
: "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
for (var i = 0; i < players.Count; i += batchSize)
{
var batch = players.Skip(i).Take(batchSize);
var parametersList = new List<object>();
foreach (var (steamId, _, _) in batch)
foreach (var (_, steamId, _, _) in batch)
{
parametersList.Add(new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
}
@@ -203,9 +196,12 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
await connection.ExecuteAsync(sql, parametersList);
}
sql = databaseProvider.GetCheckExpiredMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE'"
: "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
foreach (var (steamId, _, slot) in players)
foreach (var (_, steamId, _, slot) in players)
{
var muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
@@ -220,17 +216,9 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
catch { }
}
/// <summary>
/// Removes active mutes for players matching the specified pattern.
/// </summary>
/// <param name="playerPattern">Pattern to match player names or identifiers.</param>
/// <param name="adminSteamId">SteamID64 of the admin performing the unmute.</param>
/// <param name="reason">Reason for unmuting the player(s).</param>
/// <param name="type">Mute type to remove: 0 = GAG, 1 = MUTE, 2 = SILENCE.</param>
/// <returns>Task representing the asynchronous operation.</returns>
public async Task UnmutePlayer(string playerPattern, string adminSteamId, string reason, int type = 0)
{
if (databaseProvider == null) return;
if (database == null) return;
if (playerPattern.Length <= 1)
{
@@ -239,7 +227,8 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var muteType = type switch
{
1 => "MUTE",
@@ -247,16 +236,27 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
_ => "GAG"
};
var sqlRetrieveMutes =
databaseProvider.GetRetrieveMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
string sqlRetrieveMutes;
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
sqlRetrieveMutes = "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND " +
"type = @muteType AND status = 'ACTIVE'";
}
else
{
sqlRetrieveMutes = "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND " +
"type = @muteType AND status = 'ACTIVE' AND server_id = @serverid";
}
var mutes = await connection.QueryAsync(sqlRetrieveMutes, new { pattern = playerPattern, muteType, serverid = CS2_SimpleAdmin.ServerId });
var mutesList = mutes as dynamic[] ?? mutes.ToArray();
if (mutesList.Length == 0)
return;
var sqlAdmin = databaseProvider.GetUnmuteAdminIdQuery();
var sqlInsertUnmute = databaseProvider.GetInsertUnmuteQuery(string.IsNullOrEmpty(reason));
const string sqlAdmin = "SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
var sqlInsertUnmute = "INSERT INTO sa_unmutes (mute_id, admin_id, reason) VALUES (@muteId, @adminId, @reason); SELECT LAST_INSERT_ID();";
var sqlAdminId = await connection.ExecuteScalarAsync<int?>(sqlAdmin, new { adminSteamId });
var adminId = sqlAdminId ?? 0;
@@ -264,11 +264,21 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
foreach (var mute in mutesList)
{
int muteId = mute.id;
int? unmuteId;
int? unmuteId =
await connection.ExecuteScalarAsync<int>(sqlInsertUnmute, new { muteId, adminId, reason });
// Insert into sa_unmutes
if (reason != null)
{
unmuteId = await connection.ExecuteScalarAsync<int>(sqlInsertUnmute, new { muteId, adminId, reason });
}
else
{
sqlInsertUnmute = "INSERT INTO sa_unmutes (muteId, admin_id) VALUES (@muteId, @adminId); SELECT LAST_INSERT_ID();";
unmuteId = await connection.ExecuteScalarAsync<int>(sqlInsertUnmute, new { muteId, adminId });
}
var sqlUpdateMute = databaseProvider.GetUpdateMuteStatusQuery();
// Update sa_mutes to set unmute_id
const string sqlUpdateMute = "UPDATE sa_mutes SET status = 'UNMUTED', unmute_id = @unmuteId WHERE id = @muteId";
await connection.ExecuteAsync(sqlUpdateMute, new { unmuteId, muteId });
}
}
@@ -278,18 +288,28 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Expires all old mutes that have passed their duration according to current time.
/// </summary>
/// <returns>Task representing the asynchronous expiration operation.</returns>
public async Task ExpireOldMutes()
{
if (databaseProvider == null) return;
if (database == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetExpireMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
await using var connection = await database.GetConnectionAsync();
string sql;
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration`";
}
else
{
sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration` AND server_id = @serverid";
}
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception)

View File

@@ -2,20 +2,17 @@
using CounterStrikeSharp.API.Modules.Entities;
using Dapper;
using Microsoft.Extensions.Logging;
using MySqlConnector;
using Newtonsoft.Json;
using System.Collections.Concurrent;
using System.Text.Json;
using CounterStrikeSharp.API.Modules.Admin;
using System.Diagnostics.CodeAnalysis;
using CS2_SimpleAdmin.Database;
namespace CS2_SimpleAdmin.Managers;
public class PermissionManager(IDatabaseProvider? databaseProvider)
public class PermissionManager(Database.Database? database)
{
// Unused for now
//public static readonly ConcurrentDictionary<string, ConcurrentBag<string>> _adminCache = new ConcurrentDictionary<string, ConcurrentBag<string>>();
// public static readonly ConcurrentDictionary<SteamID, DateTime?> AdminCache = new();
public static readonly ConcurrentDictionary<SteamID, (DateTime? ExpirationTime, List<string> Flags)> AdminCache = new();
public static readonly ConcurrentDictionary<SteamID, DateTime?> AdminCache = new();
/*
public async Task<List<(List<string>, int)>> GetAdminFlags(string steamId)
@@ -60,65 +57,60 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
*/
/// <summary>
/// Retrieves all players' flags and associated data asynchronously.
/// </summary>
/// <returns>A list of tuples containing player SteamID, name, flags, immunity, and expiration time.</returns>
private async Task<List<(ulong, string ,List<string>, int, DateTime?)>> GetAllPlayersFlags()
private async Task<List<(string, string, List<string>, int, DateTime?)>> GetAllPlayersFlags()
{
if (databaseProvider == null)
return new List<(ulong, string, List<string>, int, DateTime?)>();
if (database == null) return [];
var now = Time.ActualDateTime();
var now = Time.ActualDateTime();
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAdminsQuery();
var admins = (await connection.QueryAsync(sql, new { CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId })).ToList();
try
{
await using var connection = await database.GetConnectionAsync();
var groupedPlayers = admins
.GroupBy(r => new { playerSteamId = r.player_steamid, playerName = r.player_name, r.immunity, r.ends })
.Select(g =>
{
ulong steamId = g.Key.playerSteamId switch
{
long l => (ulong)l,
int i => (ulong)i,
string s when ulong.TryParse(s, out var parsed) => parsed,
_ => 0UL
};
const string sql = """
SELECT sa_admins.player_steamid, sa_admins.player_name, sa_admins_flags.flag, sa_admins.immunity, sa_admins.ends
FROM sa_admins_flags
JOIN sa_admins ON sa_admins_flags.admin_id = sa_admins.id
WHERE (sa_admins.ends IS NULL OR sa_admins.ends > @CurrentTime)
AND (sa_admins.server_id IS NULL OR sa_admins.server_id = @serverid)
ORDER BY sa_admins.player_steamid
""";
int immunity = g.Key.immunity switch
{
int i => i,
string s when int.TryParse(s, out var parsed) => parsed,
_ => 0
};
var admins = (await connection.QueryAsync(sql, new { CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId })).ToList();
DateTime? ends = g.Key.ends as DateTime?;
// Group by player_steamid and aggregate the flags
var groupedPlayers = admins
.GroupBy(r => new { r.player_steamid, r.player_name, r.immunity, r.ends })
.Select(g => (
PlayerSteamId: (string)g.Key.player_steamid,
PlayerName: (string)g.Key.player_name,
Flags: g.Select(r => (string)r.flag).Distinct().ToList(),
Immunity: g.Key.immunity is int i ? i : int.TryParse((string)g.Key.immunity, out var immunity) ? immunity : 0,
Ends: g.Key.ends is DateTime dateTime ? dateTime : (DateTime?)null
))
.ToList();
string playerName = g.Key.playerName as string ?? string.Empty;
/*
foreach (var player in groupedPlayers)
{
Console.WriteLine($"Player SteamID: {player.PlayerSteamId}, Name: {player.PlayerName}, Flags: {string.Join(", ", player.Flags)}, Immunity: {player.Immunity}, Ends: {player.Ends}");
}
*/
// tutaj zakładamy, że Dapper zwraca już string (nie dynamic)
var flags = g.Select(r => r.flag as string ?? string.Empty)
.Distinct()
.ToList();
List<(string, string, List<string>, int, DateTime?)> filteredFlagsWithImmunity = [];
return (steamId, playerName, flags, immunity, ends);
})
.ToList();
// Add the grouped players to the list
filteredFlagsWithImmunity.AddRange(groupedPlayers);
return groupedPlayers;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
return [];
}
return filteredFlagsWithImmunity;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
return [];
}
}
/*
public async Task<Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>>> GetAllGroupsFlags()
{
@@ -184,29 +176,33 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
*/
/// <summary>
/// Retrieves all groups' data including flags and immunity asynchronously.
/// </summary>
/// <returns>A dictionary with group names as keys and tuples of flags and immunity as values.</returns>
private async Task<Dictionary<string, (List<string>, int)>> GetAllGroupsData()
{
if (databaseProvider == null) return [];
if (database == null) return [];
await using var connection = await databaseProvider.CreateConnectionAsync();
;
await using MySqlConnection connection = await database.GetConnectionAsync();
try
{
// var sql = "SELECT group_id FROM sa_groups_servers WHERE (server_id = @serverid OR server_id IS NULL)";
// var groupDataSql = connection.Query<int>(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
var sql = "SELECT group_id FROM sa_groups_servers WHERE (server_id = @serverid OR server_id IS NULL)";
var groupDataSql = connection.Query<int>(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
sql = """
SELECT g.group_id, sg.name AS group_name, sg.immunity, f.flag
FROM sa_groups_flags f
JOIN sa_groups_servers g ON f.group_id = g.group_id
JOIN sa_groups sg ON sg.id = g.group_id
WHERE (g.server_id = @serverid OR server_id IS NULL)
""";
var sql = databaseProvider.GetGroupsQuery();
var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
if (groupData.Count == 0)
if (groupDataSql.Count == 0 || groupData.Count == 0)
{
return [];
}
var groupInfoDictionary = new Dictionary<string, (List<string>, int)>();
foreach (var row in groupData)
{
var groupName = (string)row.group_name;
@@ -228,19 +224,16 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Unable to load groups from database! {exception}", ex.Message);
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
}
return [];
}
/// <summary>
/// Creates a JSON file containing groups data asynchronously.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task CrateGroupsJsonFile()
{
var groupsData = await GetAllGroupsData();
var jsonData = new Dictionary<string, object>();
foreach (var kvp in groupsData)
@@ -254,13 +247,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
jsonData[kvp.Key] = groupData;
}
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(jsonData, options);
var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented);
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "groups.json");
await File.WriteAllTextAsync(filePath, json);
}
@@ -325,187 +312,89 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
*/
/// <summary>
/// Creates a JSON file containing admins data asynchronously.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task CreateAdminsJsonFile()
{
List<(ulong identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
List<(string identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
var validPlayers = allPlayers
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
.Where(player => SteamID.TryParse(player.identity, out _)) // Filter invalid SteamID
.ToList();
// foreach (var player in allPlayers)
// {
// var (steamId, name, flags, immunity, ends) = player;
//
// Console.WriteLine($"Player SteamID: {steamId}");
// Console.WriteLine($"Player Name: {name}");
// Console.WriteLine($"Flags: {string.Join(", ", flags)}");
// Console.WriteLine($"Immunity: {immunity}");
// Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}");
// Console.WriteLine();
// }
var jsonData = validPlayers
.GroupBy(player => player.name) // Group by player name
.ToDictionary(
group => group.Key, // Use the player name as key
object (group) =>
{
// Consolidate data for players with same name
var consolidatedData = group.Aggregate(
new
{
identity = string.Empty,
immunity = 0,
flags = new List<string>(),
groups = new List<string>()
},
(acc, player) =>
{
// Merge identities
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
{
acc = acc with { identity = player.identity.ToString() };
}
// Combine immunities by maximum value
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
// Combine flags and groups
acc = acc with
{
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
groups = acc.groups.Concat(player.flags.Where(flag => flag.StartsWith($"#"))).Distinct().ToList()
};
return acc;
});
Server.NextWorldUpdate(() =>
{
var keysToRemove = new List<SteamID>();
foreach (var steamId in AdminCache.Keys.ToList())
{
var data = AdminManager.GetPlayerAdminData(steamId);
if (data != null)
{
var flagsArray = AdminCache[steamId].Flags.ToArray();
AdminManager.RemovePlayerPermissions(steamId, flagsArray);
AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray);
}
keysToRemove.Add(steamId);
}
foreach (var steamId in keysToRemove)
{
if (!AdminCache.TryRemove(steamId, out _)) continue;
var data = AdminManager.GetPlayerAdminData(steamId);
if (data == null) continue;
if (data.Flags.Count != 0 && data.Groups.Count != 0) continue;
AdminManager.ClearPlayerPermissions(steamId);
AdminManager.RemovePlayerAdminData(steamId);
}
foreach (var player in group)
{
if (SteamID.TryParse(player.identity.ToString(), out var steamId) && steamId != null)
{
AdminCache.TryAdd(steamId, (player.ends, player.flags));
}
}
});
// Server.NextFrameAsync(() =>
// {
// for (var index = 0; index < AdminCache.Keys.ToList().Count; index++)
// {
// var steamId = AdminCache.Keys.ToList()[index];
//
// var data = AdminManager.GetPlayerAdminData(steamId);
// if (data != null)
// {
// AdminManager.RemovePlayerPermissions(steamId, AdminCache[steamId].Flags.ToArray());
// AdminManager.RemovePlayerFromGroup(steamId, true, AdminCache[steamId].Flags.ToArray());
// }
//
// if (!AdminCache.TryRemove(steamId, out _)) continue;
//
// if (data == null) continue;
// if (data.Flags.ToList().Count != 0 && data.Groups.ToList().Count != 0)
// continue;
//
// AdminManager.ClearPlayerPermissions(steamId);
// AdminManager.RemovePlayerAdminData(steamId);
// }
//
// foreach (var player in group)
// {
// SteamID.TryParse(player.identity, out var steamId);
// if (steamId == null) continue;
// AdminCache.TryAdd(steamId, (player.ends, player.flags));
// }
// });
return consolidatedData;
});
var options = new JsonSerializerOptions
/*
foreach (var player in allPlayers)
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var (steamId, name, flags, immunity, ends) = player;
// Print or process each item
Console.WriteLine($"Player SteamID: {steamId}");
Console.WriteLine($"Player Name: {name}");
Console.WriteLine($"Flags: {string.Join(", ", flags)}");
Console.WriteLine($"Immunity: {immunity}");
Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}");
Console.WriteLine(); // New line for better readability
}
*/
var jsonData = validPlayers
.Select(player =>
{
SteamID.TryParse(player.identity, out var steamId);
// Update cache if SteamID is valid and not already cached
if (steamId != null && !AdminCache.ContainsKey(steamId))
{
AdminCache.TryAdd(steamId, player.ends);
}
// Create an anonymous object with player data
return new
{
playerName = player.name,
playerData = new
{
player.identity,
player.immunity,
flags = player.flags.Where(flag => flag.StartsWith("@")).ToList(),
groups = player.flags.Where(flag => flag.StartsWith("#")).ToList()
}
};
})
.ToDictionary(item => item.playerName, item => (object)item.playerData);
var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented);
var json = JsonSerializer.Serialize(jsonData, options);
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
await File.WriteAllTextAsync(filePath, json);
//await File.WriteAllTextAsync(CS2_SimpleAdmin.Instance.ModuleDirectory + "/data/admins.json", json);
}
/// <summary>
/// Deletes an admin by their SteamID from the database asynchronously.
/// </summary>
/// <param name="playerSteamId">The SteamID of the admin to delete.</param>
/// <param name="globalDelete">Whether to delete the admin globally or only for the current server.</param>
public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false)
{
if (databaseProvider == null) return;
if (database == null) return;
if (string.IsNullOrEmpty(playerSteamId)) return;
//_adminCache.TryRemove(playerSteamId, out _);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetDeleteAdminQuery(globalDelete);
await using var connection = await database.GetConnectionAsync();
var sql = globalDelete
? "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID"
: "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId";
await connection.ExecuteAsync(sql, new { PlayerSteamID = playerSteamId, CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex.Message);
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
}
}
/// <summary>
/// Adds a new admin with specified details asynchronously.
/// </summary>
/// <param name="playerSteamId">SteamID of the admin.</param>
/// <param name="playerName">Name of the admin.</param>
/// <param name="flagsList">List of flags assigned to the admin.</param>
/// <param name="immunity">Immunity level.</param>
/// <param name="time">Duration in minutes for admin expiration; 0 means permanent.</param>
/// <param name="globalAdmin">Whether the admin is global or server-specific.</param>
public async Task AddAdminBySteamId(string playerSteamId, string playerName, List<string> flagsList, int immunity = 0, int time = 0, bool globalAdmin = false)
{
if (databaseProvider == null) return;
if (database == null) return;
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return;
@@ -519,10 +408,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
// Insert admin into sa_admins table
var insertAdminSql = databaseProvider.GetAddAdminQuery();
const string insertAdminSql = "INSERT INTO `sa_admins` (`player_steamid`, `player_name`, `immunity`, `ends`, `created`, `server_id`) " +
"VALUES (@playerSteamid, @playerName, @immunity, @ends, @created, @serverid); SELECT LAST_INSERT_ID();";
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
{
playerSteamId,
@@ -536,26 +427,25 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
// Insert flags into sa_admins_flags table
foreach (var flag in flagsList)
{
// if (flag.StartsWith($"#"))
// {
// // const string sql = "SELECT id FROM `sa_groups` WHERE name = @groupName";
// // var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag });
//
// var sql = databaseProvider.GetGroupIdByNameQuery();
// var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag, CS2_SimpleAdmin.ServerId });
//
// if (groupId != null)
// {
// var updateAdminGroup = "UPDATE `sa_admins` SET group_id = @groupId WHERE id = @adminId";
// await connection.ExecuteAsync(updateAdminGroup, new
// {
// groupId,
// adminId
// });
// }
// }
if (flag.StartsWith($"#"))
{
const string sql = "SELECT id FROM `sa_groups` WHERE name = @groupName";
var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag });
if (groupId != null)
{
const string updateAdminGroup = "UPDATE `sa_admins` SET group_id = @groupId WHERE id = @adminId";
await connection.ExecuteAsync(updateAdminGroup, new
{
groupId,
adminId
});
}
}
const string insertFlagsSql = "INSERT INTO `sa_admins_flags` (`admin_id`, `flag`) " +
"VALUES (@adminId, @flag)";
var insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery();
await connection.ExecuteAsync(insertFlagsSql, new
{
adminId,
@@ -563,7 +453,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
});
}
await Server.NextWorldUpdateAsync(() =>
await Server.NextFrameAsync(() =>
{
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
});
@@ -574,24 +464,18 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Adds a new group with flags and immunity asynchronously.
/// </summary>
/// <param name="groupName">Name of the group.</param>
/// <param name="flagsList">List of flags assigned to the group.</param>
/// <param name="immunity">Immunity level of the group.</param>
/// <param name="globalGroup">Whether the group is global or server-specific.</param>
public async Task AddGroup(string groupName, List<string> flagsList, int immunity = 0, bool globalGroup = false)
{
if (databaseProvider == null) return;
if (database == null) return;
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return;
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
try
{
// Insert group into sa_groups table
var insertGroup = databaseProvider.GetAddGroupQuery();
const string insertGroup = "INSERT INTO `sa_groups` (`name`, `immunity`) " +
"VALUES (@groupName, @immunity); SELECT LAST_INSERT_ID();";
var groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
{
groupName,
@@ -601,7 +485,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
// Insert flags into sa_groups_flags table
foreach (var flag in flagsList)
{
var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
const string insertFlagsSql = "INSERT INTO `sa_groups_flags` (`group_id`, `flag`) " +
"VALUES (@groupId, @flag)";
await connection.ExecuteAsync(insertFlagsSql, new
{
@@ -610,9 +495,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
});
}
var insertGroupServer = databaseProvider.GetAddGroupServerQuery();
const string insertGroupServer = "INSERT INTO `sa_groups_servers` (`group_id`, `server_id`) " +
"VALUES (@groupId, @server_id)";
await connection.ExecuteAsync(insertGroupServer, new { groupId, server_id = globalGroup ? null : CS2_SimpleAdmin.ServerId });
await Server.NextWorldUpdateAsync(() =>
await Server.NextFrameAsync(() =>
{
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
});
@@ -620,24 +508,20 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Problem with loading admins: {exception}", ex.Message);
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
}
}
/// <summary>
/// Deletes a group by name asynchronously.
/// </summary>
/// <param name="groupName">Name of the group to delete.</param>
public async Task DeleteGroup(string groupName)
{
if (databaseProvider == null) return;
if (database == null) return;
if (string.IsNullOrEmpty(groupName)) return;
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
try
{
var sql = databaseProvider.GetDeleteGroupQuery();
const string sql = "DELETE FROM `sa_groups` WHERE name = @groupName";
await connection.ExecuteAsync(sql, new { groupName });
}
catch (Exception ex)
@@ -646,18 +530,15 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Deletes admins whose permissions have expired asynchronously.
/// </summary>
public async Task DeleteOldAdmins()
{
if (databaseProvider == null) return;
if (database == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var sql = databaseProvider.GetDeleteOldAdminsQuery();
const string sql = "DELETE FROM sa_admins WHERE ends IS NOT NULL AND ends <= @CurrentTime";
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime() });
}
catch (Exception)

View File

@@ -7,444 +7,327 @@ using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdminApi;
using Dapper;
using Microsoft.Extensions.Logging;
using ZLinq;
namespace CS2_SimpleAdmin.Managers;
internal class PlayerManager
public class PlayerManager
{
private readonly SemaphoreSlim _loadPlayerSemaphore = new(5);
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
/// <summary>
/// Loads and initializes player data when a client connects.
/// </summary>
/// <param name="player">The <see cref="CCSPlayerController"/> instance representing the connecting player.</param>
/// <param name="fullConnect">
/// Determines whether to perform a full synchronization of player data.
/// If true, full checks (bans, IP history, penalties, warns, mutes) will be loaded and applied.
/// </param>
/// <remarks>
/// This method validates the player's identity, checks for bans, updates the IP history table,
/// loads penalties (mutes/gags/warns), and optionally notifies admin players about the connecting player's penalties.
/// </remarks>
public void LoadPlayerData(CCSPlayerController player, bool fullConnect = false)
public void LoadPlayerData(CCSPlayerController player)
{
if (!player.UserId.HasValue)
if (player.IsBot || string.IsNullOrEmpty(player.IpAddress) || player.IpAddress.Contains("127.0.0.1")
|| !player.UserId.HasValue)
return;
var ipAddress = player.IpAddress?.Split(":")[0];
CS2_SimpleAdmin.PlayersInfo[player.UserId.Value] =
new PlayerInfo(player.UserId.Value, player.Slot, new SteamID(player.SteamID), player.PlayerName, ipAddress);
var userId = player.UserId.Value;
// Check if the player's IP or SteamID is in the bannedPlayers list
if (_config.OtherSettings.BanType > 0 && CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress) ||
CS2_SimpleAdmin.BannedPlayers.Contains(player.SteamID.ToString()))
{
Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION);
// Kick the player if banned
if (player.UserId.HasValue)
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
return;
}
var userId = player.UserId.Value;
var slot = player.Slot;
var steamId = player.SteamID;
var playerName = !string.IsNullOrEmpty(player.PlayerName)
? player.PlayerName
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var ipAddress = player.IpAddress?.Split(":")[0];
if (CS2_SimpleAdmin.DatabaseProvider == null || CS2_SimpleAdmin.Instance.CacheManager == null) return;
if (CS2_SimpleAdmin.Database == null) return;
// Perform asynchronous database operations within a single method
Task.Run(async () =>
{
try
{
await _loadPlayerSemaphore.WaitAsync();
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
const string selectQuery = "SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
{
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
IPAddress = ipAddress
});
if (recordExists > 0)
{
const string updateQuery = """
UPDATE `sa_players_ips`
SET used_at = CURRENT_TIMESTAMP
WHERE steamid = @SteamID AND address = @IPAddress;
""";
await connection.ExecuteAsync(updateQuery, new
{
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null),
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId,
ipAddress)
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress)
};
// CS2_SimpleAdmin._logger?.LogInformation($"Player {playerName} ({steamId} - {ipAddress}) is banned? {isBanned.ToString()}");
if (isBanned)
{
await Server.NextWorldUpdateAsync(() =>
{
// CS2_SimpleAdmin._logger?.LogInformation($"Kicking {playerName}");
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
});
return;
}
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
IPAddress = ipAddress
});
}
if (fullConnect)
else
{
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
await Server.NextWorldUpdateAsync(() =>
const string insertQuery = """
INSERT INTO `sa_players_ips` (steamid, address, used_at)
VALUES (@SteamID, @IPAddress, CURRENT_TIMESTAMP);
""";
await connection.ExecuteAsync(insertQuery, new
{
if (!CS2_SimpleAdmin.CachedPlayers.Contains(player))
CS2_SimpleAdmin.CachedPlayers.Add(player);
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
IPAddress = ipAddress
});
}
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(
$"Unable to save ip address for {CS2_SimpleAdmin.PlayersInfo[userId].Name} ({ipAddress}) {ex.Message}");
}
try
{
// Check if the player is banned
var isBanned = await CS2_SimpleAdmin.Instance.BanManager.IsPlayerBanned(CS2_SimpleAdmin.PlayersInfo[userId]);
if (isBanned)
{
// Add player's IP and SteamID to bannedPlayers list if not already present
if (_config.OtherSettings.BanType > 0 && ipAddress != null &&
!CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress))
{
CS2_SimpleAdmin.BannedPlayers.Add(ipAddress);
}
if (!CS2_SimpleAdmin.BannedPlayers.Contains(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()))
{
CS2_SimpleAdmin.BannedPlayers.Add(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString());
}
// Kick the player if banned
await Server.NextFrameAsync(() =>
{
var victim = Utilities.GetPlayerFromUserid(userId);
if (victim?.UserId == null) return;
if (CS2_SimpleAdmin.UnlockedCommands)
Server.ExecuteCommand($"banid 1 {userId}");
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
});
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null &&
CS2_SimpleAdmin.PlayersInfo[steamId] != null)
return;
}
var warns = await CS2_SimpleAdmin.Instance.WarnManager.GetPlayerWarns(CS2_SimpleAdmin.PlayersInfo[userId], false);
var (totalMutes, totalGags, totalSilences) =
await CS2_SimpleAdmin.Instance.MuteManager.GetPlayerMutes(CS2_SimpleAdmin.PlayersInfo[userId]);
CS2_SimpleAdmin.PlayersInfo[userId].TotalBans =
await CS2_SimpleAdmin.Instance.BanManager.GetPlayerBans(CS2_SimpleAdmin.PlayersInfo[userId]);
CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes = totalMutes;
CS2_SimpleAdmin.PlayersInfo[userId].TotalGags = totalGags;
CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences = totalSilences;
CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns = warns.Count;
// Check if the player is muted
var activeMutes = await CS2_SimpleAdmin.Instance.MuteManager.IsPlayerMuted(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString());
if (activeMutes.Count > 0)
{
foreach (var mute in activeMutes)
{
try
string muteType = mute.type;
DateTime ends = mute.ends;
int duration = mute.duration;
switch (muteType)
{
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
if (CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(
steamId, ipAddress))
{
const string updateQuery = """
UPDATE `sa_players_ips`
SET used_at = CURRENT_TIMESTAMP,
name = @playerName
WHERE steamid = @SteamID AND address = @IPAddress;
""";
await connection.ExecuteAsync(updateQuery, new
// Apply mute penalty based on mute type
case "GAG":
PlayerPenaltyManager.AddPenalty(CS2_SimpleAdmin.PlayersInfo[userId].Slot, PenaltyType.Gag, ends, duration);
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Gag].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_gag", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]);
break;
case "MUTE":
PlayerPenaltyManager.AddPenalty(CS2_SimpleAdmin.PlayersInfo[userId].Slot, PenaltyType.Mute, ends, duration);
await Server.NextFrameAsync(() =>
{
playerName,
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
player.VoiceFlags = VoiceFlags.Muted;
});
}
else
{
const string selectQuery =
"SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Mute].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_mute", ends.ToLocalTime().ToString(CultureInfo.InvariantCulture)]);
break;
default:
PlayerPenaltyManager.AddPenalty(CS2_SimpleAdmin.PlayersInfo[userId].Slot, PenaltyType.Silence, ends, duration);
await Server.NextFrameAsync(() =>
{
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
player.VoiceFlags = VoiceFlags.Muted;
});
if (recordExists > 0)
{
const string updateQuery = """
UPDATE `sa_players_ips`
SET used_at = CURRENT_TIMESTAMP,
name = @playerName
WHERE steamid = @SteamID AND address = @IPAddress;
""";
await connection.ExecuteAsync(updateQuery, new
{
playerName,
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
else
{
const string insertQuery = """
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP);
""";
await connection.ExecuteAsync(insertQuery, new
{
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
playerName,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
}
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Silence].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_silence", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]);
break;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(
$"Unable to save ip address for {playerInfo.Name} ({ipAddress}) {ex.Message}");
}
playerInfo.AccountsAssociated =
CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable()
.Select(x => (x.SteamId, x.PlayerName)).ToList() ?? [];
}
try
{
// var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0
// ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(
// CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString(), null)
// : CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
// ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(CS2_SimpleAdmin
// .PlayersInfo[userId].SteamId.SteamId64)
// : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString(), ipAddress);
if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(steamId, out PlayerInfo? value)) // Temp skip
{
var warns = await CS2_SimpleAdmin.Instance.WarnManager.GetPlayerWarns(value, false);
var (totalMutes, totalGags, totalSilences) =
await CS2_SimpleAdmin.Instance.MuteManager.GetPlayerMutes(value);
value.TotalBans = CS2_SimpleAdmin.Instance.CacheManager
?.GetPlayerBansBySteamId(value.SteamId.SteamId64)
.Count ?? 0;
value.TotalMutes = totalMutes;
value.TotalGags = totalGags;
value.TotalSilences = totalSilences;
value.TotalWarns = warns.Count;
var activeMutes =
await CS2_SimpleAdmin.Instance.MuteManager.IsPlayerMuted(value.SteamId.SteamId64
.ToString());
if (activeMutes.Count > 0)
{
foreach (var mute in activeMutes)
{
string muteType = mute.type;
DateTime ends = mute.ends;
int duration = mute.duration;
switch (muteType)
{
// Apply mute penalty based on mute type
case "GAG":
PlayerPenaltyManager.AddPenalty(
CS2_SimpleAdmin.PlayersInfo[steamId].Slot,
PenaltyType.Gag, ends, duration);
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Gag].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_gag", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]);
break;
case "MUTE":
PlayerPenaltyManager.AddPenalty(
CS2_SimpleAdmin.PlayersInfo[steamId].Slot,
PenaltyType.Mute, ends, duration);
await Server.NextWorldUpdateAsync(() =>
{
player.VoiceFlags = VoiceFlags.Muted;
});
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Mute].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_mute", ends.ToLocalTime().ToString(CultureInfo.InvariantCulture)]);
break;
default:
PlayerPenaltyManager.AddPenalty(
CS2_SimpleAdmin.PlayersInfo[steamId].Slot,
PenaltyType.Silence, ends, duration);
await Server.NextWorldUpdateAsync(() =>
{
player.VoiceFlags = VoiceFlags.Muted;
});
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Silence].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_silence", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]);
break;
}
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.NotifyPenaltiesToAdminOnConnect)
{
await Server.NextWorldUpdateAsync(() =>
{
foreach (var admin in Helper.GetValidPlayers()
.Where(p => (AdminManager.PlayerHasPermissions(
new SteamID(p.SteamID),
"@css/kick") ||
AdminManager.PlayerHasPermissions(
new SteamID(p.SteamID),
"@css/ban")) &&
p.Connected == PlayerConnectedState.PlayerConnected &&
!CS2_SimpleAdmin.AdminDisabledJoinComms
.Contains(p.SteamID)))
{
if (CS2_SimpleAdmin._localizer == null || admin == player) continue;
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer,
"sa_admin_penalty_info",
player.PlayerName,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalBans,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalGags,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalMutes,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalSilences,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalWarns
);
if (CS2_SimpleAdmin.PlayersInfo[steamId].AccountsAssociated.Count >= 2)
{
var associatedAcccountsChunks =
CS2_SimpleAdmin.PlayersInfo[steamId].AccountsAssociated.ChunkBy(5)
.ToList();
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})"))
);
}
}
}
});
}
}
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Error processing player connection: {exception}",
ex.Message);
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.NotifyPenaltiesToAdminOnConnect)
{
await Server.NextFrameAsync(() =>
{
foreach (var admin in Helper.GetValidPlayers()
.Where(p => (AdminManager.PlayerHasPermissions(p, "@css/kick") ||
AdminManager.PlayerHasPermissions(p, "@css/ban")) &&
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,
CS2_SimpleAdmin.PlayersInfo[userId].TotalGags,
CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes,
CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences,
CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns
);
}
});
}
}
finally
catch (Exception ex)
{
_loadPlayerSemaphore.Release();
CS2_SimpleAdmin._logger?.LogError($"Error processing player connection: {ex}");
}
});
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
{
player.Rename(name);
}
}
/// <summary>
/// Periodically checks the status of online players and applies timers for speed, gravity,
/// and penalty expiration validation.
/// </summary>
/// <remarks>
/// This method registers two repeating timers:
/// <list type="bullet">
/// <item><description>One short-interval timer to update speed/gravity modifications applied to players.</description></item>
/// <item><description>
/// One long-interval timer (default 61 seconds) to expire bans, mutes, warns, refresh caches,
/// and remove outdated penalties from connected players.
/// </description></item>
/// </list>
/// Additionally, banned players still online are kicked, and admins may be updated about mute statuses based on the configured time mode.
/// </remarks>
public void CheckPlayersTimer()
{
CS2_SimpleAdmin.Instance.AddTimer(0.12f, () =>
CS2_SimpleAdmin.Instance.AddTimer(0.1f, () =>
{
if (CS2_SimpleAdmin.SpeedPlayers.Count > 0)
if (CS2_SimpleAdmin.GravityPlayers.Count <= 0) return;
foreach (var value in CS2_SimpleAdmin.GravityPlayers)
{
foreach (var (player, speed) in CS2_SimpleAdmin.SpeedPlayers)
{
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.SetSpeed(speed);
}
}
}
if (value.Key is not
{ IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true })
continue;
if (CS2_SimpleAdmin.GravityPlayers.Count > 0)
{
foreach (var (player, gravity) in CS2_SimpleAdmin.GravityPlayers)
{
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.SetGravity(gravity);
}
}
value.Key.SetGravity(value.Value);
}
}, TimerFlags.REPEAT);
CS2_SimpleAdmin.Instance.PlayersTimer = CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
{
#if DEBUG
CS2_SimpleAdmin._logger?.LogCritical("[OnMapStart] Expired check");
#endif
if (CS2_SimpleAdmin.DatabaseProvider == null)
if (CS2_SimpleAdmin.Database == null)
return;
var tempPlayers = Helper.GetValidPlayers()
.Select(p => new
{
p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot,
})
.ToList();
var pluginInstance = CS2_SimpleAdmin.Instance;
_ = Task.Run(async () =>
var players = Helper.GetValidPlayers();
var onlinePlayers = new List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)>();
// var onlinePlayers = players
// .Where(player => player.IpAddress != null)
// .Select(player => (player.IpAddress, player.SteamID, player.UserId, player.Slot))
// .ToList();
foreach (var player in players)
{
try
{
var expireTasks = new[]
{
pluginInstance.BanManager.ExpireOldBans(),
pluginInstance.MuteManager.ExpireOldMutes(),
pluginInstance.WarnManager.ExpireOldWarns(),
pluginInstance.CacheManager?.RefreshCacheAsync() ?? Task.CompletedTask,
pluginInstance.PermissionManager.DeleteOldAdmins()
};
await Task.WhenAll(expireTasks);
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError($"Error processing players timer tasks: {ex.Message}");
if (ex is AggregateException aggregate)
{
foreach (var inner in aggregate.InnerExceptions)
{
CS2_SimpleAdmin._logger?.LogError($"Inner exception: {inner.Message}");
}
}
}
if (pluginInstance.CacheManager == null)
return;
var bannedPlayers = tempPlayers.AsValueEnumerable()
.Where(player =>
{
var playerName = player.PlayerName;
var steamId = player.SteamID;
var ip = player.IpAddress?.Split(':')[0];
return CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
{
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
};
}).ToList();
if (bannedPlayers.Count > 0)
{
foreach (var player in bannedPlayers)
{
if (!player.UserId.HasValue) continue;
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
}
}
if (_config.OtherSettings.TimeMode == 0)
{
var onlinePlayers = tempPlayers.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList();
if (tempPlayers.Count == 0 || onlinePlayers.Count == 0) return;
await pluginInstance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
}
});
if (player.IpAddress != null)
onlinePlayers.Add((player.IpAddress, player.SteamID, player.UserId, player.Slot));
}
try
{
var players = Helper.GetValidPlayers();
var penalizedSlots = players
.Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
.Select(player => new
{
Player = player,
IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _),
IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _),
IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _)
});
foreach (var entry in penalizedSlots)
var expireTasks = new[]
{
if (!entry.IsMuted && !entry.IsSilenced)
{
entry.Player.VoiceFlags = VoiceFlags.Normal;
}
}
CS2_SimpleAdmin.Instance.BanManager.ExpireOldBans(),
CS2_SimpleAdmin.Instance.MuteManager.ExpireOldMutes(),
CS2_SimpleAdmin.Instance.WarnManager.ExpireOldWarns(),
CS2_SimpleAdmin.Instance.PermissionManager.DeleteOldAdmins()
};
PlayerPenaltyManager.RemoveExpiredPenalties();
Task.WhenAll(expireTasks).ContinueWith(t =>
{
if (t is not { IsFaulted: true, Exception: not null }) return;
foreach (var ex in t.Exception.InnerExceptions)
{
CS2_SimpleAdmin._logger?.LogError($"Error expiring penalties: {ex.Message}");
}
});
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}");
CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}");
}
}, TimerFlags.REPEAT);
CS2_SimpleAdmin.BannedPlayers.Clear();
if (onlinePlayers.Count > 0)
{
try
{
Task.Run(async () =>
{
await CS2_SimpleAdmin.Instance.BanManager.CheckOnlinePlayers(onlinePlayers);
if (_config.OtherSettings.TimeMode == 0)
{
await CS2_SimpleAdmin.Instance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
}
}).ContinueWith(t =>
{
if (t is not { IsFaulted: true, Exception: not null }) return;
foreach (var ex in t.Exception.InnerExceptions)
{
CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}");
}
});
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}");
}
}
if (onlinePlayers.Count <= 0) return;
{
try
{
var penalizedSlots = players
.Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
.Select(player => new
{
Player = player,
IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute),
IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence),
IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag)
});
foreach (var entry in penalizedSlots)
{
// If the player is not muted or silenced, set voice flags to normal
if (!entry.IsMuted && !entry.IsSilenced)
{
entry.Player.VoiceFlags = VoiceFlags.Normal;
}
}
PlayerPenaltyManager.RemoveExpiredPenalties();
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}");
}
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT);
}
}

View File

@@ -8,13 +8,7 @@ public static class PlayerPenaltyManager
private static readonly ConcurrentDictionary<int, Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>> Penalties =
new();
/// <summary>
/// Adds a penalty for a specific player slot and penalty type.
/// </summary>
/// <param name="slot">The player slot where the penalty should be applied.</param>
/// <param name="penaltyType">The type of penalty to apply (e.g. gag, mute, silence).</param>
/// <param name="endDateTime">The validity expiration date/time of the penalty.</param>
/// <param name="durationInMinutes">The duration of the penalty in minutes (0 for permanent).</param>
// Add a penalty for a player
public static void AddPenalty(int slot, PenaltyType penaltyType, DateTime endDateTime, int durationInMinutes)
{
Penalties.AddOrUpdate(slot,
@@ -39,27 +33,16 @@ public static class PlayerPenaltyManager
});
}
/// <summary>
/// Determines whether a player is currently penalized with the given penalty type.
/// </summary>
/// <param name="slot">The player slot to check.</param>
/// <param name="penaltyType">The penalty type to check.</param>
/// <param name="endDateTime">The out-parameter returning the end datetime of the penalty if active.</param>
/// <returns>True if the player has an active penalty, false otherwise.</returns>
public static bool IsPenalized(int slot, PenaltyType penaltyType, out DateTime? endDateTime)
public static bool IsPenalized(int slot, PenaltyType penaltyType)
{
endDateTime = null;
//Console.WriteLine($"Checking penalties for player with slot {slot} and penalty type {penaltyType}");
if (!Penalties.TryGetValue(slot, out var penaltyDict) ||
!penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) return false;
//Console.WriteLine($"Found penalties for player with slot {slot} and penalty type {penaltyType}");
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0)
{
if (penaltiesList.Count == 0) return false;
endDateTime = penaltiesList.First().EndDateTime;
return true;
}
return penaltiesList.Count != 0;
var now = Time.ActualDateTime();
@@ -69,30 +52,31 @@ public static class PlayerPenaltyManager
// Check if the penalty is still active
if (penalty.Duration > 0 && now >= penalty.EndDateTime)
{
//Console.WriteLine($"Removing expired penalty for player with slot {slot} and penalty type {penaltyType}");
penaltiesList.Remove(penalty); // Remove expired penalty
if (penaltiesList.Count == 0)
{
//Console.WriteLine($"No more penalties of type {penaltyType} for player with slot {slot}. Removing penalty type.");
penaltyDict.Remove(penaltyType); // Remove penalty type if no more penalties exist
}
}
else if (penalty.Duration == 0 || now < penalty.EndDateTime)
{
// Set endDateTime to the end time of this active penalty
endDateTime = penalty.EndDateTime;
//Console.WriteLine($"Player with slot {slot} is penalized for type {penaltyType}");
// Return true if there's an active penalty
return true;
}
}
// Return false if no active penalties are found
//Console.WriteLine($"Player with slot {slot} is not penalized for type {penaltyType}");
return false;
// Return false if no penalties of the specified type were found for the player
//Console.WriteLine($"No penalties found for player with slot {slot} and penalty type {penaltyType}");
}
/// <summary>
/// Retrieves all penalties for a player of a specific penalty type.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="penaltyType">The penalty type to retrieve.</param>
/// <returns>A list of penalties if found, otherwise an empty list.</returns>
// Get the end datetime and duration of penalties for a player and penalty type
public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, PenaltyType penaltyType)
{
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
@@ -103,35 +87,6 @@ public static class PlayerPenaltyManager
return [];
}
/// <summary>
/// Retrieves all penalties for a player across multiple penalty types.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="penaltyType">A list of penalty types to retrieve.</param>
/// <returns>A combined list of penalties of all requested types.</returns>
public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, List<PenaltyType> penaltyType)
{
List<(DateTime EndDateTime, int Duration, bool Passed)> result = [];
if (Penalties.TryGetValue(slot, out var penaltyDict))
{
foreach (var type in penaltyType)
{
if (penaltyDict.TryGetValue(type, out var penaltiesList))
{
result.AddRange(penaltiesList);
}
}
}
return result;
}
/// <summary>
/// Retrieves all penalties for a player across all penalty types.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <returns>A dictionary with penalty types as keys and lists of penalties as values.</returns>
public static Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetAllPlayerPenalties(int slot)
{
// Check if the player has any penalties in the dictionary
@@ -142,20 +97,12 @@ public static class PlayerPenaltyManager
new Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>();
}
/// <summary>
/// Checks if a given slot has any penalties assigned.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <returns>True if the player has any penalties, false otherwise.</returns>
public static bool IsSlotInPenalties(int slot)
{
return Penalties.ContainsKey(slot);
}
/// <summary>
/// Removes all penalties assigned to a specific player slot.
/// </summary>
/// <param name="slot">The player slot.</param>
// Remove all penalties for a player slot
public static void RemoveAllPenalties(int slot)
{
if (Penalties.ContainsKey(slot))
@@ -164,19 +111,13 @@ public static class PlayerPenaltyManager
}
}
/// <summary>
/// Removes all penalties for all players.
/// </summary>
// Remove all penalties
public static void RemoveAllPenalties()
{
Penalties.Clear();
}
/// <summary>
/// Removes all penalties of a specific type from a player.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="penaltyType">The penalty type to remove.</param>
// Remove all penalties of a selected type from a specific player
public static void RemovePenaltiesByType(int slot, PenaltyType penaltyType)
{
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
@@ -186,11 +127,6 @@ public static class PlayerPenaltyManager
}
}
/// <summary>
/// Marks penalties with a specific end datetime as "passed" for a player.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="dateTime">The end datetime of penalties to mark as passed.</param>
public static void RemovePenaltiesByDateTime(int slot, DateTime dateTime)
{
if (!Penalties.TryGetValue(slot, out var penaltyDict)) return;
@@ -212,13 +148,7 @@ public static class PlayerPenaltyManager
}
}
/// <summary>
/// Removes or expires penalties automatically across all players based on their duration or "passed" flag.
/// </summary>
/// <remarks>
/// If <c>TimeMode == 0</c>, penalties are considered passed manually and are removed if flagged as such.
/// Otherwise, expired penalties are removed based on the current datetime compared with their end time.
/// </remarks>
// Remove all expired penalties for all players and penalty types
public static void RemoveExpiredPenalties()
{
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0)
@@ -244,11 +174,13 @@ public static class PlayerPenaltyManager
var now = Time.ActualDateTime();
foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating
{
// Remove expired penalties for the player
foreach (var penaltiesList in penaltyDict.Values)
{
penaltiesList.RemoveAll(p => p.Duration > 0 && now >= p.EndDateTime);
}
// Remove player slot if no penalties left
if (penaltyDict.Count == 0)
{
Penalties.TryRemove(playerSlot, out _);

View File

@@ -9,94 +9,67 @@ public class ServerManager
{
private int _getIpTryCount;
/// <summary>
/// Checks whether the server setting <c>sv_hibernate_when_empty</c> is enabled.
/// Logs an error if this setting is true, since it prevents the plugin from working properly.
/// </summary>
public static void CheckHibernationStatus()
{
var convar = ConVar.Find("sv_hibernate_when_empty");
if (convar == null || !convar.GetPrimitiveValue<bool>())
return;
CS2_SimpleAdmin._logger?.LogError("Detected setting \"sv_hibernate_when_empty true\", set false to make plugin work properly");
}
/// <summary>
/// Initiates the asynchronous process to load server data such as IP address, port, hostname, and RCON password.
/// Handles retry attempts if IP address is not immediately available.
/// Updates or inserts the server record in the database accordingly.
/// After loading, triggers admin reload and cache initialization.
/// Also optionally sends plugin usage metrics if enabled in configuration.
/// </summary>
public void LoadServerData()
{
CS2_SimpleAdmin.Instance.AddTimer(2.0f, () =>
{
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
if (_getIpTryCount > 32 && Helper.GetServerIp().StartsWith("0.0.0.0") || string.IsNullOrEmpty(Helper.GetServerIp()))
{
CS2_SimpleAdmin._logger?.LogError("Unable to load server data - can't fetch ip address!");
return;
}
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.ServerId != null || CS2_SimpleAdmin.Database == null) return;
var ipAddress = ConVar.Find("ip")?.StringValue;
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
{
ipAddress = Helper.GetServerIp();
}
if (_getIpTryCount <= 32 && (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")))
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
{
if (_getIpTryCount < 12)
{
_getIpTryCount++;
LoadServerData();
return;
}
}
var address = $"{ipAddress}:{ConVar.Find("hostport")?.GetPrimitiveValue<int>()}";
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var rconPassword = ConVar.Find("rcon_password")?.StringValue ?? "";
var hostname = ConVar.Find("hostname")!.StringValue;
CS2_SimpleAdmin.IpAddress = address;
Task.Run(async () =>
{
try
{
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
int? serverId = await connection.ExecuteScalarAsync<int?>(
"SELECT id FROM sa_servers WHERE address = @address",
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
var addressExists = await connection.ExecuteScalarAsync<bool>(
"SELECT COUNT(*) FROM sa_servers WHERE address = @address",
new { address });
if (serverId == null)
if (!addressExists)
{
await connection.ExecuteAsync(
"INSERT INTO sa_servers (address, hostname, rcon_password) VALUES (@address, @hostname, @rconPassword)",
new { address, hostname, rconPassword });
serverId = await connection.ExecuteScalarAsync<int>(
"SELECT id FROM sa_servers WHERE address = @address",
new { address });
"INSERT INTO sa_servers (address, hostname) VALUES (@address, @hostname)",
new { address, hostname });
}
else
{
await connection.ExecuteAsync(
"UPDATE sa_servers SET hostname = @hostname, rcon_password = @rconPassword WHERE address = @address",
new { address, hostname, rconPassword });
"UPDATE `sa_servers` SET `hostname` = @hostname, `id` = `id` WHERE `address` = @address",
new { address, hostname });
}
int? serverId = await connection.ExecuteScalarAsync<int>(
"SELECT `id` FROM `sa_servers` WHERE `address` = @address",
new { address });
CS2_SimpleAdmin.ServerId = serverId;
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
if (CS2_SimpleAdmin.ServerId != null)
{
await Server.NextWorldUpdateAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
await Server.NextFrameAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
}
CS2_SimpleAdmin.ServerLoaded = true;
if (CS2_SimpleAdmin.Instance.CacheManager != null)
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
}
catch (Exception ex)
{
@@ -110,7 +83,7 @@ public class ServerManager
try
{
await client.GetAsync($"https://api.daffyy.dev/index.php{queryString}");
await client.GetAsync($"https://api.daffyy.love/index.php{queryString}");
}
catch (HttpRequestException ex)
{

View File

@@ -1,37 +1,30 @@
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdminApi;
using CS2_SimpleAdminApi;
using Dapper;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin.Managers;
internal class WarnManager(IDatabaseProvider? databaseProvider)
internal class WarnManager(Database.Database? database)
{
/// <summary>
/// Adds a warning to a player with an optional issuer and reason.
/// </summary>
/// <param name="player">The player who is being warned.</param>
/// <param name="issuer">The player issuing the warning; null indicates console or system.</param>
/// <param name="reason">The reason for the warning.</param>
/// <param name="time">Optional duration of the warning in minutes (0 means permanent).</param>
/// <returns>The identifier of the inserted warning, or null if the operation failed.</returns>
public async Task<int?> WarnPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
public async Task WarnPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
if (database == null) return;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddWarnQuery(true);
await using var connection = await database.GetConnectionAsync();
const string sql =
"INSERT INTO `sa_warns` (`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " +
"VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @serverid)";
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
await connection.ExecuteAsync(sql, new
{
playerSteamid = player.SteamId.SteamId64,
playerSteamid = player.SteamId.SteamId64.ToString(),
playerName = player.Name,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
@@ -39,39 +32,28 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
return warnId;
}
catch
{
return null;
}
catch { };
}
/// <summary>
/// Adds a warning to a player identified by SteamID with optional issuer and reason.
/// </summary>
/// <param name="playerSteamId">The SteamID64 of the player being warned.</param>
/// <param name="issuer">The player issuing the warning; null indicates console or system.</param>
/// <param name="reason">The reason for the warning.</param>
/// <param name="time">Optional duration of the warning in minutes (0 means permanent).</param>
/// <returns>The identifier of the inserted warning, or null if the operation failed.</returns>
public async Task<int?> AddWarnBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
public async Task AddWarnBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
if (database == null) return;
if (string.IsNullOrEmpty(playerSteamId)) return;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddWarnQuery(false);
await using var connection = await database.GetConnectionAsync();
const string sql = "INSERT INTO `sa_warns` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " +
"VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @serverid)";
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
await connection.ExecuteAsync(sql, new
{
playerSteamid = playerSteamId,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminSteamid = issuer?.SteamId.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
@@ -79,31 +61,34 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
return warnId;
}
catch
{
return null;
}
catch { };
}
/// <summary>
/// Retrieves a list of warnings for a specific player.
/// </summary>
/// <param name="player">The player whose warnings to retrieve.</param>
/// <param name="active">If true, returns only active (non-expired) warnings; otherwise returns all warnings.</param>
/// <returns>A list of dynamic objects representing warnings, or an empty list if none found or on failure.</returns>
public async Task<List<dynamic>> GetPlayerWarns(PlayerInfo player, bool active = true)
{
if (databaseProvider == null) return [];
if (database == null) return [];
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var sql = databaseProvider.GetPlayerWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
var parameters = new { PlayerSteamID = player.SteamId.SteamId64, serverid = CS2_SimpleAdmin.ServerId };
string sql;
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
sql = active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID ORDER BY id DESC";
}
else
{
sql = active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid ORDER BY id DESC";
}
var parameters = new { PlayerSteamID = player.SteamId.SteamId64.ToString(), serverid = CS2_SimpleAdmin.ServerId };
var warns = await connection.QueryAsync<dynamic>(sql, parameters);
return warns.ToList();
@@ -114,23 +99,24 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Retrieves the count of warnings for a player specified by SteamID.
/// </summary>
/// <param name="steamId">The SteamID64 of the player.</param>
/// <param name="active">If true, counts only active (non-expired) warnings; otherwise counts all warnings.</param>
/// <returns>The count of warnings as an integer, or 0 if none found or on failure.</returns>
public async Task<int> GetPlayerWarnsCount(ulong steamId, bool active = true)
public async Task<int> GetPlayerWarnsCount(string steamId, bool active = true)
{
if (databaseProvider == null) return 0;
if (database == null) return 0;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var sql = databaseProvider.GetPlayerWarnsCountQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
var warnsCount = await connection.ExecuteScalarAsync<int>(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
return warnsCount;
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID"
: active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID'";
var muteCount = await connection.ExecuteScalarAsync<int>(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
return muteCount;
}
catch (Exception)
{
@@ -138,22 +124,19 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Removes a specific warning by its identifier from a player's record.
/// </summary>
/// <param name="player">The player whose warning will be removed.</param>
/// <param name="warnId">The identifier of the warning to remove.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UnwarnPlayer(PlayerInfo player, int warnId)
{
if (databaseProvider == null) return;
if (database == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var sql = databaseProvider.GetUnwarnByIdQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { steamid = player.SteamId.SteamId64, warnId, serverid = CS2_SimpleAdmin.ServerId });
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId AND server_id = @serverid";
await connection.ExecuteAsync(sql, new { steamid = player.SteamId.SteamId64.ToString(), warnId, serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)
{
@@ -161,41 +144,38 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
}
}
/// <summary>
/// Removes the most recent warning matching a player pattern (usually SteamID string).
/// </summary>
/// <param name="playerPattern">The pattern identifying the player whose last warning should be removed.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UnwarnPlayer(string playerPattern)
{
if (databaseProvider == null) return;
if (database == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = (SELECT MAX(id) FROM sa_warns WHERE player_steamid = @steamid AND status = 'ACTIVE')"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = (SELECT MAX(id) FROM sa_warns WHERE player_steamid = @steamid AND status = 'ACTIVE' AND server_id = @serverid)";
var sql = databaseProvider.GetUnwarnLastQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { steamid = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove last warn {exception}", ex.Message);
CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove last warn + {ex}");
}
}
/// <summary>
/// Expires old warnings based on the current time, removing or marking them as inactive.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task ExpireOldWarns()
{
if (databaseProvider == null) return;
if (database == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
await using var connection = await database.GetConnectionAsync();
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid";
var sql = databaseProvider.GetExpireWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)

View File

@@ -1,15 +1,14 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Menu;
namespace CS2_SimpleAdmin.Menus;
public static class AdminMenu
{
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
public static IMenu? CreateMenu(string title)
{
return Helper.CreateMenu(title, backAction);
return Helper.CreateMenu(title);
// return CS2_SimpleAdmin.Instance.Config.UseChatMenu ? new ChatMenu(title) : new CenterHtmlMenu(title, CS2_SimpleAdmin.Instance);
}
@@ -33,7 +32,7 @@ public static class AdminMenu
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
@@ -45,9 +44,9 @@ public static class AdminMenu
var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
List<ChatMenuOptionData> options =
[
new(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
new(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
new(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
new ChatMenuOptionData(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
new ChatMenuOptionData(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
new ChatMenuOptionData(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
];
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
@@ -56,7 +55,7 @@ public static class AdminMenu
options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
}
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
if (AdminManager.PlayerHasPermissions(admin, "@css/root"))
options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
foreach (var menuOptionData in options)

View File

@@ -1,7 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Menus;
@@ -13,7 +12,7 @@ public static class CustomCommandsMenu
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
@@ -28,7 +27,7 @@ public static class CustomCommandsMenu
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
options.AddRange(from customCommand in customCommands
where !string.IsNullOrEmpty(customCommand.DisplayName) && !string.IsNullOrEmpty(customCommand.Command)
let hasRights = AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), customCommand.Flag)
let hasRights = AdminManager.PlayerHasPermissions(admin, customCommand.Flag)
where hasRights
select new ChatMenuOptionData(customCommand.DisplayName, () =>
{

View File

@@ -1,6 +1,5 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
namespace CS2_SimpleAdmin.Menus;
@@ -37,7 +36,7 @@ public static class FunActionsMenu
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
@@ -55,45 +54,45 @@ public static class FunActionsMenu
// options added in order
if (AdminManager.CommandIsOverriden("css_god")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_god"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_god"))
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
if (AdminManager.CommandIsOverriden("css_noclip")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_noclip"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_noclip"))
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
if (AdminManager.CommandIsOverriden("css_respawn")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_respawn"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_respawn"))
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
if (AdminManager.CommandIsOverriden("css_give")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_give"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_give"))
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
if (AdminManager.CommandIsOverriden("css_strip")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_strip"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_strip"))
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
if (AdminManager.CommandIsOverriden("css_freeze")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_freeze"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_freeze"))
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
if (AdminManager.CommandIsOverriden("css_hp")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_hp"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_hp"))
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
if (AdminManager.CommandIsOverriden("css_speed")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_speed"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_speed"))
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
if (AdminManager.CommandIsOverriden("css_gravity")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gravity"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gravity"))
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
if (AdminManager.CommandIsOverriden("css_money")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_money"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_money"))
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
foreach (var menuOptionData in options)
@@ -144,10 +143,10 @@ public static class FunActionsMenu
private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
{
if (!(player.PlayerPawn.Value?.IsValid ?? false))
if (!(player?.PlayerPawn.Value?.IsValid ?? false))
return;
if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_INVALID)
if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_OBSOLETE)
CS2_SimpleAdmin.Freeze(admin, player, -1);
else
CS2_SimpleAdmin.Unfreeze(admin, player);

View File

@@ -1,6 +1,5 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Menus;
@@ -12,7 +11,7 @@ public static class ManageAdminsMenu
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root") == false)
if (AdminManager.PlayerHasPermissions(admin, "@css/root") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
@@ -24,12 +23,12 @@ public static class ManageAdminsMenu
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_admins_manage"] ?? "Admins Manage");
List<ChatMenuOptionData> options =
[
new(localizer?["sa_admin_add"] ?? "Add Admin",
new ChatMenuOptionData(localizer?["sa_admin_add"] ?? "Add Admin",
() => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_admin_add"] ?? "Add Admin", AddAdminMenu)),
new(localizer?["sa_admin_remove"] ?? "Remove Admin",
new ChatMenuOptionData(localizer?["sa_admin_remove"] ?? "Remove Admin",
() => PlayersMenu.OpenAdminPlayersMenu(admin, localizer?["sa_admin_remove"] ?? "Remove Admin", RemoveAdmin,
player => player != admin && admin.CanTarget(player))),
new(localizer?["sa_admin_reload"] ?? "Reload Admins", () => ReloadAdmins(admin))
new ChatMenuOptionData(localizer?["sa_admin_reload"] ?? "Reload Admins", () => ReloadAdmins(admin))
];
foreach (var menuOptionData in options)

View File

@@ -1,6 +1,5 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Utils;
using CS2_SimpleAdminApi;
@@ -14,7 +13,7 @@ public static class ManagePlayersMenu
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
@@ -27,10 +26,10 @@ public static class ManagePlayersMenu
List<ChatMenuOptionData> options = [];
// permissions
var hasSlay = AdminManager.CommandIsOverriden("css_slay") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_slay")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay");
var hasKick = AdminManager.CommandIsOverriden("css_kick") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_kick")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick");
var hasBan = AdminManager.CommandIsOverriden("css_ban") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_ban")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/ban");
var hasChat = AdminManager.CommandIsOverriden("css_gag") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat");
var hasSlay = AdminManager.CommandIsOverriden("css_slay") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_slay")) : AdminManager.PlayerHasPermissions(admin, "@css/slay");
var hasKick = AdminManager.CommandIsOverriden("css_kick") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_kick")) : AdminManager.PlayerHasPermissions(admin, "@css/kick");
var hasBan = AdminManager.CommandIsOverriden("css_ban") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_ban")) : AdminManager.PlayerHasPermissions(admin, "@css/ban");
var hasChat = AdminManager.CommandIsOverriden("css_gag") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gag")) : AdminManager.PlayerHasPermissions(admin, "@css/chat");
// TODO: Localize options
// options added in order
@@ -47,8 +46,8 @@ public static class ManagePlayersMenu
}
if (AdminManager.CommandIsOverriden("css_warn")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_warn"))
: AdminManager.PlayerHasPermissions(admin, "@css/kick"))
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, WarnMenu))));
if (hasBan)
@@ -57,22 +56,22 @@ public static class ManagePlayersMenu
if (hasChat)
{
if (AdminManager.CommandIsOverriden("css_gag")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gag"))
: AdminManager.PlayerHasPermissions(admin, "@css/chat"))
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, GagMenu))));
if (AdminManager.CommandIsOverriden("css_mute")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_mute"))
: AdminManager.PlayerHasPermissions(admin, "@css/chat"))
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player, MuteMenu))));
if (AdminManager.CommandIsOverriden("css_silence")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_silence"))
: AdminManager.PlayerHasPermissions(admin, "@css/chat"))
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, SilenceMenu))));
}
if (AdminManager.CommandIsOverriden("css_team")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_team"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_team"))
: AdminManager.PlayerHasPermissions(admin, "@css/kick"))
options.Add(new ChatMenuOptionData(localizer?["sa_team_force"] ?? "Force Team", () => PlayersMenu.OpenMenu(admin, localizer?["sa_team_force"] ?? "Force Team", ForceTeamMenu)));
foreach (var menuOptionData in options)
@@ -90,12 +89,12 @@ public static class ManagePlayersMenu
List<ChatMenuOptionData> options =
[
// options added in order
new("0 hp", () => ApplySlapAndKeepMenu(admin, player, 0)),
new("1 hp", () => ApplySlapAndKeepMenu(admin, player, 1)),
new("5 hp", () => ApplySlapAndKeepMenu(admin, player, 5)),
new("10 hp", () => ApplySlapAndKeepMenu(admin, player, 10)),
new("50 hp", () => ApplySlapAndKeepMenu(admin, player, 50)),
new("100 hp", () => ApplySlapAndKeepMenu(admin, player, 100)),
new ChatMenuOptionData("0 hp", () => ApplySlapAndKeepMenu(admin, player, 0)),
new ChatMenuOptionData("1 hp", () => ApplySlapAndKeepMenu(admin, player, 1)),
new ChatMenuOptionData("5 hp", () => ApplySlapAndKeepMenu(admin, player, 5)),
new ChatMenuOptionData("10 hp", () => ApplySlapAndKeepMenu(admin, player, 10)),
new ChatMenuOptionData("50 hp", () => ApplySlapAndKeepMenu(admin, player, 50)),
new ChatMenuOptionData("100 hp", () => ApplySlapAndKeepMenu(admin, player, 100)),
];
foreach (var menuOptionData in options)
@@ -149,7 +148,7 @@ public static class ManagePlayersMenu
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
CS2_SimpleAdmin.Instance.Kick(admin, player, reason);
}
internal static void BanMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
@@ -159,8 +158,6 @@ public static class ManagePlayersMenu
{
if (player is { IsValid: true })
Ban(admin, player, duration, reason);
CS2_SimpleAdmin.MenuApi?.CloseMenu(admin);
});
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player?.PlayerName}");
@@ -181,7 +178,7 @@ public static class ManagePlayersMenu
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason);
}
private static void WarnMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
@@ -211,7 +208,7 @@ public static class ManagePlayersMenu
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason);
}
internal static void GagMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
@@ -312,10 +309,10 @@ public static class ManagePlayersMenu
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_team_force"] ?? "Force Team"} {player.PlayerName}");
List<ChatMenuOptionData> options =
[
new(CS2_SimpleAdmin._localizer?["sa_team_ct"] ?? "CT", () => ForceTeam(admin, player, "ct", CsTeam.CounterTerrorist)),
new(CS2_SimpleAdmin._localizer?["sa_team_t"] ?? "T", () => ForceTeam(admin, player, "t", CsTeam.Terrorist)),
new(CS2_SimpleAdmin._localizer?["sa_team_swap"] ?? "Swap", () => ForceTeam(admin, player, "swap", CsTeam.Spectator)),
new(CS2_SimpleAdmin._localizer?["sa_team_spec"] ?? "Spec", () => ForceTeam(admin, player, "spec", CsTeam.Spectator)),
new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_ct"] ?? "CT", () => ForceTeam(admin, player, "ct", CsTeam.CounterTerrorist)),
new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_t"] ?? "T", () => ForceTeam(admin, player, "t", CsTeam.Terrorist)),
new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_swap"] ?? "Swap", () => ForceTeam(admin, player, "swap", CsTeam.Spectator)),
new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_spec"] ?? "Spec", () => ForceTeam(admin, player, "spec", CsTeam.Spectator)),
];
foreach (var menuOptionData in options)

View File

@@ -1,6 +1,5 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Menus;
@@ -12,7 +11,7 @@ public static class ManageServerMenu
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
@@ -24,9 +23,10 @@ public static class ManageServerMenu
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_server_manage"] ?? "Server Manage");
List<ChatMenuOptionData> options = [];
// permissions
var hasMap = AdminManager.CommandIsOverriden("css_map") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_map")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/changemap");
var hasPlugins = AdminManager.CommandIsOverriden("css_pluginsmanager") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_pluginsmanager")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root");
var hasMap = AdminManager.CommandIsOverriden("css_map") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_map")) : AdminManager.PlayerHasPermissions(admin, "@css/changemap");
var hasPlugins = AdminManager.CommandIsOverriden("css_pluginsmanager") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_pluginsmanager")) : AdminManager.PlayerHasPermissions(admin, "@css/root");
//bool hasMap = AdminManager.PlayerHasPermissions(admin, "@css/changemap");

View File

@@ -18,12 +18,12 @@ public static class PlayersMenu
public static void OpenAliveMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
{
OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE);
OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive);
}
public static void OpenDeadMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController?, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
{
OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE);
OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive == false);
}
public static void OpenMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)

View File

@@ -46,7 +46,7 @@ public static class ReasonMenu
{
menu?.AddMenuOption(reason, (_, _) => onSelectAction(admin, player, reason));
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
}

View File

@@ -1,39 +0,0 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
namespace CS2_SimpleAdmin.Models;
public enum BanStatus
{
[Description("ACTIVE")] ACTIVE,
[Description("UNBANNED")] UNBANNED,
[Description("EXPIRED")] EXPIRED,
[Description("")] UNKNOWN
}
public record BanRecord
{
[Column("id")]
public int Id { get; init; }
[Column("player_name")]
public string? PlayerName { get; set; }
[Column("player_steamid")]
public ulong? PlayerSteamId { get; set; }
[Column("player_ip")]
public string? PlayerIp { get; set; }
[Column("status")]
public required string Status { get; init; }
[NotMapped]
public BanStatus StatusEnum => Status.ToUpper() switch
{
"ACTIVE" => BanStatus.ACTIVE,
"UNBANNED" => BanStatus.UNBANNED,
"EXPIRED" => BanStatus.EXPIRED,
_ => BanStatus.UNKNOWN
};
}

View File

@@ -1,3 +0,0 @@
namespace CS2_SimpleAdmin.Models;
public readonly record struct IpRecord(uint Ip, DateTime UsedAt, string PlayerName);

View File

@@ -1,12 +0,0 @@
namespace CS2_SimpleAdmin.Models;
public record PlayerStats(int Score, int Kills, int Deaths, int MVPs);
public record PlayerDto(
int UserId,
string Name,
string SteamId,
string IpAddress,
uint Ping,
bool IsAdmin,
PlayerStats Stats
);

View File

@@ -1 +1 @@
1.7.7-alpha-9
1.6.7a

View File

@@ -7,9 +7,7 @@ using MenuManager;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Managers;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace CS2_SimpleAdmin;
@@ -17,13 +15,12 @@ public partial class CS2_SimpleAdmin
{
// Config
public CS2_SimpleAdminConfig Config { get; set; } = new();
// HttpClient
internal static readonly HttpClient HttpClient = new();
// Paths
internal static readonly string ConfigDirectory =
Path.Combine(Application.RootDirectory, "configs/plugins/CS2-SimpleAdmin");
internal static readonly string ConfigDirectory = Path.Combine(Application.RootDirectory, "configs/plugins/CS2-SimpleAdmin");
// Localization
public static IStringLocalizer? _localizer;
@@ -35,17 +32,16 @@ public partial class CS2_SimpleAdmin
// Command and Server Settings
public static readonly bool UnlockedCommands = CoreConfig.UnlockConCommands;
internal static string IpAddress = string.Empty;
internal static bool ServerLoaded;
internal static int? ServerId = null;
public static bool ServerLoaded;
public static int? ServerId = null;
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
// Player Management
private static readonly HashSet<int> GodPlayers = [];
internal static readonly HashSet<int> SilentPlayers = [];
private static readonly HashSet<int> SilentPlayers = [];
internal static readonly ConcurrentBag<string?> BannedPlayers = [];
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
internal static readonly List<CCSPlayerController> CachedPlayers = [];
internal static readonly List<CCSPlayerController> BotPlayers = [];
internal static readonly Dictionary<int, PlayerInfo> PlayersInfo = [];
private static readonly List<DisconnectedPlayer> DisconnectedPlayers = [];
// Discord Integration
@@ -53,35 +49,24 @@ public partial class CS2_SimpleAdmin
// Database Settings
internal string DbConnectionString = string.Empty;
// internal static Database.Database? Database;
internal static IDatabaseProvider? DatabaseProvider;
internal static Database.Database? Database;
// Logger
internal static ILogger? _logger;
// Memory Function (Game-related)
private static MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>?
_cBasePlayerControllerSetPawnFunc;
private static MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>? _cBasePlayerControllerSetPawnFunc;
// Menu API and Capabilities
internal static IMenuApi? MenuApi;
private static readonly PluginCapability<IMenuApi> MenuCapability = new("menu:nfcore");
// Shared API
internal static Api.CS2_SimpleAdminApi? SimpleAdminApi { get; private set; }
// Managers
internal PermissionManager PermissionManager = new(DatabaseProvider);
internal BanManager BanManager = new(DatabaseProvider);
internal MuteManager MuteManager = new(DatabaseProvider);
internal WarnManager WarnManager = new(DatabaseProvider);
internal CacheManager? CacheManager = new();
private static readonly PlayerManager PlayerManager = new();
// Timers
internal Timer? PlayersTimer = null;
private Api.CS2_SimpleAdminApi? SimpleAdminApi { get; set; }
// Funny list
private readonly List<string> _requiredPlugins = ["MenuManagerCore", "PlayerSettings"];
private readonly List<string> _requiredShared = ["MenuManagerApi", "PlayerSettingsApi", "AnyBaseLib", "CS2-SimpleAdminApi"];
// Managers
internal PermissionManager PermissionManager = new(Database);
internal BanManager BanManager = new(Database);
internal MuteManager MuteManager = new(Database);
internal WarnManager WarnManager = new(Database);
}

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "التحذير مسجل",
"sa_discord_penalty_unknown": "غير معروف مسجل",
"sa_player_penalty_chat_active": "{lightred}تم حظر الدردشة الخاصة بك إلى: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ كتم [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ صمت [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ سكوت [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} غيّر الجاذبية لـ {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} غيّر المال لـ {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} غيّر وضع الله لـ {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} قام بتغيير الحجم لـ {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} قتل {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} صفع {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} غيّر الخريطة إلى {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} بدأ التصويت لـ {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}نتائج التصويت لـ {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}لقد قمت بكتم {default}جميع اللاعبين أثناء حديثك",
"sa_admin_voice_unmute_all": "{Lime}لقد ألغيت كتم {default}جميع اللاعبين",
"sa_admin_voice_listen_all": "{Default}أنت الآن تسمع {lime}جميع {default}اللاعبين",
"sa_admin_voice_unlisten_all": "{Default}أنت الآن لا تسمع {lime}جميع {default}اللاعبين",
"sa_adminsay_prefix": "{RED}الإداري: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(إداري) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(لاعب) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** أصدر الأمر `{1}` على الخادم `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Warnung registriert",
"sa_discord_penalty_unknown": "Unbekanntes registriert",
"sa_player_penalty_chat_active": "{lightred}Dein Chat ist blockiert für: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Stummschaltung [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Mundtot [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Stille [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} hat die Schwerkraft von {lightred}{1}{default} geändert!",
"sa_admin_money_message": "{lightred}{0}{default} hat das Geld von {lightred}{1}{default} geändert!",
"sa_admin_god_message": "{lightred}{0}{default} hat den Gottmodus von {lightred}{1}{default} geändert!",
"sa_admin_resize_message": "{lightred}{0}{default} hat die Größe für {lightred}{1}{default} geändert!",
"sa_admin_slay_message": "{lightred}{0}{default} hat {lightred}{1}{default} getötet!",
"sa_admin_slap_message": "{lightred}{0}{default} hat {lightred}{1}{default} geschlagen!",
"sa_admin_changemap_message": "{lightred}{0}{default} hat die Karte zu {lightred}{1}{default} geändert!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} hat eine Abstimmung für {lightred}{1}{default} gestartet",
"sa_admin_vote_message_results": "{lime}ABSTIMMUNGSERGEBNISSE FÜR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Du hast {default}alle Spieler während deiner Ansprache stummgeschaltet",
"sa_admin_voice_unmute_all": "{Lime}Du hast {default}alle Spieler wieder aktiviert",
"sa_admin_voice_listen_all": "{Default}Du hörst jetzt {lime}alle {default}Spieler",
"sa_admin_voice_unlisten_all": "{Default}Du hörst jetzt {lime}nicht mehr {default}alle Spieler",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(SPIELER) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** hat den Befehl `{1}` auf dem Server `HOSTNAME` ausgeführt",

View File

@@ -63,8 +63,6 @@
"sa_discord_penalty_silence": "Silence registered",
"sa_discord_penalty_warn": "Warn registered",
"sa_discord_penalty_unknown": "Unknown registered",
"sa_player_penalty_chat_active": "{lightred}Your chat is blocked to: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} changed gravity for {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} changed money for {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} changed size for {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} slayed {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} slapped {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} changed map to {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} started voting for {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}VOTING RESULTS FOR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}You muted {default}all players during your speech",
"sa_admin_voice_unmute_all": "{Lime}You unmuted {default}all players",
"sa_admin_voice_listen_all": "{Default}You can now hear {lime}all {default}players",
"sa_admin_voice_unlisten_all": "{Default}You no longer hear {lime}all {default}players",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(PLAYER) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** issued command `{1}` on server `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Advertencia registrada",
"sa_discord_penalty_unknown": "Registro desconocido",
"sa_player_penalty_chat_active": "{lightred}Tu chat está bloqueado para: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Silenciado [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Boqueado [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silencio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} cambió la gravedad de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} cambió el dinero de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} cambió el modo dios de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} cambió el tamaño de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} mató a {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} abofeteó a {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} cambió el mapa a {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} inició una votación para {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RESULTADOS DE LA VOTACIÓN PARA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Has silenciado a {default}todos los jugadores durante tu discurso",
"sa_admin_voice_unmute_all": "{Lime}Has reactivado el sonido de {default}todos los jugadores",
"sa_admin_voice_listen_all": "{Default}Ahora puedes escuchar a {lime}todos {default}los jugadores",
"sa_admin_voice_unlisten_all": "{Default}Ya no escuchas a {lime}todos {default}los jugadores",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JUGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** ejecutó el comando `{1}` en el servidor `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "هشدار ثبت شد",
"sa_discord_penalty_unknown": "ناشناخته انجام شده",
"sa_player_penalty_chat_active": "{lightred}چت شما برای: {grey}{0} مسدود شده است",
"sa_player_penalty_info_active_mute": "➔ بی‌صدا [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ مسدود کردن صدا [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ سکوت [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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} اخراج شده‌اید!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} جاذبه {lightred}{1}{default} را تغییر داد!",
"sa_admin_money_message": "{lightred}{0}{default} پول {lightred}{1}{default} را تغییر داد!",
"sa_admin_god_message": "{lightred}{0}{default} حالت خدا را برای {lightred}{1}{default} تغییر داد!",
"sa_admin_resize_message": "{lightred}{0}{default} اندازه {lightred}{1}{default} را تغییر داد!",
"sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default} را کشت!",
"sa_admin_slap_message": "{lightred}{0}{default} به {lightred}{1}{default} سیلی زد!",
"sa_admin_changemap_message": "{lightred}{0}{default} نقشه را به {lightred}{1}{default} تغییر داد!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} رأی‌گیری برای {lightred}{1}{default} را شروع کرد",
"sa_admin_vote_message_results": "{lime}نتایج رأی‌گیری برای {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}شما {default}همه بازیکنان را در طول سخنرانی بی‌صدا کردید",
"sa_admin_voice_unmute_all": "{Lime}شما {default}همه بازیکنان را از حالت بی‌صدا خارج کردید",
"sa_admin_voice_listen_all": "{Default}اکنون می‌توانید {lime}همه {default}بازیکنان را بشنوید",
"sa_admin_voice_unlisten_all": "{Default}شما دیگر {lime}همه {default}بازیکنان را نمی‌شنوید",
"sa_adminsay_prefix": "{RED}ادمین: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ادمین) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(بازیکن) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** فرمان `{1}` را در سرور `HOSTNAME` اجرا کرد",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Avertissement enregistré",
"sa_discord_penalty_unknown": "Inconnu enregistré",
"sa_player_penalty_chat_active": "{lightred}Votre chat est bloqué pour : {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Muet [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silence [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} a modifié la gravité de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} a modifié l'argent de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} a modifié le mode dieu de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} a changé la taille de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} a tué {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} a giflé {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} a changé la carte pour {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} a lancé un vote pour {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RÉSULTATS DU VOTE POUR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Vous avez mis en sourdine {default}tous les joueurs pendant votre discours",
"sa_admin_voice_unmute_all": "{Lime}Vous avez réactivé le son de {default}tous les joueurs",
"sa_admin_voice_listen_all": "{Default}Vous entendez maintenant {lime}tous {default}les joueurs",
"sa_admin_voice_unlisten_all": "{Default}Vous n'entendez plus {lime}tous {default}les joueurs",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JOUEUR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** a exécuté la commande `{1}` sur le serveur `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Brīdinājums reģistrēts",
"sa_discord_penalty_unknown": "Nezināms reģistrēts",
"sa_player_penalty_chat_active": "{lightred}Jūsu čats ir bloķēts uz: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Izslēgts [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Klusums [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Klusēšana [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} gravitāciju!",
"sa_admin_money_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} naudu!",
"sa_admin_god_message": "{lightred}{0}{default} mainīja dieva režīmu priekš {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} mainīja izmēru {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} nogalināja {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} pa seju!",
"sa_admin_changemap_message": "{lightred}{0}{default} mainīja karti uz {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} uzsāka balsošanu par {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}BALSOŠANAS REZULTĀTI PAR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Tu esi apklusinājis {default}visus spēlētājus runas laikā",
"sa_admin_voice_unmute_all": "{Lime}Tu atvienoji {default}visus spēlētājus",
"sa_admin_voice_listen_all": "{Default}Tu tagad dzirdi {lime}visus {default}spēlētājus",
"sa_admin_voice_unlisten_all": "{Default}Tu vairs nedzirdi {lime}visus {default}spēlētājus",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(SPĒLĒTĀJS) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** izpildīja komandu `{1}` serverī `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Nowe ostrzeżenie",
"sa_discord_penalty_unknown": "Nowa nieznana blokada",
"sa_player_penalty_chat_active": "{lightred}Twój czat jest zablokowany do: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Zakneblowanie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Wyciszenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Uciszenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} zmienił grawitacje dla {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} zmienił pieniądze dla {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} zmienił tryb Boga dla {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} zmienił rozmiar dla {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} zgładził {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} uderzył {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} zmienił mapę na {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} rozpoczął głosowanie na {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}WYNIKI GŁOSOWANIA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}[{1}]",
"sa_admin_voice_mute_all": "{Lightred}Wyciszyłeś {default}wszystkich graczy na czas przemówienia",
"sa_admin_voice_unmute_all": "{Lime}Odciszyłeś {default}wszystkich graczy",
"sa_admin_voice_listen_all": "{Default}Słyszysz teraz {lime}wszystkich {default}graczy",
"sa_admin_voice_unlisten_all": "{Default}Nie słyszysz teraz {lime}wszystkich {default}graczy",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(GRACZ) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** użył komendy `{1}` na serwerze `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Aviso registrado",
"sa_discord_penalty_unknown": "Desconhecido registrado",
"sa_player_penalty_chat_active": "{lightred}Seu chat está bloqueado para: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mudo [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silêncio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} alterou o tamanho de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} matou {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} deu um tapa em {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} mudou o mapa para {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} iniciou uma votação para {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RESULTADOS DA VOTAÇÃO PARA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Você silenciou {default}todos os jogadores durante o seu discurso",
"sa_admin_voice_unmute_all": "{Lime}Você reativou o som de {default}todos os jogadores",
"sa_admin_voice_listen_all": "{Default}Agora você pode ouvir {lime}todos {default}os jogadores",
"sa_admin_voice_unlisten_all": "{Default}Você não ouve mais {lime}todos {default}os jogadores",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JOGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** executou o comando `{1}` no servidor `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Aviso registrado",
"sa_discord_penalty_unknown": "Desconhecido registrado",
"sa_player_penalty_chat_active": "{lightred}O seu chat está bloqueado para: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mudo [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silêncio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} alterou o tamanho de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} matou {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} deu um tapa em {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} mudou o mapa para {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} iniciou uma votação para {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RESULTADOS DA VOTAÇÃO PARA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Silenciaste {default}todos os jogadores durante o teu discurso",
"sa_admin_voice_unmute_all": "{Lime}Ativaste novamente o som de {default}todos os jogadores",
"sa_admin_voice_listen_all": "{Default}Agora consegues ouvir {lime}todos {default}os jogadores",
"sa_admin_voice_unlisten_all": "{Default}Já não ouves {lime}todos {default}os jogadores",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JOGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** executou o comando `{1}` no servidor `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Предупреждение зарегистрировано",
"sa_discord_penalty_unknown": "Неизвестно зарегистрировано",
"sa_player_penalty_chat_active": "{lightred}Ваш чат заблокирован для: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Мут [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Гэг [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Тишина [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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}!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} изменил гравитацию для {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} изменил количество денег у {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} изменил режим бога для {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} изменил размер {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} убил {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} дал пощечину {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} изменил карту на {lightred}{1}{default}!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} начал голосование за {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}РЕЗУЛЬТАТЫ ГОЛОСОВАНИЯ ЗА {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Вы заглушили {default}всех игроков во время своей речи",
"sa_admin_voice_unmute_all": "{Lime}Вы включили звук {default}всем игрокам",
"sa_admin_voice_listen_all": "{Default}Теперь вы слышите {lime}всех {default}игроков",
"sa_admin_voice_unlisten_all": "{Default}Вы больше не слышите {lime}всех {default}игроков",
"sa_adminsay_prefix": "{RED}АДМИН: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(ВИП ЧАТ) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(АДМИН) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(ИГРОК) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** выполнил команду `{1}` на сервере `HOSTNAME`",

View File

@@ -64,8 +64,6 @@
"sa_discord_penalty_warn": "Uyarı kaydedildi",
"sa_discord_penalty_unknown": "Bilinmeyen kaydedildi",
"sa_player_penalty_chat_active": "{lightred}Sohbetiniz şu kişiye engellendi: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Sessizlik [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]",
@@ -78,7 +76,6 @@
"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!",
@@ -108,7 +105,6 @@
"sa_admin_gravity_message": "{lightred}{0}{default} {lightred}{1}{default}'in yer çekimini değiştirdi!",
"sa_admin_money_message": "{lightred}{0}{default} {lightred}{1}{default}'in parasını değiştirdi!",
"sa_admin_god_message": "{lightred}{0}{default} {lightred}{1}{default}'in tanrı modunu değiştirdi!",
"sa_admin_resize_message": "{lightred}{0}{default} boyutu {lightred}{1}{default} için değiştirdi!",
"sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default}'i öldürdü!",
"sa_admin_slap_message": "{lightred}{0}{default} {lightred}{1}{default}'e tokat attı!",
"sa_admin_changemap_message": "{lightred}{0}{default} haritayı {lightred}{1}{default} olarak değiştirdi!",
@@ -126,11 +122,8 @@
"sa_admin_vote_message": "{lightred}{0}{default} {lightred}{1}{default} için oy kullanmaya başladı",
"sa_admin_vote_message_results": "{lime}OY SONUÇLARI {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Konuşman sırasında {default}tüm oyuncuları susturdun",
"sa_admin_voice_unmute_all": "{Lime}Tüm oyuncuların sesini {default}açtın",
"sa_admin_voice_listen_all": "{Default}Artık {lime}tüm {default}oyuncuları duyabiliyorsun",
"sa_admin_voice_unlisten_all": "{Default}Artık {lime}tüm {default}oyuncuları duymuyorsun",
"sa_adminsay_prefix": "{RED}Yönetici: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(Yönetici) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(Oyuncu) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** `{1}` komutunu `HOSTNAME` sunucusunda gerçekleştirdi",

View File

@@ -1,11 +1,11 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "未知",
"sa_no_permission": "您没有权限使用此命令。",
"sa_ban_max_duration_exceeded": "禁止时长不能超过 {lightred}{0}{default} 分钟。",
"sa_ban_perm_restricted": "您没有永久禁止权限。",
"sa_ban_max_duration_exceeded": "禁令持续时间不能超过{lightred}{0}{default}分钟。",
"sa_ban_perm_restricted": "您没有永久封禁的权限。",
"sa_admin_add": "添加管理员",
"sa_admin_remove": "移除管理员",
@@ -15,7 +15,7 @@
"sa_noclip": "穿墙模式",
"sa_respawn": "重生",
"sa_give_weapon": "给予武器",
"sa_strip_weapons": "移除武器",
"sa_strip_weapons": "剥夺武器",
"sa_freeze": "冻结",
"sa_set_hp": "设置生命值",
"sa_set_speed": "设置速度",
@@ -23,22 +23,22 @@
"sa_set_money": "设置金钱",
"sa_changemap": "更换地图",
"sa_restart_game": "重新开始游戏",
"sa_restart_game": "重游戏",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "交换",
"sa_team_spec": "观察者",
"sa_team_spec": "观",
"sa_slap": "掌掴",
"sa_slay": "杀",
"sa_slay": "杀",
"sa_kick": "踢出",
"sa_ban": "禁",
"sa_ban": "禁",
"sa_gag": "禁言",
"sa_mute": "静音",
"sa_silence": "沉默",
"sa_warn": "警告",
"sa_team_force": "强制队",
"sa_team_force": "强制队",
"sa_menu_custom_commands": "自定义命令",
"sa_menu_server_manage": "服务器管理",
@@ -47,91 +47,86 @@
"sa_menu_players_manage": "玩家管理",
"sa_menu_disconnected_title": "最近的玩家",
"sa_menu_disconnected_action_title": "选择操作",
"sa_menu_pluginsmanager_title": "插件管理",
"sa_menu_pluginsmanager_title": "管理插件",
"sa_player": "玩家",
"sa_console": "控制台",
"sa_steamid": "SteamID",
"sa_duration": "时长",
"sa_duration": "持续时间",
"sa_reason": "原因",
"sa_admin": "管理员",
"sa_permanent": "永久",
"sa_discord_penalty_ban": "禁止记录",
"sa_discord_penalty_mute": "静音记录",
"sa_discord_penalty_gag": "禁言记录",
"sa_discord_penalty_silence": "沉默记录",
"sa_discord_penalty_warn": "警告记录",
"sa_discord_penalty_unknown": "未知记录",
"sa_discord_penalty_ban": "封禁已记录",
"sa_discord_penalty_mute": "禁言已记录",
"sa_discord_penalty_gag": "禁言记录",
"sa_discord_penalty_silence": "禁声已记录",
"sa_discord_penalty_warn": "警告已注册",
"sa_discord_penalty_unknown": "未知记录",
"sa_player_penalty_info_active_mute": "➔ 静音 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
"sa_player_penalty_info_active_gag": "➔ 禁言 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
"sa_player_penalty_info_active_silence": "➔ 沉默 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
"sa_player_penalty_info_active_warn": "➔ 警告 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期 - 原因 [{lightred}{1}{default}]",
"sa_player_penalty_info_active_mute": "➔ 静音 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ 禁言 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ 沉默 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ 警告 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}] - 原因 [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ 静音 [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ 禁言 [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ 沉默 [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ 警告 [{lime}✔{default}]",
"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} 踢出!",
"sa_player_gag_message_time": "您已被 {lightred}{0}{default}{lightred}{2}{default} 禁言 {lightred}{1}{default} 分钟!",
"sa_player_gag_message_perm": "您已被 {lightred}{0}{default}{lightred}{1}{default} 永久禁言!",
"sa_player_mute_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 静音 {lightred}{1}{default} 分钟!",
"sa_player_mute_message_perm": "您已被 {lightred}{0}{default}{lightred}{1}{default} 永久静音!",
"sa_player_silence_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 沉默 {lightred}{1}{default} 分钟!",
"sa_player_silence_message_perm": "您已被 {lightred}{0}{default} {lightred}{1}{default} 永久沉默!",
"sa_player_warn_message_time": "您已被 {lightred}{0}{default} {lightred}{2}{default} 警告 {lightred}{1}{default} 分钟!",
"sa_player_warn_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久警告!",
"sa_admin_ban_message_time": "{lightred}{0}{default} 禁止了 {lightred}{1}{default} {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} 永久禁止了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} 踢出了 {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} 禁言了 {lightred}{1}{default} {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} 永久禁言了 {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} 静音了 {lightred}{1}{default} {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} 永久静音了 {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} 沉默了 {lightred}{1}{default} {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} 永久沉默了 {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} 警告了 {lightred}{1}{default} {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} 永久警告了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"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_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}踢出!",
"sa_player_gag_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁言{lightred}{1}{default}分钟!",
"sa_player_gag_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁言!",
"sa_player_mute_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁声{lightred}{1}{default}分钟!",
"sa_player_mute_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁声!",
"sa_player_silence_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁止发言{lightred}{1}{default}分钟!",
"sa_player_silence_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁止发言!",
"sa_player_warn_message_time": "您 {lightred}{0}{default} {lightred}{1}{default} 分钟内由 {lightred}{2}{default} 警告!",
"sa_player_warn_message_perm": "您 {lightred}{0}{default} {lightred}{1}{default} 永久警告!",
"sa_admin_ban_message_time": "{lightred}{0}{default} 因 {lightred}{1}{default} 被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被踢出!",
"sa_admin_gag_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 的声音被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 的声音被永久禁言 {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被警告 {lightred}{2}{default} {lightred}{3}{default} 分钟!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久警告 {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} 给了 {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} 移除了 {lightred}{1}{default} 的所有武器!",
"sa_admin_hp_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的生命值!",
"sa_admin_speed_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的速度!",
"sa_admin_gravity_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的重力!",
"sa_admin_money_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的金钱!",
"sa_admin_god_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的上帝模式!",
"sa_admin_resize_message": "{lightred}{0}{default} 更改了 {lightred}{1}{default} 的大小!",
"sa_admin_slay_message": "{lightred}{0}{default} 击杀了 {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} 掌掴了 {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} 将地图更换为 {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的穿墙模式!",
"sa_admin_strip_message": "{lightred}{0}{default} 拿走了 {lightred}{1}{default} 的所有武器!",
"sa_admin_hp_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的生命值!",
"sa_admin_speed_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的速度!",
"sa_admin_gravity_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的重力!",
"sa_admin_money_message": "{lightred}{0}{default} 改了 {lightred}{1}{default} 的金钱!",
"sa_admin_god_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的上帝模式!",
"sa_admin_slay_message": "{lightred}{0}{default} 杀死了 {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} 了 {lightred}{1}{default} 一巴掌!",
"sa_admin_changemap_message": "{lightred}{0}{default} 更改了地图为 {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} 为 {lightred}{1}{default} 切换了 noclip!",
"sa_admin_freeze_message": "{lightred}{0}{default} 冻结了 {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} 解冻了 {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} {lightred}{1}{default} 的昵称为 {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} 复活了 {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} 传送到 {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 传送到自己!",
"sa_admin_team_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 转移到 {lightred}{2}{default} 队伍!",
"sa_admin_rename_message": "{lightred}{0}{default} 更改了 {lightred}{1}{default} 的昵称为 {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} 重新生成了 {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} 传送到 {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 传送到自己身边!",
"sa_admin_team_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 转移到 {lightred}{2}{default} 队伍!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}警告",
"sa_admin_warns_unwarn": "{lime}成功移除警告{default} {gold}{0} 对于 {gold}{1}{default}!",
"sa_admin_warns_unwarn": "{lime}成功{default} 取消了对 {gold}{0}{default} 的警告 {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}投票 {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} 发起了 [{lightred}{1}{default}] 的投票",
"sa_admin_vote_message": "{lightred}{0}{default} 开始为 {lightred}{1}{default} 进行投票",
"sa_admin_vote_message_results": "{lime}投票结果 {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}你已在讲话期间静音了{default}所有玩家",
"sa_admin_voice_unmute_all": "{Lime}你已取消了{default}所有玩家的静音",
"sa_admin_voice_listen_all": "{Default}你现在可以听到{lime}所有{default}玩家了",
"sa_admin_voice_unlisten_all": "{Default}你现在不再听到{lime}所有{default}玩家了",
"sa_adminsay_prefix": "{RED}管理员: {lightred}{0}{default}",
"sa_vipchat_template": "{LIME}(VIP CHAT) {0}{default}: {1}",
"sa_adminchat_template_admin": "{LIME}(管理员) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(玩家) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** 在服务器 `HOSTNAME` 上执行了命令 `{1}`",
"sa_menu_pluginsmanager_loaded": "{lime}激活了 {default}插件 {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}停用了 {default}插件 {lightred}{0}"
}
"sa_discord_log_command": "**{0}** 在服务器 `HOSTNAME` 上发出了 `{1}` 命令",
"sa_menu_pluginsmanager_loaded": "{lime}已启用 {default}插件 {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}已禁用 {default}插件 {lightred}{0}"
}

View File

@@ -5,11 +5,10 @@
<RootNamespace>CS2_SimpleAdminApi</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.284" />
</ItemGroup>
</Project>

View File

@@ -9,97 +9,18 @@ public interface ICS2_SimpleAdminApi
{
public static readonly PluginCapability<ICS2_SimpleAdminApi?> PluginCapability = new("simpleadmin:api");
/// <summary>
/// Gets player information associated with the specified player controller.
/// </summary>
/// <param name="player">The player controller.</param>
/// <returns>PlayerInfo object representing player data.</returns>
public PlayerInfo GetPlayerInfo(CCSPlayerController player);
/// <summary>
/// Returns the database connection string used by the plugin.
/// </summary>
public string GetConnectionString();
/// <summary>
/// Returns the configured server IP address with port.
/// </summary>
public string GetServerAddress();
/// <summary>
/// Returns the internal server ID assigned in the plugin's database.
/// </summary>
public int? GetServerId();
/// <summary>
/// Returns mute-related penalties for the specified player.
/// </summary>
/// <param name="player">The player controller.</param>
/// <returns>A dictionary mapping penalty types to lists of penalties with end date, duration, and pass state.</returns>
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(CCSPlayerController player);
/// <summary>
/// Event fired when a player receives a penalty.
/// </summary>
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied;
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltied;
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltiedAdded;
/// <summary>
/// Event fired when a penalty is added to a player by SteamID.
/// </summary>
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded;
/// <summary>
/// Event to show admin activity messages.
/// </summary>
public event Action<string, string?, bool, object>? OnAdminShowActivity;
/// <summary>
/// Event fired when an admin toggles silent mode.
/// </summary>
public event Action<int, bool>? OnAdminToggleSilent;
/// <summary>
/// Issues a penalty to a player controller with specified type, reason, and optional duration.
/// </summary>
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1);
/// <summary>
/// Issues a penalty to a player identified by SteamID with specified type, reason, and optional duration.
/// </summary>
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1);
/// <summary>
/// Logs a command invoked by a caller with the command string.
/// </summary>
public void LogCommand(CCSPlayerController? caller, string command);
/// <summary>
/// Logs a command invoked by a caller with the command info object.
/// </summary>
public void LogCommand(CCSPlayerController? caller, CommandInfo command);
/// <summary>
/// Shows an admin activity message, optionally suppressing broadcasting.
/// </summary>
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs);
/// <summary>
/// Returns true if the specified admin player is in silent mode (not broadcasting activity).
/// </summary>
public bool IsAdminSilent(CCSPlayerController player);
/// <summary>
/// Returns a set of player slots representing admins currently in silent mode.
/// </summary>
public HashSet<int> ListSilentAdminsSlots();
/// <summary>
/// Registers a new command with the specified name, description, and callback.
/// </summary>
public void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback);
/// <summary>
/// Unregisters an existing command by its name.
/// </summary>
public void UnRegisterCommand(string name);
}

View File

@@ -1,5 +1,5 @@
using System.Numerics;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Utils;
namespace CS2_SimpleAdminApi;
@@ -25,16 +25,11 @@ public class PlayerInfo(
public int TotalGags { get; set; } = totalGags;
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; }
public bool IsLoaded { get; set; }
}
public class DiePosition(Vector3 position, Vector3 angle)
public struct DiePosition(Vector? position = null, QAngle? angle = null)
{
public Vector3 Position { get; } = position;
public Vector3 Angle { get; } = angle;
public Vector? Position { get; set; } = position;
public QAngle? Angle { get; set; } = angle;
}

View File

@@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>AntiDLL_CS2_SimpleAdmin</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.305" />
</ItemGroup>
<ItemGroup>
<Reference Include="AntiDLL.API">
<HintPath>AntiDLL.API.dll</HintPath>
</Reference>
<Reference Include="CS2-SimpleAdminApi">
<HintPath>CS2-SimpleAdminApi.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -1,16 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntiDLL-CS2-SimpleAdmin", "AntiDLL-CS2-SimpleAdmin.csproj", "{21D8E512-1FA9-41DD-B955-709704CEC377}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{21D8E512-1FA9-41DD-B955-709704CEC377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21D8E512-1FA9-41DD-B955-709704CEC377}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21D8E512-1FA9-41DD-B955-709704CEC377}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21D8E512-1FA9-41DD-B955-709704CEC377}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -1,4 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=L_003A_005CGITHUB_005CAntiDLL_002DCS2_002DSimpleAdmin_005CAntiDLL_002EAPI_002Edll/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=L_003A_005CGITHUB_005CAntiDLL_002DCS2_002DSimpleAdmin_005CCS2_002DSimpleAdminApi_002Edll/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AListeners_002Eg_002Ecs_002Fl_003AC_0021_003FUsers_003Fxdaff_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F713af59e56bd198a6c433c53fdfff3391c38c47a55afbc2f8954ef61b3213c7a_003FListeners_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@@ -1,153 +0,0 @@
namespace AntiDLL_CS2_SimpleAdmin;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdminApi;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using Microsoft.Extensions.Logging;
using AntiDLL.API;
public class PluginConfig : IBasePluginConfig
{
[JsonPropertyName("ConfigVersion")] public int Version { get; set; } = 1;
[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";
}
public sealed class AntiDLL_CS2_SimpleAdmin : BasePlugin, IPluginConfig<PluginConfig>
{
private int _banType;
public PluginConfig Config { get; set; } = new();
private readonly HashSet<int> _bannedPlayers = [];
private readonly HashSet<int> _detections = [];
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";
public override string ModuleAuthor => "daffyy";
public override void Load(bool hotReload)
{
RegisterListener<Listeners.OnClientDisconnect>(OnClientDisconnect);
}
public void OnConfigParsed(PluginConfig config)
{
Config = config;
}
public override void OnAllPluginsLoaded(bool hotReload)
{
try
{
var antidll = AntiDll.Get();
if (antidll == null)
{
Logger.LogError("Failed to get AntiDLL API");
Unload(false);
return;
}
antidll.OnDetection += OnDetection;
}
catch (Exception)
{
Logger.LogError("Failed to get AntiDLL API");
Unload(false);
}
if (Config.BanType != "auto" && Config.BanType != "simpleadmin")
return;
try
{
_simpleAdminApi = SimpleAdminApi.Get();
if (_simpleAdminApi != null)
_banType = 1;
}
catch (Exception)
{
Logger.LogError("Failed to get CS2-SimpleAdmin API, using command as BanType");
}
}
private void OnClientDisconnect(int playerSlot)
{
// var player = Utilities.GetPlayerFromSlot(playerSlot);
// if (player == null || !player.IsValid || player.IsBot)
// return;
_bannedPlayers.Remove(playerSlot);
_detections.Remove(playerSlot);
}
[GameEventHandler]
public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo _)
{
var player = @event.Userid;
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);
return HookResult.Continue;
}
private void OnDetection(CCSPlayerController? player, string eventName)
{
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());
}
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);
}
else if (Config.BanType == "kick")
{
player.Disconnect(NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED_VACNETABNORMALBEHAVIOR);
}
else
{
Server.ExecuteCommand(Config.CommandToExecute.Replace("{steamid64}", player.SteamID.ToString())
.Replace("{duration}", Config.Duration.ToString()).Replace("{reason}", $"\"{Config.Reason}\"")
.Replace("{userid}", player.UserId.Value.ToString()));
}
}
public override void Unload(bool hotReload)
{
RemoveListener<Listeners.OnClientDisconnect>(OnClientDisconnect);
var antidll = AntiDll.Get();
if (antidll != null)
{
antidll.OnDetection -= OnDetection;
}
}
}

View File

@@ -1,900 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"AntiDLL-CS2-SimpleAdmin/1.0.0": {
"dependencies": {
"CounterStrikeSharp.API": "1.0.305",
"AntiDLL.API": "1.0.0.0",
"CS2-SimpleAdminApi": "1.0.0.0"
},
"runtime": {
"AntiDLL-CS2-SimpleAdmin.dll": {}
}
},
"CounterStrikeSharp.API/1.0.305": {
"dependencies": {
"McMaster.NETCore.Plugins": "1.4.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.DotNet.ApiCompat.Task": "8.0.203",
"Microsoft.Extensions.Hosting": "8.0.0",
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
"Microsoft.Extensions.Localization.Abstractions": "8.0.3",
"Microsoft.Extensions.Logging": "8.0.0",
"Scrutor": "4.2.2",
"Serilog.Extensions.Logging": "8.0.0",
"Serilog.Sinks.Console": "5.0.0",
"Serilog.Sinks.File": "5.0.0",
"System.Data.DataSetExtensions": "4.5.0"
},
"runtime": {
"lib/net8.0/CounterStrikeSharp.API.dll": {
"assemblyVersion": "1.0.305.0",
"fileVersion": "1.0.305.0"
}
}
},
"McMaster.NETCore.Plugins/1.4.0": {
"dependencies": {
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
"Microsoft.Extensions.DependencyModel": "6.0.0"
},
"runtime": {
"lib/netcoreapp3.1/McMaster.NETCore.Plugins.dll": {
"assemblyVersion": "1.4.0.0",
"fileVersion": "1.4.0.0"
}
}
},
"Microsoft.CSharp/4.7.0": {},
"Microsoft.DotNet.ApiCompat.Task/8.0.203": {},
"Microsoft.DotNet.PlatformAbstractions/3.1.6": {
"runtime": {
"lib/netstandard2.0/Microsoft.DotNet.PlatformAbstractions.dll": {
"assemblyVersion": "3.1.6.0",
"fileVersion": "3.100.620.31604"
}
}
},
"Microsoft.Extensions.Configuration/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Configuration.Binder/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Binder.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Configuration.CommandLine/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.CommandLine.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Configuration.EnvironmentVariables/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Configuration.FileExtensions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"Microsoft.Extensions.FileProviders.Physical": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Configuration.Json/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "8.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"System.Text.Json": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Json.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Configuration.UserSecrets/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Configuration.Json": "8.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"Microsoft.Extensions.FileProviders.Physical": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.UserSecrets.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.DependencyInjection/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.DependencyModel/6.0.0": {
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "8.0.0",
"System.Text.Json": "8.0.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll": {
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.21.52210"
}
}
},
"Microsoft.Extensions.Diagnostics/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"System.Diagnostics.DiagnosticSource": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.FileProviders.Physical/8.0.0": {
"dependencies": {
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"Microsoft.Extensions.FileSystemGlobbing": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileProviders.Physical.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.FileSystemGlobbing/8.0.0": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileSystemGlobbing.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Hosting/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
"Microsoft.Extensions.Configuration.CommandLine": "8.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "8.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "8.0.0",
"Microsoft.Extensions.Configuration.Json": "8.0.0",
"Microsoft.Extensions.Configuration.UserSecrets": "8.0.0",
"Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Diagnostics": "8.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"Microsoft.Extensions.FileProviders.Physical": "8.0.0",
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Configuration": "8.0.0",
"Microsoft.Extensions.Logging.Console": "8.0.0",
"Microsoft.Extensions.Logging.Debug": "8.0.0",
"Microsoft.Extensions.Logging.EventLog": "8.0.0",
"Microsoft.Extensions.Logging.EventSource": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Hosting.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Hosting.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Localization.Abstractions/8.0.3": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.Localization.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.324.11615"
}
}
},
"Microsoft.Extensions.Logging/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.Configuration/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Configuration.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.Console/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Configuration": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"System.Text.Json": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Console.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.Debug/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Debug.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.EventLog/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"System.Diagnostics.EventLog": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.EventLog.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.EventSource/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0",
"System.Text.Json": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.EventSource.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Options/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Options.ConfigurationExtensions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Primitives/8.0.0": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Scrutor/4.2.2": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.DependencyModel": "6.0.0"
},
"runtime": {
"lib/net6.0/Scrutor.dll": {
"assemblyVersion": "4.0.0.0",
"fileVersion": "4.0.0.0"
}
}
},
"Serilog/3.1.1": {
"runtime": {
"lib/net7.0/Serilog.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "3.1.1.0"
}
}
},
"Serilog.Extensions.Logging/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Logging": "8.0.0",
"Serilog": "3.1.1"
},
"runtime": {
"lib/net8.0/Serilog.Extensions.Logging.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "8.0.0.0"
}
}
},
"Serilog.Sinks.Console/5.0.0": {
"dependencies": {
"Serilog": "3.1.1"
},
"runtime": {
"lib/net7.0/Serilog.Sinks.Console.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.0.0"
}
}
},
"Serilog.Sinks.File/5.0.0": {
"dependencies": {
"Serilog": "3.1.1"
},
"runtime": {
"lib/net5.0/Serilog.Sinks.File.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.0.0"
}
}
},
"System.Buffers/4.5.1": {},
"System.Data.DataSetExtensions/4.5.0": {},
"System.Diagnostics.DiagnosticSource/8.0.0": {},
"System.Diagnostics.EventLog/8.0.0": {
"runtime": {
"lib/net8.0/System.Diagnostics.EventLog.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
},
"runtimeTargets": {
"runtimes/win/lib/net8.0/System.Diagnostics.EventLog.Messages.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "8.0.0.0",
"fileVersion": "0.0.0.0"
},
"runtimes/win/lib/net8.0/System.Diagnostics.EventLog.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"System.Memory/4.5.4": {},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {},
"System.Text.Encodings.Web/8.0.0": {},
"System.Text.Json/8.0.0": {
"dependencies": {
"System.Text.Encodings.Web": "8.0.0"
}
},
"AntiDLL.API/1.0.0.0": {
"runtime": {
"AntiDLL.API.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"CS2-SimpleAdminApi/1.0.0.0": {
"runtime": {
"CS2-SimpleAdminApi.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"AntiDLL-CS2-SimpleAdmin/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"CounterStrikeSharp.API/1.0.305": {
"type": "package",
"serviceable": true,
"sha512": "sha512-WoeI/sQ85HM2UG0ADvtfm7JaWNSETPn4gwTvKTKdX7uRoNPavVuemW1jB7dCKQQMW/7So96FVL0qbqYhE91Jpw==",
"path": "counterstrikesharp.api/1.0.305",
"hashPath": "counterstrikesharp.api.1.0.305.nupkg.sha512"
},
"McMaster.NETCore.Plugins/1.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UKw5Z2/QHhkR7kiAJmqdCwVDMQV0lwsfj10+FG676r8DsJWIpxtachtEjE0qBs9WoK5GUQIqxgyFeYUSwuPszg==",
"path": "mcmaster.netcore.plugins/1.4.0",
"hashPath": "mcmaster.netcore.plugins.1.4.0.nupkg.sha512"
},
"Microsoft.CSharp/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==",
"path": "microsoft.csharp/4.7.0",
"hashPath": "microsoft.csharp.4.7.0.nupkg.sha512"
},
"Microsoft.DotNet.ApiCompat.Task/8.0.203": {
"type": "package",
"serviceable": true,
"sha512": "sha512-nPEGMojf1mj1oVixe0aiBimSn6xUoZswSjpMPZFMkZ+znYm2GEM5tWGZEWb6OSNIo5gWKyDi1WcI4IL7YiL1Zw==",
"path": "microsoft.dotnet.apicompat.task/8.0.203",
"hashPath": "microsoft.dotnet.apicompat.task.8.0.203.nupkg.sha512"
},
"Microsoft.DotNet.PlatformAbstractions/3.1.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==",
"path": "microsoft.dotnet.platformabstractions/3.1.6",
"hashPath": "microsoft.dotnet.platformabstractions.3.1.6.nupkg.sha512"
},
"Microsoft.Extensions.Configuration/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==",
"path": "microsoft.extensions.configuration/8.0.0",
"hashPath": "microsoft.extensions.configuration.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
"path": "microsoft.extensions.configuration.abstractions/8.0.0",
"hashPath": "microsoft.extensions.configuration.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Binder/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==",
"path": "microsoft.extensions.configuration.binder/8.0.0",
"hashPath": "microsoft.extensions.configuration.binder.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.CommandLine/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==",
"path": "microsoft.extensions.configuration.commandline/8.0.0",
"hashPath": "microsoft.extensions.configuration.commandline.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.EnvironmentVariables/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==",
"path": "microsoft.extensions.configuration.environmentvariables/8.0.0",
"hashPath": "microsoft.extensions.configuration.environmentvariables.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.FileExtensions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-McP+Lz/EKwvtCv48z0YImw+L1gi1gy5rHhNaNIY2CrjloV+XY8gydT8DjMR6zWeL13AFK+DioVpppwAuO1Gi1w==",
"path": "microsoft.extensions.configuration.fileextensions/8.0.0",
"hashPath": "microsoft.extensions.configuration.fileextensions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Json/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-C2wqUoh9OmRL1akaCcKSTmRU8z0kckfImG7zLNI8uyi47Lp+zd5LWAD17waPQEqCz3ioWOCrFUo+JJuoeZLOBw==",
"path": "microsoft.extensions.configuration.json/8.0.0",
"hashPath": "microsoft.extensions.configuration.json.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.UserSecrets/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ihDHu2dJYQird9pl2CbdwuNDfvCZdOS0S7SPlNfhPt0B81UTT+yyZKz2pimFZGUp3AfuBRnqUCxB2SjsZKHVUw==",
"path": "microsoft.extensions.configuration.usersecrets/8.0.0",
"hashPath": "microsoft.extensions.configuration.usersecrets.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"path": "microsoft.extensions.dependencyinjection/8.0.0",
"hashPath": "microsoft.extensions.dependencyinjection.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==",
"path": "microsoft.extensions.dependencyinjection.abstractions/8.0.0",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.DependencyModel/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-TD5QHg98m3+QhgEV1YVoNMl5KtBw/4rjfxLHO0e/YV9bPUBDKntApP4xdrVtGgCeQZHVfC2EXIGsdpRNrr87Pg==",
"path": "microsoft.extensions.dependencymodel/6.0.0",
"hashPath": "microsoft.extensions.dependencymodel.6.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==",
"path": "microsoft.extensions.diagnostics/8.0.0",
"hashPath": "microsoft.extensions.diagnostics.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
"path": "microsoft.extensions.diagnostics.abstractions/8.0.0",
"hashPath": "microsoft.extensions.diagnostics.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
"path": "microsoft.extensions.fileproviders.abstractions/8.0.0",
"hashPath": "microsoft.extensions.fileproviders.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.FileProviders.Physical/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==",
"path": "microsoft.extensions.fileproviders.physical/8.0.0",
"hashPath": "microsoft.extensions.fileproviders.physical.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.FileSystemGlobbing/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==",
"path": "microsoft.extensions.filesystemglobbing/8.0.0",
"hashPath": "microsoft.extensions.filesystemglobbing.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Hosting/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ItYHpdqVp5/oFLT5QqbopnkKlyFG9EW/9nhM6/yfObeKt6Su0wkBio6AizgRHGNwhJuAtlE5VIjow5JOTrip6w==",
"path": "microsoft.extensions.hosting/8.0.0",
"hashPath": "microsoft.extensions.hosting.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Hosting.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
"path": "microsoft.extensions.hosting.abstractions/8.0.0",
"hashPath": "microsoft.extensions.hosting.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Localization.Abstractions/8.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-k/kUPm1FQBxcs9/vsM1eF4qIOg2Sovqh/+KUGHur5Mc0Y3OFGuoz9ktBX7LA0gPz53SZhW3W3oaSaMFFcjgM6Q==",
"path": "microsoft.extensions.localization.abstractions/8.0.3",
"hashPath": "microsoft.extensions.localization.abstractions.8.0.3.nupkg.sha512"
},
"Microsoft.Extensions.Logging/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
"path": "microsoft.extensions.logging/8.0.0",
"hashPath": "microsoft.extensions.logging.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
"path": "microsoft.extensions.logging.abstractions/8.0.0",
"hashPath": "microsoft.extensions.logging.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Configuration/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ixXXV0G/12g6MXK65TLngYN9V5hQQRuV+fZi882WIoVJT7h5JvoYoxTEwCgdqwLjSneqh1O+66gM8sMr9z/rsQ==",
"path": "microsoft.extensions.logging.configuration/8.0.0",
"hashPath": "microsoft.extensions.logging.configuration.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Console/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-e+48o7DztoYog+PY430lPxrM4mm3PbA6qucvQtUDDwVo4MO+ejMw7YGc/o2rnxbxj4isPxdfKFzTxvXMwAz83A==",
"path": "microsoft.extensions.logging.console/8.0.0",
"hashPath": "microsoft.extensions.logging.console.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Debug/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-dt0x21qBdudHLW/bjMJpkixv858RRr8eSomgVbU8qljOyfrfDGi1JQvpF9w8S7ziRPtRKisuWaOwFxJM82GxeA==",
"path": "microsoft.extensions.logging.debug/8.0.0",
"hashPath": "microsoft.extensions.logging.debug.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.EventLog/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3X9D3sl7EmOu7vQp5MJrmIJBl5XSdOhZPYXUeFfYa6Nnm9+tok8x3t3IVPLhm7UJtPOU61ohFchw8rNm9tIYOQ==",
"path": "microsoft.extensions.logging.eventlog/8.0.0",
"hashPath": "microsoft.extensions.logging.eventlog.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.EventSource/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oKcPMrw+luz2DUAKhwFXrmFikZWnyc8l2RKoQwqU3KIZZjcfoJE0zRHAnqATfhRZhtcbjl/QkiY2Xjxp0xu+6w==",
"path": "microsoft.extensions.logging.eventsource/8.0.0",
"hashPath": "microsoft.extensions.logging.eventsource.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Options/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
"path": "microsoft.extensions.options/8.0.0",
"hashPath": "microsoft.extensions.options.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Options.ConfigurationExtensions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==",
"path": "microsoft.extensions.options.configurationextensions/8.0.0",
"hashPath": "microsoft.extensions.options.configurationextensions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==",
"path": "microsoft.extensions.primitives/8.0.0",
"hashPath": "microsoft.extensions.primitives.8.0.0.nupkg.sha512"
},
"Scrutor/4.2.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-t5VIYA7WJXoJJo7s4DoHakMGwTu+MeEnZumMOhTCH7kz9xWha24G7dJNxWrHPlu0ZdZAS4jDZCxxAnyaBh7uYw==",
"path": "scrutor/4.2.2",
"hashPath": "scrutor.4.2.2.nupkg.sha512"
},
"Serilog/3.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-P6G4/4Kt9bT635bhuwdXlJ2SCqqn2nhh4gqFqQueCOr9bK/e7W9ll/IoX1Ter948cV2Z/5+5v8pAfJYUISY03A==",
"path": "serilog/3.1.1",
"hashPath": "serilog.3.1.1.nupkg.sha512"
},
"Serilog.Extensions.Logging/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==",
"path": "serilog.extensions.logging/8.0.0",
"hashPath": "serilog.extensions.logging.8.0.0.nupkg.sha512"
},
"Serilog.Sinks.Console/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-IZ6bn79k+3SRXOBpwSOClUHikSkp2toGPCZ0teUkscv4dpDg9E2R2xVsNkLmwddE4OpNVO3N0xiYsAH556vN8Q==",
"path": "serilog.sinks.console/5.0.0",
"hashPath": "serilog.sinks.console.5.0.0.nupkg.sha512"
},
"Serilog.Sinks.File/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==",
"path": "serilog.sinks.file/5.0.0",
"hashPath": "serilog.sinks.file.5.0.0.nupkg.sha512"
},
"System.Buffers/4.5.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
"path": "system.buffers/4.5.1",
"hashPath": "system.buffers.4.5.1.nupkg.sha512"
},
"System.Data.DataSetExtensions/4.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-221clPs1445HkTBZPL+K9sDBdJRB8UN8rgjO3ztB0CQ26z//fmJXtlsr6whGatscsKGBrhJl5bwJuKSA8mwFOw==",
"path": "system.data.datasetextensions/4.5.0",
"hashPath": "system.data.datasetextensions.4.5.0.nupkg.sha512"
},
"System.Diagnostics.DiagnosticSource/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==",
"path": "system.diagnostics.diagnosticsource/8.0.0",
"hashPath": "system.diagnostics.diagnosticsource.8.0.0.nupkg.sha512"
},
"System.Diagnostics.EventLog/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==",
"path": "system.diagnostics.eventlog/8.0.0",
"hashPath": "system.diagnostics.eventlog.8.0.0.nupkg.sha512"
},
"System.Memory/4.5.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==",
"path": "system.memory/4.5.4",
"hashPath": "system.memory.4.5.4.nupkg.sha512"
},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
"path": "system.runtime.compilerservices.unsafe/6.0.0",
"hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
},
"System.Text.Encodings.Web/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
"path": "system.text.encodings.web/8.0.0",
"hashPath": "system.text.encodings.web.8.0.0.nupkg.sha512"
},
"System.Text.Json/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-OdrZO2WjkiEG6ajEFRABTRCi/wuXQPxeV6g8xvUJqdxMvvuCCEk86zPla8UiIQJz3durtUEbNyY/3lIhS0yZvQ==",
"path": "system.text.json/8.0.0",
"hashPath": "system.text.json.8.0.0.nupkg.sha512"
},
"AntiDLL.API/1.0.0.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
},
"CS2-SimpleAdminApi/1.0.0.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
}
}
}

Some files were not shown because too many files have changed in this diff Show More