mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-17 10:31:01 +00:00
Refactor database layer and add module/plugin improvements
Reworked the database layer to support both MySQL and SQLite via new provider classes and migration scripts for each backend. Updated the build workflow to support building and packaging additional modules, including StealthModule and BanSoundModule, and improved artifact handling. Refactored command registration to allow dynamic registration/unregistration and improved API event handling. Updated dependencies, project structure, and configuration checks for better reliability and extensibility. Added new language files, updated versioning, and removed obsolete files.
**⚠️ Warning: SQLite support is currently experimental.
Using this version requires reconfiguration of your database settings!
Plugin now uses UTC time. Please adjust your configurations accordingly!
**
This commit is contained in:
164
.github/workflows/build.yml
vendored
164
.github/workflows/build.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build
|
||||
name: Build and Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,85 +11,111 @@ on:
|
||||
- '**/README.md'
|
||||
|
||||
env:
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
PROJECT_PATH_CS2_SIMPLEADMIN: "CS2-SimpleAdmin/CS2-SimpleAdmin.csproj"
|
||||
PROJECT_PATH_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj"
|
||||
PROJECT_NAME_CS2_SIMPLEADMIN: "CS2-SimpleAdmin"
|
||||
PROJECT_PATH_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj"
|
||||
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:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions: write-all
|
||||
outputs:
|
||||
build_version: ${{ steps.get_version.outputs.VERSION }}
|
||||
steps:
|
||||
- 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 }}
|
||||
- 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 }}/shared/CS2-SimpleAdminApi
|
||||
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
|
||||
|
||||
publish:
|
||||
if: github.event_name == 'push'
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- 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
|
||||
- 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.
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ obj/
|
||||
Modules/CS2-SimpleAdmin_PlayTimeModule
|
||||
CS2-SimpleAdmin.sln.DotSettings.user
|
||||
Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user
|
||||
CS2-SimpleAdmin_BanSoundModule — kopia
|
||||
*.user
|
||||
|
||||
BIN
CS2-SimpleAdmin/3rd_party/MenuManagerApi — old.dll
vendored
BIN
CS2-SimpleAdmin/3rd_party/MenuManagerApi — old.dll
vendored
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CS2_SimpleAdmin.Managers;
|
||||
@@ -13,7 +14,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.UserId.Value];
|
||||
return CS2_SimpleAdmin.PlayersInfo[player.SteamID];
|
||||
}
|
||||
|
||||
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
|
||||
@@ -28,6 +29,7 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
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 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);
|
||||
@@ -37,6 +39,8 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
|
||||
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);
|
||||
|
||||
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
|
||||
{
|
||||
switch (penaltyType)
|
||||
@@ -123,8 +127,42 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
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);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using System.Reflection;
|
||||
using CounterStrikeSharp.API;
|
||||
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;
|
||||
@@ -19,47 +22,55 @@ 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";
|
||||
public override string ModuleVersion => "1.7.7-alpha-9";
|
||||
|
||||
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(2.0f, () =>
|
||||
AddTimer(6.0f, () =>
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var playerManager = new PlayerManager();
|
||||
|
||||
foreach (var player in Helper.GetValidPlayers())
|
||||
PlayersInfo.Clear();
|
||||
CachedPlayers.Clear();
|
||||
BotPlayers.Clear();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
|
||||
{
|
||||
playerManager.LoadPlayerData(player);
|
||||
if (!player.IsBot)
|
||||
PlayerManager.LoadPlayerData(player, true);
|
||||
else
|
||||
BotPlayers.Add(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);
|
||||
|
||||
new PlayerManager().CheckPlayersTimer();
|
||||
PlayersTimer?.Kill();
|
||||
PlayersTimer = null;
|
||||
PlayerManager.CheckPlayersTimer();
|
||||
}
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
AddTimer(5.0f, () => ReloadAdmins(null));
|
||||
|
||||
try
|
||||
{
|
||||
MenuApi = MenuCapability.Get();
|
||||
@@ -67,51 +78,141 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Unable to load required plugins ... \n{exception}", ex.Message);
|
||||
Unload(false);
|
||||
}
|
||||
|
||||
|
||||
AddTimer(6.0f, () => ReloadAdmins(null));
|
||||
RegisterEvents();
|
||||
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)
|
||||
{
|
||||
Instance = this;
|
||||
_logger = Logger;
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
Environment.FailFast(":(!");
|
||||
|
||||
if (config.DatabaseHost.Length < 1 || config.DatabaseName.Length < 1 || config.DatabaseUser.Length < 1)
|
||||
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)
|
||||
{
|
||||
throw new Exception("[CS2-SimpleAdmin] You need to setup Database credentials in config!");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
MySqlConnectionStringBuilder builder = new()
|
||||
if (missing)
|
||||
Server.ExecuteCommand($"css_plugins unload {ModuleName}");
|
||||
|
||||
Instance = this;
|
||||
|
||||
if (Config.DatabaseConfig.DatabaseType.Contains("mysql", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseHost) ||
|
||||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseName) ||
|
||||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseUser))
|
||||
{
|
||||
Server = config.DatabaseHost,
|
||||
Database = config.DatabaseName,
|
||||
UserID = config.DatabaseUser,
|
||||
Password = config.DatabasePassword,
|
||||
Port = (uint)config.DatabasePort,
|
||||
SslMode = Enum.TryParse(config.DatabaseSSlMode, true, out MySqlSslMode sslMode) ? sslMode : MySqlSslMode.Preferred,
|
||||
throw new Exception("[CS2-SimpleAdmin] You need to setup MySQL credentials in config!");
|
||||
}
|
||||
|
||||
var builder = new MySqlConnectionStringBuilder()
|
||||
{
|
||||
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,
|
||||
Pooling = true,
|
||||
MinimumPoolSize = 0,
|
||||
MaximumPoolSize = 640,
|
||||
};
|
||||
|
||||
DbConnectionString = builder.ConnectionString;
|
||||
Database = new Database.Database(DbConnectionString);
|
||||
|
||||
if (!Database.CheckDatabaseConnection(out var exception))
|
||||
DatabaseProvider = new MySqlDatabaseProvider(DbConnectionString);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.SqliteFilePath))
|
||||
{
|
||||
if (exception != null)
|
||||
Logger.LogError("Problem with database connection! \n{exception}", exception);
|
||||
|
||||
Unload(false);
|
||||
return;
|
||||
throw new Exception("[CS2-SimpleAdmin] You need to specify SQLite file path in config!");
|
||||
}
|
||||
|
||||
Task.Run(() => Database.DatabaseMigration());
|
||||
|
||||
Config = config;
|
||||
Helper.UpdateConfig(config);
|
||||
DatabaseProvider = new SqliteDatabaseProvider(ModuleDirectory + "/" + config.DatabaseConfig.SqliteFilePath);
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -122,33 +223,32 @@ 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(Database);
|
||||
BanManager = new BanManager(Database);
|
||||
MuteManager = new MuteManager(Database);
|
||||
WarnManager = new WarnManager(Database);
|
||||
PermissionManager = new PermissionManager(DatabaseProvider);
|
||||
BanManager = new BanManager(DatabaseProvider);
|
||||
MuteManager = new MuteManager(DatabaseProvider);
|
||||
WarnManager = new WarnManager(DatabaseProvider);
|
||||
}
|
||||
|
||||
private static TargetResult? GetTarget(CommandInfo command)
|
||||
private static TargetResult? GetTarget(CommandInfo command, int argument = 1)
|
||||
{
|
||||
var matches = command.GetArgTargetResult(1);
|
||||
var matches = command.GetArgTargetResult(argument);
|
||||
|
||||
if (!matches.Any())
|
||||
{
|
||||
command.ReplyToCommand($"Target {command.GetArg(1)} not found.");
|
||||
command.ReplyToCommand($"Target {command.GetArg(argument)} not found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (command.GetArg(1).StartsWith('@'))
|
||||
if (command.GetArg(argument).StartsWith('@'))
|
||||
return matches;
|
||||
|
||||
if (matches.Count() == 1)
|
||||
return matches;
|
||||
|
||||
command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(1)}\".");
|
||||
command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(argument)}\".");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -156,5 +256,13 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
{
|
||||
CacheManager?.Dispose();
|
||||
CacheManager = null;
|
||||
PlayersTimer?.Kill();
|
||||
PlayersTimer = null;
|
||||
UnregisterEvents();
|
||||
|
||||
if (hotReload)
|
||||
PlayersInfo.Clear();
|
||||
else
|
||||
Server.ExecuteCommand($"css_plugins unload {ModuleDirectory}");
|
||||
}
|
||||
}
|
||||
@@ -7,29 +7,135 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.318" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<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="Newtonsoft.Json" Version="*" />
|
||||
<PackageReference Include="ZLinq" Version="1.4.6" />
|
||||
<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>
|
||||
|
||||
<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\010_CreateWarnsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\012_AddUpdatedAtColumnToSaBansTable.sql">
|
||||
<None Update="Database\Migrations\Mysql\001_CreateTables.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\013_AddNameColumnToSaPlayerIpsTable.sql">
|
||||
<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>
|
||||
</ItemGroup>
|
||||
@@ -45,7 +151,8 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="MenuManagerApi">
|
||||
<HintPath>3rd_party\MenuManagerApi.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,89 +1,99 @@
|
||||
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 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_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_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_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_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_reloadbans", CS2_SimpleAdmin.Instance.OnRelBans),
|
||||
new CommandMapping("css_hide", CS2_SimpleAdmin.Instance.OnHideCommand),
|
||||
new CommandMapping("css_hidecomms", CS2_SimpleAdmin.Instance.OnHideCommsCommand),
|
||||
new CommandMapping("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand),
|
||||
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_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_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_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_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
|
||||
new("css_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
|
||||
|
||||
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_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_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_resize", CS2_SimpleAdmin.Instance.OnResizeCommand),
|
||||
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),
|
||||
new CommandMapping("css_adminvoice", CS2_SimpleAdmin.Instance.OnAdminVoiceCommand)
|
||||
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)
|
||||
];
|
||||
|
||||
/// <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))
|
||||
@@ -97,6 +107,10 @@ 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
|
||||
@@ -170,14 +184,26 @@ public static class RegisterCommands
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(commands, Formatting.Indented);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(commands, options);
|
||||
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 = JsonConvert.DeserializeObject<CommandsConfig>(json);
|
||||
var commandsConfig = JsonSerializer.Deserialize<CommandsConfig>(json,
|
||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
|
||||
if (commandsConfig?.Commands == null) return;
|
||||
|
||||
@@ -195,19 +221,37 @@ 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;
|
||||
|
||||
@@ -12,6 +12,11 @@ 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)
|
||||
@@ -51,9 +56,20 @@ 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 (Database == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
if (!CheckValidBan(caller, time)) return;
|
||||
|
||||
@@ -63,14 +79,17 @@ public partial class CS2_SimpleAdmin
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
// Get player and admin information
|
||||
var playerInfo = PlayersInfo[player.UserId.Value];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var playerInfo = PlayersInfo[player.SteamID];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Asynchronously handle banning logic
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
// Determine message keys and arguments based on ban time
|
||||
@@ -114,6 +133,14 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer);
|
||||
}
|
||||
|
||||
/// <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
|
||||
@@ -121,15 +148,12 @@ public partial class CS2_SimpleAdmin
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString());
|
||||
|
||||
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}.");
|
||||
}
|
||||
@@ -137,23 +161,31 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
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.ToString(), adminInfo, reason, time);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
|
||||
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 (Database == null) return;
|
||||
if (DatabaseProvider == 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)
|
||||
@@ -162,7 +194,7 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
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";
|
||||
@@ -170,15 +202,13 @@ public partial class CS2_SimpleAdmin
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
||||
|
||||
if (!CheckValidBan(caller, time)) return;
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue
|
||||
? PlayersInfo[caller.UserId.Value]
|
||||
? PlayersInfo[caller.SteamID]
|
||||
: null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
@@ -196,10 +226,14 @@ public partial class CS2_SimpleAdmin
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid, reason, time, PenaltyType.Ban, _localizer);
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Ban, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline.");
|
||||
}
|
||||
@@ -210,11 +244,16 @@ public partial class CS2_SimpleAdmin
|
||||
Server.ExecuteCommand($"banid 1 {steamId.SteamId3}");
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == 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);
|
||||
@@ -236,17 +275,20 @@ public partial class CS2_SimpleAdmin
|
||||
if (!CheckValidBan(caller, time)) return;
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue
|
||||
? PlayersInfo[caller.UserId.Value]
|
||||
? PlayersInfo[caller.SteamID]
|
||||
: null;
|
||||
|
||||
var player = Helper.GetPlayerFromIp(ipAddress);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
var players = Helper.GetPlayerFromIp(ipAddress);
|
||||
if (players.Count >= 1)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
return;
|
||||
foreach (var player in players)
|
||||
{
|
||||
if (player == null || !player.IsValid) continue;
|
||||
if (!caller.CanTarget(player))
|
||||
return;
|
||||
|
||||
Ban(caller, player, time, reason, callerName, silent: true);
|
||||
Ban(caller, player, time, reason, callerName, silent: true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -262,6 +304,12 @@ 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;
|
||||
@@ -280,14 +328,17 @@ 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 (Database == null) return;
|
||||
|
||||
if (DatabaseProvider == null) return;
|
||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
if (command.GetArg(1).Length <= 1)
|
||||
{
|
||||
command.ReplyToCommand($"Too short pattern to search.");
|
||||
@@ -300,19 +351,21 @@ public partial class CS2_SimpleAdmin
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
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 (Database == null)
|
||||
if (DatabaseProvider == null)
|
||||
return;
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
if (command.ArgCount < 2)
|
||||
@@ -327,8 +380,6 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
WarnManager warnManager = new(Database);
|
||||
|
||||
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()
|
||||
@@ -340,14 +391,23 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
Warn(caller, player, time, reason, callerName, warnManager, command);
|
||||
Warn(caller, player, time, reason, callerName, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, WarnManager? warnManager = null, CommandInfo? command = null)
|
||||
/// <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)
|
||||
{
|
||||
if (Database == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
if (!CheckValidBan(caller, time)) return;
|
||||
|
||||
@@ -364,18 +424,21 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
// Get player and admin information
|
||||
var playerInfo = PlayersInfo[player.UserId.Value];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var playerInfo = PlayersInfo[player.SteamID];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Asynchronously handle warning logic
|
||||
Task.Run(async () =>
|
||||
{
|
||||
warnManager ??= new WarnManager(Database);
|
||||
int? penaltyId = await warnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time, penaltyId);
|
||||
int? penaltyId = await WarnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
|
||||
// Check for warn thresholds and execute punish command if applicable
|
||||
var totalWarns = await warnManager.GetPlayerWarnsCount(player.SteamID.ToString());
|
||||
var totalWarns = await WarnManager.GetPlayerWarnsCount(player.SteamID);
|
||||
if (Config.WarnThreshold.Count > 0)
|
||||
{
|
||||
string? punishCommand = null;
|
||||
@@ -388,7 +451,7 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
if (!string.IsNullOrEmpty(punishCommand))
|
||||
{
|
||||
await Server.NextFrameAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
Server.ExecuteCommand(punishCommand.Replace("USERID", playerInfo.UserId.ToString()).Replace("STEAMID64", playerInfo.SteamId?.ToString()));
|
||||
});
|
||||
@@ -424,6 +487,14 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer);
|
||||
}
|
||||
|
||||
/// <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
|
||||
@@ -431,9 +502,9 @@ public partial class CS2_SimpleAdmin
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString());
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -451,11 +522,15 @@ public partial class CS2_SimpleAdmin
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await WarnManager.AddWarnBySteamid(steamid.SteamId64.ToString(), adminInfo, reason, time);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Warn, reason, time, penaltyId);
|
||||
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.ToString());
|
||||
var totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
|
||||
if (Config.WarnThreshold.Count > 0)
|
||||
{
|
||||
string? punishCommand = null;
|
||||
@@ -468,7 +543,7 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
if (!string.IsNullOrEmpty(punishCommand))
|
||||
{
|
||||
await Server.NextFrameAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
Server.ExecuteCommand(punishCommand.Replace("STEAMID64", steamid.SteamId64.ToString()));
|
||||
});
|
||||
@@ -480,11 +555,16 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
if (command.GetArg(1).Length <= 1)
|
||||
{
|
||||
@@ -493,9 +573,7 @@ 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}.");
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ 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)
|
||||
@@ -30,6 +36,11 @@ 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)
|
||||
@@ -47,6 +58,11 @@ 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)
|
||||
@@ -57,7 +73,6 @@ public partial class CS2_SimpleAdmin
|
||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
foreach (var player in Helper.GetValidPlayers())
|
||||
{
|
||||
player.SendLocalizedMessage(_localizer,
|
||||
@@ -66,6 +81,11 @@ 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)
|
||||
@@ -92,6 +112,11 @@ 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)
|
||||
@@ -103,6 +128,11 @@ public partial class CS2_SimpleAdmin
|
||||
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)
|
||||
@@ -115,6 +145,6 @@ public partial class CS2_SimpleAdmin
|
||||
VirtualFunctions.ClientPrintAll(
|
||||
HudDestination.Alert,
|
||||
utf8String.ReplaceColorTags(),
|
||||
0, 0, 0, 0);
|
||||
0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -12,23 +12,32 @@ using CS2_SimpleAdmin.Managers;
|
||||
using CS2_SimpleAdmin.Menus;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using CounterStrikeSharp.API.ValveConstants.Protobuf;
|
||||
using CS2_SimpleAdmin.Models;
|
||||
using MenuManager;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the command that shows active penalties and warns for the caller or specified player.
|
||||
/// Queries warnings and mute status, formats them locally, and sends the result to caller's chat.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing this command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(usage: "[#userid or name]", whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
public void OnPenaltiesCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (caller == null || caller.IsValid == false || !caller.UserId.HasValue || Database == null)
|
||||
if (caller == null || caller.IsValid == false || !caller.UserId.HasValue || DatabaseProvider == null)
|
||||
return;
|
||||
|
||||
var userId = caller.UserId.Value;
|
||||
var steamId = caller.SteamID;
|
||||
|
||||
if (!string.IsNullOrEmpty(command.GetArg(1)) && AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/kick"))
|
||||
{
|
||||
@@ -51,10 +60,10 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
try
|
||||
{
|
||||
var warns = await WarnManager.GetPlayerWarns(PlayersInfo[userId], false);
|
||||
var warns = await WarnManager.GetPlayerWarns(PlayersInfo[steamId], false);
|
||||
|
||||
// Check if the player is muted
|
||||
var activeMutes = await MuteManager.IsPlayerMuted(PlayersInfo[userId].SteamId.SteamId64.ToString());
|
||||
var activeMutes = await MuteManager.IsPlayerMuted(PlayersInfo[steamId].SteamId.SteamId64.ToString());
|
||||
|
||||
Dictionary<PenaltyType, List<string>> mutesList = new()
|
||||
{
|
||||
@@ -119,16 +128,16 @@ public partial class CS2_SimpleAdmin
|
||||
mutesList[PenaltyType.Silence].Add(_localizer["sa_player_penalty_info_no_active_silence"]);
|
||||
}
|
||||
|
||||
await Server.NextFrameAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
caller.SendLocalizedMessage(_localizer, "sa_player_penalty_info",
|
||||
[
|
||||
PlayersInfo[userId].Name,
|
||||
PlayersInfo[userId].TotalBans,
|
||||
PlayersInfo[userId].TotalGags,
|
||||
PlayersInfo[userId].TotalMutes,
|
||||
PlayersInfo[userId].TotalSilences,
|
||||
PlayersInfo[userId].TotalWarns,
|
||||
PlayersInfo[steamId].Name,
|
||||
PlayersInfo[steamId].TotalBans,
|
||||
PlayersInfo[steamId].TotalGags,
|
||||
PlayersInfo[steamId].TotalMutes,
|
||||
PlayersInfo[steamId].TotalSilences,
|
||||
PlayersInfo[steamId].TotalWarns,
|
||||
string.Join("\n", mutesList.SelectMany(kvp => kvp.Value)),
|
||||
string.Join("\n", warnsList)
|
||||
]);
|
||||
@@ -141,6 +150,12 @@ public partial class CS2_SimpleAdmin
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the admin voice listening mode or mutes/unmutes all players' voice.
|
||||
/// Sends confirmation messages accordingly.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing this command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[RequiresPermissions("@css/chat")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
public void OnAdminVoiceCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -152,6 +167,7 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
if (command.GetArg(2).ToLower().Equals("muteAll"))
|
||||
{
|
||||
caller.SendLocalizedMessage(_localizer, "sa_admin_voice_mute_all");
|
||||
foreach (var player in Helper.GetValidPlayers().Where(p => p != caller && !AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
|
||||
{
|
||||
player.VoiceFlags = VoiceFlags.Muted;
|
||||
@@ -160,19 +176,31 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
if (command.GetArg(2).ToLower().Equals("unmuteAll"))
|
||||
{
|
||||
caller.SendLocalizedMessage(_localizer, "sa_admin_voice_unmute_all");
|
||||
foreach (var player in Helper.GetValidPlayers().Where(p => p != caller))
|
||||
{
|
||||
if (PlayerPenaltyManager.GetPlayerPenalties(player.Slot, PenaltyType.Mute).Count == 0)
|
||||
if (PlayerPenaltyManager.GetPlayerPenalties(player.Slot, [PenaltyType.Silence, PenaltyType.Mute]).Count == 0)
|
||||
player.VoiceFlags = VoiceFlags.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
caller.VoiceFlags = caller.VoiceFlags == VoiceFlags.All ? VoiceFlags.Normal : VoiceFlags.All;
|
||||
|
||||
var enabled = caller.VoiceFlags.HasFlag(VoiceFlags.ListenAll);
|
||||
var messageKey = enabled
|
||||
? "sa_admin_voice_unlisten_all"
|
||||
: "sa_admin_voice_listen_all";
|
||||
|
||||
caller.SendLocalizedMessage(_localizer, messageKey);
|
||||
caller.VoiceFlags ^= VoiceFlags.ListenAll;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the admin menu for the caller.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[RequiresPermissions("@css/generic")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
public void OnAdminCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -183,6 +211,12 @@ public partial class CS2_SimpleAdmin
|
||||
AdminMenu.OpenMenu(caller);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays admin help text read from a file.
|
||||
/// Outputs lines one at a time as replies to the command.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[RequiresPermissions("@css/generic")]
|
||||
public void OnAdminHelpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
@@ -194,12 +228,16 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles adding a new admin with specified SteamID, name, flags, immunity, and duration.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(minArgs: 4, usage: "<steamid> <name> <flags/groups> <immunity> <duration>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/root")]
|
||||
public void OnAddAdminCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
|
||||
{
|
||||
@@ -230,9 +268,20 @@ public partial class CS2_SimpleAdmin
|
||||
AddAdmin(caller, steamid, name, flags, immunity, time, globalAdmin, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds admin permissions and groups for a player.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="steamid">SteamID as string identifying the player.</param>
|
||||
/// <param name="name">Player's name.</param>
|
||||
/// <param name="flags">Comma-separated admin flags/groups.</param>
|
||||
/// <param name="immunity">Admin immunity level.</param>
|
||||
/// <param name="time">Duration of permission (default 0 = permanent).</param>
|
||||
/// <param name="globalAdmin">Whether admin is global.</param>
|
||||
/// <param name="command">Optional command info for confirmation messages.</param>
|
||||
public static void AddAdmin(CCSPlayerController? caller, string steamid, string name, string flags, int immunity, int time = 0, bool globalAdmin = false, CommandInfo? command = null)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var flagsList = flags.Split(',').Select(flag => flag.Trim()).ToList();
|
||||
_ = Instance.PermissionManager.AddAdminBySteamId(steamid, name, flagsList, immunity, time, globalAdmin);
|
||||
@@ -248,11 +297,16 @@ public partial class CS2_SimpleAdmin
|
||||
Server.PrintToConsole(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles removing an admin's flags and groups by SteamID.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(minArgs: 1, usage: "<steamid>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/root")]
|
||||
public void OnDelAdminCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
|
||||
{
|
||||
@@ -265,9 +319,16 @@ public partial class CS2_SimpleAdmin
|
||||
RemoveAdmin(caller, steamId.SteamId64.ToString(), globalDelete, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes admin permissions and groups for a player.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="steamid">SteamID as string identifying the player.</param>
|
||||
/// <param name="globalDelete">Whether to delete globally.</param>
|
||||
/// <param name="command">Optional command info.</param>
|
||||
public void RemoveAdmin(CCSPlayerController? caller, string steamid, bool globalDelete = false, CommandInfo? command = null)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
_ = PermissionManager.DeleteAdminBySteamId(steamid, globalDelete);
|
||||
|
||||
AddTimer(2, () =>
|
||||
@@ -294,11 +355,16 @@ public partial class CS2_SimpleAdmin
|
||||
Server.PrintToConsole(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new admin group with specified flags and immunity settings.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(minArgs: 3, usage: "<group_name> <flags> <immunity>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/root")]
|
||||
public void OnAddGroup(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
if (!command.GetArg(1).StartsWith("#"))
|
||||
{
|
||||
@@ -320,9 +386,18 @@ public partial class CS2_SimpleAdmin
|
||||
AddGroup(caller, groupName, flags, immunity, globalGroup, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new admin group with specified flags and immunity level.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="name">Group name (prefix with #).</param>
|
||||
/// <param name="flags">Comma-separated flags/groups string.</param>
|
||||
/// <param name="immunity">Immunity level.</param>
|
||||
/// <param name="globalGroup">Whether group is global.</param>
|
||||
/// <param name="command">Optional command info.</param>
|
||||
private static void AddGroup(CCSPlayerController? caller, string name, string flags, int immunity, bool globalGroup, CommandInfo? command = null)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var flagsList = flags.Split(',').Select(flag => flag.Trim()).ToList();
|
||||
_ = Instance.PermissionManager.AddGroup(name, flagsList, immunity, globalGroup);
|
||||
@@ -338,11 +413,16 @@ public partial class CS2_SimpleAdmin
|
||||
Server.PrintToConsole(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles removing a group by name.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(minArgs: 1, usage: "<group_name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/root")]
|
||||
public void OnDelGroupCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
if (!command.GetArg(1).StartsWith($"#"))
|
||||
{
|
||||
@@ -355,9 +435,15 @@ public partial class CS2_SimpleAdmin
|
||||
RemoveGroup(caller, groupName, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a group.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="name">The group name to remove.</param>
|
||||
/// <param name="command">Optional command info for confirmation.</param>
|
||||
private void RemoveGroup(CCSPlayerController? caller, string name, CommandInfo? command = null)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
_ = PermissionManager.DeleteGroup(name);
|
||||
|
||||
AddTimer(2, () =>
|
||||
@@ -376,30 +462,42 @@ public partial class CS2_SimpleAdmin
|
||||
Server.PrintToConsole(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads admin and group data from database and json files.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the reload command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/root")]
|
||||
public void OnRelAdminCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
|
||||
if (DatabaseProvider == null) return;
|
||||
ReloadAdmins(caller);
|
||||
|
||||
command.ReplyToCommand("Reloaded sql admins and groups");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads bans cache.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the reload command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/root")]
|
||||
public void OnRelBans(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
_ = Instance.CacheManager?.ForceReInitializeCacheAsync();
|
||||
command.ReplyToCommand("Reloaded bans");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads admin data asynchronously and updates admin caches.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the reload command.</param>
|
||||
public void ReloadAdmins(CCSPlayerController? caller)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
@@ -411,14 +509,17 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(adminsFile))
|
||||
AddTimer(1.3f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json"));
|
||||
if (!string.IsNullOrEmpty(groupsFile))
|
||||
AddTimer(2.5f, () => AdminManager.LoadAdminGroups(ModuleDirectory + "/data/groups.json"));
|
||||
if (!string.IsNullOrEmpty(adminsFile))
|
||||
AddTimer(3.5f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json"));
|
||||
AddTimer(1, () =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(adminsFile))
|
||||
AddTimer(2.0f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json"));
|
||||
if (!string.IsNullOrEmpty(groupsFile))
|
||||
AddTimer(3.0f, () => AdminManager.LoadAdminGroups(ModuleDirectory + "/data/groups.json"));
|
||||
if (!string.IsNullOrEmpty(adminsFile))
|
||||
AddTimer(4.0f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json"));
|
||||
|
||||
_logger?.LogInformation("Loaded admins!");
|
||||
_logger?.LogInformation("Loaded admins!");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -426,6 +527,11 @@ public partial class CS2_SimpleAdmin
|
||||
//_ = _adminManager.GiveAllFlags();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles player visibility on the server, hiding or revealing them.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the hide command.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnHideCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -438,7 +544,9 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
SilentPlayers.Remove(caller.Slot);
|
||||
caller.PrintToChat($"You aren't hidden now!");
|
||||
caller.ChangeTeam(CsTeam.Spectator);
|
||||
if (caller.TeamNum <= 1)
|
||||
caller.ChangeTeam(CsTeam.Spectator);
|
||||
SimpleAdminApi?.OnAdminToggleSilentEvent(caller.Slot, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -447,13 +555,20 @@ public partial class CS2_SimpleAdmin
|
||||
if (caller.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
caller.PlayerPawn.Value?.CommitSuicide(true, false);
|
||||
|
||||
AddTimer(1.0f, () => { Server.NextFrame(() => caller.ChangeTeam(CsTeam.Spectator)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
AddTimer(1.4f, () => { Server.NextFrame(() => caller.ChangeTeam(CsTeam.None)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
caller.PrintToChat($"You are hidden now!");
|
||||
AddTimer(2.0f, () => { Server.NextFrame(() => Server.ExecuteCommand("sv_disable_teamselect_menu 0")); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
if (caller.TeamNum > 1)
|
||||
AddTimer(0.15f, () => { Server.NextWorldUpdate(() => caller.ChangeTeam(CsTeam.Spectator)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
AddTimer(0.26f, () => { Server.NextWorldUpdate(() => caller.ChangeTeam(CsTeam.None)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
AddTimer(0.50f, () => { Server.NextWorldUpdate(() => Server.ExecuteCommand("sv_disable_teamselect_menu 0")); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
SimpleAdminApi?.OnAdminToggleSilentEvent(caller.Slot, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles penalty notification visibility to admins.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player toggling notification visibility.</param>
|
||||
/// <param name="command">Command input parameters.</param>
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnHideCommsCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -473,11 +588,16 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays detailed information about target players, including admin groups, permissions, and penalties.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">Command input parameters including targets.</param>
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
public void OnWhoCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
@@ -491,39 +611,41 @@ public partial class CS2_SimpleAdmin
|
||||
if (!player.UserId.HasValue) return;
|
||||
if (!caller!.CanTarget(player)) return;
|
||||
|
||||
var playerInfo = PlayersInfo[player.UserId.Value];
|
||||
var playerInfo = PlayersInfo[player.SteamID];
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Server.NextFrameAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
Action<string> printMethod = caller == null ? Server.PrintToConsole : caller.PrintToConsole;
|
||||
|
||||
|
||||
var adminData = AdminManager.GetPlayerAdminData(new SteamID(player.SteamID));
|
||||
|
||||
printMethod($"--------- INFO ABOUT \"{playerInfo.Name}\" ---------");
|
||||
|
||||
printMethod($"• Clan: \"{player.Clan}\" Name: \"{playerInfo.Name}\"");
|
||||
printMethod($"• UserID: \"{playerInfo.UserId}\"");
|
||||
printMethod($"• SteamID64: \"{playerInfo.SteamId.SteamId64}\"");
|
||||
if (player.Connected == PlayerConnectedState.PlayerConnected)
|
||||
if (adminData != null)
|
||||
{
|
||||
printMethod($"• SteamID2: \"{playerInfo.SteamId.SteamId2}\"");
|
||||
printMethod($"• Community link: \"{playerInfo.SteamId.ToCommunityUrl()}\"");
|
||||
var flags = string.Join(",", adminData._flags);
|
||||
var groups = string.Join(",", adminData.Groups);
|
||||
|
||||
printMethod($"• Groups/Flags: \"{groups}{flags}\"");
|
||||
}
|
||||
printMethod($"• SteamID2: \"{playerInfo.SteamId.SteamId2}\"");
|
||||
printMethod($"• Community link: \"{playerInfo.SteamId.ToCommunityUrl()}\"");
|
||||
if (playerInfo.IpAddress != null && AdminManager.PlayerHasPermissions(new SteamID(caller!.SteamID), "@css/showip"))
|
||||
printMethod($"• IP Address: \"{playerInfo.IpAddress}\"");
|
||||
printMethod($"• Ping: \"{player.Ping}\"");
|
||||
if (player.Connected == PlayerConnectedState.PlayerConnected)
|
||||
{
|
||||
printMethod($"• Total Bans: \"{playerInfo.TotalBans}\"");
|
||||
printMethod($"• Total Gags: \"{playerInfo.TotalGags}\"");
|
||||
printMethod($"• Total Mutes: \"{playerInfo.TotalMutes}\"");
|
||||
printMethod($"• Total Silences: \"{playerInfo.TotalSilences}\"");
|
||||
printMethod($"• Total Warns: \"{playerInfo.TotalWarns}\"");
|
||||
printMethod($"• Total Bans: \"{playerInfo.TotalBans}\"");
|
||||
printMethod($"• Total Gags: \"{playerInfo.TotalGags}\"");
|
||||
printMethod($"• Total Mutes: \"{playerInfo.TotalMutes}\"");
|
||||
printMethod($"• Total Silences: \"{playerInfo.TotalSilences}\"");
|
||||
printMethod($"• Total Warns: \"{playerInfo.TotalWarns}\"");
|
||||
|
||||
var chunkedAccounts = playerInfo.AccountsAssociated.ChunkBy(3).ToList();
|
||||
foreach (var chunk in chunkedAccounts)
|
||||
printMethod($"• Associated Accounts: \"{string.Join(", ", chunk.Select(a => $"{a.PlayerName} ({a.SteamId})"))}\"");
|
||||
}
|
||||
var chunkedAccounts = playerInfo.AccountsAssociated.ChunkBy(3).ToList();
|
||||
foreach (var chunk in chunkedAccounts)
|
||||
printMethod($"• Associated Accounts: \"{string.Join(", ", chunk.Select(a => $"{a.PlayerName} ({a.SteamId})"))}\"");
|
||||
|
||||
printMethod($"--------- END INFO ABOUT \"{player.PlayerName}\" ---------");
|
||||
});
|
||||
@@ -531,6 +653,11 @@ public partial class CS2_SimpleAdmin
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a menu with disconnected players, allowing the caller to apply penalties like ban, mute, gag, or silence.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">The command containing parameters.</param>
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnDisconnectedCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -652,11 +779,17 @@ public partial class CS2_SimpleAdmin
|
||||
disconnectedMenu?.Open(caller);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the warning menu for a player specified by a command argument,
|
||||
/// showing active and past warns with options to remove them.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">The command containing target player identifier.</param>
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnWarnsCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null || _localizer == null || caller == null) return;
|
||||
if (DatabaseProvider == null || _localizer == null || caller == null) return;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
@@ -673,12 +806,13 @@ public partial class CS2_SimpleAdmin
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
var userId = player.UserId.Value;
|
||||
var steamId = player.SteamID;
|
||||
|
||||
IMenu? warnsMenu = Helper.CreateMenu(_localizer["sa_admin_warns_menu_title", player.PlayerName]);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var warnsList = await WarnManager.GetPlayerWarns(PlayersInfo[userId], false);
|
||||
var warnsList = await WarnManager.GetPlayerWarns(PlayersInfo[steamId], false);
|
||||
var sortedWarns = warnsList
|
||||
.OrderBy(warn => (string)warn.status == "ACTIVE" ? 0 : 1)
|
||||
.ThenByDescending(warn => (int)warn.id)
|
||||
@@ -689,12 +823,12 @@ public partial class CS2_SimpleAdmin
|
||||
warnsMenu?.AddMenuOption($"[{((string)w.status == "ACTIVE" ? $"{ChatColors.LightRed}X" : $"{ChatColors.Lime}✔️")}{ChatColors.Default}] {(string)w.reason}",
|
||||
(controller, option) =>
|
||||
{
|
||||
_ = WarnManager.UnwarnPlayer(PlayersInfo[userId], (int)w.id);
|
||||
_ = WarnManager.UnwarnPlayer(PlayersInfo[steamId], (int)w.id);
|
||||
player.PrintToChat(_localizer["sa_admin_warns_unwarn", player.PlayerName, (string)w.reason]);
|
||||
});
|
||||
});
|
||||
|
||||
await Server.NextFrameAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
warnsMenu?.Open(caller);
|
||||
});
|
||||
@@ -702,8 +836,14 @@ public partial class CS2_SimpleAdmin
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists players currently connected to the server with options to output JSON or filter duplicate IPs.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command or null for console.</param>
|
||||
/// <param name="command">The command containing output options.</param>
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||
public void OnPlayersCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var isJson = command.GetArg(1).ToLower().Equals("-json");
|
||||
@@ -740,27 +880,39 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
else
|
||||
{
|
||||
var playersJson = JsonConvert.SerializeObject(playersToTarget.Select(player =>
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
var matchStats = player.ActionTrackingServices?.MatchStats;
|
||||
|
||||
return new
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
var playerDtos = playersToTarget
|
||||
.Where(player => player.UserId.HasValue)
|
||||
.Select(player =>
|
||||
{
|
||||
player.UserId,
|
||||
Name = player.PlayerName,
|
||||
SteamId = player.SteamID.ToString(),
|
||||
IpAddress = AdminManager.PlayerHasPermissions(new SteamID(caller!.SteamID), "@css/showip") ? player.IpAddress?.Split(":")[0] ?? "Unknown" : "Unknown",
|
||||
player.Ping,
|
||||
IsAdmin = AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/ban") || AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/generic"),
|
||||
Stats = new
|
||||
{
|
||||
player.Score,
|
||||
Kills = matchStats?.Kills ?? 0,
|
||||
Deaths = matchStats?.Deaths ?? 0,
|
||||
player.MVPs
|
||||
}
|
||||
};
|
||||
}));
|
||||
var matchStats = player.ActionTrackingServices?.MatchStats;
|
||||
|
||||
return new PlayerDto(
|
||||
player.UserId.GetValueOrDefault(0),
|
||||
player.PlayerName,
|
||||
player.SteamID.ToString(),
|
||||
AdminManager.PlayerHasPermissions(new SteamID(caller!.SteamID), "@css/showip")
|
||||
? player.IpAddress?.Split(":")[0] ?? "Unknown"
|
||||
: "Unknown",
|
||||
player.Ping,
|
||||
AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/ban")
|
||||
|| AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/generic"),
|
||||
new PlayerStats(
|
||||
player.Score,
|
||||
matchStats?.Kills ?? 0,
|
||||
matchStats?.Deaths ?? 0,
|
||||
player.MVPs
|
||||
)
|
||||
);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var playersJson = JsonSerializer.Serialize(playerDtos, options);
|
||||
|
||||
if (caller != null)
|
||||
caller.PrintToConsole(playersJson);
|
||||
@@ -769,6 +921,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a kick to one or multiple players specified in the command arguments.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the kick command.</param>
|
||||
/// <param name="command">The command with target player(s) and optional reason.</param>
|
||||
[RequiresPermissions("@css/kick")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnKickCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -806,6 +963,15 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a specified player immediately with reason, notifying the server and logging the action.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the kick.</param>
|
||||
/// <param name="player">The player to be kicked.</param>
|
||||
/// <param name="reason">The reason for the kick.</param>
|
||||
/// <param name="callerName">Optional name of the kick issuer for notifications.</param>
|
||||
/// <param name="command">Optional command for logging.</param>
|
||||
public void Kick(CCSPlayerController? caller, CCSPlayerController player, string? reason = "Unknown", string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
@@ -816,8 +982,8 @@ public partial class CS2_SimpleAdmin
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
reason ??= _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
var playerInfo = PlayersInfo[player.UserId.Value];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var playerInfo = PlayersInfo[player.SteamID];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Determine message keys and arguments for the kick notification
|
||||
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) =
|
||||
@@ -847,6 +1013,11 @@ public partial class CS2_SimpleAdmin
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Kick, reason, -1, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current map to the specified map name or workshop map ID.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the map change.</param>
|
||||
/// <param name="command">The command containing the map name or ID.</param>
|
||||
[RequiresPermissions("@css/changemap")]
|
||||
[CommandHelper(minArgs: 1, usage: "<mapname>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnMapCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -855,6 +1026,12 @@ public partial class CS2_SimpleAdmin
|
||||
ChangeMap(caller, map, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes to a specified map, validating it or handling workshop maps, and notifying the server and admins.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the change.</param>
|
||||
/// <param name="map">The map name or identifier.</param>
|
||||
/// <param name="command">Optional command object for logging and replies.</param>
|
||||
public void ChangeMap(CCSPlayerController? caller, string map, CommandInfo? command = null)
|
||||
{
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
@@ -904,6 +1081,11 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, command?.GetCommandString ?? $"css_map {map}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current map to a workshop map specified by name or ID.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">The command containing the workshop map identifier.</param>
|
||||
[CommandHelper(1, "<name or id>")]
|
||||
[RequiresPermissions("@css/changemap")]
|
||||
public void OnWorkshopMapCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -912,6 +1094,12 @@ public partial class CS2_SimpleAdmin
|
||||
ChangeWorkshopMap(caller, map, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes to a specified workshop map by name or ID and notifies admins.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="map">The workshop map identifier.</param>
|
||||
/// <param name="command">Optional command for logging.</param>
|
||||
public void ChangeWorkshopMap(CCSPlayerController? caller, string map, CommandInfo? command = null)
|
||||
{
|
||||
map = map.ToLower();
|
||||
@@ -941,6 +1129,11 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, command?.GetCommandString ?? $"css_wsmap {map}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows changing a console variable's value.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">The command with cvar name and value.</param>
|
||||
[CommandHelper(2, "<cvar> <value>")]
|
||||
[RequiresPermissions("@css/cvar")]
|
||||
public void OnCvarCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -961,28 +1154,33 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
var value = command.GetArg(2);
|
||||
|
||||
Server.ExecuteCommand($"{cvar.Name} {value}");
|
||||
|
||||
command.ReplyToCommand($"{callerName} changed cvar {cvar.Name} to {value}.");
|
||||
Logger.LogInformation($"{callerName} changed cvar {cvar.Name} to {value}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an RCON command on the server.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">The command string to execute via RCON.</param>
|
||||
[CommandHelper(1, "<command>")]
|
||||
[RequiresPermissions("@css/rcon")]
|
||||
public void OnRconCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
Server.ExecuteCommand(command.ArgString);
|
||||
command.ReplyToCommand($"{callerName} executed command {command.ArgString}.");
|
||||
Logger.LogInformation($"{callerName} executed command ({command.ArgString}).");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the game.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player or console initiating the restart.</param>
|
||||
/// <param name="command">The restart command info.</param>
|
||||
[RequiresPermissions("@css/generic")]
|
||||
[CommandHelper(minArgs: 0, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnRestartCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -990,6 +1188,11 @@ public partial class CS2_SimpleAdmin
|
||||
RestartGame(caller);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens plugin manager menu for the caller with options to load or unload plugins.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player opening the plugin manager.</param>
|
||||
/// <param name="commandInfo">The command parameters.</param>
|
||||
[RequiresPermissions("@css/root")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
|
||||
public void OnPluginManagerCommand(CCSPlayerController? caller, CommandInfo commandInfo)
|
||||
@@ -1060,6 +1263,10 @@ public partial class CS2_SimpleAdmin
|
||||
pluginsMenu?.Open(caller);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the game process by issuing the restart game command to the server and logging the action.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin or console requesting the restart.</param>
|
||||
public static void RestartGame(CCSPlayerController? admin)
|
||||
{
|
||||
Helper.LogCommand(admin, "css_restartgame");
|
||||
|
||||
@@ -11,11 +11,16 @@ 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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
@@ -49,9 +54,19 @@ 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 (Database == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
if (!CheckValidMute(caller, time)) return;
|
||||
|
||||
@@ -59,14 +74,18 @@ public partial class CS2_SimpleAdmin
|
||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
// Get player and admin information
|
||||
var playerInfo = PlayersInfo[player.UserId.Value];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var playerInfo = PlayersInfo[player.SteamID];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Asynchronously handle gag logic
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time, penaltyId);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
// Add penalty to the player's penalty manager
|
||||
@@ -91,7 +110,7 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
// Increment the player's total gags count
|
||||
PlayersInfo[player.UserId.Value].TotalGags++;
|
||||
PlayersInfo[player.SteamID].TotalGags++;
|
||||
|
||||
// Log the gag command and send Discord notification
|
||||
if (!silent)
|
||||
@@ -105,16 +124,23 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Gag, _localizer);
|
||||
}
|
||||
|
||||
internal void AddGag(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
|
||||
/// <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.UserId.Value] : null;
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString());
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -131,19 +157,28 @@ public partial class CS2_SimpleAdmin
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64.ToString(), adminInfo, reason, time, 3);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Gag, reason, time, penaltyId);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
// Set caller name
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
@@ -158,7 +193,7 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
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";
|
||||
@@ -169,7 +204,7 @@ public partial class CS2_SimpleAdmin
|
||||
if (!CheckValidMute(caller, time)) return;
|
||||
|
||||
// Get player and admin info
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Attempt to match player based on SteamID
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
@@ -191,10 +226,14 @@ public partial class CS2_SimpleAdmin
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time, penaltyId);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid, reason, time, PenaltyType.Gag, _localizer);
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Gag, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Gag has been added offline.");
|
||||
}
|
||||
@@ -203,11 +242,16 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
||||
var pattern = command.GetArg(1);
|
||||
@@ -228,7 +272,7 @@ 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.ToString());
|
||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -252,8 +296,8 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Gag);
|
||||
|
||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalGags > 0)
|
||||
PlayersInfo[namePlayer.UserId.Value].TotalGags--;
|
||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalGags > 0)
|
||||
PlayersInfo[namePlayer.SteamID].TotalGags--;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
@@ -273,11 +317,16 @@ 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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
@@ -311,9 +360,19 @@ 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 (Database == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
if (!CheckValidMute(caller, time)) return;
|
||||
|
||||
@@ -321,8 +380,8 @@ public partial class CS2_SimpleAdmin
|
||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
// Get player and admin information
|
||||
var playerInfo = PlayersInfo[player.UserId.Value];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var playerInfo = PlayersInfo[player.SteamID];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Set player's voice flags to muted
|
||||
player.VoiceFlags = VoiceFlags.Muted;
|
||||
@@ -331,7 +390,11 @@ public partial class CS2_SimpleAdmin
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time, penaltyId);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
// Add penalty to the player's penalty manager
|
||||
@@ -356,7 +419,7 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
// Increment the player's total mutes count
|
||||
PlayersInfo[player.UserId.Value].TotalMutes++;
|
||||
PlayersInfo[player.SteamID].TotalMutes++;
|
||||
|
||||
// Log the mute command and send Discord notification
|
||||
if (!silent)
|
||||
@@ -370,11 +433,16 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Mute, _localizer);
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
// Set caller name
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
@@ -389,7 +457,7 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
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";
|
||||
@@ -400,7 +468,7 @@ public partial class CS2_SimpleAdmin
|
||||
if (!CheckValidMute(caller, time)) return;
|
||||
|
||||
// Get player and admin info
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Attempt to match player based on SteamID
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
@@ -422,10 +490,14 @@ public partial class CS2_SimpleAdmin
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time, penaltyId);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid, reason, time, PenaltyType.Mute, _localizer);
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Mute, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Mute has been added offline.");
|
||||
}
|
||||
@@ -434,6 +506,14 @@ public partial class CS2_SimpleAdmin
|
||||
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
|
||||
@@ -441,9 +521,9 @@ public partial class CS2_SimpleAdmin
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString());
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -460,19 +540,29 @@ public partial class CS2_SimpleAdmin
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64.ToString(), adminInfo, reason, time, 1);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Mute, reason, time, penaltyId);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
||||
var pattern = command.GetArg(1);
|
||||
@@ -493,7 +583,7 @@ 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.ToString());
|
||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -519,8 +609,8 @@ public partial class CS2_SimpleAdmin
|
||||
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Mute);
|
||||
namePlayer.VoiceFlags = VoiceFlags.Normal;
|
||||
|
||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalMutes > 0)
|
||||
PlayersInfo[namePlayer.UserId.Value].TotalMutes--;
|
||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalMutes > 0)
|
||||
PlayersInfo[namePlayer.SteamID].TotalMutes--;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
@@ -540,11 +630,17 @@ 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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
@@ -578,9 +674,19 @@ 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 (Database == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
if (!CheckValidMute(caller, time)) return;
|
||||
|
||||
@@ -588,14 +694,18 @@ public partial class CS2_SimpleAdmin
|
||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
// Get player and admin information
|
||||
var playerInfo = PlayersInfo[player.UserId.Value];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var playerInfo = PlayersInfo[player.SteamID];
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Asynchronously handle silence logic
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2); // Assuming 2 is the type for silence
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time, penaltyId);
|
||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
// Add penalty to the player's penalty manager
|
||||
@@ -621,7 +731,7 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
// Increment the player's total silences count
|
||||
PlayersInfo[player.UserId.Value].TotalSilences++;
|
||||
PlayersInfo[player.SteamID].TotalSilences++;
|
||||
|
||||
// Log the silence command and send Discord notification
|
||||
if (!silent)
|
||||
@@ -635,11 +745,17 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Silence, _localizer);
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
// Set caller name
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
@@ -654,7 +770,7 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
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";
|
||||
@@ -665,7 +781,7 @@ public partial class CS2_SimpleAdmin
|
||||
if (!CheckValidMute(caller, time)) return;
|
||||
|
||||
// Get player and admin info
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
// Attempt to match player based on SteamID
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
@@ -687,10 +803,14 @@ public partial class CS2_SimpleAdmin
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason, time, penaltyId);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason,
|
||||
time, penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid, reason, time, PenaltyType.Silence, _localizer);
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Silence, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Silence has been added offline.");
|
||||
}
|
||||
@@ -699,6 +819,14 @@ public partial class CS2_SimpleAdmin
|
||||
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
|
||||
@@ -706,9 +834,9 @@ public partial class CS2_SimpleAdmin
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null;
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64.ToString());
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -725,19 +853,29 @@ public partial class CS2_SimpleAdmin
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64.ToString(), adminInfo, reason, time, 2);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Silence, reason, time, penaltyId);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
||||
var pattern = command.GetArg(1);
|
||||
@@ -758,7 +896,7 @@ 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.ToString());
|
||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -788,8 +926,8 @@ public partial class CS2_SimpleAdmin
|
||||
// Reset voice flags to normal
|
||||
namePlayer.VoiceFlags = VoiceFlags.Normal;
|
||||
|
||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalSilences > 0)
|
||||
PlayersInfo[namePlayer.UserId.Value].TotalSilences--;
|
||||
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalSilences > 0)
|
||||
PlayersInfo[namePlayer.SteamID].TotalSilences--;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
@@ -809,6 +947,12 @@ 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;
|
||||
|
||||
@@ -8,6 +8,12 @@ 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)
|
||||
|
||||
@@ -8,6 +8,11 @@ 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)
|
||||
@@ -31,6 +36,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -58,6 +70,12 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -82,6 +100,12 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -111,6 +135,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -133,6 +162,11 @@ public partial class CS2_SimpleAdmin
|
||||
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)
|
||||
@@ -153,8 +187,8 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
sceneNode.GetSkeletonInstance().Scale = size;
|
||||
player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
Server.NextFrame(() =>
|
||||
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
|
||||
});
|
||||
@@ -173,6 +207,14 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -206,6 +248,11 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -224,6 +271,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
|
||||
@@ -11,9 +11,15 @@ namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
internal static readonly Dictionary<int, float> SpeedPlayers = [];
|
||||
internal static readonly Dictionary<CCSPlayerController, 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)
|
||||
@@ -32,6 +38,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -59,6 +72,12 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -70,13 +89,6 @@ public partial class CS2_SimpleAdmin
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).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"))
|
||||
{
|
||||
@@ -98,6 +110,15 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -137,6 +158,14 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -163,6 +192,12 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -184,6 +219,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -214,6 +256,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -236,6 +283,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -263,6 +317,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -288,6 +347,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -299,9 +365,9 @@ public partial class CS2_SimpleAdmin
|
||||
player.SetSpeed(speed);
|
||||
|
||||
if (speed == 1f)
|
||||
SpeedPlayers.Remove(player.Slot);
|
||||
SpeedPlayers.Remove(player);
|
||||
else
|
||||
SpeedPlayers[player.Slot] = speed;
|
||||
SpeedPlayers[player] = speed;
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
@@ -319,6 +385,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -344,6 +415,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -375,6 +453,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -401,6 +484,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -427,6 +517,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -457,6 +552,13 @@ public partial class CS2_SimpleAdmin
|
||||
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;
|
||||
@@ -485,6 +587,11 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -534,6 +641,15 @@ public partial class CS2_SimpleAdmin
|
||||
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
|
||||
@@ -581,6 +697,11 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, 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)
|
||||
@@ -626,6 +747,11 @@ public partial class CS2_SimpleAdmin
|
||||
});
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -676,6 +802,11 @@ 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)
|
||||
@@ -700,6 +831,13 @@ public partial class CS2_SimpleAdmin
|
||||
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
|
||||
@@ -715,8 +853,8 @@ 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.UserId.Value, out var value) && value.DiePosition != null)
|
||||
|
||||
if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.SteamID, out var value) && value.DiePosition != null)
|
||||
playerPawn.Teleport(value.DiePosition?.Position, value.DiePosition?.Angle);
|
||||
|
||||
// Log the command
|
||||
@@ -733,146 +871,270 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
/// <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]")]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnGotoCommand(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;
|
||||
IEnumerable<CCSPlayerController> playersToTeleport;
|
||||
CCSPlayerController? destinationPlayer;
|
||||
|
||||
// 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();
|
||||
if (command.ArgCount < 3)
|
||||
{
|
||||
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
return;
|
||||
|
||||
// Log the command
|
||||
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
|
||||
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))
|
||||
foreach (var player in playersToTeleport)
|
||||
{
|
||||
if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null)
|
||||
if (player.PlayerPawn?.Value == null || destinationPlayer?.PlayerPawn?.Value == null)
|
||||
continue;
|
||||
|
||||
// Teleport the caller to the player and toggle noclip
|
||||
caller.TeleportPlayer(player);
|
||||
// caller.PlayerPawn.Value.ToggleNoclip();
|
||||
player.TeleportPlayer(destinationPlayer);
|
||||
|
||||
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
|
||||
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");
|
||||
|
||||
AddTimer(4, () =>
|
||||
{
|
||||
if (!caller.IsValid || caller.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");
|
||||
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");
|
||||
}
|
||||
});
|
||||
|
||||
// Prepare message key and arguments for the teleport notification
|
||||
var activityMessageKey = "sa_admin_tp_message";
|
||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
|
||||
|
||||
// Show admin activity
|
||||
if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, false, adminActivityArgs);
|
||||
Helper.ShowAdminActivity("sa_admin_tp_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
|
||||
/// <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...]")]
|
||||
[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)
|
||||
IEnumerable<CCSPlayerController> playersToTeleport;
|
||||
CCSPlayerController? destinationPlayer;
|
||||
|
||||
if (command.ArgCount < 3)
|
||||
{
|
||||
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
return;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null || !targets.Any())
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
// 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
|
||||
// Log 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))
|
||||
foreach (var player in playersToTeleport)
|
||||
{
|
||||
if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null)
|
||||
if (player.PlayerPawn?.Value == null || destinationPlayer.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");
|
||||
|
||||
// Teleport
|
||||
player.TeleportPlayer(destinationPlayer);
|
||||
|
||||
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
|
||||
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");
|
||||
|
||||
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");
|
||||
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");
|
||||
}
|
||||
});
|
||||
|
||||
// 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)
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, false, adminActivityArgs);
|
||||
Helper.ShowAdminActivity("sa_admin_bring_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [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);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -38,67 +38,67 @@ public class Discord
|
||||
[JsonPropertyName("DiscordPenaltyBanSettings")]
|
||||
public DiscordPenaltySetting[] DiscordPenaltyBanSettings { get; set; } =
|
||||
[
|
||||
new DiscordPenaltySetting { Name = "Color", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Webhook", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ImageUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Footer", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Time", Value = "{relative}" },
|
||||
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("DiscordPenaltyMuteSettings")]
|
||||
public DiscordPenaltySetting[] DiscordPenaltyMuteSettings { get; set; } =
|
||||
[
|
||||
new DiscordPenaltySetting { Name = "Color", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Webhook", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ImageUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Footer", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Time", Value = "{relative}" },
|
||||
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("DiscordPenaltyGagSettings")]
|
||||
public DiscordPenaltySetting[] DiscordPenaltyGagSettings { get; set; } =
|
||||
[
|
||||
new DiscordPenaltySetting { Name = "Color", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Webhook", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ImageUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Footer", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Time", Value = "{relative}" },
|
||||
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("DiscordPenaltySilenceSettings")]
|
||||
public DiscordPenaltySetting[] DiscordPenaltySilenceSettings { get; set; } =
|
||||
[
|
||||
new DiscordPenaltySetting { Name = "Color", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Webhook", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ImageUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Footer", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Time", Value = "{relative}" },
|
||||
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("DiscordPenaltyWarnSettings")]
|
||||
public DiscordPenaltySetting[] DiscordPenaltyWarnSettings { get; set; } =
|
||||
[
|
||||
new DiscordPenaltySetting { Name = "Color", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Webhook", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "ImageUrl", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Footer", Value = "" },
|
||||
new DiscordPenaltySetting { Name = "Time", Value = "{relative}" },
|
||||
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 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}" },
|
||||
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}" },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -124,15 +124,15 @@ public class MenuConfig
|
||||
[JsonPropertyName("Durations")]
|
||||
public DurationItem[] Durations { get; set; } =
|
||||
[
|
||||
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 }
|
||||
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 }
|
||||
];
|
||||
|
||||
[JsonPropertyName("BanReasons")]
|
||||
@@ -177,18 +177,18 @@ public class MenuConfig
|
||||
[JsonPropertyName("AdminFlags")]
|
||||
public AdminFlag[] AdminFlags { get; set; } =
|
||||
[
|
||||
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" }
|
||||
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" }
|
||||
];
|
||||
}
|
||||
|
||||
@@ -247,26 +247,11 @@ public class OtherSettings
|
||||
|
||||
public class CS2_SimpleAdminConfig : BasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 24;
|
||||
|
||||
[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";
|
||||
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 25;
|
||||
|
||||
[JsonPropertyName("DatabaseConfig")]
|
||||
public DatabaseConfig DatabaseConfig { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("OtherSettings")]
|
||||
public OtherSettings OtherSettings { get; set; } = new();
|
||||
|
||||
@@ -303,4 +288,38 @@ 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
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ 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)
|
||||
@@ -26,6 +31,11 @@ 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)
|
||||
@@ -35,11 +45,11 @@ public class Database(string dbConnectionString)
|
||||
}
|
||||
}
|
||||
|
||||
public void DatabaseMigration()
|
||||
{
|
||||
Migration migrator = new(this);
|
||||
migrator.ExecuteMigrations();
|
||||
}
|
||||
// public async Task DatabaseMigration()
|
||||
// {
|
||||
// Migration migrator = new(this);
|
||||
// await migrator.ExecuteMigrationsAsync();
|
||||
// }
|
||||
|
||||
public bool CheckDatabaseConnection(out string? exception)
|
||||
{
|
||||
|
||||
60
CS2-SimpleAdmin/Database/IDatabaseProvider.cs
Normal file
60
CS2-SimpleAdmin/Database/IDatabaseProvider.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,61 +1,94 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MySqlConnector;
|
||||
using System.Data.Common;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CS2_SimpleAdmin.Database;
|
||||
|
||||
public class Migration(Database database)
|
||||
public class Migration(string migrationsPath)
|
||||
{
|
||||
public void ExecuteMigrations()
|
||||
/// <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()
|
||||
{
|
||||
var migrationsDirectory = CS2_SimpleAdmin.Instance.ModuleDirectory + "/Database/Migrations";
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
var files = Directory.GetFiles(migrationsPath, "*.sql").OrderBy(f => f).ToList();
|
||||
if (files.Count == 0) return;
|
||||
|
||||
var files = Directory.GetFiles(migrationsDirectory, "*.sql")
|
||||
.OrderBy(f => f);
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
|
||||
using var connection = database.GetConnection();
|
||||
await using (var cmd = connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = """
|
||||
CREATE TABLE IF NOT EXISTS sa_migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version TEXT NOT NULL
|
||||
);
|
||||
|
||||
""";
|
||||
|
||||
// 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);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
|
||||
// Get the last applied migration version
|
||||
var lastAppliedVersion = GetLastAppliedVersion(connection);
|
||||
var lastAppliedVersion = await GetLastAppliedVersionAsync(connection);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var version = Path.GetFileNameWithoutExtension(file);
|
||||
if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0)
|
||||
continue;
|
||||
|
||||
// Check if the migration has already been applied
|
||||
if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0) continue;
|
||||
var sqlScript = File.ReadAllText(file);
|
||||
try
|
||||
{
|
||||
var sqlScript = await File.ReadAllTextAsync(file);
|
||||
|
||||
using var cmdMigration = new MySqlCommand(sqlScript, connection);
|
||||
cmdMigration.ExecuteNonQuery();
|
||||
await using (var cmdMigration = connection.CreateCommand())
|
||||
{
|
||||
cmdMigration.CommandText = sqlScript;
|
||||
await cmdMigration.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
// Update the last applied migration version
|
||||
UpdateLastAppliedVersion(connection, version);
|
||||
await UpdateLastAppliedVersionAsync(connection, version);
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied.");
|
||||
CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex, $"Error applying migration \"{version}\".");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLastAppliedVersion(MySqlConnection connection)
|
||||
/// <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)
|
||||
{
|
||||
using var cmd = new MySqlCommand("SELECT `version` FROM `sa_migrations` ORDER BY `id` DESC LIMIT 1;", connection);
|
||||
var result = cmd.ExecuteScalar();
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "SELECT version FROM sa_migrations ORDER BY id DESC LIMIT 1;";
|
||||
var result = await cmd.ExecuteScalarAsync();
|
||||
return result?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static void UpdateLastAppliedVersion(MySqlConnection connection, string version)
|
||||
/// <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)
|
||||
{
|
||||
using var cmd = new MySqlCommand("INSERT INTO `sa_migrations` (`version`) VALUES (@Version);", connection);
|
||||
cmd.Parameters.AddWithValue("@Version", version);
|
||||
cmd.ExecuteNonQuery();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
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 `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`;
|
||||
ALTER TABLE `sa_players_ips` ADD INDEX(`used_at`);
|
||||
@@ -16,8 +16,8 @@ CREATE TABLE IF NOT EXISTS `sa_unmutes` (
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
INSERT INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
|
||||
VALUES (-1, 'Console', 'Console', '', '0', NULL, NULL, NOW());
|
||||
INSERT IGNORE INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
|
||||
VALUES (0, 'Console', 'Console', '', '0', NULL, NULL, NOW());
|
||||
|
||||
UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
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`;
|
||||
@@ -0,0 +1,3 @@
|
||||
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);
|
||||
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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`)
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
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
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
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) != '';
|
||||
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
@@ -0,0 +1,21 @@
|
||||
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;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_mutes` ADD `passed` INTEGER NULL;
|
||||
@@ -0,0 +1,7 @@
|
||||
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`)
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
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'
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_servers` ADD `rcon_password` VARCHAR(128) NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -0,0 +1,9 @@
|
||||
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);
|
||||
@@ -0,0 +1,3 @@
|
||||
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`);
|
||||
@@ -0,0 +1,15 @@
|
||||
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]*';
|
||||
384
CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs
Normal file
384
CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs
Normal file
@@ -0,0 +1,384 @@
|
||||
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;
|
||||
}
|
||||
|
||||
366
CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs
Normal file
366
CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs
Normal file
@@ -0,0 +1,366 @@
|
||||
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";
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using System.Numerics;
|
||||
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;
|
||||
@@ -12,7 +12,6 @@ using System.Text;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.UserMessages;
|
||||
using CounterStrikeSharp.API.ValveConstants.Protobuf;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
@@ -23,7 +22,9 @@ public partial class CS2_SimpleAdmin
|
||||
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);
|
||||
@@ -34,6 +35,22 @@ 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();
|
||||
@@ -53,7 +70,7 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
private void OnGameServerSteamAPIActivated()
|
||||
{
|
||||
if (_serverLoading)
|
||||
if (ServerLoaded || _serverLoading)
|
||||
return;
|
||||
|
||||
_serverLoading = true;
|
||||
@@ -72,7 +89,18 @@ public partial class CS2_SimpleAdmin
|
||||
Logger.LogCritical("[OnClientDisconnect] Before");
|
||||
#endif
|
||||
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
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)
|
||||
return HookResult.Continue;
|
||||
|
||||
#if DEBUG
|
||||
@@ -94,32 +122,28 @@ 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.UserId.Value, out _);
|
||||
|
||||
if (!PermissionManager.AdminCache.TryGetValue(steamId, out var data)
|
||||
PlayerPenaltyManager.RemoveAllPenalties(player.Slot);
|
||||
|
||||
if (player.UserId.HasValue)
|
||||
PlayersInfo.TryRemove(player.SteamID, out _);
|
||||
|
||||
if (!PermissionManager.AdminCache.TryGetValue(steamId, out var data)
|
||||
|| !(data.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);
|
||||
|
||||
@@ -131,37 +155,68 @@ public partial class CS2_SimpleAdmin
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClientConnect(int playerslot, string name, string ipaddress)
|
||||
|
||||
private void OnClientConnect(int playerslot, string name, string ipAddress)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnClientConnect]");
|
||||
#endif
|
||||
if (Config.OtherSettings.BanType == 0)
|
||||
|
||||
var player = Utilities.GetPlayerFromSlot(playerslot);
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
return;
|
||||
|
||||
if (Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0]))
|
||||
return;
|
||||
|
||||
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);
|
||||
// });
|
||||
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)
|
||||
@@ -172,15 +227,16 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
var player = @event.Userid;
|
||||
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
if (player == null || !player.IsValid)
|
||||
return HookResult.Continue;
|
||||
|
||||
// if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.UserId.Value, out PlayerInfo? value) &&
|
||||
// value.WaitingForKick)
|
||||
// return HookResult.Continue;
|
||||
|
||||
new PlayerManager().LoadPlayerData(player);
|
||||
if (player is { IsBot: true, IsHLTV: false })
|
||||
{
|
||||
BotPlayers.Add(player);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
PlayerManager.LoadPlayerData(player, true);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
@@ -228,26 +284,25 @@ public partial class CS2_SimpleAdmin
|
||||
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");
|
||||
|
||||
if (_localizer == null || endDateTime is null) return HookResult.Continue;
|
||||
|
||||
if (CoreConfig.PublicChatTrigger.Concat(CoreConfig.SilentChatTrigger).Any(trigger => message.StartsWith(trigger)))
|
||||
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--)
|
||||
{
|
||||
foreach (var recipient in um.Recipients)
|
||||
if (um.Recipients[i] != author)
|
||||
{
|
||||
if (recipient == author)
|
||||
continue;
|
||||
|
||||
um.Recipients.Remove(recipient);
|
||||
um.Recipients.RemoveAt(i);
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage()));
|
||||
return HookResult.Stop;
|
||||
return HookResult.Continue;
|
||||
|
||||
// author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage()));
|
||||
}
|
||||
|
||||
private HookResult ComamndListenerHandler(CCSPlayerController? player, CommandInfo info)
|
||||
@@ -286,8 +341,17 @@ public partial class CS2_SimpleAdmin
|
||||
if (!command.Contains("say"))
|
||||
return HookResult.Continue;
|
||||
|
||||
if (!Config.OtherSettings.UserMessageGagChatType)
|
||||
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)))
|
||||
{
|
||||
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))
|
||||
{
|
||||
@@ -295,17 +359,9 @@ public partial class CS2_SimpleAdmin
|
||||
player.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", player.GetLanguage()));
|
||||
return HookResult.Stop;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.GetArg(1).StartsWith($"/")
|
||||
|| info.GetArg(1).StartsWith($"!"))
|
||||
return HookResult.Continue;
|
||||
|
||||
if (info.GetArg(1).Length == 0)
|
||||
return HookResult.Stop;
|
||||
// }
|
||||
|
||||
if (command == "say" && info.GetArg(1).StartsWith($"@") &&
|
||||
AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat"))
|
||||
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;
|
||||
@@ -395,10 +451,13 @@ 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(1.0f, () => ServerManager.CheckHibernationStatus());
|
||||
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
|
||||
|
||||
// AddTimer(34, () =>
|
||||
// {
|
||||
@@ -421,9 +480,6 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
if (player is null || @event.Attacker is null || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.PlayerPawn.Value == null)
|
||||
return HookResult.Continue;
|
||||
|
||||
if (SpeedPlayers.TryGetValue(player.Slot, out var speedPlayer))
|
||||
AddTimer(0.15f, () => player.SetSpeed(speedPlayer));
|
||||
|
||||
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
|
||||
|
||||
@@ -441,22 +497,21 @@ public partial class CS2_SimpleAdmin
|
||||
if (player?.UserId == null || !player.IsValid || player.IsHLTV || player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return HookResult.Continue;
|
||||
|
||||
SpeedPlayers.Remove(player.Slot);
|
||||
SpeedPlayers.Remove(player);
|
||||
GravityPlayers.Remove(player);
|
||||
|
||||
if (!PlayersInfo.ContainsKey(player.UserId.Value) || @event.Attacker == null)
|
||||
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.UserId.Value].DiePosition = new DiePosition(
|
||||
new Vector(
|
||||
PlayersInfo[player.SteamID].DiePosition = new DiePosition(
|
||||
new Vector3(
|
||||
playerPosition?.X ?? 0,
|
||||
playerPosition?.Y ?? 0,
|
||||
playerPosition?.Z ?? 0
|
||||
),
|
||||
new QAngle(
|
||||
new Vector3(
|
||||
playerRotation?.X ?? 0,
|
||||
playerRotation?.Y ?? 0,
|
||||
playerRotation?.Z ?? 0
|
||||
@@ -470,17 +525,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;
|
||||
|
||||
info.DontBroadcast = true;
|
||||
|
||||
if (@event.Team > 1)
|
||||
if (@event is { Oldteam: <= 1, Team: >= 1 })
|
||||
{
|
||||
SilentPlayers.Remove(player.Slot);
|
||||
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
@@ -13,11 +15,21 @@ 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"]);
|
||||
@@ -25,6 +37,12 @@ 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;
|
||||
@@ -36,6 +54,12 @@ public static class PlayerExtensions
|
||||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(target);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -44,6 +68,11 @@ public static class PlayerExtensions
|
||||
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;
|
||||
@@ -52,14 +81,24 @@ 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.GravityScale = gravity;
|
||||
playerPawnValue.ActualGravityScale = 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;
|
||||
@@ -70,6 +109,11 @@ 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;
|
||||
@@ -85,36 +129,76 @@ 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 Vector(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
|
||||
var newPos = new Vector3(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, pawn.AbsRotation!, pawn.AbsVelocity);
|
||||
pawn.Teleport(newPos, newRotation, newVelocity);
|
||||
}
|
||||
|
||||
/// <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 Vector(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
|
||||
var newPos = new Vector3(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, pawn.AbsRotation!, pawn.AbsVelocity);
|
||||
pawn.Teleport(newPos, newRotation, newVelocity);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -131,6 +215,11 @@ 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";
|
||||
@@ -158,6 +247,11 @@ 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)
|
||||
@@ -176,6 +270,11 @@ 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)
|
||||
@@ -186,7 +285,7 @@ public static class PlayerExtensions
|
||||
/* Teleport in a random direction - thank you, Mani!*/
|
||||
/* Thank you AM & al!*/
|
||||
var random = new Random();
|
||||
var vel = new Vector(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
|
||||
var vel = new Vector3(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);
|
||||
@@ -217,6 +316,16 @@ 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)
|
||||
{
|
||||
@@ -235,6 +344,16 @@ 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)
|
||||
{
|
||||
|
||||
@@ -49,33 +49,29 @@ internal static class Helper
|
||||
|
||||
public static List<CCSPlayerController> GetPlayerFromName(string name)
|
||||
{
|
||||
return Utilities.GetPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
return GetValidPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public static CCSPlayerController? GetPlayerFromSteamid64(string steamid)
|
||||
public static CCSPlayerController? GetPlayerFromSteamid64(ulong steamid)
|
||||
{
|
||||
return GetValidPlayers().FirstOrDefault(x => x.SteamID.ToString().Equals(steamid, StringComparison.OrdinalIgnoreCase));
|
||||
return GetValidPlayers().FirstOrDefault(x => x.SteamID == steamid);
|
||||
}
|
||||
|
||||
public static CCSPlayerController? GetPlayerFromIp(string ipAddress)
|
||||
public static List<CCSPlayerController> GetPlayerFromIp(string ipAddress)
|
||||
{
|
||||
return GetValidPlayers().FirstOrDefault(x => x.IpAddress != null && x.IpAddress.Split(":")[0].Equals(ipAddress));
|
||||
return CS2_SimpleAdmin.CachedPlayers.FindAll(x => x.IpAddress != null && x.IpAddress.Split(":")[0].Equals(ipAddress));
|
||||
}
|
||||
|
||||
public static List<CCSPlayerController> GetValidPlayers()
|
||||
{
|
||||
return Utilities.GetPlayers().AsValueEnumerable()
|
||||
.Where(p => p is { IsValid: true, IsBot: false, Connected: PlayerConnectedState.PlayerConnected })
|
||||
.ToList();
|
||||
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().ToList();
|
||||
}
|
||||
|
||||
public static List<CCSPlayerController> GetValidPlayersWithBots()
|
||||
{
|
||||
return Utilities.GetPlayers().AsValueEnumerable()
|
||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList();
|
||||
return CS2_SimpleAdmin.CachedPlayers.Concat(CS2_SimpleAdmin.BotPlayers).AsValueEnumerable().ToList();
|
||||
}
|
||||
|
||||
|
||||
// public static bool IsValidSteamId64(string input)
|
||||
// {
|
||||
// const string pattern = @"^\d{17}$";
|
||||
@@ -141,10 +137,36 @@ internal static class Helper
|
||||
if (player == null || !player.IsValid || player.IsHLTV)
|
||||
return;
|
||||
|
||||
if (player.UserId.HasValue && CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.UserId.Value, out var value))
|
||||
if (player.UserId.HasValue && CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
|
||||
value.WaitingForKick = true;
|
||||
|
||||
player.CommitSuicide(true, 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)
|
||||
{
|
||||
@@ -155,6 +177,7 @@ internal static class Helper
|
||||
|
||||
// Server.ExecuteCommand($"kickid {player.UserId}");
|
||||
|
||||
playerPawn?.Colorize();
|
||||
player.Disconnect(reason);
|
||||
});
|
||||
}
|
||||
@@ -162,6 +185,7 @@ internal static class Helper
|
||||
{
|
||||
// Server.ExecuteCommand($"kickid {player.UserId}");
|
||||
|
||||
playerPawn?.Colorize();
|
||||
player.Disconnect(reason);
|
||||
}
|
||||
|
||||
@@ -186,11 +210,41 @@ internal static class Helper
|
||||
if (!player.IsValid || player.IsHLTV)
|
||||
return;
|
||||
|
||||
if (player.UserId.HasValue && CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.UserId.Value, out var value))
|
||||
if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
|
||||
{
|
||||
if (value.WaitingForKick)
|
||||
return;
|
||||
|
||||
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, () =>
|
||||
@@ -237,6 +291,7 @@ internal static class Helper
|
||||
|
||||
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.");
|
||||
@@ -256,7 +311,6 @@ internal static class Helper
|
||||
{ "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))
|
||||
{
|
||||
@@ -309,7 +363,6 @@ 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[
|
||||
@@ -454,16 +507,13 @@ 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]; // Convert argument to string if not null
|
||||
// Replace "CALLER" placeholder in the argument string
|
||||
var arg = formattedMessageArgs[i];
|
||||
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||
{
|
||||
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
@@ -471,7 +521,6 @@ internal static class Helper
|
||||
};
|
||||
}
|
||||
|
||||
// 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()]);
|
||||
@@ -535,10 +584,8 @@ internal static class Helper
|
||||
|
||||
bool[] inlineFlags = [true, true, true, false, false];
|
||||
var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"];
|
||||
var colorHex = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value ?? "#FFFFFF";
|
||||
|
||||
if (string.IsNullOrEmpty(colorHex))
|
||||
colorHex = "#FFFFFF";
|
||||
var colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value;
|
||||
var colorHex = string.IsNullOrWhiteSpace(colorValue) ? "#FFFFFF" : colorValue.Trim();
|
||||
|
||||
var embed = new Embed
|
||||
{
|
||||
@@ -631,7 +678,8 @@ internal static class Helper
|
||||
|
||||
bool[] inlineFlags = [true, true, true, false, false];
|
||||
var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"];
|
||||
var colorHex = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value ?? "#FFFFFF";
|
||||
var colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value;
|
||||
var colorHex = string.IsNullOrWhiteSpace(colorValue) ? "#FFFFFF" : colorValue.Trim();
|
||||
|
||||
var embed = new Embed
|
||||
{
|
||||
@@ -803,9 +851,11 @@ 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);
|
||||
@@ -844,8 +894,8 @@ public static class PluginInfo
|
||||
logger.LogError(ex, "An error occurred while checking version.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ShowAd(string moduleVersion)
|
||||
|
||||
private static void ShowAd(string moduleVersion)
|
||||
{
|
||||
Console.WriteLine(" ");
|
||||
Console.WriteLine(" _______ ___ __ __ _______ ___ _______ _______ ______ __ __ ___ __ _ ");
|
||||
@@ -975,8 +1025,21 @@ public static class IpHelper
|
||||
{
|
||||
public static uint IpToUint(string ipAddress)
|
||||
{
|
||||
return (uint)BitConverter.ToInt32(System.Net.IPAddress.Parse(ipAddress).GetAddressBytes().Reverse().ToArray(),
|
||||
0);
|
||||
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)
|
||||
|
||||
@@ -5,36 +5,37 @@ using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MySqlConnector;
|
||||
using System.Text;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
internal class BanManager(Database.Database? database)
|
||||
internal class BanManager(IDatabaseProvider? databaseProvider)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
if (database == null) return null;
|
||||
|
||||
if (databaseProvider == null) return null;
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
await using MySqlConnection connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
try
|
||||
{
|
||||
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);
|
||||
SELECT LAST_INSERT_ID();
|
||||
""";
|
||||
|
||||
var sql = databaseProvider.GetAddBanQuery();
|
||||
|
||||
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = player.SteamId.SteamId64.ToString(),
|
||||
playerSteamid = player.SteamId.SteamId64,
|
||||
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",
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
banReason = reason,
|
||||
duration = time,
|
||||
@@ -45,37 +46,36 @@ internal class BanManager(Database.Database? database)
|
||||
|
||||
return banId;
|
||||
}
|
||||
catch
|
||||
catch(Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int?> AddBanBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
|
||||
/// <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 (database == null) return null;
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return null;
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
await using MySqlConnection connection = await database.GetConnectionAsync();
|
||||
|
||||
const string 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);
|
||||
SELECT LAST_INSERT_ID();
|
||||
""";
|
||||
|
||||
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.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
banReason = reason,
|
||||
duration = time,
|
||||
@@ -86,15 +86,23 @@ internal class BanManager(Database.Database? database)
|
||||
|
||||
return banId;
|
||||
}
|
||||
catch (Exception)
|
||||
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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
if (string.IsNullOrEmpty(playerIp)) return;
|
||||
|
||||
@@ -103,15 +111,13 @@ internal class BanManager(Database.Database? database)
|
||||
|
||||
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 using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetAddBanByIpQuery();
|
||||
await connection.ExecuteAsync(sql, new
|
||||
{
|
||||
playerIp,
|
||||
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
banReason = reason,
|
||||
duration = time,
|
||||
@@ -123,370 +129,308 @@ internal class BanManager(Database.Database? database)
|
||||
catch { }
|
||||
}
|
||||
|
||||
public async Task<bool> IsPlayerBanned(PlayerInfo player)
|
||||
// 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 })
|
||||
{
|
||||
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.ToString(),
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task<int> GetPlayerBans(PlayerInfo player)
|
||||
try
|
||||
{
|
||||
if (database == null) return 0;
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sqlRetrieveBans = databaseProvider.GetUnbanRetrieveBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
|
||||
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 })
|
||||
{
|
||||
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;
|
||||
}
|
||||
try
|
||||
|
||||
var sqlAdminId = databaseProvider.GetUnbanAdminIdQuery();
|
||||
var adminId = await connection.ExecuteScalarAsync<int?>(sqlAdminId, new { adminSteamId }) ?? 0;
|
||||
|
||||
foreach (var ban in bansList)
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
int banId = ban.id;
|
||||
|
||||
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 sqlInsertUnban = databaseProvider.GetInsertUnbanQuery(reason != null);
|
||||
var unbanId = await connection.ExecuteScalarAsync<int>(sqlInsertUnban, new { banId, adminId, reason });
|
||||
|
||||
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 : [],
|
||||
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.UserId.Value].WaitingForKick) 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}");
|
||||
var sqlUpdateBan = databaseProvider.GetUpdateBanStatusQuery();
|
||||
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 : [],
|
||||
// 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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
var currentTime = Time.ActualDateTime();
|
||||
|
||||
try
|
||||
{
|
||||
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 using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetExpireBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
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 = 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
|
||||
""";
|
||||
|
||||
sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdmin.Models;
|
||||
using Dapper;
|
||||
using ZLinq;
|
||||
@@ -8,7 +9,7 @@ namespace CS2_SimpleAdmin.Managers;
|
||||
internal class CacheManager: IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
|
||||
private readonly ConcurrentDictionary<string, List<BanRecord>> _steamIdIndex = [];
|
||||
private readonly ConcurrentDictionary<ulong, List<BanRecord>> _steamIdIndex = [];
|
||||
private readonly ConcurrentDictionary<uint, List<BanRecord>> _ipIndex = [];
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
|
||||
@@ -17,21 +18,26 @@ internal class CacheManager: IDisposable
|
||||
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.Database == null) return;
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (!CS2_SimpleAdmin.ServerLoaded) return;
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
Clear();
|
||||
_cachedIgnoredIps = new HashSet<uint>(
|
||||
CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps
|
||||
.Select(IpHelper.IpToUint));
|
||||
_cachedIgnoredIps = CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps
|
||||
.AsValueEnumerable()
|
||||
.Select(IpHelper.IpToUint)
|
||||
.ToHashSet();
|
||||
|
||||
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
List<BanRecord> bans;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
@@ -40,6 +46,7 @@ internal class CacheManager: IDisposable
|
||||
"""
|
||||
SELECT
|
||||
id AS Id,
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
@@ -52,6 +59,7 @@ internal class CacheManager: IDisposable
|
||||
"""
|
||||
SELECT
|
||||
id AS Id,
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
@@ -59,20 +67,16 @@ internal class CacheManager: IDisposable
|
||||
WHERE server_id = @serverId
|
||||
""", new {serverId = CS2_SimpleAdmin.ServerId})).ToList();
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
foreach (var ban in bans)
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
_banCache.TryAdd(ban.Id, ban);
|
||||
}
|
||||
|
||||
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
|
||||
{
|
||||
var ipSet = new HashSet<IpRecord>(
|
||||
group
|
||||
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 =>
|
||||
{
|
||||
@@ -80,32 +84,34 @@ internal class CacheManager: IDisposable
|
||||
return new IpRecord(
|
||||
g.Key,
|
||||
latest.used_at,
|
||||
!string.IsNullOrEmpty(latest.name)
|
||||
? latest.name
|
||||
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
|
||||
string.IsNullOrEmpty(latest.name)
|
||||
? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
|
||||
: latest.name
|
||||
);
|
||||
}),
|
||||
new IpRecordComparer()
|
||||
);
|
||||
})
|
||||
.ToHashSet(new IpRecordComparer());
|
||||
|
||||
_playerIpsCache.AddOrUpdate(
|
||||
group.Key,
|
||||
_ => ipSet,
|
||||
(_, existingSet) =>
|
||||
{
|
||||
foreach (var ip in ipSet)
|
||||
_playerIpsCache.AddOrUpdate(
|
||||
group.Key,
|
||||
_ => ipSet,
|
||||
(_, existingSet) =>
|
||||
{
|
||||
existingSet.Remove(ip);
|
||||
existingSet.Add(ip);
|
||||
}
|
||||
|
||||
return 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 = DateTime.Now.AddSeconds(-1);
|
||||
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -114,27 +120,36 @@ internal class CacheManager: IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.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.Database == null) return;
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (!_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
|
||||
List<BanRecord> updatedBans;
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
IEnumerable<BanRecord> updatedBans;
|
||||
|
||||
var allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
||||
|
||||
@@ -143,45 +158,45 @@ internal class CacheManager: IDisposable
|
||||
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 }
|
||||
)).ToList();
|
||||
));
|
||||
}
|
||||
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 }
|
||||
)).ToList();
|
||||
));
|
||||
}
|
||||
|
||||
foreach (var id in _banCache.Keys)
|
||||
{
|
||||
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
|
||||
|
||||
// Remove from steamIdIndex
|
||||
if (!string.IsNullOrWhiteSpace(ban.PlayerSteamId) &&
|
||||
_steamIdIndex.TryGetValue(ban.PlayerSteamId, out var steamBans))
|
||||
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, out _);
|
||||
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
|
||||
}
|
||||
|
||||
// Remove from ipIndex
|
||||
if (!string.IsNullOrWhiteSpace(ban.PlayerIp) &&
|
||||
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) &&
|
||||
_ipIndex.TryGetValue(ipUInt, out var ipBans))
|
||||
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)
|
||||
@@ -189,59 +204,64 @@ internal class CacheManager: IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
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})).ToList();
|
||||
|
||||
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
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()
|
||||
);
|
||||
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 }));
|
||||
|
||||
_playerIpsCache.AddOrUpdate(
|
||||
group.Key,
|
||||
_ => ipSet,
|
||||
(_, existingSet) =>
|
||||
{
|
||||
foreach (var newEntry in ipSet)
|
||||
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) =>
|
||||
{
|
||||
existingSet.Remove(newEntry);
|
||||
existingSet.Add(newEntry);
|
||||
}
|
||||
return existingSet;
|
||||
});
|
||||
foreach (var newEntry in ipSet)
|
||||
{
|
||||
existingSet.Remove(newEntry);
|
||||
existingSet.Add(newEntry);
|
||||
}
|
||||
|
||||
return existingSet;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedBans.Count == 0)
|
||||
return;
|
||||
|
||||
|
||||
foreach (var ban in updatedBans)
|
||||
{
|
||||
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
||||
}
|
||||
|
||||
RebuildIndexes();
|
||||
_lastUpdateTime = DateTime.Now.AddSeconds(-1);
|
||||
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
@@ -249,14 +269,14 @@ internal class CacheManager: IDisposable
|
||||
|
||||
foreach (var ban in _banCache.Values)
|
||||
{
|
||||
if (ban.Status != "ACTIVE")
|
||||
if (ban.StatusEnum != BanStatus.ACTIVE)
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ban.PlayerSteamId))
|
||||
if (ban.PlayerSteamId != null)
|
||||
{
|
||||
var steamId = ban.PlayerSteamId;
|
||||
_steamIdIndex.AddOrUpdate(
|
||||
steamId,
|
||||
steamId.Value,
|
||||
key => [ban],
|
||||
(key, list) =>
|
||||
{
|
||||
@@ -264,7 +284,9 @@ internal class CacheManager: IDisposable
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) continue;
|
||||
|
||||
if (ban.PlayerIp != null &&
|
||||
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
|
||||
{
|
||||
@@ -280,71 +302,233 @@ internal class CacheManager: IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList();
|
||||
public List<BanRecord> GetPlayerBansBySteamId(string steamId) => _steamIdIndex.TryGetValue(steamId, out var bans) ? bans : [];
|
||||
|
||||
/// <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;
|
||||
|
||||
return _playerIpsCache.AsValueEnumerable()
|
||||
.SelectMany(kvp => kvp.Value
|
||||
.Where(entry => entry.Ip == ipAsUint)
|
||||
.Select(entry => (kvp.Key, entry.UsedAt, entry.PlayerName)))
|
||||
.ToList();
|
||||
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(string? steamId, string? ipAddress)
|
||||
// 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)
|
||||
{
|
||||
if (steamId != null && _steamIdIndex.ContainsKey(steamId))
|
||||
return true;
|
||||
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 (ipAddress == null)
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
return false;
|
||||
|
||||
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
|
||||
return false;
|
||||
|
||||
return !_cachedIgnoredIps.Contains(ipUInt) &&
|
||||
_ipIndex.ContainsKey(ipUInt);
|
||||
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)
|
||||
// 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)
|
||||
{
|
||||
var steamIdStr = steamId.ToString();
|
||||
|
||||
if (_steamIdIndex.ContainsKey(steamIdStr))
|
||||
return true;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
|
||||
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 now = DateTime.Now;
|
||||
var cutoff = now.AddDays(-7);
|
||||
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
if (ipAddress != null)
|
||||
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
|
||||
{
|
||||
var ipAsUint = IpHelper.IpToUint(ipAddress);
|
||||
|
||||
if (!_cachedIgnoredIps.Contains(ipAsUint))
|
||||
{
|
||||
ipData.Add(new IpRecord(
|
||||
ipAsUint,
|
||||
now.AddSeconds(-2), // artificially recent
|
||||
CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
|
||||
));
|
||||
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,20 +537,122 @@ internal class CacheManager: IDisposable
|
||||
if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
|
||||
continue;
|
||||
|
||||
if (_ipIndex.ContainsKey(ipRecord.Ip))
|
||||
return true;
|
||||
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.Any(x => x.Ip == IpHelper.IpToUint(ipAddress));
|
||||
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()
|
||||
@@ -379,6 +665,9 @@ internal class CacheManager: IDisposable
|
||||
_cachedIgnoredIps.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears and disposes of all cached data and marks the object as disposed.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
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 json = JsonConvert.SerializeObject(payload);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload, options);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
@@ -34,6 +46,12 @@ 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;
|
||||
@@ -61,7 +79,12 @@ public class DiscordManager(string webhookUrl)
|
||||
}
|
||||
};
|
||||
|
||||
var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
var jsonPayload = JsonSerializer.Serialize(payload, options);
|
||||
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await httpClient.PostAsync(webhookUrl, content);
|
||||
@@ -73,6 +96,11 @@ 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($"#"))
|
||||
@@ -84,6 +112,9 @@ 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; }
|
||||
@@ -96,6 +127,12 @@ 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
|
||||
@@ -109,12 +146,18 @@ 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; }
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
using CS2_SimpleAdminApi;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
internal class MuteManager(Database.Database? database)
|
||||
internal class MuteManager(IDatabaseProvider? databaseProvider)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
if (database == null) return null;
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
@@ -22,21 +32,14 @@ internal class MuteManager(Database.Database? database)
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
SELECT LAST_INSERT_ID();
|
||||
""";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddMuteQuery(true);
|
||||
|
||||
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = player.SteamId.SteamId64.ToString(),
|
||||
playerSteamid = player.SteamId.SteamId64,
|
||||
playerName = player.Name,
|
||||
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
muteReason = reason,
|
||||
duration = time,
|
||||
@@ -55,10 +58,18 @@ internal class MuteManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int?> AddMuteBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
|
||||
/// <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)
|
||||
{
|
||||
if (database == null) return null;
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return null;
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
@@ -72,20 +83,13 @@ internal class MuteManager(Database.Database? database)
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
SELECT LAST_INSERT_ID();
|
||||
""";
|
||||
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddMuteQuery(false);
|
||||
|
||||
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = playerSteamId,
|
||||
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
muteReason = reason,
|
||||
duration = time,
|
||||
@@ -103,9 +107,14 @@ internal class MuteManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return [];
|
||||
if (databaseProvider == null) return [];
|
||||
|
||||
if (string.IsNullOrEmpty(steamId))
|
||||
{
|
||||
@@ -119,24 +128,11 @@ internal class MuteManager(Database.Database? database)
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var currentTime = Time.ActualDateTime();
|
||||
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 sql = databaseProvider.GetIsMutedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
|
||||
|
||||
var parameters = new { PlayerSteamID = steamId, CurrentTime = currentTime, serverid = CS2_SimpleAdmin.ServerId };
|
||||
var activeMutes = (await connection.QueryAsync(sql, parameters)).ToList();
|
||||
return activeMutes;
|
||||
@@ -147,35 +143,26 @@ internal class MuteManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return (0,0,0);
|
||||
if (databaseProvider == null) return (0,0,0);
|
||||
|
||||
try
|
||||
{
|
||||
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;
|
||||
""";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
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.ToString(),
|
||||
PlayerSteamID = playerInfo.SteamId.SteamId64,
|
||||
CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
@@ -187,18 +174,21 @@ internal class MuteManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
const int batchSize = 20;
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
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";
|
||||
var sql = databaseProvider.GetUpdateMutePassedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
|
||||
for (var i = 0; i < players.Count; i += batchSize)
|
||||
{
|
||||
@@ -213,10 +203,7 @@ internal class MuteManager(Database.Database? database)
|
||||
await connection.ExecuteAsync(sql, parametersList);
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
sql = databaseProvider.GetCheckExpiredMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
|
||||
foreach (var (steamId, _, slot) in players)
|
||||
{
|
||||
@@ -233,9 +220,17 @@ internal class MuteManager(Database.Database? database)
|
||||
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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
if (playerPattern.Length <= 1)
|
||||
{
|
||||
@@ -244,8 +239,7 @@ internal class MuteManager(Database.Database? database)
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var muteType = type switch
|
||||
{
|
||||
1 => "MUTE",
|
||||
@@ -253,27 +247,16 @@ internal class MuteManager(Database.Database? database)
|
||||
_ => "GAG"
|
||||
};
|
||||
|
||||
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 sqlRetrieveMutes =
|
||||
databaseProvider.GetRetrieveMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
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;
|
||||
|
||||
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 sqlAdmin = databaseProvider.GetUnmuteAdminIdQuery();
|
||||
var sqlInsertUnmute = databaseProvider.GetInsertUnmuteQuery(string.IsNullOrEmpty(reason));
|
||||
|
||||
var sqlAdminId = await connection.ExecuteScalarAsync<int?>(sqlAdmin, new { adminSteamId });
|
||||
var adminId = sqlAdminId ?? 0;
|
||||
@@ -281,21 +264,11 @@ internal class MuteManager(Database.Database? database)
|
||||
foreach (var mute in mutesList)
|
||||
{
|
||||
int muteId = mute.id;
|
||||
int? unmuteId;
|
||||
|
||||
// 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 });
|
||||
}
|
||||
int? unmuteId =
|
||||
await connection.ExecuteScalarAsync<int>(sqlInsertUnmute, new { muteId, adminId, reason });
|
||||
|
||||
// Update sa_mutes to set unmute_id
|
||||
const string sqlUpdateMute = "UPDATE sa_mutes SET status = 'UNMUTED', unmute_id = @unmuteId WHERE id = @muteId";
|
||||
var sqlUpdateMute = databaseProvider.GetUpdateMuteStatusQuery();
|
||||
await connection.ExecuteAsync(sqlUpdateMute, new { unmuteId, muteId });
|
||||
}
|
||||
}
|
||||
@@ -305,28 +278,18 @@ internal class MuteManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
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 using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetExpireMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
|
||||
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
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(Database.Database? database)
|
||||
public class PermissionManager(IDatabaseProvider? databaseProvider)
|
||||
{
|
||||
// Unused for now
|
||||
//public static readonly ConcurrentDictionary<string, ConcurrentBag<string>> _adminCache = new ConcurrentDictionary<string, ConcurrentBag<string>>();
|
||||
@@ -59,59 +60,65 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
*/
|
||||
|
||||
private async Task<List<(string, string, List<string>, int, DateTime?)>> GetAllPlayersFlags()
|
||||
/// <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()
|
||||
{
|
||||
if (database == null) return [];
|
||||
if (databaseProvider == null)
|
||||
return new List<(ulong, string, List<string>, int, DateTime?)>();
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var now = Time.ActualDateTime();
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
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();
|
||||
|
||||
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
|
||||
""";
|
||||
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
|
||||
};
|
||||
|
||||
var admins = (await connection.QueryAsync(sql, new { CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId })).ToList();
|
||||
int immunity = g.Key.immunity switch
|
||||
{
|
||||
int i => i,
|
||||
string s when int.TryParse(s, out var parsed) => parsed,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
// 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();
|
||||
DateTime? ends = g.Key.ends as DateTime?;
|
||||
|
||||
|
||||
// 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}");
|
||||
// }
|
||||
|
||||
List<(string, string, List<string>, int, DateTime?)> filteredFlagsWithImmunity = [];
|
||||
string playerName = g.Key.playerName as string ?? string.Empty;
|
||||
|
||||
// Add the grouped players to the list
|
||||
filteredFlagsWithImmunity.AddRange(groupedPlayers);
|
||||
// tutaj zakładamy, że Dapper zwraca już string (nie dynamic)
|
||||
var flags = g.Select(r => r.flag as string ?? string.Empty)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
return filteredFlagsWithImmunity;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
|
||||
return [];
|
||||
}
|
||||
return (steamId, playerName, flags, immunity, ends);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return groupedPlayers;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public async Task<Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>>> GetAllGroupsFlags()
|
||||
{
|
||||
@@ -177,33 +184,29 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
*/
|
||||
|
||||
/// <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 (database == null) return [];
|
||||
if (databaseProvider == null) return [];
|
||||
|
||||
await using MySqlConnection connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
;
|
||||
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();
|
||||
|
||||
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 = "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 = databaseProvider.GetGroupsQuery();
|
||||
var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
||||
|
||||
if (groupDataSql.Count == 0 || groupData.Count == 0)
|
||||
if (groupData.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var groupInfoDictionary = new Dictionary<string, (List<string>, int)>();
|
||||
|
||||
foreach (var row in groupData)
|
||||
{
|
||||
var groupName = (string)row.group_name;
|
||||
@@ -231,10 +234,13 @@ public class PermissionManager(Database.Database? database)
|
||||
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)
|
||||
@@ -248,7 +254,13 @@ public class PermissionManager(Database.Database? database)
|
||||
jsonData[kvp.Key] = groupData;
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(jsonData, options);
|
||||
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "groups.json");
|
||||
await File.WriteAllTextAsync(filePath, json);
|
||||
}
|
||||
@@ -313,11 +325,15 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
*/
|
||||
|
||||
/// <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<(string identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
|
||||
List<(ulong identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
|
||||
var validPlayers = allPlayers
|
||||
.Where(player => SteamID.TryParse(player.identity, out _))
|
||||
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
|
||||
.ToList();
|
||||
|
||||
// foreach (var player in allPlayers)
|
||||
@@ -335,10 +351,10 @@ public class PermissionManager(Database.Database? database)
|
||||
var jsonData = validPlayers
|
||||
.GroupBy(player => player.name) // Group by player name
|
||||
.ToDictionary(
|
||||
group => group.Key, // Use the player name as the key
|
||||
group => group.Key, // Use the player name as key
|
||||
object (group) =>
|
||||
{
|
||||
// Consolidate data for players with the same name
|
||||
// Consolidate data for players with same name
|
||||
var consolidatedData = group.Aggregate(
|
||||
new
|
||||
{
|
||||
@@ -349,16 +365,16 @@ public class PermissionManager(Database.Database? database)
|
||||
},
|
||||
(acc, player) =>
|
||||
{
|
||||
// Merge identities and use the latest or first non-null identity
|
||||
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity))
|
||||
// Merge identities
|
||||
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
|
||||
{
|
||||
acc = acc with { identity = player.identity };
|
||||
acc = acc with { identity = player.identity.ToString() };
|
||||
}
|
||||
|
||||
// Combine immunities by taking the maximum value
|
||||
// Combine immunities by maximum value
|
||||
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
|
||||
|
||||
// Combine flags and groups, ensuring no duplicates
|
||||
// Combine flags and groups
|
||||
acc = acc with
|
||||
{
|
||||
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
|
||||
@@ -368,7 +384,7 @@ public class PermissionManager(Database.Database? database)
|
||||
return acc;
|
||||
});
|
||||
|
||||
Server.NextFrameAsync(() =>
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
var keysToRemove = new List<SteamID>();
|
||||
|
||||
@@ -399,7 +415,7 @@ public class PermissionManager(Database.Database? database)
|
||||
|
||||
foreach (var player in group)
|
||||
{
|
||||
if (SteamID.TryParse(player.identity, out var steamId) && steamId != null)
|
||||
if (SteamID.TryParse(player.identity.ToString(), out var steamId) && steamId != null)
|
||||
{
|
||||
AdminCache.TryAdd(steamId, (player.ends, player.flags));
|
||||
}
|
||||
@@ -440,7 +456,13 @@ public class PermissionManager(Database.Database? database)
|
||||
return consolidatedData;
|
||||
});
|
||||
|
||||
var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(jsonData, options);
|
||||
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
|
||||
|
||||
await File.WriteAllTextAsync(filePath, json);
|
||||
@@ -448,32 +470,42 @@ public class PermissionManager(Database.Database? database)
|
||||
//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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return;
|
||||
|
||||
//_adminCache.TryRemove(playerSteamId, out _);
|
||||
|
||||
try
|
||||
{
|
||||
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 using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetDeleteAdminQuery(globalDelete);
|
||||
await connection.ExecuteAsync(sql, new { PlayerSteamID = playerSteamId, CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
|
||||
CS2_SimpleAdmin._logger?.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return;
|
||||
|
||||
@@ -487,12 +519,10 @@ public class PermissionManager(Database.Database? database)
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
// Insert admin into sa_admins table
|
||||
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 insertAdminSql = databaseProvider.GetAddAdminQuery();
|
||||
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
|
||||
{
|
||||
playerSteamId,
|
||||
@@ -506,36 +536,26 @@ public class PermissionManager(Database.Database? database)
|
||||
// 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 });
|
||||
|
||||
const string sql = """
|
||||
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
|
||||
""";
|
||||
|
||||
var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag, CS2_SimpleAdmin.ServerId });
|
||||
|
||||
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)";
|
||||
// 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
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
var insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery();
|
||||
await connection.ExecuteAsync(insertFlagsSql, new
|
||||
{
|
||||
adminId,
|
||||
@@ -554,18 +574,24 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return;
|
||||
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
try
|
||||
{
|
||||
// Insert group into sa_groups table
|
||||
const string insertGroup = "INSERT INTO `sa_groups` (`name`, `immunity`) " +
|
||||
"VALUES (@groupName, @immunity); SELECT LAST_INSERT_ID();";
|
||||
var insertGroup = databaseProvider.GetAddGroupQuery();
|
||||
var groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
|
||||
{
|
||||
groupName,
|
||||
@@ -575,8 +601,7 @@ public class PermissionManager(Database.Database? database)
|
||||
// Insert flags into sa_groups_flags table
|
||||
foreach (var flag in flagsList)
|
||||
{
|
||||
const string insertFlagsSql = "INSERT INTO `sa_groups_flags` (`group_id`, `flag`) " +
|
||||
"VALUES (@groupId, @flag)";
|
||||
var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
|
||||
|
||||
await connection.ExecuteAsync(insertFlagsSql, new
|
||||
{
|
||||
@@ -585,11 +610,8 @@ public class PermissionManager(Database.Database? database)
|
||||
});
|
||||
}
|
||||
|
||||
const string insertGroupServer = "INSERT INTO `sa_groups_servers` (`group_id`, `server_id`) " +
|
||||
"VALUES (@groupId, @server_id)";
|
||||
|
||||
var insertGroupServer = databaseProvider.GetAddGroupServerQuery();
|
||||
await connection.ExecuteAsync(insertGroupServer, new { groupId, server_id = globalGroup ? null : CS2_SimpleAdmin.ServerId });
|
||||
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
|
||||
@@ -602,16 +624,20 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
if (string.IsNullOrEmpty(groupName)) return;
|
||||
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
try
|
||||
{
|
||||
const string sql = "DELETE FROM `sa_groups` WHERE name = @groupName";
|
||||
var sql = databaseProvider.GetDeleteGroupQuery();
|
||||
await connection.ExecuteAsync(sql, new { groupName });
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -620,15 +646,18 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes admins whose permissions have expired asynchronously.
|
||||
/// </summary>
|
||||
public async Task DeleteOldAdmins()
|
||||
{
|
||||
if (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
const string sql = "DELETE FROM sa_admins WHERE ends IS NOT NULL AND ends <= @CurrentTime";
|
||||
var sql = databaseProvider.GetDeleteOldAdminsQuery();
|
||||
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime() });
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -11,285 +11,360 @@ using ZLinq;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
public class PlayerManager
|
||||
internal 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)
|
||||
{
|
||||
if (player.IsBot || string.IsNullOrEmpty(player.IpAddress) || player.IpAddress.Contains("127.0.0.1"))
|
||||
return;
|
||||
|
||||
if (!player.UserId.HasValue)
|
||||
{
|
||||
Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION);
|
||||
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);
|
||||
|
||||
|
||||
// if (!player.UserId.HasValue)
|
||||
// {
|
||||
// Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION);
|
||||
// return;
|
||||
// }
|
||||
|
||||
var userId = player.UserId.Value;
|
||||
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(userId))
|
||||
{
|
||||
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION);
|
||||
}
|
||||
|
||||
var steamId64 = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64;
|
||||
var steamId = steamId64.ToString();
|
||||
|
||||
if (CS2_SimpleAdmin.Database == null) return;
|
||||
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;
|
||||
|
||||
// Perform asynchronous database operations within a single method
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var isBanned = CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
try
|
||||
{
|
||||
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, null),
|
||||
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
|
||||
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(steamId64, ipAddress)
|
||||
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(steamId, ipAddress)
|
||||
};
|
||||
await _loadPlayerSemaphore.WaitAsync();
|
||||
|
||||
if (isBanned)
|
||||
{
|
||||
// Kick the player if banned
|
||||
await Server.NextFrameAsync(() =>
|
||||
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
||||
{
|
||||
if (!player.UserId.HasValue) return;
|
||||
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64, ipAddress))
|
||||
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
|
||||
|
||||
const string updateQuery = """
|
||||
UPDATE `sa_players_ips`
|
||||
SET used_at = CURRENT_TIMESTAMP
|
||||
WHERE steamid = @SteamID AND address = @IPAddress;
|
||||
""";
|
||||
await connection.ExecuteAsync(updateQuery, new
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
|
||||
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)
|
||||
};
|
||||
|
||||
const string selectQuery =
|
||||
"SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
|
||||
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
|
||||
// CS2_SimpleAdmin._logger?.LogInformation($"Player {playerName} ({steamId} - {ipAddress}) is banned? {isBanned.ToString()}");
|
||||
|
||||
if (isBanned)
|
||||
{
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
// CS2_SimpleAdmin._logger?.LogInformation($"Kicking {playerName}");
|
||||
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
});
|
||||
|
||||
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
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const string insertQuery = """
|
||||
INSERT INTO `sa_players_ips` (steamid, address, used_at)
|
||||
VALUES (@SteamID, @IPAddress, CURRENT_TIMESTAMP);
|
||||
""";
|
||||
await connection.ExecuteAsync(insertQuery, new
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(
|
||||
$"Unable to save ip address for {CS2_SimpleAdmin.PlayersInfo[userId].Name} ({ipAddress}) {ex.Message}");
|
||||
}
|
||||
|
||||
// Get all accounts associated to the player (ip address)
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].AccountsAssociated =
|
||||
CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).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 (fullConnect || !fullConnect) // Temp skip
|
||||
{
|
||||
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 = CS2_SimpleAdmin.Instance.CacheManager?.GetPlayerBansBySteamId(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()).Count ?? 0;
|
||||
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)
|
||||
{
|
||||
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[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(() =>
|
||||
{
|
||||
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[userId].Slot, PenaltyType.Silence, ends, duration);
|
||||
await Server.NextFrameAsync(() =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.NotifyPenaltiesToAdminOnConnect && fullConnect)
|
||||
if (fullConnect)
|
||||
{
|
||||
var associatedAcccountsChunks = CS2_SimpleAdmin.PlayersInfo[userId].AccountsAssociated.ChunkBy(5).ToList();
|
||||
|
||||
await Server.NextFrameAsync(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
);
|
||||
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
||||
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
||||
|
||||
foreach (var chunk in associatedAcccountsChunks)
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
if (!CS2_SimpleAdmin.CachedPlayers.Contains(player))
|
||||
CS2_SimpleAdmin.CachedPlayers.Add(player);
|
||||
});
|
||||
|
||||
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null &&
|
||||
CS2_SimpleAdmin.PlayersInfo[steamId] != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
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
|
||||
{
|
||||
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_associated_accounts",
|
||||
player.PlayerName,
|
||||
string.Join(", ",
|
||||
chunk.Select(a => $"{a.PlayerName} ({a.SteamId})"))
|
||||
);
|
||||
playerName,
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const string selectQuery =
|
||||
"SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
|
||||
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
|
||||
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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
finally
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Error processing player connection: {exception}", ex.Message);
|
||||
_loadPlayerSemaphore.Release();
|
||||
}
|
||||
});
|
||||
|
||||
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.1f, () =>
|
||||
CS2_SimpleAdmin.Instance.AddTimer(0.12f, () =>
|
||||
{
|
||||
if (CS2_SimpleAdmin.GravityPlayers.Count <= 0) return;
|
||||
|
||||
foreach (var value in CS2_SimpleAdmin.GravityPlayers.Where(value => value.Key is
|
||||
{ IsValid: true, Connected: PlayerConnectedState.PlayerConnected } || value.Key.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE))
|
||||
if (CS2_SimpleAdmin.SpeedPlayers.Count > 0)
|
||||
{
|
||||
value.Key.SetGravity(value.Value);
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, TimerFlags.REPEAT);
|
||||
|
||||
CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
|
||||
CS2_SimpleAdmin.Instance.PlayersTimer = CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
|
||||
{
|
||||
#if DEBUG
|
||||
CS2_SimpleAdmin._logger?.LogCritical("[OnMapStart] Expired check");
|
||||
#endif
|
||||
if (CS2_SimpleAdmin.Database == null)
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
return;
|
||||
|
||||
var tempPlayers = Helper.GetValidPlayers()
|
||||
.Select(p => new
|
||||
{
|
||||
p.SteamID, p.IpAddress, p.UserId, p.Slot,
|
||||
p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot,
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var pluginInstance = CS2_SimpleAdmin.Instance;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var expireTasks = new Task[]
|
||||
var expireTasks = new[]
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.BanManager.ExpireOldBans(),
|
||||
CS2_SimpleAdmin.Instance.MuteManager.ExpireOldMutes(),
|
||||
CS2_SimpleAdmin.Instance.WarnManager.ExpireOldWarns(),
|
||||
CS2_SimpleAdmin.Instance.CacheManager?.RefreshCacheAsync() ?? Task.CompletedTask,
|
||||
CS2_SimpleAdmin.Instance.PermissionManager.DeleteOldAdmins()
|
||||
pluginInstance.BanManager.ExpireOldBans(),
|
||||
pluginInstance.MuteManager.ExpireOldMutes(),
|
||||
pluginInstance.WarnManager.ExpireOldWarns(),
|
||||
pluginInstance.CacheManager?.RefreshCacheAsync() ?? Task.CompletedTask,
|
||||
pluginInstance.PermissionManager.DeleteOldAdmins()
|
||||
};
|
||||
|
||||
await Task.WhenAll(expireTasks);
|
||||
@@ -306,30 +381,40 @@ public class PlayerManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginInstance.CacheManager == null)
|
||||
return;
|
||||
|
||||
var bannedPlayers = tempPlayers.AsValueEnumerable()
|
||||
.Where(player =>
|
||||
{
|
||||
return CS2_SimpleAdmin.Instance.CacheManager != null && CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
var playerName = player.PlayerName;
|
||||
var steamId = player.SteamID;
|
||||
var ip = player.IpAddress?.Split(':')[0];
|
||||
|
||||
return CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
{
|
||||
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), null),
|
||||
_ =>
|
||||
CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(player.SteamID.ToString(), player.IpAddress?.Split(":")[0])
|
||||
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();
|
||||
|
||||
foreach (var player in bannedPlayers)
|
||||
}).ToList();
|
||||
|
||||
if (bannedPlayers.Count > 0)
|
||||
{
|
||||
if (player.UserId.HasValue)
|
||||
await Server.NextFrameAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
|
||||
foreach (var player in bannedPlayers)
|
||||
{
|
||||
if (!player.UserId.HasValue) continue;
|
||||
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
|
||||
}
|
||||
}
|
||||
|
||||
var onlinePlayers = tempPlayers.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList();
|
||||
if (tempPlayers.Count == 0 || onlinePlayers.Count == 0) return;
|
||||
if (_config.OtherSettings.TimeMode == 0)
|
||||
{
|
||||
await CS2_SimpleAdmin.Instance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -348,7 +433,6 @@ public class PlayerManager
|
||||
|
||||
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;
|
||||
@@ -361,7 +445,6 @@ public class PlayerManager
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}");
|
||||
}
|
||||
|
||||
}, TimerFlags.REPEAT);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,13 @@ public static class PlayerPenaltyManager
|
||||
private static readonly ConcurrentDictionary<int, Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>> Penalties =
|
||||
new();
|
||||
|
||||
// Add a penalty for a player
|
||||
/// <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>
|
||||
public static void AddPenalty(int slot, PenaltyType penaltyType, DateTime endDateTime, int durationInMinutes)
|
||||
{
|
||||
Penalties.AddOrUpdate(slot,
|
||||
@@ -33,6 +39,13 @@ 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)
|
||||
{
|
||||
endDateTime = null;
|
||||
@@ -74,7 +87,12 @@ public static class PlayerPenaltyManager
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the end datetime and duration of penalties for a player and penalty type
|
||||
/// <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>
|
||||
public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, PenaltyType penaltyType)
|
||||
{
|
||||
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
|
||||
@@ -85,6 +103,35 @@ 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
|
||||
@@ -95,12 +142,20 @@ 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);
|
||||
}
|
||||
|
||||
// Remove all penalties for a player slot
|
||||
/// <summary>
|
||||
/// Removes all penalties assigned to a specific player slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The player slot.</param>
|
||||
public static void RemoveAllPenalties(int slot)
|
||||
{
|
||||
if (Penalties.ContainsKey(slot))
|
||||
@@ -109,13 +164,19 @@ public static class PlayerPenaltyManager
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all penalties
|
||||
/// <summary>
|
||||
/// Removes all penalties for all players.
|
||||
/// </summary>
|
||||
public static void RemoveAllPenalties()
|
||||
{
|
||||
Penalties.Clear();
|
||||
}
|
||||
|
||||
// Remove all penalties of a selected type from a specific player
|
||||
/// <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>
|
||||
public static void RemovePenaltiesByType(int slot, PenaltyType penaltyType)
|
||||
{
|
||||
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
|
||||
@@ -125,6 +186,11 @@ 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;
|
||||
@@ -146,7 +212,13 @@ public static class PlayerPenaltyManager
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all expired penalties for all players and penalty types
|
||||
/// <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>
|
||||
public static void RemoveExpiredPenalties()
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0)
|
||||
@@ -172,13 +244,11 @@ 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 _);
|
||||
|
||||
@@ -9,22 +9,32 @@ 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(1.2f, () =>
|
||||
CS2_SimpleAdmin.Instance.AddTimer(2.0f, () =>
|
||||
{
|
||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.ServerId != null || CS2_SimpleAdmin.Database == null) return;
|
||||
|
||||
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!");
|
||||
@@ -32,7 +42,6 @@ public class ServerManager
|
||||
}
|
||||
|
||||
var ipAddress = ConVar.Find("ip")?.StringValue;
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
|
||||
{
|
||||
ipAddress = Helper.GetServerIp();
|
||||
@@ -40,23 +49,22 @@ public class ServerManager
|
||||
if (_getIpTryCount <= 32 && (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")))
|
||||
{
|
||||
_getIpTryCount++;
|
||||
|
||||
|
||||
LoadServerData();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var address = $"{ipAddress}:{ConVar.Find("hostport")?.GetPrimitiveValue<int>()}";
|
||||
var hostname = ConVar.Find("hostname")!.StringValue;
|
||||
var rconPassword = ConVar.Find("rcon_password")!.StringValue;
|
||||
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var rconPassword = ConVar.Find("rcon_password")?.StringValue ?? "";
|
||||
CS2_SimpleAdmin.IpAddress = address;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
|
||||
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
int? serverId = await connection.ExecuteScalarAsync<int?>(
|
||||
"SELECT id FROM sa_servers WHERE address = @address",
|
||||
new { address });
|
||||
@@ -79,7 +87,6 @@ public class ServerManager
|
||||
}
|
||||
|
||||
CS2_SimpleAdmin.ServerId = serverId;
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
|
||||
|
||||
if (CS2_SimpleAdmin.ServerId != null)
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
using CS2_SimpleAdminApi;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
internal class WarnManager(Database.Database? database)
|
||||
internal class WarnManager(IDatabaseProvider? databaseProvider)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
if (database == null) return null;
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
SELECT LAST_INSERT_ID();
|
||||
""";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddWarnQuery(true);
|
||||
|
||||
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = player.SteamId.SteamId64.ToString(),
|
||||
playerSteamid = player.SteamId.SteamId64,
|
||||
playerName = player.Name,
|
||||
adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
muteReason = reason,
|
||||
duration = time,
|
||||
@@ -46,30 +48,30 @@ internal class WarnManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int?> AddWarnBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
|
||||
/// <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)
|
||||
{
|
||||
if (database == null) return null;
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return null;
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
SELECT LAST_INSERT_ID();
|
||||
""";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddWarnQuery(false);
|
||||
|
||||
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = playerSteamId,
|
||||
adminSteamid = issuer?.SteamId.ToString() ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
muteReason = reason,
|
||||
duration = time,
|
||||
@@ -86,30 +88,22 @@ internal class WarnManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return [];
|
||||
if (databaseProvider == null) return [];
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
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 sql = databaseProvider.GetPlayerWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
|
||||
var parameters = new { PlayerSteamID = player.SteamId.SteamId64, serverid = CS2_SimpleAdmin.ServerId };
|
||||
var warns = await connection.QueryAsync<dynamic>(sql, parameters);
|
||||
|
||||
return warns.ToList();
|
||||
@@ -120,24 +114,23 @@ internal class WarnManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetPlayerWarnsCount(string steamId, bool active = true)
|
||||
/// <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)
|
||||
{
|
||||
if (database == null) return 0;
|
||||
if (databaseProvider == null) return 0;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -145,19 +138,22 @@ internal class WarnManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
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 });
|
||||
var sql = databaseProvider.GetUnwarnByIdQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { steamid = player.SteamId.SteamId64, warnId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -165,36 +161,20 @@ internal class WarnManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
|
||||
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
|
||||
? """
|
||||
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;
|
||||
""";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetUnwarnLastQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { steamid = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -203,18 +183,19 @@ internal class WarnManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (database == null) return;
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
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";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetExpireWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -45,9 +45,9 @@ public static class AdminMenu
|
||||
var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
List<ChatMenuOptionData> options =
|
||||
[
|
||||
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)),
|
||||
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)),
|
||||
];
|
||||
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
|
||||
@@ -24,12 +24,12 @@ public static class ManageAdminsMenu
|
||||
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_admins_manage"] ?? "Admins Manage");
|
||||
List<ChatMenuOptionData> options =
|
||||
[
|
||||
new ChatMenuOptionData(localizer?["sa_admin_add"] ?? "Add Admin",
|
||||
new(localizer?["sa_admin_add"] ?? "Add Admin",
|
||||
() => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_admin_add"] ?? "Add Admin", AddAdminMenu)),
|
||||
new ChatMenuOptionData(localizer?["sa_admin_remove"] ?? "Remove Admin",
|
||||
new(localizer?["sa_admin_remove"] ?? "Remove Admin",
|
||||
() => PlayersMenu.OpenAdminPlayersMenu(admin, localizer?["sa_admin_remove"] ?? "Remove Admin", RemoveAdmin,
|
||||
player => player != admin && admin.CanTarget(player))),
|
||||
new ChatMenuOptionData(localizer?["sa_admin_reload"] ?? "Reload Admins", () => ReloadAdmins(admin))
|
||||
new(localizer?["sa_admin_reload"] ?? "Reload Admins", () => ReloadAdmins(admin))
|
||||
];
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
|
||||
@@ -90,12 +90,12 @@ public static class ManagePlayersMenu
|
||||
List<ChatMenuOptionData> options =
|
||||
[
|
||||
// options added in order
|
||||
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)),
|
||||
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)),
|
||||
];
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
@@ -312,10 +312,10 @@ public static class ManagePlayersMenu
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_team_force"] ?? "Force Team"} {player.PlayerName}");
|
||||
List<ChatMenuOptionData> options =
|
||||
[
|
||||
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)),
|
||||
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)),
|
||||
];
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
|
||||
@@ -24,7 +24,6 @@ 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");
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
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; set; }
|
||||
public int Id { get; init; }
|
||||
|
||||
[Column("player_name")]
|
||||
public string? PlayerName { get; set; }
|
||||
|
||||
[Column("player_steamid")]
|
||||
public string? PlayerSteamId { get; set; }
|
||||
public ulong? PlayerSteamId { get; set; }
|
||||
|
||||
[Column("player_ip")]
|
||||
public string? PlayerIp { get; set; }
|
||||
|
||||
[Column("status")]
|
||||
public string Status { get; set; }
|
||||
public required string Status { get; init; }
|
||||
|
||||
[NotMapped]
|
||||
public BanStatus StatusEnum => Status.ToUpper() switch
|
||||
{
|
||||
"ACTIVE" => BanStatus.ACTIVE,
|
||||
"UNBANNED" => BanStatus.UNBANNED,
|
||||
"EXPIRED" => BanStatus.EXPIRED,
|
||||
_ => BanStatus.UNKNOWN
|
||||
};
|
||||
}
|
||||
|
||||
12
CS2-SimpleAdmin/Models/PlayerDto.cs
Normal file
12
CS2-SimpleAdmin/Models/PlayerDto.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
);
|
||||
@@ -1 +1 @@
|
||||
1.7.7-alpha
|
||||
1.7.7-alpha-9
|
||||
@@ -7,7 +7,9 @@ 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;
|
||||
|
||||
@@ -15,12 +17,13 @@ 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;
|
||||
@@ -40,7 +43,9 @@ public partial class CS2_SimpleAdmin
|
||||
private static readonly HashSet<int> GodPlayers = [];
|
||||
internal static readonly HashSet<int> SilentPlayers = [];
|
||||
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
|
||||
internal static readonly ConcurrentDictionary<int, PlayerInfo> PlayersInfo = [];
|
||||
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
|
||||
internal static readonly List<CCSPlayerController> CachedPlayers = [];
|
||||
internal static readonly List<CCSPlayerController> BotPlayers = [];
|
||||
private static readonly List<DisconnectedPlayer> DisconnectedPlayers = [];
|
||||
|
||||
// Discord Integration
|
||||
@@ -48,25 +53,35 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
// Database Settings
|
||||
internal string DbConnectionString = string.Empty;
|
||||
internal static Database.Database? Database;
|
||||
// internal static Database.Database? Database;
|
||||
internal static IDatabaseProvider? DatabaseProvider;
|
||||
|
||||
// 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; set; }
|
||||
|
||||
internal static Api.CS2_SimpleAdminApi? SimpleAdminApi { get; private set; }
|
||||
|
||||
// Managers
|
||||
internal PermissionManager PermissionManager = new(Database);
|
||||
internal BanManager BanManager = new(Database);
|
||||
internal MuteManager MuteManager = new(Database);
|
||||
internal WarnManager WarnManager = new(Database);
|
||||
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;
|
||||
|
||||
// Funny list
|
||||
private readonly List<string> _requiredPlugins = ["MenuManagerCore", "PlayerSettings"];
|
||||
private readonly List<string> _requiredShared = ["MenuManagerApi", "PlayerSettingsApi", "AnyBaseLib", "CS2-SimpleAdminApi"];
|
||||
}
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(إداري) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(لاعب) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(SPIELER) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(PLAYER) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(JUGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ادمین) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(بازیکن) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(JOUEUR) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_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}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(GRACZ) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(JOGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(JOGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(АДМИН) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(ИГРОК) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(Yönetici) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(Oyuncu) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -124,6 +124,10 @@
|
||||
"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_adminchat_template_admin": "{LIME}(管理员) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(玩家) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
<RootNamespace>CS2_SimpleAdminApi</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.318" />
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,23 +9,97 @@ 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;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Numerics;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CS2_SimpleAdminApi;
|
||||
|
||||
@@ -28,10 +28,13 @@ public class PlayerInfo(
|
||||
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 struct DiePosition(Vector position, QAngle angle)
|
||||
public class DiePosition(Vector3 position, Vector3 angle)
|
||||
{
|
||||
public Vector Position { get; set; } = position;
|
||||
public QAngle Angle { get; set; } = angle;
|
||||
public Vector3 Position { get; } = position;
|
||||
public Vector3 Angle { get; } = angle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
BIN
Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdminApi.dll
Normal file
BIN
Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdminApi.dll
Normal file
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CS2_SimpleAdmin_BanSoundModule;
|
||||
|
||||
public class CS2_SimpleAdmin_BanSoundModule: BasePlugin
|
||||
{
|
||||
public override string ModuleName => "[CS2-SimpleAdmin] BanSound Module";
|
||||
public override string ModuleVersion => "v1.0.0";
|
||||
public override string ModuleAuthor => "daffyy";
|
||||
|
||||
private static ICS2_SimpleAdminApi? _sharedApi;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_sharedApi = _pluginCapability.Get();
|
||||
if (_sharedApi == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin SharedApi not found");
|
||||
Unload(false);
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterListener<Listeners.OnServerPrecacheResources>(OnServerPrecacheResources);
|
||||
|
||||
_sharedApi.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
}
|
||||
|
||||
private void OnServerPrecacheResources(ResourceManifest manifest)
|
||||
{
|
||||
manifest.AddResource("soundevents/soundevents_addon.vsndevts");
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(PlayerInfo playerInfo, PlayerInfo? admin, PenaltyType penaltyType,
|
||||
string reason, int duration, int? penaltyId, int? serverId)
|
||||
{
|
||||
if (penaltyType != PenaltyType.Ban || admin == null)
|
||||
return;
|
||||
|
||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot))
|
||||
{
|
||||
var filter = new RecipientFilter(player);
|
||||
player?.EmitSound("bansound", volume: 0.9f, recipients: filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>CS2_SimpleAdmin_ExampleModule</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.335" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>CS2-SimpleAdminApi.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-SimpleAdmin_BanSoundModule", "CS2-SimpleAdmin_BanSoundModule.csproj", "{D940F3E9-0E3F-467A-B336-149E3A624FB6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D940F3E9-0E3F-467A-B336-149E3A624FB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,939 +0,0 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v8.0",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v8.0": {
|
||||
"CS2-SimpleAdmin_RedisInform/1.0.0": {
|
||||
"dependencies": {
|
||||
"CounterStrikeSharp.API": "1.0.305",
|
||||
"StackExchange.Redis": "2.8.24",
|
||||
"CS2-SimpleAdminApi": "1.0.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"CS2-SimpleAdmin_RedisInform.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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pipelines.Sockets.Unofficial/2.2.8": {
|
||||
"dependencies": {
|
||||
"System.IO.Pipelines": "5.0.1"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net5.0/Pipelines.Sockets.Unofficial.dll": {
|
||||
"assemblyVersion": "1.0.0.0",
|
||||
"fileVersion": "2.2.8.1080"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"StackExchange.Redis/2.8.24": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||
"Pipelines.Sockets.Unofficial": "2.2.8"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/StackExchange.Redis.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.8.24.3255"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.IO.Pipelines/5.0.1": {
|
||||
"runtime": {
|
||||
"lib/netcoreapp3.0/System.IO.Pipelines.dll": {
|
||||
"assemblyVersion": "5.0.0.1",
|
||||
"fileVersion": "5.0.120.57516"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"CS2-SimpleAdminApi/1.0.0.0": {
|
||||
"runtime": {
|
||||
"CS2-SimpleAdminApi.dll": {
|
||||
"assemblyVersion": "1.0.0.0",
|
||||
"fileVersion": "1.0.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"CS2-SimpleAdmin_RedisInform/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"
|
||||
},
|
||||
"Pipelines.Sockets.Unofficial/2.2.8": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-zG2FApP5zxSx6OcdJQLbZDk2AVlN2BNQD6MorwIfV6gVj0RRxWPEp2LXAxqDGZqeNV1Zp0BNPcNaey/GXmTdvQ==",
|
||||
"path": "pipelines.sockets.unofficial/2.2.8",
|
||||
"hashPath": "pipelines.sockets.unofficial.2.2.8.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"
|
||||
},
|
||||
"StackExchange.Redis/2.8.24": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-GWllmsFAtLyhm4C47cOCipGxyEi1NQWTFUHXnJ8hiHOsK/bH3T5eLkWPVW+LRL6jDiB3g3izW3YEHgLuPoJSyA==",
|
||||
"path": "stackexchange.redis/2.8.24",
|
||||
"hashPath": "stackexchange.redis.2.8.24.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.IO.Pipelines/5.0.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-qEePWsaq9LoEEIqhbGe6D5J8c9IqQOUuTzzV6wn1POlfdLkJliZY3OlB0j0f17uMWlqZYjH7txj+2YbyrIA8Yg==",
|
||||
"path": "system.io.pipelines/5.0.1",
|
||||
"hashPath": "system.io.pipelines.5.0.1.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"
|
||||
},
|
||||
"CS2-SimpleAdminApi/1.0.0.0": {
|
||||
"type": "reference",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user