mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-18 10:43:23 +00:00
Compare commits
43 Commits
c31addb9e6
...
build-1.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099e91b43b | ||
|
|
206c18db66 | ||
|
|
78318102fe | ||
|
|
2edacc2b3f | ||
|
|
e1e66441f2 | ||
|
|
cc54b9e879 | ||
|
|
640e618f3b | ||
|
|
23d174c4a5 | ||
|
|
b7371adf26 | ||
|
|
9154748ce6 | ||
|
|
da9830ee05 | ||
|
|
b41ac992c0 | ||
|
|
af0bda8f3a | ||
|
|
e2529cd646 | ||
|
|
9d2cd34845 | ||
|
|
5701455de0 | ||
|
|
b97426313b | ||
|
|
3ab63c05db | ||
|
|
f654d6b085 | ||
|
|
676a18d9b4 | ||
|
|
d75a092047 | ||
|
|
d34ca64970 | ||
|
|
f69f1277f8 | ||
|
|
b6c876d709 | ||
|
|
888d6b0152 | ||
|
|
5d62c743dd | ||
|
|
62b1987fde | ||
|
|
708ae6cb90 | ||
|
|
2d77e86d59 | ||
|
|
babcbc2119 | ||
|
|
64e5f1156e | ||
|
|
8cc0398f6b | ||
|
|
ab14956ae5 | ||
|
|
9163caeb0d | ||
|
|
f2e4b84b29 | ||
|
|
3f1b6b3bf7 | ||
|
|
8af805632a | ||
|
|
8c94a867d3 | ||
|
|
023e1a031b | ||
|
|
1f1c214357 | ||
|
|
2defb2fe14 | ||
|
|
b2ebe136c3 | ||
|
|
5668c0ad7b |
171
.github/workflows/build.yml
vendored
171
.github/workflows/build.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Build
|
||||
name: Build and Publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths-ignore:
|
||||
@@ -11,85 +12,117 @@ 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_FUNCOMMANDSMODULE: "Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj"
|
||||
PROJECT_NAME_FUNCOMMANDSMODULE: "CS2-SimpleAdmin_FunCommands"
|
||||
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_FUNCOMMANDSMODULE }}
|
||||
dotnet build ${{ env.PROJECT_PATH_FUNCOMMANDSMODULE }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_FUNCOMMANDSMODULE }}
|
||||
dotnet restore ${{ env.PROJECT_PATH_STEALTHMODULE }}
|
||||
dotnet build ${{ env.PROJECT_PATH_STEALTHMODULE }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_STEALTHMODULE }}
|
||||
|
||||
- name: Combine projects
|
||||
run: |
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_FunCommands
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_StealthModule
|
||||
mkdir -p ${{ env.OUTPUT_PATH }}/shared/CS2-SimpleAdminApi
|
||||
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin/
|
||||
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_FUNCOMMANDSMODULE }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_FunCommands
|
||||
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-${{ 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.
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -3,4 +3,10 @@ obj/
|
||||
.vs/
|
||||
.git
|
||||
.vscode/
|
||||
.idea/
|
||||
.idea/
|
||||
Modules/CS2-SimpleAdmin_PlayTimeModule
|
||||
CS2-SimpleAdmin.sln.DotSettings.user
|
||||
Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user
|
||||
CS2-SimpleAdmin_BanSoundModule — kopia
|
||||
*.user
|
||||
CLAUDE.md
|
||||
|
||||
BIN
CS2-SimpleAdmin/3rd_party/MenuManagerApi.dll
vendored
BIN
CS2-SimpleAdmin/3rd_party/MenuManagerApi.dll
vendored
Binary file not shown.
@@ -1,40 +1,57 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CS2_SimpleAdmin.Managers;
|
||||
using CS2_SimpleAdmin.Menus;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace CS2_SimpleAdmin.Api;
|
||||
|
||||
public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
{
|
||||
public event Action? OnSimpleAdminReady;
|
||||
public void OnSimpleAdminReadyEvent() => OnSimpleAdminReady?.Invoke();
|
||||
|
||||
public PlayerInfo GetPlayerInfo(CCSPlayerController player)
|
||||
{
|
||||
if (!player.UserId.HasValue)
|
||||
throw new KeyNotFoundException("Player with specific UserId not found");
|
||||
|
||||
return CS2_SimpleAdmin.PlayersInfo[player.UserId.Value];
|
||||
return !player.UserId.HasValue
|
||||
? throw new KeyNotFoundException("Player with specific UserId not found")
|
||||
: CS2_SimpleAdmin.PlayersInfo[player.SteamID];
|
||||
}
|
||||
|
||||
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
|
||||
public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
|
||||
public int? GetServerId() => CS2_SimpleAdmin.ServerId;
|
||||
|
||||
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(CCSPlayerController player)
|
||||
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
|
||||
public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
|
||||
public int? GetServerId() => CS2_SimpleAdmin.ServerId;
|
||||
|
||||
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(
|
||||
CCSPlayerController player)
|
||||
{
|
||||
return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot);
|
||||
}
|
||||
|
||||
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltied;
|
||||
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltiedAdded;
|
||||
|
||||
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 = -1) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, CS2_SimpleAdmin.ServerId);
|
||||
|
||||
public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
|
||||
int duration) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration, CS2_SimpleAdmin.ServerId);
|
||||
int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration,
|
||||
penaltyId, CS2_SimpleAdmin.ServerId);
|
||||
|
||||
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
|
||||
public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
|
||||
int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration,
|
||||
penaltyId, CS2_SimpleAdmin.ServerId);
|
||||
|
||||
public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false,
|
||||
params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
|
||||
|
||||
public void OnAdminToggleSilentEvent(int slot, bool status) => OnAdminToggleSilent?.Invoke(slot, status);
|
||||
|
||||
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType,
|
||||
string reason, int duration = -1)
|
||||
{
|
||||
switch (penaltyType)
|
||||
{
|
||||
@@ -73,6 +90,41 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
}
|
||||
}
|
||||
|
||||
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason,
|
||||
int duration = -1)
|
||||
{
|
||||
switch (penaltyType)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddBan(admin, steamid, duration, reason);
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Gag:
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddGag(admin, steamid, duration, reason);
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Mute:
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddMute(admin, steamid, duration, reason);
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Silence:
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddSilence(admin, steamid, duration, reason);
|
||||
break;
|
||||
}
|
||||
case PenaltyType.Warn:
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddWarn(admin, steamid, duration, reason);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void LogCommand(CCSPlayerController? caller, string command)
|
||||
{
|
||||
Helper.LogCommand(caller, command);
|
||||
@@ -82,4 +134,184 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
{
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
public bool IsAdminSilent(CCSPlayerController player)
|
||||
{
|
||||
return CS2_SimpleAdmin.SilentPlayers.Contains(player.Slot);
|
||||
}
|
||||
|
||||
public HashSet<int> ListSilentAdminsSlots()
|
||||
{
|
||||
return CS2_SimpleAdmin.SilentPlayers;
|
||||
}
|
||||
|
||||
public void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("Command name cannot be null or empty.", nameof(name));
|
||||
|
||||
ArgumentNullException.ThrowIfNull(callback);
|
||||
|
||||
var definition = new CommandDefinition(name, description ?? "No description", callback);
|
||||
if (!RegisterCommands._commandDefinitions.TryGetValue(name, out var list))
|
||||
{
|
||||
list = new List<CommandDefinition>();
|
||||
RegisterCommands._commandDefinitions[name] = list;
|
||||
}
|
||||
|
||||
list.Add(definition);
|
||||
}
|
||||
|
||||
public void UnRegisterCommand(string commandName)
|
||||
{
|
||||
var definitions = RegisterCommands._commandDefinitions[commandName];
|
||||
if (definitions.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var definition in definitions)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.RemoveCommand(commandName, definition.Callback);
|
||||
}
|
||||
}
|
||||
|
||||
public TargetResult? GetTarget(CommandInfo command)
|
||||
{
|
||||
return CS2_SimpleAdmin.GetTarget(command);
|
||||
}
|
||||
|
||||
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false,
|
||||
params object[] messageArgs)
|
||||
{
|
||||
Helper.ShowAdminActivity(messageKey, callerName, dontPublish, messageArgs);
|
||||
}
|
||||
|
||||
public void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null,
|
||||
bool dontPublish = false)
|
||||
{
|
||||
Helper.ShowAdminActivityTranslated(translatedMessage, callerName, dontPublish);
|
||||
}
|
||||
|
||||
public void ShowAdminActivityLocalized(object moduleLocalizer, string messageKey, string? callerName = null,
|
||||
bool dontPublish = false, params object[] messageArgs)
|
||||
{
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Helper.ShowAdminActivityLocalized(localizer, messageKey, callerName, dontPublish, messageArgs);
|
||||
}
|
||||
|
||||
public void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||
Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
{
|
||||
if (menuFactory(player) is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
||||
|
||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
||||
menuBuilder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return menuBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UnregisterMenu(string categoryId, string menuId)
|
||||
{
|
||||
Menus.MenuManager.Instance.UnregisterMenu(categoryId, menuId);
|
||||
}
|
||||
|
||||
public object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)
|
||||
{
|
||||
var builder = new MenuBuilder(title);
|
||||
builder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public List<CCSPlayerController> GetValidPlayers()
|
||||
{
|
||||
return Helper.GetValidPlayers();
|
||||
}
|
||||
|
||||
public object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||
{
|
||||
var menu = (MenuBuilder)CreateMenuWithBack(title, categoryId, admin);
|
||||
var players = Helper.GetValidPlayers().Where(filter);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
menu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
onSelect(admin, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
||||
string? permission = null)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.AddOption(name, action, disabled, permission);
|
||||
}
|
||||
|
||||
public void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory,
|
||||
bool disabled = false, string? permission = null)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.AddSubMenu(name, player =>
|
||||
{
|
||||
var subMenu = subMenuFactory(player);
|
||||
if (subMenu is not MenuBuilder builder)
|
||||
throw new InvalidOperationException("SubMenu factory must return MenuBuilder");
|
||||
return builder;
|
||||
}, disabled, permission);
|
||||
}
|
||||
|
||||
public void OpenMenu(object menu, CCSPlayerController player)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.OpenMenu(player);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
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_SimpleAdmin.Menus;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MySqlConnector;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
[MinimumApiVersion(284)]
|
||||
[MinimumApiVersion(300)]
|
||||
public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdminConfig>
|
||||
{
|
||||
internal static CS2_SimpleAdmin Instance { get; private set; } = new();
|
||||
@@ -19,87 +22,200 @@ 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.6.8a";
|
||||
public override string ModuleVersion => "1.7.8-beta-2";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
RegisterEvents();
|
||||
|
||||
|
||||
if (hotReload)
|
||||
{
|
||||
ServerLoaded = false;
|
||||
OnGameServerSteamAPIActivated();
|
||||
_serverLoading = false;
|
||||
|
||||
CacheManager?.Dispose();
|
||||
CacheManager = new CacheManager();
|
||||
|
||||
// OnGameServerSteamAPIActivated();
|
||||
OnMapStart(string.Empty);
|
||||
|
||||
AddTimer(2.0f, () =>
|
||||
AddTimer(6.0f, () =>
|
||||
{
|
||||
if (Database == null) return;
|
||||
|
||||
Helper.GetValidPlayers().ForEach(player =>
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
PlayersInfo.Clear();
|
||||
CachedPlayers.Clear();
|
||||
BotPlayers.Clear();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
|
||||
{
|
||||
var playerManager = new PlayerManager();
|
||||
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();
|
||||
|
||||
Menus.MenuManager.Instance.InitializeDefaultCategories();
|
||||
BasicMenu.Initialize();
|
||||
}
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
AddTimer(3.0f, () => ReloadAdmins(null));
|
||||
try
|
||||
{
|
||||
MenuApi = MenuCapability.Get();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Unable to load required plugins ... \n{exception}", ex.Message);
|
||||
Unload(false);
|
||||
}
|
||||
|
||||
MenuApi = MenuCapability.Get();
|
||||
if (MenuApi == null)
|
||||
Logger.LogError("MenuManager Core not found...");
|
||||
|
||||
RegisterCommands.InitializeCommands();
|
||||
AddTimer(6.0f, () => ReloadAdmins(null));
|
||||
RegisterEvents();
|
||||
AddTimer(0.5f, 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,
|
||||
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())
|
||||
DatabaseProvider = new MySqlDatabaseProvider(DbConnectionString);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.SqliteFilePath))
|
||||
{
|
||||
Logger.LogError("Unable connect to database!");
|
||||
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");
|
||||
@@ -110,33 +226,46 @@ 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)
|
||||
internal 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;
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
CacheManager?.Dispose();
|
||||
CacheManager = null;
|
||||
PlayersTimer?.Kill();
|
||||
PlayersTimer = null;
|
||||
UnregisterEvents();
|
||||
|
||||
if (hotReload)
|
||||
PlayersInfo.Clear();
|
||||
else
|
||||
Server.ExecuteCommand($"css_plugins unload {ModuleDirectory}");
|
||||
}
|
||||
}
|
||||
@@ -7,25 +7,132 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.286" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="MySqlConnector" Version="2.4.0-beta.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="*" />
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<DebuggerSupport>false</DebuggerSupport>
|
||||
<!-- <GenerateDependencyFile>false</GenerateDependencyFile>-->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340">
|
||||
<PrivateAssets>none</PrivateAssets>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
|
||||
<PackageReference Include="ZLinq" Version="1.5.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="Database\Migrations\010_CreateWarnsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="Database\Migrations\Mysql\001_CreateTables.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\002_CreateFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\003_ChangeColumnsPosition.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\004_MoveOldFlagsToFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\005_CreateUnbansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\006_ServerGroupsFeature.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\007_ServerGroupsGlobal.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\008_OnlineTimeInPenalties.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\009_BanAllUsedIpAddress.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\010_CreateWarnsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\011_AddRconColumnToServersTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\012_AddUpdatedAtColumnToSaBansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\013_AddNameColumnToSaPlayerIpsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\014_AddIndexesToMutesTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\015_steamidToBigInt.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\016_OptimizeTablesAndIndexes.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>
|
||||
<None Update="Database\Migrations\Sqlite\016_OptimizeTablesAndIndexes.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Database\Migrations\*.sql" CopyToOutputDirectory="PreserveNewest" />
|
||||
@@ -38,7 +145,8 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="MenuManagerApi">
|
||||
<HintPath>3rd_party\MenuManagerApi.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
public static class RegisterCommands
|
||||
{
|
||||
internal static readonly Dictionary<string, IList<CommandDefinition>> _commandDefinitions =
|
||||
new(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
private delegate void CommandCallback(CCSPlayerController? caller, CommandInfo.CommandCallback callback);
|
||||
|
||||
private static readonly string CommandsPath = Path.Combine(CS2_SimpleAdmin.ConfigDirectory, "Commands.json");
|
||||
private static readonly List<CommandMapping> CommandMappings =
|
||||
[
|
||||
new 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_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 CommandMapping("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
|
||||
new CommandMapping("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
|
||||
new CommandMapping("css_give", CS2_SimpleAdmin.Instance.OnGiveCommand),
|
||||
new CommandMapping("css_strip", CS2_SimpleAdmin.Instance.OnStripCommand),
|
||||
new CommandMapping("css_hp", CS2_SimpleAdmin.Instance.OnHpCommand),
|
||||
new CommandMapping("css_speed", CS2_SimpleAdmin.Instance.OnSpeedCommand),
|
||||
new CommandMapping("css_gravity", CS2_SimpleAdmin.Instance.OnGravityCommand),
|
||||
new CommandMapping("css_money", CS2_SimpleAdmin.Instance.OnMoneyCommand),
|
||||
new CommandMapping("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
|
||||
new CommandMapping("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
|
||||
new CommandMapping("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
|
||||
new CommandMapping("css_respawn", CS2_SimpleAdmin.Instance.OnRespawnCommand),
|
||||
new CommandMapping("css_tp", CS2_SimpleAdmin.Instance.OnGotoCommand),
|
||||
new CommandMapping("css_bring", CS2_SimpleAdmin.Instance.OnBringCommand),
|
||||
new CommandMapping("css_pluginsmanager", CS2_SimpleAdmin.Instance.OnPluginManagerCommand)
|
||||
new("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
|
||||
new("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
|
||||
new("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
|
||||
new("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
|
||||
new("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
|
||||
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))
|
||||
@@ -94,6 +94,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
|
||||
@@ -120,6 +124,7 @@ public static class RegisterCommands
|
||||
{ "css_addgroup", new Command { Aliases = ["css_addgroup"] } },
|
||||
{ "css_delgroup", new Command { Aliases = ["css_delgroup"] } },
|
||||
{ "css_reloadadmins", new Command { Aliases = ["css_reloadadmins"] } },
|
||||
{ "css_reloadbans", new Command { Aliases = ["css_reloadbans"] } },
|
||||
{ "css_hide", new Command { Aliases = ["css_hide", "css_stealth"] } },
|
||||
{ "css_hidecomms", new Command { Aliases = ["css_hidecomms"] } },
|
||||
{ "css_who", new Command { Aliases = ["css_who"] } },
|
||||
@@ -142,66 +147,88 @@ public static class RegisterCommands
|
||||
{ "css_addsilence", new Command { Aliases = ["css_addsilence"] } },
|
||||
{ "css_unsilence", new Command { Aliases = ["css_unsilence"] } },
|
||||
{ "css_vote", new Command { Aliases = ["css_vote"] } },
|
||||
{ "css_noclip", new Command { Aliases = ["css_noclip"] } },
|
||||
{ "css_freeze", new Command { Aliases = ["css_freeze"] } },
|
||||
{ "css_unfreeze", new Command { Aliases = ["css_unfreeze"] } },
|
||||
{ "css_godmode", new Command { Aliases = ["css_godmode"] } },
|
||||
{ "css_slay", new Command { Aliases = ["css_slay"] } },
|
||||
{ "css_slap", new Command { Aliases = ["css_slap"] } },
|
||||
{ "css_give", new Command { Aliases = ["css_give"] } },
|
||||
{ "css_strip", new Command { Aliases = ["css_strip"] } },
|
||||
{ "css_hp", new Command { Aliases = ["css_hp"] } },
|
||||
{ "css_speed", new Command { Aliases = ["css_speed"] } },
|
||||
{ "css_gravity", new Command { Aliases = ["css_gravity"] } },
|
||||
{ "css_money", new Command { Aliases = ["css_money"] } },
|
||||
{ "css_team", new Command { Aliases = ["css_team"] } },
|
||||
{ "css_rename", new Command { Aliases = ["css_rename"] } },
|
||||
{ "css_prename", new Command { Aliases = ["css_prename"] } },
|
||||
{ "css_respawn", new Command { Aliases = ["css_respawn"] } },
|
||||
{ "css_resize", new Command { Aliases = ["css_resize", "css_size"] } },
|
||||
{ "css_tp", new Command { Aliases = ["css_tp", "css_tpto", "css_goto"] } },
|
||||
{ "css_bring", new Command { Aliases = ["css_bring", "css_tphere"] } },
|
||||
{ "css_pluginsmanager", new Command { Aliases = ["css_pluginsmanager", "css_pluginmanager"] } }
|
||||
{ "css_pluginsmanager", new Command { Aliases = ["css_pluginsmanager", "css_pluginmanager"] } },
|
||||
{ "css_adminvoice", new Command { Aliases = ["css_adminvoice", "css_listenall"] } }
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
foreach (var command in commandsConfig.Commands)
|
||||
if (commandsConfig?.Commands != null)
|
||||
{
|
||||
if (command.Value.Aliases == null) continue;
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogInformation(
|
||||
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
|
||||
|
||||
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
|
||||
if (mapping == null || command.Value.Aliases.Length == 0) continue;
|
||||
|
||||
foreach (var alias in command.Value.Aliases)
|
||||
foreach (var command in commandsConfig.Commands)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
|
||||
if (command.Value.Aliases == null) continue;
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogInformation(
|
||||
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
|
||||
|
||||
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
|
||||
if (mapping == null || command.Value.Aliases.Length == 0) continue;
|
||||
|
||||
foreach (var alias in command.Value.Aliases)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (name, definitions) in _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)
|
||||
@@ -19,9 +24,7 @@ public partial class CS2_SimpleAdmin
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
if (command.ArgCount < 2)
|
||||
return;
|
||||
|
||||
var reason = _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList();
|
||||
@@ -31,14 +34,18 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
|
||||
reason = command.GetArg(3);
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
{
|
||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player,
|
||||
ManagePlayersMenu.BanMenu);
|
||||
@@ -49,37 +56,42 @@ 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;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Freeze player pawn if alive
|
||||
if (player.PawnIsAlive)
|
||||
{
|
||||
player.Pawn.Value?.Freeze();
|
||||
}
|
||||
|
||||
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
||||
? caller.PlayerName
|
||||
: (_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 () =>
|
||||
{
|
||||
await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
|
||||
int? penaltyId = await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
// Update banned players list
|
||||
if (playerInfo.IpAddress != null && !BannedPlayers.Contains(playerInfo.IpAddress))
|
||||
BannedPlayers.Add(playerInfo.IpAddress);
|
||||
if (!BannedPlayers.Contains(player.SteamID.ToString()))
|
||||
BannedPlayers.Add(player.SteamID.ToString());
|
||||
|
||||
// Determine message keys and arguments based on ban time
|
||||
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
|
||||
? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm",
|
||||
@@ -95,19 +107,13 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message if necessary
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Schedule a kick timer
|
||||
if (player.UserId.HasValue)
|
||||
{
|
||||
AddTimer(Config.OtherSettings.KickTime, () =>
|
||||
{
|
||||
if (player is { IsValid: true, UserId: not null })
|
||||
{
|
||||
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED);
|
||||
}
|
||||
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED, Config.OtherSettings.KickTime);
|
||||
}
|
||||
|
||||
// Execute ban command if necessary
|
||||
@@ -125,14 +131,61 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a ban for a player by their SteamID, including offline bans.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the ban command.</param>
|
||||
/// <param name="steamid">SteamID of the player to ban.</param>
|
||||
/// <param name="time">Ban duration in minutes (0 means permanent).</param>
|
||||
/// <param name="reason">Reason for banning.</param>
|
||||
/// <param name="banManager">Optional ban manager for database operations.</param>
|
||||
internal void AddBan(CCSPlayerController? caller, SteamID steamid, int time, string reason, BanManager? banManager = null)
|
||||
{
|
||||
// Set default caller name if not provided
|
||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
return;
|
||||
Ban(caller, player, time, reason, callerName, silent: true);
|
||||
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(steamid))
|
||||
return;
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await BanManager.AddBanBySteamid(steamid.SteamId64, adminInfo, reason, time);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Ban, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Ban, _localizer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles banning a player by specifying their SteamID via command.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command, or null if console.</param>
|
||||
/// <param name="command">Command information including arguments (SteamID, time, reason).</param>
|
||||
[RequiresPermissions("@css/ban")]
|
||||
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (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)
|
||||
@@ -141,21 +194,21 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
var reason = command.ArgCount >= 3 && !string.IsNullOrEmpty(command.GetArg(3))
|
||||
? command.GetArg(3)
|
||||
var steamid = steamId.SteamId64;
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
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 matches = Helper.GetPlayerFromSteamid64(steamid);
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
@@ -166,11 +219,21 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
||||
return;
|
||||
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
|
||||
int? penaltyId = await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Ban, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline.");
|
||||
}
|
||||
@@ -179,15 +242,18 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
if (UnlockedCommands)
|
||||
Server.ExecuteCommand($"banid 1 {steamId.SteamId3}");
|
||||
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles banning a player by their IP address, supporting offline banning if player is not online.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the ban command.</param>
|
||||
/// <param name="command">The command containing the IP, time, and reason arguments.</param>
|
||||
[RequiresPermissions("@css/ban")]
|
||||
[CommandHelper(minArgs: 1, usage: "<ip> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnBanIpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (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);
|
||||
@@ -198,26 +264,31 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var reason = command.ArgCount >= 3 && !string.IsNullOrEmpty(command.GetArg(3))
|
||||
? command.GetArg(3)
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
||||
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
if (!CheckValidBan(caller, time)) return;
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue
|
||||
? PlayersInfo[caller.UserId.Value]
|
||||
? PlayersInfo[caller.SteamID]
|
||||
: null;
|
||||
|
||||
var matches = Helper.GetPlayerFromIp(ipAddress);
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
|
||||
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
|
||||
{
|
||||
@@ -233,13 +304,19 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the ban duration is valid based on the caller's permissions and configured limits.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the ban command.</param>
|
||||
/// <param name="duration">Requested ban duration in minutes.</param>
|
||||
/// <returns>True if ban duration is valid; otherwise, false.</returns>
|
||||
private bool CheckValidBan(CCSPlayerController? caller, int duration)
|
||||
{
|
||||
if (caller == null) return true;
|
||||
|
||||
var canPermBan = AdminManager.PlayerHasPermissions(caller, "@css/permban");
|
||||
var canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
|
||||
|
||||
if (duration <= 0 && canPermBan == false)
|
||||
if (duration <= 0 && !canPermBan)
|
||||
{
|
||||
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
|
||||
return false;
|
||||
@@ -251,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.");
|
||||
@@ -266,27 +346,31 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
var pattern = command.GetArg(1);
|
||||
var reason = command.GetArg(2);
|
||||
|
||||
var reason = command.ArgCount >= 2
|
||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
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)
|
||||
return;
|
||||
|
||||
var reason = _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList();
|
||||
@@ -296,49 +380,65 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
WarnManager warnManager = new(Database);
|
||||
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
|
||||
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
|
||||
reason = command.GetArg(3);
|
||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
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;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= _localizer?["sa_console"] ?? "Console";
|
||||
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
// Freeze player pawn if alive
|
||||
if (player.PawnIsAlive)
|
||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
{
|
||||
player.Pawn.Value?.Freeze();
|
||||
player.PlayerPawn?.Value?.Freeze();
|
||||
AddTimer(5.0f, () => player.PlayerPawn?.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
}
|
||||
|
||||
// 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);
|
||||
await warnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
|
||||
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;
|
||||
@@ -351,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()));
|
||||
});
|
||||
@@ -374,7 +474,7 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message if necessary
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Log the warning command
|
||||
@@ -385,14 +485,86 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
// Send Discord notification for the warning
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a warning to a player by their SteamID, including support for offline players.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the warn command.</param>
|
||||
/// <param name="steamid">SteamID of the player to warn.</param>
|
||||
/// <param name="time">Warning duration in minutes.</param>
|
||||
/// <param name="reason">Reason for the warning.</param>
|
||||
/// <param name="warnManager">Optional warn manager instance.</param>
|
||||
internal void AddWarn(CCSPlayerController? caller, SteamID steamid, int time, string reason, WarnManager? warnManager = null)
|
||||
{
|
||||
// Set default caller name if not provided
|
||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
return;
|
||||
|
||||
Warn(caller, player, time, reason, callerName);
|
||||
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(steamid))
|
||||
return;
|
||||
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await WarnManager.AddWarnBySteamid(steamid.SteamId64, adminInfo, reason, time);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Warn, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
|
||||
// Check for warn thresholds and execute punish command if applicable
|
||||
var totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
|
||||
if (Config.WarnThreshold.Count > 0)
|
||||
{
|
||||
string? punishCommand = null;
|
||||
var lastKey = Config.WarnThreshold.Keys.Max();
|
||||
|
||||
if (totalWarns >= lastKey)
|
||||
punishCommand = Config.WarnThreshold[lastKey];
|
||||
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
|
||||
punishCommand = value;
|
||||
|
||||
if (!string.IsNullOrEmpty(punishCommand))
|
||||
{
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
Server.ExecuteCommand(punishCommand.Replace("STEAMID64", steamid.SteamId64.ToString()));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Warn, _localizer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles removing a warning (unwarn) by a pattern string.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the unwarn command.</param>
|
||||
/// <param name="command">The command containing target pattern.</param>
|
||||
[RequiresPermissions("@css/kick")]
|
||||
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnUnwarnCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
if (command.GetArg(1).Length <= 1)
|
||||
{
|
||||
@@ -401,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}.");
|
||||
}
|
||||
|
||||
@@ -5,11 +5,18 @@ using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using System.Text;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a chat message only to admins that have chat permission.
|
||||
/// The message is encoded properly to handle UTF-8 characters.
|
||||
/// </summary>
|
||||
/// <param name="caller">The admin player sending the message, or null for console.</param>
|
||||
/// <param name="command">The command input containing the message.</param>
|
||||
[CommandHelper(1, "<message>")]
|
||||
[RequiresPermissions("@css/chat")]
|
||||
public void OnAdminToAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -20,7 +27,7 @@ public partial class CS2_SimpleAdmin
|
||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
||||
|
||||
foreach (var player in Helper.GetValidPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, "@css/chat")))
|
||||
.Where(p => AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
|
||||
{
|
||||
if (_localizer != null)
|
||||
player.PrintToChat(_localizer["sa_adminchat_template_admin",
|
||||
@@ -29,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)
|
||||
@@ -46,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)
|
||||
@@ -56,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,
|
||||
@@ -65,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)
|
||||
@@ -91,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)
|
||||
@@ -99,10 +125,14 @@ public partial class CS2_SimpleAdmin
|
||||
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
Helper.PrintToCenterAll(utf8String.ReplaceColorTags());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a HUD alert message to all players.
|
||||
/// </summary>
|
||||
/// <param name="caller">The admin or console sending the message.</param>
|
||||
/// <param name="command">The command input containing the message.</param>
|
||||
[CommandHelper(1, "<message>")]
|
||||
[RequiresPermissions("@css/chat")]
|
||||
public void OnAdminHudSayCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -115,6 +145,6 @@ public partial class CS2_SimpleAdmin
|
||||
VirtualFunctions.ClientPrintAll(
|
||||
HudDestination.Alert,
|
||||
utf8String.ReplaceColorTags(),
|
||||
0, 0, 0, 0);
|
||||
0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CS2_SimpleAdmin.Managers;
|
||||
using CS2_SimpleAdmin.Menus;
|
||||
using CS2_SimpleAdminApi;
|
||||
@@ -10,15 +11,18 @@ 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 reason = _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
||||
@@ -28,13 +32,18 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
|
||||
reason = command.GetArg(3);
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (!caller!.CanTarget(player)) return;
|
||||
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
{
|
||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player,
|
||||
ManagePlayersMenu.GagMenu);
|
||||
@@ -45,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;
|
||||
|
||||
@@ -55,13 +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 () =>
|
||||
{
|
||||
await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time);
|
||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
// Add penalty to the player's penalty manager
|
||||
@@ -82,11 +106,11 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -98,14 +122,63 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Gag, _localizer);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a gag penalty to a player identified by SteamID, supporting offline players.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command or null for console.</param>
|
||||
/// <param name="steamid">SteamID of the target player.</param>
|
||||
/// <param name="time">Duration in minutes (0 for permanent).</param>
|
||||
/// <param name="reason">Reason for the gag.</param>
|
||||
internal void AddGag(CCSPlayerController? caller, SteamID steamid, int time, string reason)
|
||||
{
|
||||
// Set default caller name if not provided
|
||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
return;
|
||||
|
||||
Gag(caller, player, time, reason, callerName, silent: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(steamid))
|
||||
return;
|
||||
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 3);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Gag, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Gag, _localizer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -120,20 +193,21 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0
|
||||
? command.GetArg(3)
|
||||
: (_localizer?["sa_unknown"] ?? "Unknown");
|
||||
var steamid = steamId.SteamId64;
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
||||
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 matches = Helper.GetPlayerFromSteamid64(steamid);
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -145,30 +219,48 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
||||
return;
|
||||
|
||||
// Asynchronous gag operation for offline players
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time);
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Gag, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Gag has been added offline.");
|
||||
}
|
||||
|
||||
// Log the gag command and respond to the command
|
||||
Helper.LogCommand(caller, command);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles removing a gag penalty ('ungag') of a player, either by SteamID or pattern match.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the ungag command or null for console.</param>
|
||||
/// <param name="command">Command input containing SteamID or player name and optional reason.</param>
|
||||
[RequiresPermissions("@css/chat")]
|
||||
[CommandHelper(minArgs: 1, usage: "<steamid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnUngagCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
|
||||
var pattern = command.GetArg(1);
|
||||
var reason = command.GetArg(2);
|
||||
var reason = command.ArgCount >= 2
|
||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
if (pattern.Length <= 1)
|
||||
{
|
||||
command.ReplyToCommand($"Too short pattern to search.");
|
||||
@@ -180,8 +272,7 @@ public partial class CS2_SimpleAdmin
|
||||
// Check if pattern is a valid SteamID64
|
||||
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
|
||||
{
|
||||
var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString());
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -205,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 () =>
|
||||
{
|
||||
@@ -226,15 +317,18 @@ 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 reason = _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
||||
@@ -244,13 +338,18 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
|
||||
reason = command.GetArg(3);
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (!caller!.CanTarget(player)) return;
|
||||
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
{
|
||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player,
|
||||
ManagePlayersMenu.MuteMenu);
|
||||
@@ -261,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;
|
||||
|
||||
@@ -271,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;
|
||||
@@ -280,7 +389,12 @@ public partial class CS2_SimpleAdmin
|
||||
// Asynchronously handle mute logic
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1);
|
||||
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
// Add penalty to the player's penalty manager
|
||||
@@ -301,11 +415,11 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -317,14 +431,18 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Mute, _localizer);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the 'addmute' command that adds a mute penalty to a player specified by SteamID.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command or null for console.</param>
|
||||
/// <param name="command">Command input includes SteamID, optional time, and reason.</param>
|
||||
[RequiresPermissions("@css/chat")]
|
||||
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnAddMuteCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
// Set caller name
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
@@ -339,20 +457,21 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0
|
||||
? command.GetArg(3)
|
||||
: (_localizer?["sa_unknown"] ?? "Unknown");
|
||||
var steamid = steamId.SteamId64;
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
||||
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 matches = Helper.GetPlayerFromSteamid64(steamid);
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -364,30 +483,95 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
||||
return;
|
||||
|
||||
// Asynchronous mute operation for offline players
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1);
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Mute, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Mute has been added offline.");
|
||||
}
|
||||
|
||||
// Log the mute command and respond to the command
|
||||
Helper.LogCommand(caller, command);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously adds a mute penalty to a player by Steam ID. Handles both online and offline players.
|
||||
/// </summary>
|
||||
/// <param name="caller">The admin/player issuing the mute.</param>
|
||||
/// <param name="steamid">The Steam ID of the player to mute.</param>
|
||||
/// <param name="time">Duration of the mute in minutes.</param>
|
||||
/// <param name="reason">Reason for the mute.</param>
|
||||
/// <param name="muteManager">Optional mute manager instance for handling database ops.</param>
|
||||
internal void AddMute(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
|
||||
{
|
||||
// Set default caller name if not provided
|
||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
return;
|
||||
|
||||
Mute(caller, player, time, reason, callerName, silent: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(steamid))
|
||||
return;
|
||||
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 1);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Mute, reason, time,
|
||||
penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Mute, _localizer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
var reason = command.GetArg(2);
|
||||
var reason = command.ArgCount >= 2
|
||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
if (pattern.Length <= 1)
|
||||
{
|
||||
command.ReplyToCommand("Too short pattern to search.");
|
||||
@@ -399,8 +583,7 @@ public partial class CS2_SimpleAdmin
|
||||
// Check if pattern is a valid SteamID64
|
||||
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
|
||||
{
|
||||
var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString());
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -426,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 () =>
|
||||
{
|
||||
@@ -447,15 +630,19 @@ 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 reason = _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
||||
@@ -465,13 +652,18 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.ArgCount >= 3 && command.GetArg(3).Length > 0)
|
||||
reason = command.GetArg(3);
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
var time = Helper.ParsePenaltyTime(command.GetArg(2));
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (!caller!.CanTarget(player)) return;
|
||||
if (!int.TryParse(command.GetArg(2), out var time) && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
|
||||
{
|
||||
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player,
|
||||
ManagePlayersMenu.SilenceMenu);
|
||||
@@ -482,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;
|
||||
|
||||
@@ -492,13 +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 () =>
|
||||
{
|
||||
await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2); // Assuming 2 is the type for silence
|
||||
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
|
||||
@@ -520,11 +727,11 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -536,14 +743,19 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Silence, _localizer);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the 'AddSilence' command, applying a silence penalty to a player specified by SteamID,
|
||||
/// with support for offline player penalties.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin issuing the command.</param>
|
||||
/// <param name="command">The command input containing SteamID, optional time, and reason.</param>
|
||||
[RequiresPermissions("@css/chat")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnAddSilenceCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (Database == null) return;
|
||||
if (DatabaseProvider == null) return;
|
||||
|
||||
// Set caller name
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
@@ -558,20 +770,21 @@ public partial class CS2_SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
var steamid = steamId.SteamId64.ToString();
|
||||
var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0
|
||||
? command.GetArg(3)
|
||||
: (_localizer?["sa_unknown"] ?? "Unknown");
|
||||
var steamid = steamId.SteamId64;
|
||||
var reason = command.ArgCount >= 3
|
||||
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
|
||||
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 matches = Helper.GetPlayerFromSteamid64(steamid);
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -583,43 +796,107 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
|
||||
return;
|
||||
|
||||
// Asynchronous silence operation for offline players
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2);
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason,
|
||||
time, penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Silence, _localizer);
|
||||
|
||||
command.ReplyToCommand($"Player with steamid {steamid} is not online. Silence has been added offline.");
|
||||
}
|
||||
|
||||
// Log the silence command and respond to the command
|
||||
Helper.LogCommand(caller, command);
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a silence penalty to a player by Steam ID. Manages both online and offline player cases.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin/player initiating the silence.</param>
|
||||
/// <param name="steamid">Steam ID of player.</param>
|
||||
/// <param name="time">Duration of silence.</param>
|
||||
/// <param name="reason">Reason for the penalty.</param>
|
||||
/// <param name="muteManager">Optional mute manager for DB operations.</param>
|
||||
internal void AddSilence(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
|
||||
{
|
||||
// Set default caller name if not provided
|
||||
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
|
||||
? caller.PlayerName
|
||||
: (_localizer?["sa_console"] ?? "Console");
|
||||
|
||||
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
|
||||
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
if (!caller.CanTarget(player))
|
||||
return;
|
||||
|
||||
Silence(caller, player, time, reason, callerName, silent: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!caller.CanTarget(steamid))
|
||||
return;
|
||||
|
||||
// Asynchronous ban operation if player is not online or not found
|
||||
Task.Run(async () =>
|
||||
{
|
||||
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 2);
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Silence, reason,
|
||||
time, penaltyId);
|
||||
});
|
||||
});
|
||||
|
||||
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Silence, _localizer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
var reason = command.GetArg(2);
|
||||
var reason = command.ArgCount >= 2
|
||||
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
|
||||
: _localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
|
||||
|
||||
if (pattern.Length <= 1)
|
||||
{
|
||||
command.ReplyToCommand("Too short pattern to search.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Check if pattern is a valid SteamID64
|
||||
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
|
||||
{
|
||||
var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString());
|
||||
var player = matches.Count == 1 ? matches.FirstOrDefault() : null;
|
||||
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
|
||||
|
||||
if (player != null && player.IsValid)
|
||||
{
|
||||
@@ -649,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 () =>
|
||||
{
|
||||
@@ -670,13 +947,19 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates mute penalty duration based on admin privileges and configured max duration.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin/player issuing the mute.</param>
|
||||
/// <param name="duration">Requested duration in minutes.</param>
|
||||
/// <returns>True if mute penalty duration is allowed; false otherwise.</returns>
|
||||
private bool CheckValidMute(CCSPlayerController? caller, int duration)
|
||||
{
|
||||
if (caller == null) return true;
|
||||
|
||||
var canPermMute = AdminManager.PlayerHasPermissions(caller, "@css/permmute");
|
||||
var canPermMute = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permmute");
|
||||
|
||||
if (duration <= 0 && canPermMute == false)
|
||||
if (duration <= 0 && !canPermMute)
|
||||
{
|
||||
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
|
||||
return false;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,215 +1,307 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player =>
|
||||
player.IsValid &&
|
||||
player is { PawnIsAlive: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
NoClip(caller, player, callerName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Toggle no-clip mode for the player
|
||||
player.Pawn.Value?.ToggleNoclip();
|
||||
|
||||
// Determine message keys and arguments for the no-clip notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_noclip_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
{
|
||||
Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
God(caller, player, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Toggle god mode for the player
|
||||
if (!GodPlayers.Add(player.Slot))
|
||||
{
|
||||
GodPlayers.Remove(player.Slot);
|
||||
}
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message key and arguments for the god mode notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_god_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name> [duration]")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
Freeze(caller, player, time, callerName, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Freeze player pawn
|
||||
player.Pawn.Value?.Freeze();
|
||||
|
||||
// Determine message keys and arguments for the freeze notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_freeze_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Schedule unfreeze for the player if time is specified
|
||||
if (time > 0)
|
||||
{
|
||||
Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
}
|
||||
|
||||
// Log the command and send Discord notification
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
Unfreeze(caller, player, callerName, command);
|
||||
});
|
||||
}
|
||||
|
||||
internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Unfreeze player pawn
|
||||
player.Pawn.Value?.Unfreeze();
|
||||
|
||||
// Determine message keys and arguments for the unfreeze notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_unfreeze_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Log the command and send Discord notification
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
}
|
||||
// using System.Globalization;
|
||||
// using CounterStrikeSharp.API;
|
||||
// using CounterStrikeSharp.API.Core;
|
||||
// using CounterStrikeSharp.API.Modules.Admin;
|
||||
// using CounterStrikeSharp.API.Modules.Commands;
|
||||
//
|
||||
// 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)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player =>
|
||||
// player.IsValid &&
|
||||
// player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (caller!.CanTarget(player))
|
||||
// {
|
||||
// NoClip(caller, player, callerName);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Toggles no-clip mode for a player and shows admin activity messages.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin toggling no-clip.</param>
|
||||
// /// <param name="player">The target player whose no-clip state changes.</param>
|
||||
// /// <param name="callerName">Optional caller name for messages.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!player.IsValid) return;
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Toggle no-clip mode for the player
|
||||
// player.Pawn.Value?.ToggleNoclip();
|
||||
//
|
||||
// // Determine message keys and arguments for the no-clip notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_noclip_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
//
|
||||
// // Log the command
|
||||
// if (command == null)
|
||||
// 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)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
//
|
||||
// var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
// return;
|
||||
//
|
||||
// if (caller!.CanTarget(player))
|
||||
// {
|
||||
// God(caller, player, command);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Toggles god mode for a player and notifies admins.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin toggling god mode.</param>
|
||||
// /// <param name="player">The target player whose god mode changes.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Toggle god mode for the player
|
||||
// if (!GodPlayers.Add(player.Slot))
|
||||
// {
|
||||
// GodPlayers.Remove(player.Slot);
|
||||
// }
|
||||
//
|
||||
// // Log the command
|
||||
// if (command == null)
|
||||
// Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
//
|
||||
// // Determine message key and arguments for the god mode notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_god_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <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)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
// int.TryParse(command.GetArg(2), out var time);
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (caller!.CanTarget(player))
|
||||
// {
|
||||
// Freeze(caller, player, time, callerName, command);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Resizes the target player(s) models to a specified scale.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player issuing the resize command.</param>
|
||||
// /// <param name="command">The command input containing targets and scale factor.</param>
|
||||
// [CommandHelper(1, "<#userid or name> [size]")]
|
||||
// [RequiresPermissions("@css/slay")]
|
||||
// public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
// float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (!caller!.CanTarget(player)) return;
|
||||
//
|
||||
// var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
|
||||
// if (sceneNode == null) return;
|
||||
//
|
||||
// sceneNode.GetSkeletonInstance().Scale = size;
|
||||
// player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
|
||||
//
|
||||
// Server.NextWorldUpdate(() =>
|
||||
// {
|
||||
// Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
|
||||
// });
|
||||
//
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_resize_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Freezes a single player and optionally schedules automatic unfreeze after a duration.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin freezing the player.</param>
|
||||
// /// <param name="player">The player to freeze.</param>
|
||||
// /// <param name="time">Duration of freeze in seconds.</param>
|
||||
// /// <param name="callerName">Optional name for notifications.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!player.IsValid) return;
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Freeze player pawn
|
||||
// player.Pawn.Value?.Freeze();
|
||||
//
|
||||
// // Determine message keys and arguments for the freeze notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_freeze_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
//
|
||||
// // Schedule unfreeze for the player if time is specified
|
||||
// if (time > 0)
|
||||
// {
|
||||
// Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
// }
|
||||
//
|
||||
// // Log the command and send Discord notification
|
||||
// if (command == null)
|
||||
// Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
|
||||
// }
|
||||
//
|
||||
// /// <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)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// Unfreeze(caller, player, callerName, command);
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Unfreezes a single player and notifies admins.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin unfreezing the player.</param>
|
||||
// /// <param name="player">The player to unfreeze.</param>
|
||||
// /// <param name="callerName">Optional name for notifications.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!player.IsValid) return;
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Unfreeze player pawn
|
||||
// player.Pawn.Value?.Unfreeze();
|
||||
//
|
||||
// // Determine message keys and arguments for the unfreeze notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_unfreeze_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
//
|
||||
// // Log the command and send Discord notification
|
||||
// if (command == null)
|
||||
// Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
// }
|
||||
// }
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Globalization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
@@ -10,9 +11,12 @@ namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
internal static readonly Dictionary<int, float> SpeedPlayers = [];
|
||||
internal static readonly Dictionary<CCSPlayerController, float> GravityPlayers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Executes the 'slay' command, forcing the targeted players to commit suicide.
|
||||
/// Checks player validity and permissions.
|
||||
/// </summary>
|
||||
/// <param name="caller">Player or console issuing the command.</param>
|
||||
/// <param name="command">Command details, including targets.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -21,14 +25,23 @@ public partial class CS2_SimpleAdmin
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
Slay(caller, player, callerName, command);
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the actual slay action on a player, with notification and logging.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console issuing the slay.</param>
|
||||
/// <param name="player">Target player to slay.</param>
|
||||
/// <param name="callerName">Optional name to display as the slayer.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return;
|
||||
@@ -48,386 +61,19 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Log the command and send Discord notification
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
[CommandHelper(minArgs: 2, usage: "<#userid or name> <weapon>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnGiveCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
var weaponName = command.GetArg(2);
|
||||
|
||||
// check if item is typed
|
||||
// if (weaponName.Length < 2)
|
||||
// {
|
||||
// command.ReplyToCommand($"No weapon typed.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// check if weapon is knife
|
||||
if (weaponName.Contains("_knife") || weaponName.Contains("bayonet"))
|
||||
{
|
||||
if (CoreConfig.FollowCS2ServerGuidelines)
|
||||
{
|
||||
command.ReplyToCommand($"Cannot Give {weaponName} because it's illegal to be given.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
GiveWeapon(caller, player, weaponName, callerName, command);
|
||||
});
|
||||
}
|
||||
|
||||
private static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, string weaponName, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
var weapons = WeaponHelper.GetWeaponsByPartialName(weaponName);
|
||||
|
||||
switch (weapons.Count)
|
||||
{
|
||||
case 0:
|
||||
return;
|
||||
case > 1:
|
||||
{
|
||||
var weaponList = string.Join(", ", weapons.Select(w => w.EnumMemberValue));
|
||||
command?.ReplyToCommand($"Found weapons with a similar name: {weaponList}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Give weapon to the player
|
||||
player.GiveNamedItem(weapons.First().EnumValue);
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weaponName}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message keys and arguments for the weapon give notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_give_message",
|
||||
new object[] { "CALLER", player.PlayerName, weaponName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, CsItem weapon, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Give weapon to the player
|
||||
player.GiveNamedItem(weapon);
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weapon.ToString()}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message keys and arguments for the weapon give notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_give_message",
|
||||
new object[] { "CALLER", player.PlayerName, weapon.ToString() });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnStripCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
StripWeapons(caller, player, callerName, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void StripWeapons(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Check if player is valid, alive, and connected
|
||||
if (!player.IsValid || !player.PawnIsAlive || player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
// Strip weapons from the player
|
||||
player.RemoveWeapons();
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_strip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message keys and arguments for the weapon strip notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_strip_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <health>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnHpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
int.TryParse(command.GetArg(2), out var health);
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
SetHp(caller, player, health, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetHp(CCSPlayerController? caller, CCSPlayerController player, int health, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid || player.IsHLTV) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Set player's health
|
||||
player.SetHp(health);
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_hp {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {health}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message keys and arguments for the HP set notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_hp_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <speed>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnSpeedCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var speed);
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
SetSpeed(caller, player, speed, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetSpeed(CCSPlayerController? caller, CCSPlayerController player, float speed, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Set player's speed
|
||||
player.SetSpeed(speed);
|
||||
|
||||
if (speed == 1f)
|
||||
SpeedPlayers.Remove(player.Slot);
|
||||
else
|
||||
SpeedPlayers[player.Slot] = speed;
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_speed {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {speed}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message keys and arguments for the speed set notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_speed_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <gravity>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnGravityCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var gravity);
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
SetGravity(caller, player, gravity, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetGravity(CCSPlayerController? caller, CCSPlayerController player, float gravity, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Set player's gravity
|
||||
player.SetGravity(gravity);
|
||||
|
||||
if (gravity == 1f)
|
||||
GravityPlayers.Remove(player);
|
||||
else
|
||||
GravityPlayers[player] = gravity;
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_gravity {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {gravity}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message keys and arguments for the gravity set notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_gravity_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <money>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnMoneyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
int.TryParse(command.GetArg(2), out var money);
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
SetMoney(caller, player, money, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetMoney(CCSPlayerController? caller, CCSPlayerController player, int money, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Set player's money
|
||||
player.SetMoney(money);
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_money {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {money}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message keys and arguments for the money set notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_money_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies damage as a slap effect to the targeted players.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin executing the slap command.</param>
|
||||
/// <param name="command">The command including targets and optional damage value.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [damage]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnSlapCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -437,7 +83,7 @@ public partial class CS2_SimpleAdmin
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList();
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
if (command.ArgCount >= 2)
|
||||
{
|
||||
@@ -454,8 +100,17 @@ public partial class CS2_SimpleAdmin
|
||||
Slap(caller, player, damage, command);
|
||||
}
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies slap damage to a specific player with notifications and logging.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin applying the slap effect.</param>
|
||||
/// <param name="player">The target player to slap.</param>
|
||||
/// <param name="damage">The damage amount to apply.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
@@ -465,13 +120,12 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
// Apply slap damage to the player
|
||||
player.Pawn.Value?.Slap(damage);
|
||||
player.EmitSound("BaseGrenade.JumpThrowM");
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
|
||||
// Determine message key and arguments for the slap notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_slap_message",
|
||||
@@ -482,10 +136,15 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
if (_localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the team of targeted players with optional kill on switch.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin issuing the command.</param>
|
||||
/// <param name="command">The command containing targets, team info, and optional kill flag.</param>
|
||||
[RequiresPermissions("@css/kick")]
|
||||
[CommandHelper(minArgs: 2, usage: "<#userid or name> [<ct/tt/spec>] [-k]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnTeamCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -531,8 +190,19 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
ChangeTeam(caller, player, _teamName, teamNum, kill, command);
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the team of a player with various conditions and logs the operation.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin issuing the change.</param>
|
||||
/// <param name="player">The target player.</param>
|
||||
/// <param name="teamName">Team name string.</param>
|
||||
/// <param name="teamNum">Team enumeration value.</param>
|
||||
/// <param name="kill">If true, kills player on team change.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null)
|
||||
{
|
||||
// Check if the player is valid and connected
|
||||
@@ -548,7 +218,7 @@ public partial class CS2_SimpleAdmin
|
||||
// Change team based on the provided teamName and conditions
|
||||
if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (player.PawnIsAlive && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
||||
player.SwitchTeam(teamNum);
|
||||
else
|
||||
player.ChangeTeam(teamNum);
|
||||
@@ -559,7 +229,7 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist;
|
||||
teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT";
|
||||
if (player.PawnIsAlive && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
||||
player.SwitchTeam(_teamNum);
|
||||
else
|
||||
player.ChangeTeam(_teamNum);
|
||||
@@ -569,8 +239,6 @@ public partial class CS2_SimpleAdmin
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message key and arguments for the team change notification
|
||||
var activityMessageKey = "sa_admin_team_message";
|
||||
@@ -579,9 +247,14 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
||||
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
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)
|
||||
@@ -620,13 +293,18 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
||||
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
|
||||
// Rename the player
|
||||
player.Rename(newName);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames permamently targeted players to a new name.
|
||||
/// </summary>
|
||||
/// <param name="caller">The admin issuing the pre-rename command.</param>
|
||||
/// <param name="command">The command containing targets and new alias.</param>
|
||||
[CommandHelper(1, "<#userid or name> <new name>")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
public void OnPrenameCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
@@ -661,7 +339,7 @@ public partial class CS2_SimpleAdmin
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs);
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Determine if the new name is valid and update the renamed players list
|
||||
@@ -677,146 +355,270 @@ public partial class CS2_SimpleAdmin
|
||||
});
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
public void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
Respawn(caller, player, callerName, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void Respawn(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
// Check if the caller can target the player
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
// Ensure the player's pawn is valid before attempting to respawn
|
||||
if (_cBasePlayerControllerSetPawnFunc == null || player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) return;
|
||||
|
||||
// Perform the respawn operation
|
||||
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)
|
||||
playerPawn.Teleport(value.DiePosition?.Position, value.DiePosition?.Angle);
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
else
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Determine message key and arguments for the respawn notification
|
||||
var activityMessageKey = "sa_admin_respawn_message";
|
||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
||||
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, 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.PawnIsAlive) 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, PawnIsAlive: true }).Where(caller.CanTarget))
|
||||
foreach (var player in playersToTeleport)
|
||||
{
|
||||
if (caller.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);
|
||||
|
||||
// Set a timer to toggle noclip back after 3 seconds
|
||||
AddTimer(3, () => caller.PlayerPawn.Value.ToggleNoclip());
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
|
||||
// Prepare message key and arguments for the teleport notification
|
||||
var activityMessageKey = "sa_admin_tp_message";
|
||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
|
||||
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");
|
||||
|
||||
// Show admin activity
|
||||
if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
AddTimer(4, () =>
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, adminActivityArgs);
|
||||
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
||||
{
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
|
||||
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
{
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
});
|
||||
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity("sa_admin_tp_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[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.PawnIsAlive) return;
|
||||
IEnumerable<CCSPlayerController> playersToTeleport;
|
||||
CCSPlayerController? destinationPlayer;
|
||||
|
||||
// Get the target players
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null || targets.Count() > 1) return;
|
||||
if (command.ArgCount < 3)
|
||||
{
|
||||
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
return;
|
||||
|
||||
var playersToTarget = targets.Players
|
||||
.Where(player => player is { IsValid: true, IsHLTV: false })
|
||||
.ToList();
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null || !targets.Any())
|
||||
return;
|
||||
|
||||
// Log the command
|
||||
destinationPlayer = caller;
|
||||
|
||||
playersToTeleport = targets.Players
|
||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var destination = GetTarget(command);
|
||||
if (destination == null || destination.Count() != 1)
|
||||
return;
|
||||
|
||||
destinationPlayer = destination.Players.FirstOrDefault(p =>
|
||||
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
||||
|
||||
if (destinationPlayer == null)
|
||||
return;
|
||||
|
||||
// Rest args = targets to teleport
|
||||
var targets = GetTarget(command, 2);
|
||||
if (targets == null || !targets.Any())
|
||||
return;
|
||||
|
||||
playersToTeleport = targets.Players
|
||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (destinationPlayer == null || !playersToTeleport.Any())
|
||||
return;
|
||||
|
||||
// Log command
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Process each player to teleport
|
||||
foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }).Where(caller.CanTarget))
|
||||
foreach (var player in playersToTeleport)
|
||||
{
|
||||
if (caller.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();
|
||||
// Teleport
|
||||
player.TeleportPlayer(destinationPlayer);
|
||||
|
||||
// Set a timer to toggle noclip back after 3 seconds
|
||||
AddTimer(3, () => caller.PlayerPawn.Value.ToggleNoclip());
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
|
||||
// Prepare message key and arguments for the bring notification
|
||||
var activityMessageKey = "sa_admin_bring_message";
|
||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
|
||||
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");
|
||||
|
||||
// Show admin activity
|
||||
if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
AddTimer(4, () =>
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, adminActivityArgs);
|
||||
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
||||
{
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
|
||||
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
{
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
});
|
||||
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity("sa_admin_bring_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [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,56 +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() { Name = "Color", Value = "" },
|
||||
new() { Name = "Webhook", Value = "" },
|
||||
new() { Name = "ThumbnailUrl", Value = "" },
|
||||
new() { Name = "ImageUrl", Value = "" },
|
||||
new() { Name = "Footer", Value = "" },
|
||||
new() { Name = "Time", Value = "{relative}" },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -113,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")]
|
||||
@@ -166,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" }
|
||||
];
|
||||
}
|
||||
|
||||
@@ -224,30 +235,23 @@ public class OtherSettings
|
||||
|
||||
[JsonPropertyName("UserMessageGagChatType")]
|
||||
public bool UserMessageGagChatType { get; set; } = false;
|
||||
|
||||
[JsonPropertyName("CheckMultiAccountsByIp")]
|
||||
public bool CheckMultiAccountsByIp { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("AdditionalCommandsToLog")]
|
||||
public List<string> AdditionalCommandsToLog = new();
|
||||
public List<string> AdditionalCommandsToLog { get; set; } = new();
|
||||
[JsonPropertyName("IgnoredIps")]
|
||||
public List<string> IgnoredIps { get; set; } = new();
|
||||
}
|
||||
|
||||
public class CS2_SimpleAdminConfig : BasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 23;
|
||||
|
||||
[JsonPropertyName("DatabaseHost")]
|
||||
public string DatabaseHost { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("DatabasePort")]
|
||||
public int DatabasePort { get; set; } = 3306;
|
||||
|
||||
[JsonPropertyName("DatabaseUser")]
|
||||
public string DatabaseUser { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("DatabasePassword")]
|
||||
public string DatabasePassword { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("DatabaseName")]
|
||||
public string DatabaseName { get; set; } = "";
|
||||
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 25;
|
||||
|
||||
[JsonPropertyName("DatabaseConfig")]
|
||||
public DatabaseConfig DatabaseConfig { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("OtherSettings")]
|
||||
public OtherSettings OtherSettings { get; set; } = new();
|
||||
|
||||
@@ -284,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,22 +45,24 @@ 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()
|
||||
public bool CheckDatabaseConnection(out string? exception)
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
|
||||
exception = null;
|
||||
|
||||
try
|
||||
{
|
||||
return connection.Ping();
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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,105 @@
|
||||
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();
|
||||
await using (var cmd = connection.CreateCommand())
|
||||
{
|
||||
if (migrationsPath.Contains("sqlite", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
cmd.CommandText = """
|
||||
CREATE TABLE IF NOT EXISTS sa_migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version TEXT NOT NULL
|
||||
);
|
||||
|
||||
""";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.CommandText = """
|
||||
CREATE TABLE IF NOT EXISTS sa_migrations (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
version VARCHAR(128) NOT NULL
|
||||
);
|
||||
""";
|
||||
}
|
||||
|
||||
using var connection = database.GetConnection();
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
// Create sa_migrations table if not exists
|
||||
using var cmd = new MySqlCommand("""
|
||||
CREATE TABLE IF NOT EXISTS `sa_migrations` (
|
||||
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`version` VARCHAR(255) NOT NULL
|
||||
);
|
||||
""", connection);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,7 +20,6 @@ CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
ALTER TABLE `sa_admins` ADD `group_id` INT NULL AFTER `created`;
|
||||
|
||||
ALTER TABLE `sa_groups_flags` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `sa_groups_servers` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `sa_admins` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE SET NULL;
|
||||
@@ -1,8 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`steamid` bigint(20) NOT NULL,
|
||||
`address` varchar(64) NOT NULL,
|
||||
`used_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `steamid` (`steamid`,`address`)
|
||||
PRIMARY KEY (`steamid`, `address`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_servers` ADD `rcon_password` varchar(128) NULL AFTER `hostname`;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `status`;
|
||||
@@ -0,0 +1,5 @@
|
||||
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,33 @@
|
||||
-- Migration 016: Optimize tables and indexes
|
||||
-- Add proper indexes for all tables to improve query performance
|
||||
|
||||
-- Optimize sa_players_ips table indexes
|
||||
-- Add index on used_at for efficient date-based queries
|
||||
ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
||||
|
||||
-- Optimize sa_bans table indexes
|
||||
-- Add composite indexes for common query patterns
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||
|
||||
-- Optimize sa_admins table indexes
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||
|
||||
-- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||
-- Add index for expire queries
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||
|
||||
-- Optimize sa_warns table indexes (if exists)
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
||||
|
||||
-- Add index on sa_servers for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||
@@ -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,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
||||
`steamid` INTEGER NOT NULL,
|
||||
`address` INTEGER NOT NULL
|
||||
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`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,4 @@
|
||||
TRUNCATE TABLE `sa_players_ips`;
|
||||
|
||||
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]*';
|
||||
@@ -0,0 +1,33 @@
|
||||
-- Migration 016: Optimize tables and indexes
|
||||
-- Add proper indexes for all tables to improve query performance
|
||||
|
||||
-- Optimize sa_players_ips table indexes
|
||||
-- Add index on used_at for efficient date-based queries
|
||||
CREATE INDEX IF NOT EXISTS `idx_used_at` ON `sa_players_ips` (`used_at` DESC);
|
||||
|
||||
-- Optimize sa_bans table indexes
|
||||
-- Add composite indexes for common query patterns
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||
|
||||
-- Optimize sa_admins table indexes
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||
|
||||
-- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||
-- Add index for expire queries
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||
|
||||
-- Optimize sa_warns table indexes (if exists)
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
||||
|
||||
-- Add index on sa_servers for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||
393
CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs
Normal file
393
CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs
Normal file
@@ -0,0 +1,393 @@
|
||||
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();
|
||||
|
||||
await using var cmd = connection.CreateCommand();
|
||||
|
||||
cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
cmd.CommandText = "SET time_zone = '+00:00';";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
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;
|
||||
@@ -17,9 +17,14 @@ namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
private bool _serverLoading;
|
||||
|
||||
private void RegisterEvents()
|
||||
{
|
||||
RegisterListener<Listeners.OnMapStart>(OnMapStart);
|
||||
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||
RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
|
||||
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
|
||||
if (Config.OtherSettings.UserMessageGagChatType)
|
||||
HookUserMessage(118, HookUmChat);
|
||||
@@ -30,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();
|
||||
@@ -49,22 +70,35 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
private void OnGameServerSteamAPIActivated()
|
||||
{
|
||||
if (ServerLoaded || _serverLoading)
|
||||
return;
|
||||
|
||||
_serverLoading = true;
|
||||
new ServerManager().LoadServerData();
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
[GameEventHandler(HookMode.Pre)]
|
||||
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||
{
|
||||
if (@event.Reason is 149 or 6)
|
||||
info.DontBroadcast = true;
|
||||
|
||||
var player = @event.Userid;
|
||||
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnClientDisconnect] Before");
|
||||
#endif
|
||||
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
{
|
||||
if (player == null || !player.IsValid || player.IsHLTV)
|
||||
return HookResult.Continue;
|
||||
|
||||
BotPlayers.Remove(player);
|
||||
CachedPlayers.Remove(player);
|
||||
|
||||
SilentPlayers.Remove(player.Slot);
|
||||
|
||||
if (player.IsBot)
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnClientDisconnect] After Check");
|
||||
@@ -85,26 +119,30 @@ 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.Remove(player.UserId.Value);
|
||||
PlayersInfo.TryRemove(player.SteamID, out _);
|
||||
|
||||
var authorizedSteamId = player.AuthorizedSteamID;
|
||||
if (authorizedSteamId == null || !PermissionManager.AdminCache.TryGetValue(authorizedSteamId,
|
||||
out var expirationTime)
|
||||
|| !(expirationTime <= Time.ActualDateTime())) return HookResult.Continue;
|
||||
if (!PermissionManager.AdminCache.TryGetValue(steamId, out var data)
|
||||
|| !(data.ExpirationTime <= Time.ActualDateTime()))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
AdminManager.ClearPlayerPermissions(authorizedSteamId);
|
||||
AdminManager.RemovePlayerAdminData(authorizedSteamId);
|
||||
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);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
@@ -115,16 +153,87 @@ public partial class CS2_SimpleAdmin
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClientConnect(int playerslot, string name, string ipAddress)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnClientConnect]");
|
||||
#endif
|
||||
|
||||
var player = Utilities.GetPlayerFromSlot(playerslot);
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
return;
|
||||
|
||||
PlayerManager.LoadPlayerData(player);
|
||||
}
|
||||
|
||||
private void OnClientConnected(int playerslot)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnClientConnected]");
|
||||
#endif
|
||||
|
||||
var player = Utilities.GetPlayerFromSlot(playerslot);
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
return;
|
||||
|
||||
PlayerManager.LoadPlayerData(player);
|
||||
}
|
||||
|
||||
// private void OnClientConnect(int playerslot, string name, string ipaddress)
|
||||
// {
|
||||
// #if DEBUG
|
||||
// Logger.LogCritical("[OnClientConnect]");
|
||||
// #endif
|
||||
// if (Config.OtherSettings.BanType == 0)
|
||||
// return;
|
||||
//
|
||||
// if (Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0]))
|
||||
// return;
|
||||
//
|
||||
// var testPlayer = Utilities.GetPlayerFromSlot(playerslot);
|
||||
// if (testPlayer == null)
|
||||
// return;
|
||||
// Logger.LogInformation($"Gracz {testPlayer.PlayerName} ({testPlayer.SteamID.ToString()}) Czas: {DateTime.Now}");
|
||||
//
|
||||
// Server.NextFrame((() =>
|
||||
// {
|
||||
// var player = Utilities.GetPlayerFromSlot(playerslot);
|
||||
// if (player == null || !player.IsValid || player.IsBot)
|
||||
// return;
|
||||
//
|
||||
// Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
// }));
|
||||
//
|
||||
// // Server.NextFrame(() =>
|
||||
// // {
|
||||
// // var player = Utilities.GetPlayerFromSlot(playerslot);
|
||||
// //
|
||||
// // if (player == null || !player.IsValid || player.IsBot)
|
||||
// // return;
|
||||
// //
|
||||
// // new PlayerManager().LoadPlayerData(player);
|
||||
// // });
|
||||
// }
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerFullConnect(EventPlayerConnectFull @event, GameEventInfo info)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnPlayerFullConnect]");
|
||||
#endif
|
||||
|
||||
var player = @event.Userid;
|
||||
|
||||
if (player == null || !player.IsValid || player.IsBot)
|
||||
if (player == null || !player.IsValid)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -132,13 +241,9 @@ public partial class CS2_SimpleAdmin
|
||||
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.LogCritical("[OnRoundEnd]");
|
||||
Logger.LogCritical("[OnRoundStart]");
|
||||
#endif
|
||||
|
||||
GodPlayers.Clear();
|
||||
SpeedPlayers.Clear();
|
||||
GravityPlayers.Clear();
|
||||
|
||||
foreach (var player in PlayersInfo.Values)
|
||||
{
|
||||
player.DiePosition = null;
|
||||
@@ -172,12 +277,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 is not null)
|
||||
author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage()));
|
||||
return HookResult.Stop;
|
||||
|
||||
if (_localizer == null || endDateTime == null)
|
||||
return HookResult.Continue;
|
||||
|
||||
// um.Recipients.Clear();
|
||||
var message = um.ReadString("param2");
|
||||
var triggers = CoreConfig.PublicChatTrigger.Concat(CoreConfig.SilentChatTrigger);
|
||||
if (!triggers.Any(trigger => message.StartsWith(trigger))) return HookResult.Stop;
|
||||
|
||||
for (var i = um.Recipients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (um.Recipients[i] != author)
|
||||
{
|
||||
um.Recipients.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
|
||||
// author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage()));
|
||||
}
|
||||
|
||||
private HookResult ComamndListenerHandler(CCSPlayerController? player, CommandInfo info)
|
||||
@@ -209,15 +327,24 @@ public partial class CS2_SimpleAdmin
|
||||
if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return HookResult.Continue;
|
||||
|
||||
return !AdminManager.CanPlayerTarget(player, target) ? HookResult.Stop : HookResult.Continue;
|
||||
return !player.CanTarget(target) ? HookResult.Stop : HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -225,17 +352,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(player, "@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;
|
||||
@@ -244,10 +363,10 @@ public partial class CS2_SimpleAdmin
|
||||
if (command != "say_team" || !info.GetArg(1).StartsWith($"@")) return HookResult.Continue;
|
||||
|
||||
StringBuilder sb = new();
|
||||
if (AdminManager.PlayerHasPermissions(player, "@css/chat"))
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat"))
|
||||
{
|
||||
sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
|
||||
{
|
||||
p.PrintToChat(sb.ToString());
|
||||
}
|
||||
@@ -256,7 +375,7 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
|
||||
player.PrintToChat(sb.ToString());
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
|
||||
{
|
||||
p.PrintToChat(sb.ToString());
|
||||
}
|
||||
@@ -302,10 +421,10 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
StringBuilder sb = new();
|
||||
|
||||
if (AdminManager.PlayerHasPermissions(player, "@css/chat"))
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat"))
|
||||
{
|
||||
sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
|
||||
{
|
||||
p.PrintToChat(sb.ToString());
|
||||
}
|
||||
@@ -314,7 +433,7 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
|
||||
player.PrintToChat(sb.ToString());
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat")))
|
||||
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
|
||||
{
|
||||
p.PrintToChat(sb.ToString());
|
||||
}
|
||||
@@ -325,19 +444,22 @@ public partial class CS2_SimpleAdmin
|
||||
|
||||
private void OnMapStart(string mapName)
|
||||
{
|
||||
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
|
||||
AddTimer(3.0f, () => ReloadAdmins(null));
|
||||
if (!ServerLoaded || ServerId == null)
|
||||
AddTimer(2.0f, OnGameServerSteamAPIActivated);
|
||||
|
||||
AddTimer(34, () =>
|
||||
{
|
||||
if (!ServerLoaded)
|
||||
OnGameServerSteamAPIActivated();
|
||||
});
|
||||
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
|
||||
AddTimer(5.0f, () => ReloadAdmins(null));
|
||||
|
||||
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
|
||||
|
||||
// AddTimer(34, () =>
|
||||
// {
|
||||
// if (!ServerLoaded)
|
||||
// OnGameServerSteamAPIActivated();
|
||||
// });
|
||||
|
||||
GodPlayers.Clear();
|
||||
SilentPlayers.Clear();
|
||||
SpeedPlayers.Clear();
|
||||
GravityPlayers.Clear();
|
||||
|
||||
PlayerPenaltyManager.RemoveAllPenalties();
|
||||
}
|
||||
@@ -347,11 +469,8 @@ public partial class CS2_SimpleAdmin
|
||||
{
|
||||
var player = @event.Userid;
|
||||
|
||||
if (player is null || @event.Attacker is null || !player.PawnIsAlive || player.PlayerPawn.Value == null)
|
||||
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))
|
||||
player.SetSpeed(speedPlayer);
|
||||
|
||||
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
|
||||
|
||||
@@ -365,23 +484,24 @@ public partial class CS2_SimpleAdmin
|
||||
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
|
||||
if (player?.UserId == null || player.IsBot || player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
|
||||
if (player?.UserId == null || !player.IsValid || player.IsHLTV ||
|
||||
player.Connected != PlayerConnectedState.PlayerConnected || !PlayersInfo.ContainsKey(player.SteamID) ||
|
||||
@event.Attacker == null)
|
||||
return HookResult.Continue;
|
||||
|
||||
SpeedPlayers.Remove(player.Slot);
|
||||
GravityPlayers.Remove(player);
|
||||
|
||||
PlayersInfo[player.UserId.Value].DiePosition = new DiePosition(
|
||||
new Vector(
|
||||
player.PlayerPawn.Value?.AbsOrigin?.X ?? 0,
|
||||
player.PlayerPawn.Value?.AbsOrigin?.Y ?? 0,
|
||||
player.PlayerPawn.Value?.AbsOrigin?.Z ?? 0
|
||||
var playerPosition = player.PlayerPawn.Value?.AbsOrigin;
|
||||
var playerRotation = player.PlayerPawn.Value?.AbsRotation;
|
||||
PlayersInfo[player.SteamID].DiePosition = new DiePosition(
|
||||
new Vector3(
|
||||
playerPosition?.X ?? 0,
|
||||
playerPosition?.Y ?? 0,
|
||||
playerPosition?.Z ?? 0
|
||||
),
|
||||
new QAngle(
|
||||
player.PlayerPawn.Value?.AbsRotation?.X ?? 0,
|
||||
player.PlayerPawn.Value?.AbsRotation?.Y ?? 0,
|
||||
player.PlayerPawn.Value?.AbsRotation?.Z ?? 0
|
||||
new Vector3(
|
||||
playerRotation?.X ?? 0,
|
||||
playerRotation?.Y ?? 0,
|
||||
playerRotation?.Z ?? 0
|
||||
)
|
||||
);
|
||||
|
||||
@@ -392,17 +512,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;
|
||||
}
|
||||
|
||||
12
CS2-SimpleAdmin/Extensions/EnumerableExtensions.cs
Normal file
12
CS2-SimpleAdmin/Extensions/EnumerableExtensions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<IEnumerable<T>> ChunkBy<T>(this IEnumerable<T> source, int chunkSize)
|
||||
{
|
||||
return source
|
||||
.Select((x, i) => new { Index = i, Value = x })
|
||||
.GroupBy(x => x.Index / chunkSize)
|
||||
.Select(x => x.Select(v => v.Value));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -32,9 +50,29 @@ public static class PlayerExtensions
|
||||
|
||||
return AdminManager.CanPlayerTarget(controller, target) ||
|
||||
AdminManager.CanPlayerTarget(new SteamID(controller.SteamID),
|
||||
new SteamID(target.SteamID));
|
||||
new SteamID(target.SteamID)) ||
|
||||
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;
|
||||
|
||||
return AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), steamId) ||
|
||||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(steamId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the movement speed modifier of the player controller.
|
||||
/// </summary>
|
||||
/// <param name="controller">The player controller.</param>
|
||||
/// <param name="speed">The speed modifier value.</param>
|
||||
public static void SetSpeed(this CCSPlayerController? controller, float speed)
|
||||
{
|
||||
var playerPawnValue = controller?.PlayerPawn.Value;
|
||||
@@ -43,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;
|
||||
@@ -61,10 +109,15 @@ public static class PlayerExtensions
|
||||
if (controller != null) Utilities.SetStateChanged(controller, "CCSPlayerController", "m_pInGameMoneyServices");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the player's health points.
|
||||
/// </summary>
|
||||
/// <param name="controller">The player controller.</param>
|
||||
/// <param name="health">The health value, default is 100.</param>
|
||||
public static void SetHp(this CCSPlayerController? controller, int health = 100)
|
||||
{
|
||||
if (controller == null) return;
|
||||
if ((health <= 0 || !controller.PawnIsAlive || controller.PlayerPawn.Value == null)) return;
|
||||
if (health <= 0 || controller.PlayerPawn.Value == null || controller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return;
|
||||
|
||||
controller.PlayerPawn.Value.Health = health;
|
||||
|
||||
@@ -76,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)
|
||||
@@ -122,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";
|
||||
@@ -149,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)
|
||||
@@ -167,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)
|
||||
@@ -177,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);
|
||||
@@ -208,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)
|
||||
{
|
||||
@@ -217,15 +335,25 @@ public static class PlayerExtensions
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.Append(localizer[messageKey, messageArgs]);
|
||||
|
||||
|
||||
foreach (var part in Helper.SeparateLines(sb.ToString()))
|
||||
{
|
||||
var lineWithPrefix = localizer["sa_prefix"] + part.Trim();
|
||||
var lineWithPrefix = (CS2_SimpleAdmin._localizer?["sa_prefix"] ?? "") + part.Trim();
|
||||
controller.PrintToChat(lineWithPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CS2_SimpleAdmin.Managers;
|
||||
using MenuManager;
|
||||
using ZLinq;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
@@ -48,42 +49,34 @@ 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 List<CCSPlayerController> GetPlayerFromSteamid64(string steamid)
|
||||
public static CCSPlayerController? GetPlayerFromSteamid64(ulong steamid)
|
||||
{
|
||||
return GetValidPlayers().FindAll(x =>
|
||||
x.SteamID.ToString().Equals(steamid, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
return GetValidPlayers().FirstOrDefault(x => x.SteamID == steamid);
|
||||
}
|
||||
|
||||
public static List<CCSPlayerController> GetPlayerFromIp(string ipAddress)
|
||||
{
|
||||
return GetValidPlayers().FindAll(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().FindAll(p => p is
|
||||
{ IsValid: true, IsBot: false, Connected: PlayerConnectedState.PlayerConnected });
|
||||
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().ToList();
|
||||
}
|
||||
|
||||
public static List<CCSPlayerController> GetValidPlayersWithBots()
|
||||
{
|
||||
return CS2_SimpleAdmin.CachedPlayers.Concat(CS2_SimpleAdmin.BotPlayers).AsValueEnumerable().ToList();
|
||||
}
|
||||
|
||||
public static IEnumerable<CCSPlayerController?> GetValidPlayersWithBots()
|
||||
{
|
||||
return Utilities.GetPlayers().FindAll(p =>
|
||||
p is { IsValid: true, IsBot: false, IsHLTV: false } or { IsValid: true, IsBot: true, IsHLTV: false }
|
||||
);
|
||||
}
|
||||
|
||||
public static bool IsValidSteamId64(string input)
|
||||
{
|
||||
const string pattern = @"^\d{17}$";
|
||||
return Regex.IsMatch(input, pattern);
|
||||
}
|
||||
// public static bool IsValidSteamId64(string input)
|
||||
// {
|
||||
// const string pattern = @"^\d{17}$";
|
||||
// return Regex.IsMatch(input, pattern);
|
||||
// }
|
||||
|
||||
public static bool ValidateSteamId(string input, out SteamID? steamId)
|
||||
{
|
||||
@@ -93,7 +86,7 @@ internal static class Helper
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!SteamID.TryParse(input, out var parsedSteamId)) return false;
|
||||
|
||||
steamId = parsedSteamId;
|
||||
@@ -137,14 +130,67 @@ internal static class Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static void KickPlayer(int userId, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED)
|
||||
public static void KickPlayer(int userId, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0)
|
||||
{
|
||||
var player = Utilities.GetPlayerFromUserid(userId);
|
||||
|
||||
if (player == null || !player.IsValid || player.IsHLTV)
|
||||
return;
|
||||
|
||||
if (player.UserId.HasValue && CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
|
||||
value.WaitingForKick = true;
|
||||
|
||||
player.Disconnect(reason);
|
||||
// player.CommitSuicide(true, true);
|
||||
player.VoiceFlags = VoiceFlags.Muted;
|
||||
var playerPawn = player.PlayerPawn.Value;
|
||||
|
||||
if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
{
|
||||
playerPawn.Freeze();
|
||||
playerPawn.Colorize(255, 0, 0);
|
||||
|
||||
var weaponServices = playerPawn.WeaponServices;
|
||||
if (weaponServices == null)
|
||||
return;
|
||||
|
||||
foreach (var _weap in weaponServices.MyWeapons)
|
||||
{
|
||||
var weapon = _weap.Value;;
|
||||
if (weapon == null || !weapon.IsValid)
|
||||
continue;
|
||||
if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot"))
|
||||
continue;
|
||||
|
||||
weapon.NextPrimaryAttackTick = Server.TickCount + 999;
|
||||
weapon.NextSecondaryAttackTick = Server.TickCount + 999;
|
||||
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick");
|
||||
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick");
|
||||
}
|
||||
}
|
||||
|
||||
if (delay > 0)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddTimer(delay, () =>
|
||||
{
|
||||
if (!player.IsValid || player.IsHLTV)
|
||||
return;
|
||||
|
||||
// Server.ExecuteCommand($"kickid {player.UserId}");
|
||||
|
||||
playerPawn?.Colorize();
|
||||
player.Disconnect(reason);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Server.ExecuteCommand($"kickid {player.UserId}");
|
||||
|
||||
playerPawn?.Colorize();
|
||||
player.Disconnect(reason);
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED)
|
||||
Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");
|
||||
|
||||
// if (!string.IsNullOrEmpty(reason))
|
||||
// {
|
||||
@@ -158,6 +204,141 @@ internal static class Helper
|
||||
//
|
||||
// Server.ExecuteCommand($"kickid {userId} {reason}");
|
||||
}
|
||||
|
||||
public static void KickPlayer(CCSPlayerController player, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0)
|
||||
{
|
||||
if (!player.IsValid || player.IsHLTV)
|
||||
return;
|
||||
|
||||
if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
|
||||
{
|
||||
if (value.WaitingForKick)
|
||||
return;
|
||||
|
||||
value.WaitingForKick = true;
|
||||
}
|
||||
|
||||
player.VoiceFlags = VoiceFlags.Muted;
|
||||
var playerPawn = player.PlayerPawn.Value;
|
||||
if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
{
|
||||
playerPawn.Freeze();
|
||||
playerPawn.Colorize(255, 0, 0);
|
||||
|
||||
var weaponServices = playerPawn.WeaponServices;
|
||||
if (weaponServices == null)
|
||||
return;
|
||||
|
||||
foreach (var _weap in weaponServices.MyWeapons)
|
||||
{
|
||||
var weapon = _weap.Value;
|
||||
;
|
||||
if (weapon == null || !weapon.IsValid)
|
||||
continue;
|
||||
if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot"))
|
||||
continue;
|
||||
|
||||
weapon.NextPrimaryAttackTick = Server.TickCount + 999;
|
||||
weapon.NextSecondaryAttackTick = Server.TickCount + 999;
|
||||
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick");
|
||||
Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick");
|
||||
}
|
||||
}
|
||||
|
||||
if (delay > 0)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddTimer(delay, () =>
|
||||
{
|
||||
if (!player.IsValid || player.IsHLTV)
|
||||
return;
|
||||
|
||||
// if (!string.IsNullOrEmpty(reason))
|
||||
// {
|
||||
// var escapeChars = reason.IndexOfAny([';', '|']);
|
||||
//
|
||||
// if (escapeChars != -1)
|
||||
// {
|
||||
// reason = reason[..escapeChars];
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Server.ExecuteCommand($"kickid {player.UserId}");
|
||||
player.Disconnect(reason);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Server.ExecuteCommand($"kickid {player.UserId}");
|
||||
|
||||
player.Disconnect(reason);
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED)
|
||||
Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");
|
||||
|
||||
// if (!string.IsNullOrEmpty(reason))
|
||||
// {
|
||||
// var escapeChars = reason.IndexOfAny([';', '|']);
|
||||
//
|
||||
// if (escapeChars != -1)
|
||||
// {
|
||||
// reason = reason[..escapeChars];
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Server.ExecuteCommand($"kickid {userId} {reason}");
|
||||
}
|
||||
|
||||
public static int ParsePenaltyTime(string time)
|
||||
{
|
||||
time = time.ToLower();
|
||||
if (string.IsNullOrWhiteSpace(time) || !time.Any(char.IsDigit))
|
||||
{
|
||||
// CS2_SimpleAdmin._logger?.LogError("Time string cannot be null or empty.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (time.Equals($"0"))
|
||||
return 0;
|
||||
|
||||
var timeUnits = new Dictionary<string, int>
|
||||
{
|
||||
{ "m", 1 }, // Minute
|
||||
{ "h", 60 }, // Hour
|
||||
{ "d", 1440 }, // Day (24 * 60)
|
||||
{ "w", 10080 }, // Week (7 * 24 * 60)
|
||||
{ "mo", 43200 }, // Month (30 * 24 * 60)
|
||||
{ "y", 525600 } // Year (365 * 24 * 60)
|
||||
};
|
||||
|
||||
// Check if the input is purely numeric (e.g., "10" for 10 minutes)
|
||||
if (int.TryParse(time, out var numericMinutes))
|
||||
{
|
||||
return numericMinutes;
|
||||
}
|
||||
|
||||
int totalMinutes = 0;
|
||||
|
||||
var regex = new Regex(@"(\d+)([a-z]+)");
|
||||
var matches = regex.Matches(time);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
var value = int.Parse(match.Groups[1].Value); // Numeric part
|
||||
var unit = match.Groups[2].Value; // Unit part
|
||||
|
||||
if (timeUnits.TryGetValue(unit, out var minutesPerUnit))
|
||||
{
|
||||
totalMinutes += value * minutesPerUnit;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Invalid time unit '{unit}' in time string.", nameof(time));
|
||||
}
|
||||
}
|
||||
|
||||
return totalMinutes > 0 ? totalMinutes : -1;
|
||||
}
|
||||
|
||||
public static void PrintToCenterAll(string message)
|
||||
{
|
||||
@@ -182,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[
|
||||
@@ -258,15 +438,23 @@ internal static class Helper
|
||||
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command]));
|
||||
}
|
||||
|
||||
public static void ShowAdminActivity(string messageKey, string? callerName = null, params object[] messageArgs)
|
||||
public static void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
|
||||
{
|
||||
string[] publishActions = ["ban", "gag", "silence", "mute"];
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
|
||||
if (CS2_SimpleAdmin._localizer == null) return;
|
||||
|
||||
// Determine the localized message key
|
||||
var localizedMessageKey = $"{messageKey}";
|
||||
if (string.IsNullOrWhiteSpace(callerName))
|
||||
callerName = CS2_SimpleAdmin._localizer["sa_console"];
|
||||
|
||||
var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();
|
||||
|
||||
if (!dontPublish && publishActions.Any(messageKey.Contains))
|
||||
{
|
||||
CS2_SimpleAdmin.SimpleAdminApi?.OnAdminShowActivityEvent(messageKey, callerName, dontPublish, messageArgs);
|
||||
}
|
||||
|
||||
// // Replace placeholder based on showActivityType
|
||||
// for (var i = 0; i < formattedMessageArgs.Length; i++)
|
||||
// {
|
||||
@@ -279,8 +467,19 @@ internal static class Helper
|
||||
// _ => arg
|
||||
// };
|
||||
// }
|
||||
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
|
||||
|
||||
foreach (var controller in GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false }))
|
||||
if (!validPlayers.Any())
|
||||
return;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
|
||||
{
|
||||
validPlayers = validPlayers.Where(c =>
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
|
||||
}
|
||||
|
||||
foreach (var controller in validPlayers.ToList())
|
||||
{
|
||||
var currentMessageArgs = (string[])formattedMessageArgs.Clone();
|
||||
|
||||
@@ -290,14 +489,96 @@ internal static class Helper
|
||||
var arg = currentMessageArgs[i];
|
||||
currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||
{
|
||||
1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(controller, "@css/kick") || AdminManager.PlayerHasPermissions(controller, "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
2 => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
|
||||
_ => arg
|
||||
1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
_ => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
|
||||
};
|
||||
}
|
||||
|
||||
// Send the localized message to each player
|
||||
controller.SendLocalizedMessage(CS2_SimpleAdmin._localizer, localizedMessageKey, currentMessageArgs.Cast<object>().ToArray());
|
||||
controller.SendLocalizedMessage(CS2_SimpleAdmin._localizer, messageKey, currentMessageArgs.Cast<object>().ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows admin activity with a custom translated message (for modules with their own localizer).
|
||||
/// </summary>
|
||||
public static void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false)
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
|
||||
if (CS2_SimpleAdmin._localizer == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(callerName))
|
||||
callerName = CS2_SimpleAdmin._localizer["sa_console"];
|
||||
|
||||
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
|
||||
|
||||
if (!validPlayers.Any())
|
||||
return;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
|
||||
{
|
||||
validPlayers = validPlayers.Where(c =>
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
|
||||
}
|
||||
|
||||
foreach (var controller in validPlayers.ToList())
|
||||
{
|
||||
// Replace "CALLER" placeholder based on showActivityType
|
||||
var message = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||
{
|
||||
1 => translatedMessage.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
_ => translatedMessage.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
|
||||
};
|
||||
|
||||
// Send the pre-translated message to the player
|
||||
controller.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows admin activity using module's localizer for per-player language support.
|
||||
/// Each player receives the message in their configured language using SendLocalizedMessage.
|
||||
/// </summary>
|
||||
public static void ShowAdminActivityLocalized(IStringLocalizer moduleLocalizer, string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
|
||||
if (CS2_SimpleAdmin._localizer == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(callerName))
|
||||
callerName = CS2_SimpleAdmin._localizer["sa_console"];
|
||||
|
||||
var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();
|
||||
|
||||
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
|
||||
|
||||
if (!validPlayers.Any())
|
||||
return;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
|
||||
{
|
||||
validPlayers = validPlayers.Where(c =>
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
|
||||
}
|
||||
|
||||
foreach (var controller in validPlayers.ToList())
|
||||
{
|
||||
var currentMessageArgs = (string[])formattedMessageArgs.Clone();
|
||||
|
||||
// Replace "CALLER" placeholder based on showActivityType
|
||||
for (var i = 0; i < currentMessageArgs.Length; i++)
|
||||
{
|
||||
var arg = currentMessageArgs[i];
|
||||
currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||
{
|
||||
1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
_ => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
|
||||
};
|
||||
}
|
||||
|
||||
// Send the localized message to each player using their language
|
||||
controller.SendLocalizedMessage(moduleLocalizer, messageKey, currentMessageArgs.Cast<object>().ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,25 +590,20 @@ 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"]),
|
||||
2 => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
|
||||
_ => arg
|
||||
};
|
||||
}
|
||||
|
||||
// Print the localized message to the center of the screen for the player
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
|
||||
@@ -344,7 +620,7 @@ internal static class Helper
|
||||
public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, CCSPlayerController? target, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer)
|
||||
{
|
||||
if (localizer == null) return;
|
||||
|
||||
|
||||
var penaltySetting = penalty switch
|
||||
{
|
||||
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings,
|
||||
@@ -358,7 +634,7 @@ internal static class Helper
|
||||
var webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(webhookUrl)) return;
|
||||
|
||||
|
||||
const string defaultCommunityUrl = "<https://steamcommunity.com/profiles/0>";
|
||||
var callerCommunityUrl = caller != null ? $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
|
||||
var targetCommunityUrl = target != null ? $"<{new SteamID(target.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
|
||||
@@ -391,7 +667,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
|
||||
{
|
||||
@@ -430,11 +707,104 @@ internal static class Helper
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log or handle the exception
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to send discord webhook: {exception}", ex.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, string steamId, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer)
|
||||
{
|
||||
if (localizer == null) return;
|
||||
|
||||
var penaltySetting = penalty switch
|
||||
{
|
||||
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings,
|
||||
PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyMuteSettings,
|
||||
PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyGagSettings,
|
||||
PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltySilenceSettings,
|
||||
PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyWarnSettings,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
|
||||
};
|
||||
|
||||
var webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(webhookUrl)) return;
|
||||
const string defaultCommunityUrl = "<https://steamcommunity.com/profiles/0>";
|
||||
var callerCommunityUrl = caller != null ? $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
|
||||
var targetCommunityUrl = $"<{new SteamID(ulong.Parse(steamId)).ToCommunityUrl()}>";
|
||||
|
||||
var callerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
|
||||
var targetName = steamId;
|
||||
var targetSteamId = steamId;
|
||||
|
||||
var futureTime = Time.ActualDateTime().AddMinutes(duration);
|
||||
var futureUnixTimestamp = new DateTimeOffset(futureTime).ToUnixTimeSeconds();
|
||||
|
||||
string time;
|
||||
|
||||
if (penaltySetting.FirstOrDefault(s => s.Name.Equals("Time"))?.Value == "{relative}")
|
||||
time = duration != 0 ? $"<t:{futureUnixTimestamp}:R>" : localizer["sa_permanent"];
|
||||
else
|
||||
time = duration != 0 ? ConvertMinutesToTime(duration) : localizer["sa_permanent"];
|
||||
|
||||
string[] fieldNames = [
|
||||
localizer["sa_player"],
|
||||
localizer["sa_steamid"],
|
||||
localizer["sa_duration"],
|
||||
localizer["sa_reason"],
|
||||
localizer["sa_admin"]];
|
||||
string[] fieldValues =
|
||||
[
|
||||
$"[{targetName}]({targetCommunityUrl})", $"||{targetSteamId}||", time, reason,
|
||||
$"[{callerName}]({callerCommunityUrl})"
|
||||
];
|
||||
|
||||
bool[] inlineFlags = [true, true, true, false, false];
|
||||
var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"];
|
||||
var colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value;
|
||||
var colorHex = string.IsNullOrWhiteSpace(colorValue) ? "#FFFFFF" : colorValue.Trim();
|
||||
|
||||
var embed = new Embed
|
||||
{
|
||||
Color = DiscordManager.ColorFromHex(colorHex),
|
||||
Title = penalty switch
|
||||
{
|
||||
PenaltyType.Ban => localizer["sa_discord_penalty_ban"],
|
||||
PenaltyType.Mute => localizer["sa_discord_penalty_mute"],
|
||||
PenaltyType.Gag => localizer["sa_discord_penalty_gag"],
|
||||
PenaltyType.Silence => localizer["sa_discord_penalty_silence"],
|
||||
PenaltyType.Warn => localizer["sa_discord_penalty_warn"],
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
|
||||
},
|
||||
Description = $"{hostname}",
|
||||
ThumbnailUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ThumbnailUrl"))?.Value,
|
||||
ImageUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ImageUrl"))?.Value,
|
||||
Footer = new Footer
|
||||
{
|
||||
Text = penaltySetting.FirstOrDefault(s => s.Name.Equals("Footer"))?.Value
|
||||
},
|
||||
|
||||
Timestamp = Time.ActualDateTime().ToUniversalTime().ToString("o"),
|
||||
};
|
||||
|
||||
for (var i = 0; i < fieldNames.Length; i++)
|
||||
{
|
||||
embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]);
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await new DiscordManager(webhookUrl).SendEmbedAsync(embed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static string GenerateMessageDiscord(string message)
|
||||
{
|
||||
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
@@ -507,7 +877,7 @@ internal static class Helper
|
||||
if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || CS2_SimpleAdmin._localizer == null)
|
||||
return;
|
||||
|
||||
if (caller != null && caller.IsValid == false)
|
||||
if (caller != null && !caller.IsValid)
|
||||
caller = null;
|
||||
|
||||
var callerName = caller == null ? CS2_SimpleAdmin._localizer["sa_console"] : caller.PlayerName;
|
||||
@@ -519,32 +889,38 @@ internal static class Helper
|
||||
commandString]));
|
||||
}
|
||||
|
||||
public static IMenu? CreateMenu(string title)
|
||||
#pragma warning disable CS8604
|
||||
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null, Action<CCSPlayerController>? resetAction = null)
|
||||
{
|
||||
if (CS2_SimpleAdmin.MenuApi == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var menuType = CS2_SimpleAdmin.Instance.Config.MenuConfigs.MenuType.ToLower();
|
||||
|
||||
var menu = menuType switch
|
||||
{
|
||||
_ when menuType.Equals("selectable", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.NewMenu(title),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("dynamic", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.ButtonMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ButtonMenu, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("center", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.CenterMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.CenterMenu, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("chat", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.ChatMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ChatMenu, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("console", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.NewMenuForcetype(title, MenuType.ConsoleMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ConsoleMenu, backAction, resetAction),
|
||||
|
||||
_ => CS2_SimpleAdmin.MenuApi?.NewMenu(title)
|
||||
_ => CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction)
|
||||
};
|
||||
|
||||
return menu;
|
||||
}
|
||||
#pragma warning restore CS8604
|
||||
|
||||
internal static IPluginManager? GetPluginManager()
|
||||
{
|
||||
@@ -557,15 +933,18 @@ internal static class Helper
|
||||
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PluginInfo
|
||||
{
|
||||
internal static async Task CheckVersion(string localVersion, ILogger logger)
|
||||
{
|
||||
ShowAd(localVersion);
|
||||
const string versionUrl = "https://raw.githubusercontent.com/daffyyyy/CS2-SimpleAdmin/main/CS2-SimpleAdmin/VERSION";
|
||||
var client = CS2_SimpleAdmin.HttpClient;
|
||||
|
||||
client.Timeout = TimeSpan.FromSeconds(3);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.GetAsync(versionUrl);
|
||||
@@ -604,8 +983,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(" _______ ___ __ __ _______ ___ _______ _______ ______ __ __ ___ __ _ ");
|
||||
@@ -647,6 +1026,7 @@ public static class Time
|
||||
{
|
||||
public static DateTime ActualDateTime()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone;
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
@@ -730,3 +1110,48 @@ public static class WeaponHelper
|
||||
return filteredWeapons; // Return all relevant matches for the partial input
|
||||
}
|
||||
}
|
||||
|
||||
public static class IpHelper
|
||||
{
|
||||
public static uint IpToUint(string ipAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||
throw new ArgumentException("IP address cannot be null or empty.", nameof(ipAddress));
|
||||
|
||||
if (!System.Net.IPAddress.TryParse(ipAddress, out var ip))
|
||||
throw new FormatException($"Invalid IP address format: {ipAddress}");
|
||||
|
||||
// Ensure it's IPv4 (IPv6 will throw)
|
||||
if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
|
||||
throw new FormatException("Only IPv4 addresses are supported.");
|
||||
|
||||
byte[] bytes = ip.GetAddressBytes();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(bytes); // Ensure big-endian (network order)
|
||||
|
||||
return BitConverter.ToUInt32(bytes, 0);
|
||||
}
|
||||
|
||||
public static bool TryConvertIpToUint(string ipString, out uint ipUint)
|
||||
{
|
||||
ipUint = 0;
|
||||
if (string.IsNullOrWhiteSpace(ipString))
|
||||
return false;
|
||||
|
||||
if (!System.Net.IPAddress.TryParse(ipString, out var ipAddress))
|
||||
return false;
|
||||
|
||||
var bytes = ipAddress.GetAddressBytes();
|
||||
if (bytes.Length != 4)
|
||||
return false;
|
||||
|
||||
ipUint = IpToUint(ipString);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string UintToIp(uint ipAddress)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(ipAddress).Reverse().ToArray();
|
||||
return new System.Net.IPAddress(bytes).ToString();
|
||||
}
|
||||
}
|
||||
@@ -5,31 +5,36 @@ 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)
|
||||
{
|
||||
public async Task BanPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
|
||||
/// <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;
|
||||
|
||||
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)";
|
||||
|
||||
await connection.ExecuteAsync(sql, new
|
||||
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,
|
||||
@@ -37,30 +42,39 @@ internal class BanManager(Database.Database? database)
|
||||
created = now,
|
||||
serverid = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
return banId;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
|
||||
return null;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public async Task 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;
|
||||
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return;
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
await using MySqlConnection connection = await database.GetConnectionAsync();
|
||||
|
||||
var sql = "INSERT INTO `sa_bans` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " +
|
||||
"VALUES (@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid)";
|
||||
|
||||
await connection.ExecuteAsync(sql, new
|
||||
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,
|
||||
@@ -68,13 +82,26 @@ internal class BanManager(Database.Database? database)
|
||||
created = now,
|
||||
serverid = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
return banId;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
|
||||
return null;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
@@ -83,15 +110,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,
|
||||
@@ -103,336 +128,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
|
||||
{
|
||||
var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
|
||||
SELECT COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM sa_bans
|
||||
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
AND status = 'ACTIVE'
|
||||
AND (duration = 0 OR ends > @CurrentTime)
|
||||
), 0)
|
||||
+
|
||||
COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM sa_bans
|
||||
JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
|
||||
WHERE sa_bans.status = 'ACTIVE'
|
||||
AND sa_players_ips.address = @PlayerIP
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sa_bans
|
||||
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
AND status = 'ACTIVE'
|
||||
AND (duration = 0 OR ends > @CurrentTime)
|
||||
)
|
||||
), 0) AS TotalBanCount;
|
||||
""" : """
|
||||
SELECT COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM sa_bans
|
||||
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
AND status = 'ACTIVE'
|
||||
AND (duration = 0 OR ends > @CurrentTime)
|
||||
AND server_id = @ServerId
|
||||
), 0)
|
||||
+
|
||||
COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM sa_bans
|
||||
JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
|
||||
WHERE sa_bans.status = 'ACTIVE'
|
||||
AND sa_players_ips.address = @PlayerIP
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sa_bans
|
||||
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
AND status = 'ACTIVE'
|
||||
AND (duration = 0 OR ends > @CurrentTime)
|
||||
AND server_id = @ServerId
|
||||
)
|
||||
), 0) AS TotalBanCount;
|
||||
""";
|
||||
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
|
||||
var parameters = new
|
||||
{
|
||||
PlayerSteamID = player.SteamId.SteamId64.ToString(),
|
||||
PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 ||
|
||||
string.IsNullOrEmpty(player.IpAddress)
|
||||
? null
|
||||
: player.IpAddress,
|
||||
PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty,
|
||||
CurrentTime = currentTime,
|
||||
CS2_SimpleAdmin.ServerId
|
||||
};
|
||||
|
||||
banCount = await connection.ExecuteScalarAsync<int>(sql, parameters);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to check ban status for {PlayerName} ({ExceptionMessage})",
|
||||
player.Name, ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return banCount > 0;
|
||||
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 : [],
|
||||
ServerId = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
var valueTuples = bannedPlayers.ToList();
|
||||
var bannedSteamIds = valueTuples.Select(b => b.PlayerSteamID).ToHashSet();
|
||||
var bannedIps = valueTuples.Select(b => b.PlayerIP).ToHashSet();
|
||||
|
||||
foreach (var player in filteredPlayers.Where(player => bannedSteamIds.Contains(player.SteamID) ||
|
||||
(checkIpBans && bannedIps.Contains(player.IpAddress ?? ""))))
|
||||
{
|
||||
if (!player.UserId.HasValue) continue;
|
||||
|
||||
await Server.NextFrameAsync(() =>
|
||||
{
|
||||
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}");
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
725
CS2-SimpleAdmin/Managers/CacheManager.cs
Normal file
725
CS2-SimpleAdmin/Managers/CacheManager.cs
Normal file
@@ -0,0 +1,725 @@
|
||||
using System.Collections.Concurrent;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdmin.Models;
|
||||
using Dapper;
|
||||
using ZLinq;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
internal class CacheManager: IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
|
||||
private readonly ConcurrentDictionary<ulong, List<BanRecord>> _steamIdIndex = [];
|
||||
private readonly ConcurrentDictionary<uint, List<BanRecord>> _ipIndex = [];
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
|
||||
private HashSet<uint> _cachedIgnoredIps = [];
|
||||
|
||||
private DateTime _lastUpdateTime = DateTime.MinValue;
|
||||
private bool _isInitialized;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and builds the ban and IP cache from the database. Loads bans, player IP history, and config settings.
|
||||
/// </summary>
|
||||
/// <returns>Asynchronous task representing the initialization process.</returns>
|
||||
public async Task InitializeCacheAsync()
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (!CS2_SimpleAdmin.ServerLoaded) return;
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
Clear();
|
||||
_cachedIgnoredIps = CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps
|
||||
.AsValueEnumerable()
|
||||
.Select(IpHelper.IpToUint)
|
||||
.ToHashSet();
|
||||
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
List<BanRecord> bans;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
{
|
||||
bans = (await connection.QueryAsync<BanRecord>(
|
||||
"""
|
||||
SELECT
|
||||
id AS Id,
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
FROM sa_bans
|
||||
""")).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
bans = (await connection.QueryAsync<BanRecord>(
|
||||
"""
|
||||
SELECT
|
||||
id AS Id,
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
FROM sa_bans
|
||||
WHERE server_id = @serverId
|
||||
""", new {serverId = CS2_SimpleAdmin.ServerId})).ToList();
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
// Optimization: Load IP history and build cache in single pass
|
||||
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 steamid, address, used_at DESC");
|
||||
|
||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var currentSteamId = 0UL;
|
||||
var currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
|
||||
var latestIpTimestamps = new Dictionary<uint, DateTime>();
|
||||
|
||||
foreach (var record in ipHistory)
|
||||
{
|
||||
// When we encounter a new steamid, save the previous one
|
||||
if (record.steamid != currentSteamId && currentSteamId != 0)
|
||||
{
|
||||
_playerIpsCache[currentSteamId] = currentIpSet;
|
||||
currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
|
||||
latestIpTimestamps.Clear();
|
||||
}
|
||||
|
||||
currentSteamId = record.steamid;
|
||||
|
||||
// Only keep the latest timestamp for each IP
|
||||
if (!latestIpTimestamps.TryGetValue(record.address, out var existingTimestamp) ||
|
||||
record.used_at > existingTimestamp)
|
||||
{
|
||||
latestIpTimestamps[record.address] = record.used_at;
|
||||
currentIpSet.Add(new IpRecord(
|
||||
record.address,
|
||||
record.used_at,
|
||||
string.IsNullOrEmpty(record.name) ? unknownName : record.name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Don't forget the last steamid
|
||||
if (currentSteamId != 0)
|
||||
{
|
||||
_playerIpsCache[currentSteamId] = currentIpSet;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ban in bans.AsValueEnumerable())
|
||||
_banCache.TryAdd(ban.Id, ban);
|
||||
|
||||
RebuildIndexes();
|
||||
|
||||
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cached data and reinitializes the cache from the database.
|
||||
/// </summary>
|
||||
/// <returns>Asynchronous task representing the reinitialization process.</returns>
|
||||
public async Task ForceReInitializeCacheAsync()
|
||||
{
|
||||
_isInitialized = false;
|
||||
|
||||
_banCache.Clear();
|
||||
_playerIpsCache.Clear();
|
||||
_cachedIgnoredIps = [];
|
||||
_lastUpdateTime = DateTime.MinValue;
|
||||
|
||||
await InitializeCacheAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the in-memory cache with updated or new data from the database since the last update time.
|
||||
/// Also updates multi-account IP history if enabled.
|
||||
/// </summary>
|
||||
/// <returns>Asynchronous task representing the refresh operation.</returns>
|
||||
public async Task RefreshCacheAsync()
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (!_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
IEnumerable<BanRecord> updatedBans;
|
||||
|
||||
// Optimization: Only get IDs for comparison if we need to check for deletions
|
||||
// Most of the time bans are just added/updated, not deleted
|
||||
HashSet<int>? allIds = null;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
{
|
||||
updatedBans = (await connection.QueryAsync<BanRecord>(
|
||||
"""
|
||||
SELECT id AS Id,
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
||||
""",
|
||||
new { lastUpdate = _lastUpdateTime }
|
||||
));
|
||||
|
||||
// Optimization: Only fetch all IDs if there were updates
|
||||
var updatedList = updatedBans.ToList();
|
||||
if (updatedList.Count > 0)
|
||||
{
|
||||
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
||||
}
|
||||
updatedBans = updatedList;
|
||||
}
|
||||
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 }
|
||||
));
|
||||
|
||||
// Optimization: Only fetch all IDs if there were updates
|
||||
var updatedList = updatedBans.ToList();
|
||||
if (updatedList.Count > 0)
|
||||
{
|
||||
allIds = (await connection.QueryAsync<int>(
|
||||
"SELECT id FROM sa_bans WHERE server_id = @serverId",
|
||||
new { serverId = CS2_SimpleAdmin.ServerId }
|
||||
)).ToHashSet();
|
||||
}
|
||||
updatedBans = updatedList;
|
||||
}
|
||||
|
||||
// Optimization: Only process deletions if we have the full ID list
|
||||
if (allIds != null)
|
||||
{
|
||||
foreach (var id in _banCache.Keys)
|
||||
{
|
||||
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
|
||||
|
||||
if (ban.PlayerSteamId != null &&
|
||||
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
|
||||
{
|
||||
steamBans.RemoveAll(b => b.Id == id);
|
||||
if (steamBans.Count == 0)
|
||||
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
|
||||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
|
||||
{
|
||||
ipBans.RemoveAll(b => b.Id == id);
|
||||
if (ipBans.Count == 0)
|
||||
_ipIndex.TryRemove(ipUInt, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
|
||||
"SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300",
|
||||
new { lastUpdate = _lastUpdateTime }));
|
||||
|
||||
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
|
||||
{
|
||||
var ipSet = new HashSet<IpRecord>(
|
||||
group
|
||||
.GroupBy(x => x.address)
|
||||
.Select(g =>
|
||||
{
|
||||
var latest = g.MaxBy(x => x.used_at);
|
||||
return new IpRecord(
|
||||
g.Key,
|
||||
latest.used_at,
|
||||
!string.IsNullOrEmpty(latest.name)
|
||||
? latest.name
|
||||
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
|
||||
);
|
||||
}),
|
||||
new IpRecordComparer()
|
||||
);
|
||||
|
||||
_playerIpsCache.AddOrUpdate(
|
||||
group.Key,
|
||||
_ => ipSet,
|
||||
(_, existingSet) =>
|
||||
{
|
||||
foreach (var newEntry in ipSet)
|
||||
{
|
||||
existingSet.Remove(newEntry);
|
||||
existingSet.Add(newEntry);
|
||||
}
|
||||
|
||||
return existingSet;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache with new/modified bans
|
||||
var hasUpdates = false;
|
||||
foreach (var ban in updatedBans)
|
||||
{
|
||||
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
||||
hasUpdates = true;
|
||||
}
|
||||
|
||||
// Always rebuild indexes if there were any updates
|
||||
// This ensures status changes (ACTIVE -> UNBANNED) are reflected
|
||||
if (hasUpdates)
|
||||
{
|
||||
RebuildIndexes();
|
||||
}
|
||||
|
||||
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the internal indexes for fast lookup of active bans by Steam ID and IP address.
|
||||
/// Clears and repopulates both indexes based on the current in-memory ban cache.
|
||||
/// </summary>
|
||||
private void RebuildIndexes()
|
||||
{
|
||||
_steamIdIndex.Clear();
|
||||
_ipIndex.Clear();
|
||||
|
||||
// Optimization: Cache config value to avoid repeated property access
|
||||
var banType = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType;
|
||||
var checkIpBans = banType != 0;
|
||||
|
||||
// Optimization: Pre-filter only ACTIVE bans to avoid checking status in loop
|
||||
var activeBans = _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
|
||||
foreach (var ban in activeBans)
|
||||
{
|
||||
// Index by Steam ID
|
||||
if (ban.PlayerSteamId.HasValue)
|
||||
{
|
||||
var steamId = ban.PlayerSteamId.Value;
|
||||
if (!_steamIdIndex.TryGetValue(steamId, out var steamList))
|
||||
{
|
||||
steamList = new List<BanRecord>();
|
||||
_steamIdIndex[steamId] = steamList;
|
||||
}
|
||||
steamList.Add(ban);
|
||||
}
|
||||
|
||||
// Index by IP (only if IP bans are enabled)
|
||||
if (checkIpBans && !string.IsNullOrEmpty(ban.PlayerIp) &&
|
||||
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
|
||||
{
|
||||
if (!_ipIndex.TryGetValue(ipUInt, out var ipList))
|
||||
{
|
||||
ipList = new List<BanRecord>();
|
||||
_ipIndex[ipUInt] = ipList;
|
||||
}
|
||||
ipList.Add(ban);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all ban records currently stored in the cache.
|
||||
/// </summary>
|
||||
/// <returns>List of all <see cref="BanRecord"/> objects.</returns>
|
||||
public List<BanRecord> GetAllBans() => _banCache.Values.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves only active ban records from the cache.
|
||||
/// </summary>
|
||||
/// <returns>List of active <see cref="BanRecord"/> objects.</returns>
|
||||
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all ban records for a specific player by their Steam ID.
|
||||
/// </summary>
|
||||
/// <param name="steamId">64-bit Steam ID of the player.</param>
|
||||
/// <returns>List of <see cref="BanRecord"/> objects associated with the Steam ID.</returns>
|
||||
public List<BanRecord> GetPlayerBansBySteamId(ulong steamId) => _steamIdIndex.TryGetValue(steamId, out var bans) ? bans : [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets all known Steam accounts that have used the specified IP address.
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">The IP address to search for, in string format.</param>
|
||||
/// <returns>
|
||||
/// List of tuples containing the Steam ID, last used time, and player name for each matching entry.
|
||||
/// </returns>
|
||||
public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
|
||||
{
|
||||
var ipAsUint = IpHelper.IpToUint(ipAddress);
|
||||
var results = new List<(ulong, DateTime, string)>();
|
||||
|
||||
// Optimization: Direct lookup using HashSet.Contains instead of TryGetValue
|
||||
var searchRecord = new IpRecord(ipAsUint, default, null!);
|
||||
|
||||
foreach (var (steamId, ipSet) in _playerIpsCache)
|
||||
{
|
||||
// Optimization: Single pass through the set
|
||||
foreach (var entry in ipSet)
|
||||
{
|
||||
if (entry.Ip == ipAsUint)
|
||||
{
|
||||
results.Add((steamId, entry.UsedAt, entry.PlayerName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// public IEnumerable<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
|
||||
// {
|
||||
// var ipAsUint = IpHelper.IpToUint(ipAddress);
|
||||
//
|
||||
// return _playerIpsCache.SelectMany(kvp => kvp.Value
|
||||
// .Where(entry => entry.Ip == ipAsUint)
|
||||
// .Select(entry => (kvp.Key, entry.UsedAt, entry.PlayerName)));
|
||||
// }
|
||||
|
||||
private bool IsIpBanned(string ipAddress)
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
|
||||
var ipUInt = IpHelper.IpToUint(ipAddress);
|
||||
return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
|
||||
}
|
||||
|
||||
// public bool IsPlayerBanned(ulong? steamId, string? ipAddress)
|
||||
// {
|
||||
// if (steamId != null && _steamIdIndex.ContainsKey(steamId.Value))
|
||||
// return true;
|
||||
//
|
||||
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
// return false;
|
||||
//
|
||||
// if (string.IsNullOrEmpty(ipAddress) || !IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
|
||||
// return false;
|
||||
//
|
||||
// return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player is currently banned by Steam ID or IP address.
|
||||
/// If a partial ban record is found, updates it with the latest player information.
|
||||
/// </summary>
|
||||
/// <param name="playerName">Name of the player attempting to connect.</param>
|
||||
/// <param name="steamId">Optional 64-bit Steam ID of the player.</param>
|
||||
/// <param name="ipAddress">Optional IP address of the player.</param>
|
||||
/// <returns>True if the player is banned, otherwise false.</returns>
|
||||
public bool IsPlayerBanned(string playerName, ulong? steamId, string? ipAddress)
|
||||
{
|
||||
BanRecord? record;
|
||||
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
|
||||
{
|
||||
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (record != null)
|
||||
{
|
||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||
(!record.PlayerSteamId.HasValue))
|
||||
{
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress) ||
|
||||
!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) ||
|
||||
_cachedIgnoredIps.Contains(ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false;
|
||||
|
||||
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (record == null) return false;
|
||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||
(!record.PlayerSteamId.HasValue && steamId.HasValue))
|
||||
{
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress)
|
||||
// {
|
||||
// if (_steamIdIndex.ContainsKey(steamId))
|
||||
// return true;
|
||||
//
|
||||
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
// return false;
|
||||
//
|
||||
// if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
|
||||
// return false;
|
||||
//
|
||||
// // var now = Time.ActualDateTime();
|
||||
// var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
||||
// var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
//
|
||||
// if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
|
||||
// {
|
||||
// if (!_cachedIgnoredIps.Contains(ipAsUint))
|
||||
// {
|
||||
// ipData.Add(new IpRecord(
|
||||
// ipAsUint,
|
||||
// Time.ActualDateTime().AddSeconds(-2),
|
||||
// unknownName
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // foreach (var ipRecord in ipData)
|
||||
// // {
|
||||
// // // Skip if too old or in ignored list
|
||||
// // if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
|
||||
// // continue;
|
||||
// //
|
||||
// // // Check if IP is banned
|
||||
// // if (_ipIndex.ContainsKey(ipRecord.Ip))
|
||||
// // return true;
|
||||
// // }
|
||||
//
|
||||
// foreach (var ipRecord in ipData)
|
||||
// {
|
||||
// if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
|
||||
// continue;
|
||||
//
|
||||
// if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords)) continue;
|
||||
//
|
||||
// var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
// if (activeBan == null) continue;
|
||||
//
|
||||
// if (!string.IsNullOrEmpty(activeBan.PlayerName) && activeBan.PlayerSteamId.HasValue) return true;
|
||||
//
|
||||
// _ = Task.Run(() => UpdatePlayerData(
|
||||
// activeBan.PlayerName,
|
||||
// steamId,
|
||||
// ipAddress
|
||||
// ));
|
||||
//
|
||||
// if (string.IsNullOrEmpty(activeBan.PlayerName) && !string.IsNullOrEmpty(unknownName))
|
||||
// activeBan.PlayerName = unknownName;
|
||||
//
|
||||
// activeBan.PlayerSteamId ??= steamId;
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player or any IP previously associated with them is currently banned.
|
||||
/// Also updates ban records with missing player info if found.
|
||||
/// </summary>
|
||||
/// <param name="playerName">Current player name.</param>
|
||||
/// <param name="steamId">64-bit Steam ID of the player.</param>
|
||||
/// <param name="ipAddress">Current IP address of the player (optional).</param>
|
||||
/// <returns>True if the player or their known IPs are banned, otherwise false.</returns>
|
||||
public bool IsPlayerOrAnyIpBanned(string playerName, ulong steamId, string? ipAddress)
|
||||
{
|
||||
if (_steamIdIndex.TryGetValue(steamId, out var steamBans))
|
||||
{
|
||||
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp))
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
return false;
|
||||
|
||||
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
|
||||
return false;
|
||||
|
||||
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
|
||||
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
|
||||
{
|
||||
if (!_cachedIgnoredIps.Contains(ipAsUint))
|
||||
{
|
||||
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ipRecord in ipData)
|
||||
{
|
||||
if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
|
||||
continue;
|
||||
|
||||
if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords))
|
||||
continue;
|
||||
|
||||
var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan == null)
|
||||
continue;
|
||||
|
||||
if (string.IsNullOrEmpty(activeBan.PlayerName))
|
||||
activeBan.PlayerName = unknownName;
|
||||
|
||||
activeBan.PlayerSteamId ??= steamId;
|
||||
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given IP address is known (previously recorded) for the specified Steam ID.
|
||||
/// </summary>
|
||||
/// <param name="steamId">64-bit Steam ID of the player.</param>
|
||||
/// <param name="ipAddress">IP address to check.</param>
|
||||
/// <returns>True if the IP is recorded for the player, otherwise false.</returns>
|
||||
public bool HasIpForPlayer(ulong steamId, string ipAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||
return false;
|
||||
|
||||
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUint))
|
||||
return false;
|
||||
|
||||
return _playerIpsCache.TryGetValue(steamId, out var ipData) &&
|
||||
ipData.Contains(new IpRecord(ipUint, default, null!));
|
||||
}
|
||||
|
||||
// public bool HasIpForPlayer(ulong steamId, string ipAddress)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(ipAddress))
|
||||
// return false;
|
||||
//
|
||||
// return _playerIpsCache.TryGetValue(steamId, out var ipData)
|
||||
// && ipData.Any(x => x.Ip == IpHelper.IpToUint(ipAddress));
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Updates existing active ban records in the database with the latest known player name and IP address.
|
||||
/// Also updates in-memory cache to reflect these changes.
|
||||
/// </summary>
|
||||
/// <param name="playerName">Current player name.</param>
|
||||
/// <param name="steamId">Optional Steam ID of the player.</param>
|
||||
/// <param name="ipAddress">Optional IP address of the player.</param>
|
||||
/// <returns>Asynchronous task representing the update operation.</returns>
|
||||
private async Task UpdatePlayerData(string? playerName, ulong? steamId, string? ipAddress)
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
return;
|
||||
|
||||
var baseSql = """
|
||||
UPDATE sa_bans
|
||||
SET
|
||||
player_ip = COALESCE(player_ip, @PlayerIP),
|
||||
player_name = COALESCE(player_name, @PlayerName)
|
||||
WHERE
|
||||
(player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
AND status = 'ACTIVE'
|
||||
AND (duration = 0 OR ends > @CurrentTime)
|
||||
""";
|
||||
|
||||
if (!CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
{
|
||||
baseSql += " AND server_id = @ServerId;";
|
||||
}
|
||||
|
||||
var parameters = new
|
||||
{
|
||||
PlayerSteamID = steamId,
|
||||
PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0
|
||||
|| string.IsNullOrEmpty(ipAddress)
|
||||
|| CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(ipAddress)
|
||||
? null
|
||||
: ipAddress,
|
||||
PlayerName = string.IsNullOrEmpty(playerName) ? string.Empty : playerName,
|
||||
CurrentTime = Time.ActualDateTime(),
|
||||
CS2_SimpleAdmin.ServerId
|
||||
};
|
||||
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
await connection.ExecuteAsync(baseSql, parameters);
|
||||
|
||||
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
|
||||
{
|
||||
foreach (var rec in steamRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rec.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
|
||||
rec.PlayerIp = ipAddress;
|
||||
|
||||
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
|
||||
rec.PlayerName = playerName;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ipAddress) && IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)
|
||||
&& _ipIndex.TryGetValue(ipUInt, out var ipRecords))
|
||||
{
|
||||
foreach (var rec in ipRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
|
||||
{
|
||||
if (!rec.PlayerSteamId.HasValue && steamId.HasValue)
|
||||
rec.PlayerSteamId = steamId;
|
||||
|
||||
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
|
||||
rec.PlayerName = playerName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
_steamIdIndex.Clear();
|
||||
_ipIndex.Clear();
|
||||
|
||||
_banCache.Clear();
|
||||
_playerIpsCache.Clear();
|
||||
_cachedIgnoredIps.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears and disposes of all cached data and marks the object as disposed.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
Clear();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class IpRecordComparer : IEqualityComparer<IpRecord>
|
||||
{
|
||||
public bool Equals(IpRecord x, IpRecord y)
|
||||
=> x.Ip == y.Ip;
|
||||
|
||||
public int GetHashCode(IpRecord obj)
|
||||
=> obj.Ip.GetHashCode();
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
public async Task MutePlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
|
||||
/// <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;
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
@@ -22,16 +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)";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddMuteQuery(true);
|
||||
|
||||
await connection.ExecuteAsync(sql, new
|
||||
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,
|
||||
@@ -40,19 +48,28 @@ internal class MuteManager(Database.Database? database)
|
||||
type = muteType,
|
||||
serverid = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
return muteId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex.Message);
|
||||
};
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task 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;
|
||||
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return;
|
||||
|
||||
if (databaseProvider == null) return null;
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
@@ -66,14 +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)";
|
||||
|
||||
await connection.ExecuteAsync(sql, new
|
||||
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,
|
||||
@@ -82,13 +98,23 @@ internal class MuteManager(Database.Database? database)
|
||||
type = muteType,
|
||||
serverid = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
return muteId;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch { };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player with the given SteamID currently has any active mutes.
|
||||
/// </summary>
|
||||
/// <param name="steamId">SteamID64 of the player to check.</param>
|
||||
/// <returns>List of active mute records; empty list if none or on error.</returns>
|
||||
public async Task<List<dynamic>> IsPlayerMuted(string steamId)
|
||||
{
|
||||
if (database == null) return [];
|
||||
if (databaseProvider == null) return [];
|
||||
|
||||
if (string.IsNullOrEmpty(steamId))
|
||||
{
|
||||
@@ -102,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;
|
||||
@@ -130,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
|
||||
});
|
||||
|
||||
@@ -170,25 +174,28 @@ internal class MuteManager(Database.Database? database)
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckOnlineModeMutes(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players)
|
||||
/// <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
|
||||
{
|
||||
var batchSize = 10;
|
||||
await using var connection = await database.GetConnectionAsync();
|
||||
const int batchSize = 20;
|
||||
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)
|
||||
{
|
||||
var batch = players.Skip(i).Take(batchSize);
|
||||
var parametersList = new List<object>();
|
||||
|
||||
foreach (var (_, steamId, _, _) in batch)
|
||||
foreach (var (steamId, _, _) in batch)
|
||||
{
|
||||
parametersList.Add(new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
@@ -196,12 +203,9 @@ 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)
|
||||
foreach (var (steamId, _, slot) in players)
|
||||
{
|
||||
var muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
|
||||
@@ -216,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)
|
||||
{
|
||||
@@ -227,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",
|
||||
@@ -236,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;
|
||||
@@ -264,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 });
|
||||
}
|
||||
}
|
||||
@@ -288,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,17 +2,20 @@
|
||||
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>>();
|
||||
public static readonly ConcurrentDictionary<SteamID, DateTime?> AdminCache = new();
|
||||
// public static readonly ConcurrentDictionary<SteamID, DateTime?> AdminCache = new();
|
||||
public static readonly ConcurrentDictionary<SteamID, (DateTime? ExpirationTime, List<string> Flags)> AdminCache = new();
|
||||
|
||||
/*
|
||||
public async Task<List<(List<string>, int)>> GetAdminFlags(string steamId)
|
||||
@@ -57,60 +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}");
|
||||
}
|
||||
*/
|
||||
string playerName = g.Key.playerName as string ?? string.Empty;
|
||||
|
||||
List<(string, string, List<string>, int, DateTime?)> filteredFlagsWithImmunity = [];
|
||||
// tutaj zakładamy, że Dapper zwraca już string (nie dynamic)
|
||||
var flags = g.Select(r => r.flag as string ?? string.Empty)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
// Add the grouped players to the list
|
||||
filteredFlagsWithImmunity.AddRange(groupedPlayers);
|
||||
return (steamId, playerName, flags, immunity, ends);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return filteredFlagsWithImmunity;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
|
||||
return [];
|
||||
}
|
||||
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()
|
||||
{
|
||||
@@ -176,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;
|
||||
@@ -224,16 +228,19 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load groups from database! {exception}", ex.Message);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -247,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);
|
||||
}
|
||||
@@ -312,89 +325,187 @@ 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 _)) // Filter invalid SteamID
|
||||
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
|
||||
.ToList();
|
||||
|
||||
/*
|
||||
foreach (var player in allPlayers)
|
||||
// foreach (var player in allPlayers)
|
||||
// {
|
||||
// var (steamId, name, flags, immunity, ends) = player;
|
||||
//
|
||||
// Console.WriteLine($"Player SteamID: {steamId}");
|
||||
// Console.WriteLine($"Player Name: {name}");
|
||||
// Console.WriteLine($"Flags: {string.Join(", ", flags)}");
|
||||
// Console.WriteLine($"Immunity: {immunity}");
|
||||
// Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}");
|
||||
// Console.WriteLine();
|
||||
// }
|
||||
|
||||
var jsonData = validPlayers
|
||||
.GroupBy(player => player.name) // Group by player name
|
||||
.ToDictionary(
|
||||
group => group.Key, // Use the player name as key
|
||||
object (group) =>
|
||||
{
|
||||
// Consolidate data for players with same name
|
||||
var consolidatedData = group.Aggregate(
|
||||
new
|
||||
{
|
||||
identity = string.Empty,
|
||||
immunity = 0,
|
||||
flags = new List<string>(),
|
||||
groups = new List<string>()
|
||||
},
|
||||
(acc, player) =>
|
||||
{
|
||||
// Merge identities
|
||||
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
|
||||
{
|
||||
acc = acc with { identity = player.identity.ToString() };
|
||||
}
|
||||
|
||||
// Combine immunities by maximum value
|
||||
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
|
||||
|
||||
// Combine flags and groups
|
||||
acc = acc with
|
||||
{
|
||||
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
|
||||
groups = acc.groups.Concat(player.flags.Where(flag => flag.StartsWith($"#"))).Distinct().ToList()
|
||||
};
|
||||
|
||||
return acc;
|
||||
});
|
||||
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
var keysToRemove = new List<SteamID>();
|
||||
|
||||
foreach (var steamId in AdminCache.Keys.ToList())
|
||||
{
|
||||
var data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data != null)
|
||||
{
|
||||
var flagsArray = AdminCache[steamId].Flags.ToArray();
|
||||
AdminManager.RemovePlayerPermissions(steamId, flagsArray);
|
||||
AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray);
|
||||
}
|
||||
|
||||
keysToRemove.Add(steamId);
|
||||
}
|
||||
|
||||
foreach (var steamId in keysToRemove)
|
||||
{
|
||||
if (!AdminCache.TryRemove(steamId, out _)) continue;
|
||||
|
||||
var data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data == null) continue;
|
||||
if (data.Flags.Count != 0 && data.Groups.Count != 0) continue;
|
||||
|
||||
AdminManager.ClearPlayerPermissions(steamId);
|
||||
AdminManager.RemovePlayerAdminData(steamId);
|
||||
}
|
||||
|
||||
foreach (var player in group)
|
||||
{
|
||||
if (SteamID.TryParse(player.identity.ToString(), out var steamId) && steamId != null)
|
||||
{
|
||||
AdminCache.TryAdd(steamId, (player.ends, player.flags));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Server.NextFrameAsync(() =>
|
||||
// {
|
||||
// for (var index = 0; index < AdminCache.Keys.ToList().Count; index++)
|
||||
// {
|
||||
// var steamId = AdminCache.Keys.ToList()[index];
|
||||
//
|
||||
// var data = AdminManager.GetPlayerAdminData(steamId);
|
||||
// if (data != null)
|
||||
// {
|
||||
// AdminManager.RemovePlayerPermissions(steamId, AdminCache[steamId].Flags.ToArray());
|
||||
// AdminManager.RemovePlayerFromGroup(steamId, true, AdminCache[steamId].Flags.ToArray());
|
||||
// }
|
||||
//
|
||||
// if (!AdminCache.TryRemove(steamId, out _)) continue;
|
||||
//
|
||||
// if (data == null) continue;
|
||||
// if (data.Flags.ToList().Count != 0 && data.Groups.ToList().Count != 0)
|
||||
// continue;
|
||||
//
|
||||
// AdminManager.ClearPlayerPermissions(steamId);
|
||||
// AdminManager.RemovePlayerAdminData(steamId);
|
||||
// }
|
||||
//
|
||||
// foreach (var player in group)
|
||||
// {
|
||||
// SteamID.TryParse(player.identity, out var steamId);
|
||||
// if (steamId == null) continue;
|
||||
// AdminCache.TryAdd(steamId, (player.ends, player.flags));
|
||||
// }
|
||||
// });
|
||||
|
||||
return consolidatedData;
|
||||
});
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
var (steamId, name, flags, immunity, ends) = player;
|
||||
|
||||
// Print or process each item
|
||||
Console.WriteLine($"Player SteamID: {steamId}");
|
||||
Console.WriteLine($"Player Name: {name}");
|
||||
Console.WriteLine($"Flags: {string.Join(", ", flags)}");
|
||||
Console.WriteLine($"Immunity: {immunity}");
|
||||
Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}");
|
||||
Console.WriteLine(); // New line for better readability
|
||||
}
|
||||
*/
|
||||
|
||||
var jsonData = validPlayers
|
||||
.Select(player =>
|
||||
{
|
||||
SteamID.TryParse(player.identity, out var steamId);
|
||||
|
||||
// Update cache if SteamID is valid and not already cached
|
||||
if (steamId != null && !AdminCache.ContainsKey(steamId))
|
||||
{
|
||||
AdminCache.TryAdd(steamId, player.ends);
|
||||
}
|
||||
|
||||
// Create an anonymous object with player data
|
||||
return new
|
||||
{
|
||||
playerName = player.name,
|
||||
playerData = new
|
||||
{
|
||||
player.identity,
|
||||
player.immunity,
|
||||
flags = player.flags.Where(flag => flag.StartsWith("@")).ToList(),
|
||||
groups = player.flags.Where(flag => flag.StartsWith("#")).ToList()
|
||||
}
|
||||
};
|
||||
})
|
||||
.ToDictionary(item => item.playerName, item => (object)item.playerData);
|
||||
|
||||
var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented);
|
||||
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);
|
||||
|
||||
//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;
|
||||
|
||||
@@ -408,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,
|
||||
@@ -427,25 +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 });
|
||||
|
||||
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,
|
||||
@@ -453,7 +563,7 @@ public class PermissionManager(Database.Database? database)
|
||||
});
|
||||
}
|
||||
|
||||
await Server.NextFrameAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
|
||||
});
|
||||
@@ -464,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,
|
||||
@@ -485,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
|
||||
{
|
||||
@@ -495,12 +610,9 @@ 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.NextFrameAsync(() =>
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
|
||||
});
|
||||
@@ -508,20 +620,24 @@ public class PermissionManager(Database.Database? database)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
|
||||
CS2_SimpleAdmin._logger?.LogError("Problem with loading admins: {exception}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -530,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)
|
||||
|
||||
@@ -7,327 +7,414 @@ using CounterStrikeSharp.API.ValveConstants.Protobuf;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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;
|
||||
|
||||
public void LoadPlayerData(CCSPlayerController player)
|
||||
/// <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")
|
||||
|| !player.UserId.HasValue)
|
||||
return;
|
||||
|
||||
var ipAddress = player.IpAddress?.Split(":")[0];
|
||||
|
||||
CS2_SimpleAdmin.PlayersInfo[player.UserId.Value] =
|
||||
new PlayerInfo(player.UserId.Value, player.Slot, new SteamID(player.SteamID), player.PlayerName, ipAddress);
|
||||
|
||||
var userId = player.UserId.Value;
|
||||
|
||||
// Check if the player's IP or SteamID is in the bannedPlayers list
|
||||
if (_config.OtherSettings.BanType > 0 && CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress) ||
|
||||
CS2_SimpleAdmin.BannedPlayers.Contains(player.SteamID.ToString()))
|
||||
if (!player.UserId.HasValue)
|
||||
{
|
||||
// Kick the player if banned
|
||||
if (player.UserId.HasValue)
|
||||
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Database == null) return;
|
||||
var userId = player.UserId.Value;
|
||||
var slot = player.Slot;
|
||||
var steamId = player.SteamID;
|
||||
var playerName = !string.IsNullOrEmpty(player.PlayerName)
|
||||
? player.PlayerName
|
||||
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var ipAddress = player.IpAddress?.Split(":")[0];
|
||||
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null || CS2_SimpleAdmin.Instance.CacheManager == null) return;
|
||||
|
||||
// Perform asynchronous database operations within a single method
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
|
||||
const string selectQuery = "SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
|
||||
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
|
||||
await _loadPlayerSemaphore.WaitAsync();
|
||||
|
||||
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
|
||||
IPAddress = ipAddress
|
||||
});
|
||||
|
||||
if (recordExists > 0)
|
||||
{
|
||||
const string updateQuery = """
|
||||
UPDATE `sa_players_ips`
|
||||
SET used_at = CURRENT_TIMESTAMP
|
||||
WHERE steamid = @SteamID AND address = @IPAddress;
|
||||
""";
|
||||
await connection.ExecuteAsync(updateQuery, new
|
||||
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
|
||||
IPAddress = ipAddress
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const string insertQuery = """
|
||||
INSERT INTO `sa_players_ips` (steamid, address, used_at)
|
||||
VALUES (@SteamID, @IPAddress, CURRENT_TIMESTAMP);
|
||||
""";
|
||||
await connection.ExecuteAsync(insertQuery, new
|
||||
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
||||
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
|
||||
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId,
|
||||
ipAddress)
|
||||
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress)
|
||||
};
|
||||
|
||||
// CS2_SimpleAdmin._logger?.LogInformation($"Player {playerName} ({steamId} - {ipAddress}) is banned? {isBanned.ToString()}");
|
||||
|
||||
if (isBanned)
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64,
|
||||
IPAddress = ipAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(
|
||||
$"Unable to save ip address for {CS2_SimpleAdmin.PlayersInfo[userId].Name} ({ipAddress}) {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check if the player is banned
|
||||
var isBanned = await CS2_SimpleAdmin.Instance.BanManager.IsPlayerBanned(CS2_SimpleAdmin.PlayersInfo[userId]);
|
||||
|
||||
if (isBanned)
|
||||
{
|
||||
// Add player's IP and SteamID to bannedPlayers list if not already present
|
||||
if (_config.OtherSettings.BanType > 0 && ipAddress != null &&
|
||||
!CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress))
|
||||
{
|
||||
CS2_SimpleAdmin.BannedPlayers.Add(ipAddress);
|
||||
}
|
||||
|
||||
if (!CS2_SimpleAdmin.BannedPlayers.Contains(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()))
|
||||
{
|
||||
CS2_SimpleAdmin.BannedPlayers.Add(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString());
|
||||
}
|
||||
|
||||
// Kick the player if banned
|
||||
await Server.NextFrameAsync(() =>
|
||||
{
|
||||
var victim = Utilities.GetPlayerFromUserid(userId);
|
||||
|
||||
if (victim?.UserId == null) return;
|
||||
|
||||
if (CS2_SimpleAdmin.UnlockedCommands)
|
||||
Server.ExecuteCommand($"banid 1 {userId}");
|
||||
|
||||
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var warns = await CS2_SimpleAdmin.Instance.WarnManager.GetPlayerWarns(CS2_SimpleAdmin.PlayersInfo[userId], false);
|
||||
var (totalMutes, totalGags, totalSilences) =
|
||||
await CS2_SimpleAdmin.Instance.MuteManager.GetPlayerMutes(CS2_SimpleAdmin.PlayersInfo[userId]);
|
||||
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalBans =
|
||||
await CS2_SimpleAdmin.Instance.BanManager.GetPlayerBans(CS2_SimpleAdmin.PlayersInfo[userId]);
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes = totalMutes;
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalGags = totalGags;
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences = totalSilences;
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns = warns.Count;
|
||||
|
||||
// Check if the player is muted
|
||||
var activeMutes = await CS2_SimpleAdmin.Instance.MuteManager.IsPlayerMuted(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString());
|
||||
|
||||
if (activeMutes.Count > 0)
|
||||
{
|
||||
foreach (var mute in activeMutes)
|
||||
{
|
||||
string muteType = mute.type;
|
||||
DateTime ends = mute.ends;
|
||||
int duration = mute.duration;
|
||||
switch (muteType)
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
// 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(() =>
|
||||
// CS2_SimpleAdmin._logger?.LogInformation($"Kicking {playerName}");
|
||||
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullConnect)
|
||||
{
|
||||
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
||||
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
||||
|
||||
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();
|
||||
|
||||
// Eliminates the need for SELECT COUNT and duplicate UPDATE queries
|
||||
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
|
||||
var ipUint = IpHelper.IpToUint(ipAddress);
|
||||
|
||||
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE pattern
|
||||
const string upsertQuery = """
|
||||
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
|
||||
await connection.ExecuteAsync(upsertQuery, new
|
||||
{
|
||||
SteamID = steamId64,
|
||||
playerName,
|
||||
IPAddress = ipUint
|
||||
});
|
||||
|
||||
// // Cache will be updated on next refresh cycle
|
||||
// if (!CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(steamId, ipAddress))
|
||||
// {
|
||||
// // IP association will be reflected after cache refresh
|
||||
// }
|
||||
}
|
||||
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)
|
||||
{
|
||||
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(() =>
|
||||
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(() =>
|
||||
{
|
||||
player.VoiceFlags = VoiceFlags.Muted;
|
||||
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})"))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// 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.NextFrameAsync(() =>
|
||||
catch (Exception ex)
|
||||
{
|
||||
foreach (var admin in Helper.GetValidPlayers()
|
||||
.Where(p => (AdminManager.PlayerHasPermissions(p, "@css/kick") ||
|
||||
AdminManager.PlayerHasPermissions(p, "@css/ban")) &&
|
||||
p.Connected == PlayerConnectedState.PlayerConnected && !CS2_SimpleAdmin.AdminDisabledJoinComms.Contains(p.SteamID)))
|
||||
{
|
||||
if (CS2_SimpleAdmin._localizer != null && admin != player)
|
||||
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_penalty_info",
|
||||
player.PlayerName,
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalBans,
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalGags,
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes,
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences,
|
||||
CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns
|
||||
);
|
||||
}
|
||||
});
|
||||
CS2_SimpleAdmin._logger?.LogError("Error processing player connection: {exception}",
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
finally
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Error processing player connection: {ex}");
|
||||
_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, () =>
|
||||
{
|
||||
if (CS2_SimpleAdmin.GravityPlayers.Count <= 0) return;
|
||||
|
||||
foreach (var value in CS2_SimpleAdmin.GravityPlayers)
|
||||
{
|
||||
if (value.Key is not
|
||||
{ IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true })
|
||||
continue;
|
||||
|
||||
value.Key.SetGravity(value.Value);
|
||||
}
|
||||
}, 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 players = Helper.GetValidPlayers();
|
||||
var onlinePlayers = new List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)>();
|
||||
// var onlinePlayers = players
|
||||
// .Where(player => player.IpAddress != null)
|
||||
// .Select(player => (player.IpAddress, player.SteamID, player.UserId, player.Slot))
|
||||
// .ToList();
|
||||
// Optimization: Get players once and avoid allocating anonymous types
|
||||
var validPlayers = Helper.GetValidPlayers();
|
||||
if (validPlayers.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var player in players)
|
||||
// Use ValueTuple instead of anonymous type - better performance and less allocations
|
||||
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
||||
foreach (var p in validPlayers)
|
||||
{
|
||||
if (player.IpAddress != null)
|
||||
onlinePlayers.Add((player.IpAddress, player.SteamID, player.UserId, player.Slot));
|
||||
tempPlayers.Add((p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot));
|
||||
}
|
||||
|
||||
|
||||
var pluginInstance = CS2_SimpleAdmin.Instance;
|
||||
var config = _config.OtherSettings; // Cache config access
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Run all expire tasks in parallel
|
||||
var expireTasks = new[]
|
||||
{
|
||||
pluginInstance.BanManager.ExpireOldBans(),
|
||||
pluginInstance.MuteManager.ExpireOldMutes(),
|
||||
pluginInstance.WarnManager.ExpireOldWarns(),
|
||||
pluginInstance.CacheManager?.RefreshCacheAsync() ?? Task.CompletedTask,
|
||||
pluginInstance.PermissionManager.DeleteOldAdmins()
|
||||
};
|
||||
|
||||
await Task.WhenAll(expireTasks);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Error processing players timer tasks: {ex.Message}");
|
||||
|
||||
if (ex is AggregateException aggregate)
|
||||
{
|
||||
foreach (var inner in aggregate.InnerExceptions)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Inner exception: {inner.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginInstance.CacheManager == null)
|
||||
return;
|
||||
|
||||
// Optimization: Cache ban type and multi-account check to avoid repeated config access
|
||||
var banType = config.BanType;
|
||||
var checkMultiAccounts = config.CheckMultiAccountsByIp;
|
||||
|
||||
var bannedPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>();
|
||||
|
||||
// Manual loop instead of LINQ - better performance
|
||||
foreach (var player in tempPlayers)
|
||||
{
|
||||
var playerName = player.PlayerName;
|
||||
var steamId = player.SteamID;
|
||||
var ip = player.IpAddress?.Split(':')[0];
|
||||
|
||||
bool isBanned = banType switch
|
||||
{
|
||||
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
||||
_ => checkMultiAccounts
|
||||
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
|
||||
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
|
||||
};
|
||||
|
||||
if (isBanned)
|
||||
{
|
||||
bannedPlayers.Add(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (bannedPlayers.Count > 0)
|
||||
{
|
||||
foreach (var player in bannedPlayers)
|
||||
{
|
||||
if (!player.UserId.HasValue) continue;
|
||||
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
|
||||
}
|
||||
}
|
||||
|
||||
if (config.TimeMode == 0)
|
||||
{
|
||||
// Optimization: Manual projection instead of LINQ
|
||||
var onlinePlayers = new List<(ulong, int?, int)>(tempPlayers.Count);
|
||||
foreach (var player in tempPlayers)
|
||||
{
|
||||
onlinePlayers.Add((player.SteamID, player.UserId, player.Slot));
|
||||
}
|
||||
|
||||
if (onlinePlayers.Count > 0)
|
||||
{
|
||||
await pluginInstance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var expireTasks = new[]
|
||||
// Optimization: Process penalties without LINQ allocations
|
||||
var players = Helper.GetValidPlayers();
|
||||
foreach (var player in players)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.BanManager.ExpireOldBans(),
|
||||
CS2_SimpleAdmin.Instance.MuteManager.ExpireOldMutes(),
|
||||
CS2_SimpleAdmin.Instance.WarnManager.ExpireOldWarns(),
|
||||
CS2_SimpleAdmin.Instance.PermissionManager.DeleteOldAdmins()
|
||||
};
|
||||
if (!PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
|
||||
continue;
|
||||
|
||||
Task.WhenAll(expireTasks).ContinueWith(t =>
|
||||
{
|
||||
if (t is not { IsFaulted: true, Exception: not null }) return;
|
||||
|
||||
foreach (var ex in t.Exception.InnerExceptions)
|
||||
var isMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _);
|
||||
var isSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _);
|
||||
|
||||
// Only reset voice flags if not muted or silenced
|
||||
if (!isMuted && !isSilenced)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Error expiring penalties: {ex.Message}");
|
||||
player.VoiceFlags = VoiceFlags.Normal;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PlayerPenaltyManager.RemoveExpiredPenalties();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}");
|
||||
CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}");
|
||||
}
|
||||
|
||||
CS2_SimpleAdmin.BannedPlayers.Clear();
|
||||
|
||||
if (onlinePlayers.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await CS2_SimpleAdmin.Instance.BanManager.CheckOnlinePlayers(onlinePlayers);
|
||||
|
||||
if (_config.OtherSettings.TimeMode == 0)
|
||||
{
|
||||
await CS2_SimpleAdmin.Instance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
|
||||
}
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
if (t is not { IsFaulted: true, Exception: not null }) return;
|
||||
|
||||
foreach (var ex in t.Exception.InnerExceptions)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Unexpected error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (onlinePlayers.Count <= 0) return;
|
||||
|
||||
{
|
||||
try
|
||||
{
|
||||
var penalizedSlots = players
|
||||
.Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
|
||||
.Select(player => new
|
||||
{
|
||||
Player = player,
|
||||
IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _),
|
||||
IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _),
|
||||
IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _)
|
||||
});
|
||||
|
||||
foreach (var entry in penalizedSlots)
|
||||
{
|
||||
// If the player is not muted or silenced, set voice flags to normal
|
||||
if (!entry.IsMuted && !entry.IsSilenced)
|
||||
{
|
||||
entry.Player.VoiceFlags = VoiceFlags.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerPenaltyManager.RemoveExpiredPenalties();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT);
|
||||
}, 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,54 @@ public class ServerManager
|
||||
{
|
||||
private int _getIpTryCount;
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the server setting <c>sv_hibernate_when_empty</c> is enabled.
|
||||
/// Logs an error if this setting is true, since it prevents the plugin from working properly.
|
||||
/// </summary>
|
||||
public static void CheckHibernationStatus()
|
||||
{
|
||||
var convar = ConVar.Find("sv_hibernate_when_empty");
|
||||
if (convar == null || !convar.GetPrimitiveValue<bool>())
|
||||
return;
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogError("Detected setting \"sv_hibernate_when_empty true\", set false to make plugin work properly");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates the asynchronous process to load server data such as IP address, port, hostname, and RCON password.
|
||||
/// Handles retry attempts if IP address is not immediately available.
|
||||
/// Updates or inserts the server record in the database accordingly.
|
||||
/// After loading, triggers admin reload and cache initialization.
|
||||
/// Also optionally sends plugin usage metrics if enabled in configuration.
|
||||
/// </summary>
|
||||
public void LoadServerData()
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddTimer(2.0f, () =>
|
||||
{
|
||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.ServerId != null || CS2_SimpleAdmin.Database == null) return;
|
||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
|
||||
var ipAddress = ConVar.Find("ip")?.StringValue;
|
||||
// Optimization: Get server IP once and reuse
|
||||
var serverIp = Helper.GetServerIp();
|
||||
var isInvalidIp = string.IsNullOrEmpty(serverIp) || serverIp.StartsWith("0.0.0");
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
|
||||
// Check if we've exceeded retry limit with invalid IP
|
||||
if (_getIpTryCount > 32 && isInvalidIp)
|
||||
{
|
||||
ipAddress = Helper.GetServerIp();
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load server data - can't fetch ip address!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization: Cache ConVar lookups
|
||||
var ipConVar = ConVar.Find("ip");
|
||||
var ipAddress = ipConVar?.StringValue;
|
||||
|
||||
// Use Helper IP if ConVar IP is invalid
|
||||
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
|
||||
{
|
||||
if (_getIpTryCount < 12)
|
||||
ipAddress = serverIp;
|
||||
|
||||
// Retry if still invalid and under retry limit
|
||||
if (_getIpTryCount <= 32 && isInvalidIp)
|
||||
{
|
||||
_getIpTryCount++;
|
||||
LoadServerData();
|
||||
@@ -32,44 +64,53 @@ public class ServerManager
|
||||
}
|
||||
}
|
||||
|
||||
var address = $"{ipAddress}:{ConVar.Find("hostport")?.GetPrimitiveValue<int>()}";
|
||||
var hostname = ConVar.Find("hostname")!.StringValue;
|
||||
CS2_SimpleAdmin.IpAddress = address;
|
||||
// Optimization: Cache remaining ConVar lookups
|
||||
var hostportConVar = ConVar.Find("hostport");
|
||||
var hostnameConVar = ConVar.Find("hostname");
|
||||
var rconPasswordConVar = ConVar.Find("rcon_password");
|
||||
|
||||
var address = $"{ipAddress}:{hostportConVar?.GetPrimitiveValue<int>()}";
|
||||
var hostname = hostnameConVar?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var rconPassword = rconPasswordConVar?.StringValue ?? "";
|
||||
CS2_SimpleAdmin.IpAddress = address;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
|
||||
var addressExists = await connection.ExecuteScalarAsync<bool>(
|
||||
"SELECT COUNT(*) FROM sa_servers WHERE address = @address",
|
||||
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 });
|
||||
|
||||
if (!addressExists)
|
||||
if (serverId == null)
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"INSERT INTO sa_servers (address, hostname) VALUES (@address, @hostname)",
|
||||
new { address, hostname });
|
||||
"INSERT INTO sa_servers (address, hostname, rcon_password) VALUES (@address, @hostname, @rconPassword)",
|
||||
new { address, hostname, rconPassword });
|
||||
|
||||
serverId = await connection.ExecuteScalarAsync<int>(
|
||||
"SELECT id FROM sa_servers WHERE address = @address",
|
||||
new { address });
|
||||
}
|
||||
else
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"UPDATE `sa_servers` SET `hostname` = @hostname, `id` = `id` WHERE `address` = @address",
|
||||
new { address, hostname });
|
||||
"UPDATE sa_servers SET hostname = @hostname, rcon_password = @rconPassword WHERE address = @address",
|
||||
new { address, hostname, rconPassword });
|
||||
}
|
||||
|
||||
int? serverId = await connection.ExecuteScalarAsync<int>(
|
||||
"SELECT `id` FROM `sa_servers` WHERE `address` = @address",
|
||||
new { address });
|
||||
|
||||
CS2_SimpleAdmin.ServerId = serverId;
|
||||
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
|
||||
|
||||
if (CS2_SimpleAdmin.ServerId != null)
|
||||
{
|
||||
await Server.NextFrameAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
|
||||
await Server.NextWorldUpdateAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
|
||||
}
|
||||
|
||||
CS2_SimpleAdmin.ServerLoaded = true;
|
||||
if (CS2_SimpleAdmin.Instance.CacheManager != null)
|
||||
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -83,7 +124,7 @@ public class ServerManager
|
||||
|
||||
try
|
||||
{
|
||||
await client.GetAsync($"https://api.daffyy.love/index.php{queryString}");
|
||||
await client.GetAsync($"https://api.daffyy.dev/index.php{queryString}");
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
@@ -91,6 +132,8 @@ public class ServerManager
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CS2_SimpleAdmin.SimpleAdminApi?.OnSimpleAdminReadyEvent();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,30 +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)
|
||||
{
|
||||
public async Task WarnPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
|
||||
/// <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;
|
||||
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)";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddWarnQuery(true);
|
||||
|
||||
await connection.ExecuteAsync(sql, new
|
||||
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,
|
||||
@@ -32,28 +39,39 @@ internal class WarnManager(Database.Database? database)
|
||||
created = now,
|
||||
serverid = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
return warnId;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch { };
|
||||
}
|
||||
|
||||
public async Task 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;
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return;
|
||||
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)";
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddWarnQuery(false);
|
||||
|
||||
await connection.ExecuteAsync(sql, new
|
||||
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,
|
||||
@@ -61,34 +79,31 @@ internal class WarnManager(Database.Database? database)
|
||||
created = now,
|
||||
serverid = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
return warnId;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch { };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of warnings for a specific player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player whose warnings to retrieve.</param>
|
||||
/// <param name="active">If true, returns only active (non-expired) warnings; otherwise returns all warnings.</param>
|
||||
/// <returns>A list of dynamic objects representing warnings, or an empty list if none found or on failure.</returns>
|
||||
public async Task<List<dynamic>> GetPlayerWarns(PlayerInfo player, bool active = true)
|
||||
{
|
||||
if (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();
|
||||
@@ -99,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)
|
||||
{
|
||||
@@ -124,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)
|
||||
{
|
||||
@@ -144,38 +161,41 @@ 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 SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = (SELECT MAX(id) FROM sa_warns WHERE player_steamid = @steamid AND status = 'ACTIVE')"
|
||||
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = (SELECT MAX(id) FROM sa_warns WHERE player_steamid = @steamid AND status = 'ACTIVE' AND server_id = @serverid)";
|
||||
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)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove last warn + {ex}");
|
||||
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove last warn {exception}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Menu;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public static class AdminMenu
|
||||
{
|
||||
public static IMenu? CreateMenu(string title)
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
return Helper.CreateMenu(title);
|
||||
MenuManager.Instance.OpenMainMenu(admin);
|
||||
}
|
||||
|
||||
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
|
||||
{
|
||||
return Helper.CreateMenu(title, backAction);
|
||||
// return CS2_SimpleAdmin.Instance.Config.UseChatMenu ? new ChatMenu(title) : new CenterHtmlMenu(title, CS2_SimpleAdmin.Instance);
|
||||
}
|
||||
|
||||
@@ -26,44 +32,44 @@ public static class AdminMenu
|
||||
// }
|
||||
}
|
||||
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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)),
|
||||
];
|
||||
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
if (customCommands.Count > 0)
|
||||
{
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
|
||||
}
|
||||
|
||||
if (AdminManager.PlayerHasPermissions(admin, "@css/root"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
{
|
||||
var menuName = menuOptionData.Name;
|
||||
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
|
||||
}
|
||||
|
||||
if (menu != null) OpenMenu(admin, menu);
|
||||
}
|
||||
// public static void OpenMenu(CCSPlayerController admin)
|
||||
// {
|
||||
// if (admin.IsValid == false)
|
||||
// return;
|
||||
//
|
||||
// var localizer = CS2_SimpleAdmin._localizer;
|
||||
// if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
|
||||
// {
|
||||
// admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
// "[SimpleAdmin] " +
|
||||
// (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
// List<ChatMenuOptionData> options =
|
||||
// [
|
||||
// new(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
|
||||
// new(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
|
||||
// new(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
|
||||
// ];
|
||||
//
|
||||
// var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
// if (customCommands.Count > 0)
|
||||
// {
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
|
||||
// }
|
||||
//
|
||||
// if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
|
||||
//
|
||||
// foreach (var menuOptionData in options)
|
||||
// {
|
||||
// var menuName = menuOptionData.Name;
|
||||
// menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
|
||||
// }
|
||||
//
|
||||
// if (menu != null) OpenMenu(admin, menu);
|
||||
// }
|
||||
}
|
||||
602
CS2-SimpleAdmin/Menus/BasicMenu.cs
Normal file
602
CS2-SimpleAdmin/Menus/BasicMenu.cs
Normal file
@@ -0,0 +1,602 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public abstract class BasicMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes all menus in the system by registering them with the MenuManager.
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
var manager = MenuManager.Instance;
|
||||
|
||||
// Players category menus
|
||||
manager.RegisterMenu("players", "slap", "Slap Player", CreateSlapMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "slay", "Slay Player", CreateSlayMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "warn", "Warn Player", CreateWarnMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
|
||||
manager.RegisterMenu("players", "gag", "Gag Player", CreateGagMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "mute", "Mute Player", CreateMuteMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "silence", "Silence Player", CreateSilenceMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "team", "Force Team", CreateForceTeamMenu, "@css/kick");
|
||||
|
||||
// Server category menus
|
||||
manager.RegisterMenu("server", "plugins", "Manage Plugins", CreatePluginsMenu, "@css/root");
|
||||
manager.RegisterMenu("server", "changemap", "Change Map", CreateChangeMapMenu, "@css/changemap");
|
||||
manager.RegisterMenu("server", "restart", "Restart Game", CreateRestartGameMenu, "@css/generic");
|
||||
manager.RegisterMenu("server", "custom", "Custom Commands", CreateCustomCommandsMenu, "@css/generic");
|
||||
|
||||
// Admin category menus
|
||||
manager.RegisterMenu("admin", "add", "Add Admin", CreateAddAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "remove", "Remove Admin", CreateRemoveAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "reload", "Reload Admins", CreateReloadAdminsMenu, "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for slapping players with selectable damage amounts.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the slap menu.</returns>
|
||||
|
||||
private static MenuBuilder CreateSlapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slapMenu = new MenuBuilder(localizer?["sa_slap"] ?? "Slap Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, player));
|
||||
}
|
||||
|
||||
return slapMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates damage selection submenu for slapping a specific player.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player executing the slap.</param>
|
||||
/// <param name="target">The target player to be slapped.</param>
|
||||
/// <returns>A MenuBuilder instance for the slap damage menu.</returns>
|
||||
private static MenuBuilder CreateSlapDamageMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var slapDamageMenu = new MenuBuilder($"Slap: {target.PlayerName}");
|
||||
var damages = new[] { 0, 1, 5, 10, 50, 100 };
|
||||
|
||||
foreach (var damage in damages)
|
||||
{
|
||||
slapDamageMenu.AddOption($"{damage} HP", _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Slap(admin, target, damage);
|
||||
// Keep menu open for consecutive slaps
|
||||
CreateSlapDamageMenu(admin, target).OpenMenu(admin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return slapDamageMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for slaying (killing) players.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the slay menu.</returns>
|
||||
private static MenuBuilder CreateSlayMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slayMenu = new MenuBuilder(localizer?["sa_slay"] ?? "Slay Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
slayMenu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Slay(admin, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return slayMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for kicking players with reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the kick menu.</returns>
|
||||
private static MenuBuilder CreateKickMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var kickMenu = new MenuBuilder(localizer?["sa_kick"] ?? "Kick Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return kickMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for warning players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the warn menu.</returns>
|
||||
private static MenuBuilder CreateWarnMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var warnMenu = new MenuBuilder(localizer?["sa_warn"] ?? "Warn Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Warn",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Warn", PenaltyType.Warn,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return warnMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for banning players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the ban menu.</returns>
|
||||
private static MenuBuilder CreateBanMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var banMenu = new MenuBuilder(localizer?["sa_ban"] ?? "Ban Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Ban",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Ban", PenaltyType.Ban,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return banMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for gagging (text chat muting) players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the gag menu.</returns>
|
||||
private static MenuBuilder CreateGagMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var gagMenu = new MenuBuilder(localizer?["sa_gag"] ?? "Gag Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Gag",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Gag", PenaltyType.Gag,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return gagMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for muting (voice chat muting) players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the mute menu.</returns>
|
||||
private static MenuBuilder CreateMuteMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var muteMenu = new MenuBuilder(localizer?["sa_mute"] ?? "Mute Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Mute",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Mute", PenaltyType.Mute,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return muteMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for silencing (both text and voice chat muting) players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the silence menu.</returns>
|
||||
private static MenuBuilder CreateSilenceMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var silenceMenu = new MenuBuilder(localizer?["sa_silence"] ?? "Silence Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Silence",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Silence", PenaltyType.Silence,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return silenceMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for forcing players to switch teams.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the force team menu.</returns>
|
||||
private static MenuBuilder CreateForceTeamMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamMenu = new MenuBuilder(localizer?["sa_team_force"] ?? "Force Team");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return teamMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates team selection submenu for forcing a specific player to a team.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player executing the team change.</param>
|
||||
/// <param name="target">The target player to be moved.</param>
|
||||
/// <returns>A MenuBuilder instance for the team selection menu.</returns>
|
||||
private static MenuBuilder CreateTeamSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamSelectionMenu = new MenuBuilder($"Force Team: {target.PlayerName}");
|
||||
|
||||
var teams = new[]
|
||||
{
|
||||
(localizer?["sa_team_ct"] ?? "CT", "ct", CsTeam.CounterTerrorist),
|
||||
(localizer?["sa_team_t"] ?? "T", "t", CsTeam.Terrorist),
|
||||
(localizer?["sa_team_swap"] ?? "Swap", "swap", CsTeam.Spectator),
|
||||
(localizer?["sa_team_spec"] ?? "Spec", "spec", CsTeam.Spectator)
|
||||
};
|
||||
|
||||
foreach (var (name, teamName, teamNum) in teams)
|
||||
{
|
||||
teamSelectionMenu.AddOption(name, _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.ChangeTeam(admin, target, teamName, teamNum, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return teamSelectionMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for managing server plugins.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the plugins menu.</returns>
|
||||
private static MenuBuilder CreatePluginsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var pluginsMenu = new MenuBuilder(localizer?["sa_menu_pluginsmanager_title"] ?? "Manage Plugins");
|
||||
|
||||
pluginsMenu.AddOption("Open Plugins Manager", _ =>
|
||||
{
|
||||
admin.ExecuteClientCommandFromServer("css_pluginsmanager");
|
||||
});
|
||||
|
||||
return pluginsMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for changing the current map (includes default and workshop maps).
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the change map menu.</returns>
|
||||
private static MenuBuilder CreateChangeMapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mapMenu = new MenuBuilder(localizer?["sa_changemap"] ?? "Change Map");
|
||||
|
||||
// Add default maps
|
||||
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
|
||||
foreach (var map in maps)
|
||||
{
|
||||
mapMenu.AddOption(map, _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ChangeMap(admin, map);
|
||||
});
|
||||
}
|
||||
|
||||
// Add workshop maps
|
||||
var wsMaps = CS2_SimpleAdmin.Instance.Config.WorkshopMaps;
|
||||
foreach (var wsMap in wsMaps)
|
||||
{
|
||||
mapMenu.AddOption($"{wsMap.Key} (WS)", _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ChangeWorkshopMap(admin, wsMap.Value?.ToString() ?? wsMap.Key);
|
||||
});
|
||||
}
|
||||
|
||||
return mapMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for restarting the current game/round.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the restart game menu.</returns>
|
||||
private static MenuBuilder CreateRestartGameMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var restartMenu = new MenuBuilder(localizer?["sa_restart_game"] ?? "Restart Game");
|
||||
|
||||
restartMenu.AddOption("Restart Round", _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.RestartGame(admin);
|
||||
});
|
||||
|
||||
return restartMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for executing custom server commands defined in configuration.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the custom commands menu.</returns>
|
||||
private static MenuBuilder CreateCustomCommandsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var customMenu = new MenuBuilder(localizer?["sa_menu_custom_commands"] ?? "Custom Commands");
|
||||
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
|
||||
foreach (var customCommand in customCommands)
|
||||
{
|
||||
if (string.IsNullOrEmpty(customCommand.DisplayName) || string.IsNullOrEmpty(customCommand.Command))
|
||||
continue;
|
||||
|
||||
var steamId = new SteamID(admin.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, customCommand.Flag))
|
||||
continue;
|
||||
|
||||
customMenu.AddOption(customCommand.DisplayName, _ =>
|
||||
{
|
||||
Helper.TryLogCommandOnDiscord(admin, customCommand.Command);
|
||||
|
||||
if (customCommand.ExecuteOnClient)
|
||||
admin.ExecuteClientCommandFromServer(customCommand.Command);
|
||||
else
|
||||
Server.ExecuteCommand(customCommand.Command);
|
||||
});
|
||||
}
|
||||
|
||||
return customMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for adding admin privileges to players.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the add admin menu.</returns>
|
||||
private static MenuBuilder CreateAddAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var addAdminMenu = new MenuBuilder(localizer?["sa_admin_add"] ?? "Add Admin");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, player));
|
||||
}
|
||||
|
||||
return addAdminMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates admin flags selection submenu for granting specific permissions to a player.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player granting permissions.</param>
|
||||
/// <param name="target">The target player to receive admin privileges.</param>
|
||||
/// <returns>A MenuBuilder instance for the admin flags menu.</returns>
|
||||
private static MenuBuilder CreateAdminFlagsMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var flagsMenu = new MenuBuilder($"Add Admin: {target.PlayerName}");
|
||||
|
||||
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
|
||||
{
|
||||
var hasFlag = AdminManager.PlayerHasPermissions(target, adminFlag.Flag);
|
||||
flagsMenu.AddOption(adminFlag.Name, _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.AddAdmin(admin, target.SteamID.ToString(), target.PlayerName, adminFlag.Flag, 10);
|
||||
}
|
||||
}, hasFlag); // Disabled if player already has this flag
|
||||
}
|
||||
|
||||
return flagsMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for removing admin privileges from players.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the remove admin menu.</returns>
|
||||
private static MenuBuilder CreateRemoveAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var removeAdminMenu = new MenuBuilder(localizer?["sa_admin_remove"] ?? "Remove Admin");
|
||||
|
||||
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
||||
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
||||
p != admin &&
|
||||
admin.CanTarget(p));
|
||||
|
||||
foreach (var player in adminPlayers)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
removeAdminMenu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return removeAdminMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for reloading admin list from database.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the reload admins menu.</returns>
|
||||
private static MenuBuilder CreateReloadAdminsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var reloadMenu = new MenuBuilder(localizer?["sa_admin_reload"] ?? "Reload Admins");
|
||||
|
||||
reloadMenu.AddOption("Reload Admins", _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(admin);
|
||||
});
|
||||
|
||||
return reloadMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates duration selection submenu for time-based penalties (ban, mute, gag, etc.).
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting duration.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
||||
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||
{
|
||||
var durationMenu = new MenuBuilder($"{actionName} Duration: {player.PlayerName}");
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
durationMenu.AddOption(durationItem.Name, _ =>
|
||||
{
|
||||
onSelectAction(admin, player, durationItem.Duration);
|
||||
});
|
||||
}
|
||||
|
||||
return durationMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates reason selection submenu for penalties with predefined reasons from configuration.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting reason.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="penaltyType">The type of penalty to determine which reason list to use.</param>
|
||||
/// <param name="onSelectAction">Callback action executed when reason is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the reason menu.</returns>
|
||||
private static MenuBuilder CreateReasonMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
PenaltyType penaltyType, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
|
||||
{
|
||||
var reasonMenu = new MenuBuilder($"{actionName} Reason: {player.PlayerName}");
|
||||
|
||||
var reasons = penaltyType switch
|
||||
{
|
||||
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons,
|
||||
PenaltyType.Kick => CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons,
|
||||
PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
|
||||
PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons,
|
||||
PenaltyType.Gag or PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
|
||||
_ => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons
|
||||
};
|
||||
|
||||
foreach (var reason in reasons)
|
||||
{
|
||||
reasonMenu.AddOption(reason, _ =>
|
||||
{
|
||||
onSelectAction(admin, player, reason);
|
||||
});
|
||||
}
|
||||
|
||||
return reasonMenu.WithBackButton();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
@@ -8,11 +9,11 @@ public static class CustomCommandsMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
@@ -27,7 +28,7 @@ public static class CustomCommandsMenu
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
options.AddRange(from customCommand in customCommands
|
||||
where !string.IsNullOrEmpty(customCommand.DisplayName) && !string.IsNullOrEmpty(customCommand.Command)
|
||||
let hasRights = AdminManager.PlayerHasPermissions(admin, customCommand.Flag)
|
||||
let hasRights = AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), customCommand.Flag)
|
||||
where hasRights
|
||||
select new ChatMenuOptionData(customCommand.DisplayName, () =>
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ public static class DurationMenu
|
||||
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||
{
|
||||
var menu = AdminMenu.CreateMenu(menuName);
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
|
||||
@@ -20,7 +19,6 @@ public static class DurationMenu
|
||||
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
|
||||
{
|
||||
var menu = AdminMenu.CreateMenu(menuName);
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
|
||||
|
||||
@@ -1,266 +1,267 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public static class FunActionsMenu
|
||||
{
|
||||
private static Dictionary<int, CsItem>? _weaponsCache;
|
||||
|
||||
private static Dictionary<int, CsItem> GetWeaponsCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_weaponsCache != null) return _weaponsCache;
|
||||
|
||||
var weaponsArray = Enum.GetValues(typeof(CsItem));
|
||||
|
||||
// avoid duplicates in the menu
|
||||
_weaponsCache = new Dictionary<int, CsItem>();
|
||||
foreach (CsItem item in weaponsArray)
|
||||
{
|
||||
if (item == CsItem.Tablet)
|
||||
continue;
|
||||
|
||||
_weaponsCache[(int)item] = item;
|
||||
}
|
||||
|
||||
return _weaponsCache;
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands");
|
||||
List<ChatMenuOptionData> options = [];
|
||||
|
||||
//var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats");
|
||||
//var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay");
|
||||
|
||||
// options added in order
|
||||
|
||||
if (AdminManager.CommandIsOverriden("css_god")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_god"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
|
||||
if (AdminManager.CommandIsOverriden("css_noclip")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_noclip"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
|
||||
if (AdminManager.CommandIsOverriden("css_respawn")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_respawn"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
|
||||
if (AdminManager.CommandIsOverriden("css_give")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_give"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
|
||||
|
||||
if (AdminManager.CommandIsOverriden("css_strip")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_strip"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
|
||||
if (AdminManager.CommandIsOverriden("css_freeze")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_freeze"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
|
||||
if (AdminManager.CommandIsOverriden("css_hp")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_hp"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
|
||||
if (AdminManager.CommandIsOverriden("css_speed")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_speed"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
|
||||
if (AdminManager.CommandIsOverriden("css_gravity")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gravity"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
|
||||
if (AdminManager.CommandIsOverriden("css_money")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_money"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
{
|
||||
var menuName = menuOptionData.Name;
|
||||
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void GodMode(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.God(admin, player);
|
||||
}
|
||||
|
||||
private static void NoClip(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.NoClip(admin, player);
|
||||
}
|
||||
|
||||
private static void Respawn(CCSPlayerController? admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.Respawn(admin, player);
|
||||
}
|
||||
|
||||
private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}");
|
||||
|
||||
foreach (var weapon in GetWeaponsCache)
|
||||
{
|
||||
menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue)
|
||||
{
|
||||
CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue);
|
||||
}
|
||||
|
||||
private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.StripWeapons(admin, player);
|
||||
}
|
||||
|
||||
private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
if (!(player?.PlayerPawn.Value?.IsValid ?? false))
|
||||
return;
|
||||
|
||||
if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_OBSOLETE)
|
||||
CS2_SimpleAdmin.Freeze(admin, player, -1);
|
||||
else
|
||||
CS2_SimpleAdmin.Unfreeze(admin, player);
|
||||
}
|
||||
|
||||
private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var hpArray = new[]
|
||||
{
|
||||
new Tuple<string, int>("1", 1),
|
||||
new Tuple<string, int>("10", 10),
|
||||
new Tuple<string, int>("25", 25),
|
||||
new Tuple<string, int>("50", 50),
|
||||
new Tuple<string, int>("100", 100),
|
||||
new Tuple<string, int>("200", 200),
|
||||
new Tuple<string, int>("500", 500),
|
||||
new Tuple<string, int>("999", 999)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in hpArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp)
|
||||
{
|
||||
CS2_SimpleAdmin.SetHp(admin, player, hp);
|
||||
}
|
||||
|
||||
private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var speedArray = new[]
|
||||
{
|
||||
new Tuple<string, float>("0.1", .1f),
|
||||
new Tuple<string, float>("0.25", .25f),
|
||||
new Tuple<string, float>("0.5", .5f),
|
||||
new Tuple<string, float>("0.75", .75f),
|
||||
new Tuple<string, float>("1", 1),
|
||||
new Tuple<string, float>("2", 2),
|
||||
new Tuple<string, float>("3", 3),
|
||||
new Tuple<string, float>("4", 4)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in speedArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed)
|
||||
{
|
||||
CS2_SimpleAdmin.SetSpeed(admin, player, speed);
|
||||
}
|
||||
|
||||
private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var gravityArray = new[]
|
||||
{
|
||||
new Tuple<string, float>("0.1", .1f),
|
||||
new Tuple<string, float>("0.25", .25f),
|
||||
new Tuple<string, float>("0.5", .5f),
|
||||
new Tuple<string, float>("0.75", .75f),
|
||||
new Tuple<string, float>("1", 1),
|
||||
new Tuple<string, float>("2", 2)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in gravityArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity)
|
||||
{
|
||||
CS2_SimpleAdmin.SetGravity(admin, player, gravity);
|
||||
}
|
||||
|
||||
private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var moneyArray = new[]
|
||||
{
|
||||
new Tuple<string, int>("$0", 0),
|
||||
new Tuple<string, int>("$1000", 1000),
|
||||
new Tuple<string, int>("$2500", 2500),
|
||||
new Tuple<string, int>("$5000", 5000),
|
||||
new Tuple<string, int>("$10000", 10000),
|
||||
new Tuple<string, int>("$16000", 16000)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in moneyArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money)
|
||||
{
|
||||
CS2_SimpleAdmin.SetMoney(admin, player, money);
|
||||
}
|
||||
}
|
||||
// using CounterStrikeSharp.API.Core;
|
||||
// using CounterStrikeSharp.API.Modules.Admin;
|
||||
// using CounterStrikeSharp.API.Modules.Entities;
|
||||
// using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
//
|
||||
// namespace CS2_SimpleAdmin.Menus;
|
||||
//
|
||||
// public static class FunActionsMenu
|
||||
// {
|
||||
// private static Dictionary<int, CsItem>? _weaponsCache;
|
||||
//
|
||||
// private static Dictionary<int, CsItem> GetWeaponsCache
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (_weaponsCache != null) return _weaponsCache;
|
||||
//
|
||||
// var weaponsArray = Enum.GetValues(typeof(CsItem));
|
||||
//
|
||||
// // avoid duplicates in the menu
|
||||
// _weaponsCache = new Dictionary<int, CsItem>();
|
||||
// foreach (CsItem item in weaponsArray)
|
||||
// {
|
||||
// if (item == CsItem.Tablet)
|
||||
// continue;
|
||||
//
|
||||
// _weaponsCache[(int)item] = item;
|
||||
// }
|
||||
//
|
||||
// return _weaponsCache;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void OpenMenu(CCSPlayerController admin)
|
||||
// {
|
||||
// if (!admin.IsValid)
|
||||
// return;
|
||||
//
|
||||
// var localizer = CS2_SimpleAdmin._localizer;
|
||||
// if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
// {
|
||||
// admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
// "[SimpleAdmin] " +
|
||||
// (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands");
|
||||
// List<ChatMenuOptionData> options = [];
|
||||
//
|
||||
// //var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats");
|
||||
// //var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay");
|
||||
//
|
||||
// // options added in order
|
||||
//
|
||||
// if (AdminManager.CommandIsOverriden("css_god")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_god"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
|
||||
// if (AdminManager.CommandIsOverriden("css_noclip")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_noclip"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
|
||||
// if (AdminManager.CommandIsOverriden("css_respawn")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_respawn"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
|
||||
// if (AdminManager.CommandIsOverriden("css_give")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_give"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
|
||||
//
|
||||
// if (AdminManager.CommandIsOverriden("css_strip")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_strip"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
|
||||
// if (AdminManager.CommandIsOverriden("css_freeze")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_freeze"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
|
||||
// if (AdminManager.CommandIsOverriden("css_hp")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_hp"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
|
||||
// if (AdminManager.CommandIsOverriden("css_speed")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_speed"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
|
||||
// if (AdminManager.CommandIsOverriden("css_gravity")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gravity"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
|
||||
// if (AdminManager.CommandIsOverriden("css_money")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_money"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
|
||||
//
|
||||
// foreach (var menuOptionData in options)
|
||||
// {
|
||||
// var menuName = menuOptionData.Name;
|
||||
// menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void GodMode(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.God(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void NoClip(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.NoClip(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void Respawn(CCSPlayerController? admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.Respawn(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var weapon in GetWeaponsCache)
|
||||
// {
|
||||
// menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue)
|
||||
// {
|
||||
// CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue);
|
||||
// }
|
||||
//
|
||||
// private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.StripWeapons(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// if (!(player.PlayerPawn.Value?.IsValid ?? false))
|
||||
// return;
|
||||
//
|
||||
// if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_INVALID)
|
||||
// CS2_SimpleAdmin.Freeze(admin, player, -1);
|
||||
// else
|
||||
// CS2_SimpleAdmin.Unfreeze(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var hpArray = new[]
|
||||
// {
|
||||
// new Tuple<string, int>("1", 1),
|
||||
// new Tuple<string, int>("10", 10),
|
||||
// new Tuple<string, int>("25", 25),
|
||||
// new Tuple<string, int>("50", 50),
|
||||
// new Tuple<string, int>("100", 100),
|
||||
// new Tuple<string, int>("200", 200),
|
||||
// new Tuple<string, int>("500", 500),
|
||||
// new Tuple<string, int>("999", 999)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in hpArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetHp(admin, player, hp);
|
||||
// }
|
||||
//
|
||||
// private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var speedArray = new[]
|
||||
// {
|
||||
// new Tuple<string, float>("0.1", .1f),
|
||||
// new Tuple<string, float>("0.25", .25f),
|
||||
// new Tuple<string, float>("0.5", .5f),
|
||||
// new Tuple<string, float>("0.75", .75f),
|
||||
// new Tuple<string, float>("1", 1),
|
||||
// new Tuple<string, float>("2", 2),
|
||||
// new Tuple<string, float>("3", 3),
|
||||
// new Tuple<string, float>("4", 4)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in speedArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetSpeed(admin, player, speed);
|
||||
// }
|
||||
//
|
||||
// private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var gravityArray = new[]
|
||||
// {
|
||||
// new Tuple<string, float>("0.1", .1f),
|
||||
// new Tuple<string, float>("0.25", .25f),
|
||||
// new Tuple<string, float>("0.5", .5f),
|
||||
// new Tuple<string, float>("0.75", .75f),
|
||||
// new Tuple<string, float>("1", 1),
|
||||
// new Tuple<string, float>("2", 2)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in gravityArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetGravity(admin, player, gravity);
|
||||
// }
|
||||
//
|
||||
// private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var moneyArray = new[]
|
||||
// {
|
||||
// new Tuple<string, int>("$0", 0),
|
||||
// new Tuple<string, int>("$1000", 1000),
|
||||
// new Tuple<string, int>("$2500", 2500),
|
||||
// new Tuple<string, int>("$5000", 5000),
|
||||
// new Tuple<string, int>("$10000", 10000),
|
||||
// new Tuple<string, int>("$16000", 16000)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in moneyArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetMoney(admin, player, money);
|
||||
// }
|
||||
// }
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
@@ -7,11 +8,11 @@ public static class ManageAdminsMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(admin, "@css/root") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
@@ -23,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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
@@ -9,11 +10,11 @@ public static class ManagePlayersMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
@@ -26,10 +27,10 @@ public static class ManagePlayersMenu
|
||||
List<ChatMenuOptionData> options = [];
|
||||
|
||||
// permissions
|
||||
var hasSlay = AdminManager.CommandIsOverriden("css_slay") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_slay")) : AdminManager.PlayerHasPermissions(admin, "@css/slay");
|
||||
var hasKick = AdminManager.CommandIsOverriden("css_kick") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_kick")) : AdminManager.PlayerHasPermissions(admin, "@css/kick");
|
||||
var hasBan = AdminManager.CommandIsOverriden("css_ban") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_ban")) : AdminManager.PlayerHasPermissions(admin, "@css/ban");
|
||||
var hasChat = AdminManager.CommandIsOverriden("css_gag") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gag")) : AdminManager.PlayerHasPermissions(admin, "@css/chat");
|
||||
var hasSlay = AdminManager.CommandIsOverriden("css_slay") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_slay")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay");
|
||||
var hasKick = AdminManager.CommandIsOverriden("css_kick") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_kick")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick");
|
||||
var hasBan = AdminManager.CommandIsOverriden("css_ban") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_ban")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/ban");
|
||||
var hasChat = AdminManager.CommandIsOverriden("css_gag") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat");
|
||||
|
||||
// TODO: Localize options
|
||||
// options added in order
|
||||
@@ -46,8 +47,8 @@ public static class ManagePlayersMenu
|
||||
}
|
||||
|
||||
if (AdminManager.CommandIsOverriden("css_warn")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_warn"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/kick"))
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, WarnMenu))));
|
||||
|
||||
if (hasBan)
|
||||
@@ -56,22 +57,22 @@ public static class ManagePlayersMenu
|
||||
if (hasChat)
|
||||
{
|
||||
if (AdminManager.CommandIsOverriden("css_gag")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gag"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/chat"))
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, GagMenu))));
|
||||
if (AdminManager.CommandIsOverriden("css_mute")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_mute"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/chat"))
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player, MuteMenu))));
|
||||
if (AdminManager.CommandIsOverriden("css_silence")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_silence"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/chat"))
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, SilenceMenu))));
|
||||
}
|
||||
|
||||
if (AdminManager.CommandIsOverriden("css_team")
|
||||
? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_team"))
|
||||
: AdminManager.PlayerHasPermissions(admin, "@css/kick"))
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_team"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_team_force"] ?? "Force Team", () => PlayersMenu.OpenMenu(admin, localizer?["sa_team_force"] ?? "Force Team", ForceTeamMenu)));
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
@@ -89,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)
|
||||
@@ -148,7 +149,7 @@ public static class ManagePlayersMenu
|
||||
{
|
||||
if (player is not { IsValid: true }) return;
|
||||
|
||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason);
|
||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
|
||||
}
|
||||
|
||||
internal static void BanMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
|
||||
@@ -180,7 +181,7 @@ public static class ManagePlayersMenu
|
||||
{
|
||||
if (player is not { IsValid: true }) return;
|
||||
|
||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason);
|
||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
|
||||
}
|
||||
|
||||
private static void WarnMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
|
||||
@@ -210,7 +211,7 @@ public static class ManagePlayersMenu
|
||||
{
|
||||
if (player is not { IsValid: true }) return;
|
||||
|
||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason);
|
||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
|
||||
}
|
||||
|
||||
internal static void GagMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
|
||||
@@ -311,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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
@@ -7,11 +8,11 @@ public static class ManageServerMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
@@ -23,10 +24,9 @@ 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(admin, AdminManager.GetPermissionOverrides("css_map")) : AdminManager.PlayerHasPermissions(admin, "@css/changemap");
|
||||
var hasPlugins = AdminManager.CommandIsOverriden("css_pluginsmanager") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_pluginsmanager")) : AdminManager.PlayerHasPermissions(admin, "@css/root");
|
||||
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");
|
||||
|
||||
//bool hasMap = AdminManager.PlayerHasPermissions(admin, "@css/changemap");
|
||||
|
||||
|
||||
170
CS2-SimpleAdmin/Menus/MenuBuilder.cs
Normal file
170
CS2-SimpleAdmin/Menus/MenuBuilder.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
public class MenuBuilder(string title)
|
||||
{
|
||||
private readonly List<MenuOption> _options = [];
|
||||
private MenuBuilder? _parentMenu;
|
||||
private Action<CCSPlayerController>? _backAction;
|
||||
private Action<CCSPlayerController>? _resetAction;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option with an action.
|
||||
/// </summary>
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = action,
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option that opens a submenu.
|
||||
/// </summary>
|
||||
public MenuBuilder AddSubMenu(string name, Func<MenuBuilder> subMenuFactory, bool disabled = false, string? permission = null)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = player =>
|
||||
{
|
||||
var subMenu = subMenuFactory();
|
||||
subMenu.SetParent(this);
|
||||
// Automatically add back button to submenu
|
||||
subMenu.WithBackButton();
|
||||
subMenu.OpenMenu(player);
|
||||
},
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option that opens a submenu (with player parameter in factory).
|
||||
/// </summary>
|
||||
public MenuBuilder AddSubMenu(string name, Func<CCSPlayerController, MenuBuilder> subMenuFactory, bool disabled = false, string? permission = null)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = player =>
|
||||
{
|
||||
var subMenu = subMenuFactory(player);
|
||||
subMenu.SetParent(this);
|
||||
// Automatically add back button to submenu
|
||||
subMenu.WithBackButton();
|
||||
subMenu.OpenMenu(player);
|
||||
},
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a back button to return to the previous menu.
|
||||
/// </summary>
|
||||
public MenuBuilder WithBackButton()
|
||||
{
|
||||
if (_parentMenu != null)
|
||||
{
|
||||
_backAction = player => _parentMenu.OpenMenu(player);
|
||||
|
||||
// Add back option at the end of menu
|
||||
// AddOption(backButtonText, _backAction);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the parent menu (for navigation).
|
||||
/// </summary>
|
||||
private void SetParent(MenuBuilder parent)
|
||||
{
|
||||
_parentMenu = parent;
|
||||
_backAction = player => _parentMenu.OpenMenu(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the menu for a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to open the menu for.</param>
|
||||
public void OpenMenu(CCSPlayerController player)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
|
||||
// Use MenuManager dependency
|
||||
var menu = Helper.CreateMenu(title, _backAction);
|
||||
if (menu == null) return;
|
||||
|
||||
foreach (var option in _options)
|
||||
{
|
||||
// Check permissions if required
|
||||
if (!string.IsNullOrEmpty(option.Permission))
|
||||
{
|
||||
var steamId = new CounterStrikeSharp.API.Modules.Entities.SteamID(player.SteamID);
|
||||
if (!CounterStrikeSharp.API.Modules.Admin.AdminManager.PlayerHasPermissions(steamId, option.Permission))
|
||||
{
|
||||
continue; // Skip option if player doesn't have permission
|
||||
}
|
||||
}
|
||||
|
||||
menu.AddMenuOption(option.Name, (menuPlayer, menuOption) =>
|
||||
{
|
||||
option.Action?.Invoke(menuPlayer);
|
||||
}, option.Disabled);
|
||||
}
|
||||
|
||||
menu.Open(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all menu options.
|
||||
/// </summary>
|
||||
/// <returns>This MenuBuilder instance for chaining.</returns>
|
||||
public MenuBuilder Clear()
|
||||
{
|
||||
_options.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a reset action for the menu.
|
||||
/// </summary>
|
||||
/// <param name="resetAction">The action to execute on reset.</param>
|
||||
/// <returns>This MenuBuilder instance for chaining.</returns>
|
||||
public MenuBuilder WithResetAction(Action<CCSPlayerController> resetAction)
|
||||
{
|
||||
_resetAction = resetAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom back action for the menu.
|
||||
/// </summary>
|
||||
/// <param name="backAction">The action to execute when going back (nullable).</param>
|
||||
/// <returns>This MenuBuilder instance for chaining.</returns>
|
||||
public MenuBuilder WithBackAction(Action<CCSPlayerController>? backAction)
|
||||
{
|
||||
_backAction = backAction;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an option within a menu.
|
||||
/// </summary>
|
||||
public class MenuOption
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public Action<CCSPlayerController>? Action { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string? Permission { get; set; }
|
||||
}
|
||||
|
||||
225
CS2-SimpleAdmin/Menus/MenuManager.cs
Normal file
225
CS2-SimpleAdmin/Menus/MenuManager.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public class MenuManager
|
||||
{
|
||||
private static MenuManager? _instance;
|
||||
public static MenuManager Instance => _instance ??= new MenuManager();
|
||||
|
||||
private readonly Dictionary<string, Func<CCSPlayerController, MenuBuilder>> _menuFactories = [];
|
||||
private readonly Dictionary<string, MenuCategory> _menuCategories = [];
|
||||
|
||||
/// <summary>
|
||||
/// Provides public access to menu categories (for API usage).
|
||||
/// </summary>
|
||||
/// <returns>Dictionary of menu categories keyed by category ID.</returns>
|
||||
public Dictionary<string, MenuCategory> GetMenuCategories()
|
||||
{
|
||||
return _menuCategories;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new menu category with specified permissions.
|
||||
/// </summary>
|
||||
/// <param name="categoryId">Unique identifier for the category.</param>
|
||||
/// <param name="categoryName">Display name of the category.</param>
|
||||
/// <param name="permission">Required permission to access this category (default: @css/generic).</param>
|
||||
public void RegisterCategory(string categoryId, string categoryName, string permission = "@css/generic")
|
||||
{
|
||||
_menuCategories[categoryId] = new MenuCategory
|
||||
{
|
||||
Id = categoryId,
|
||||
Name = categoryName,
|
||||
Permission = permission,
|
||||
MenuFactories = new Dictionary<string, Func<CCSPlayerController, MenuBuilder>>()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu within a category (API for other plugins).
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuName">Display name of the menu.</param>
|
||||
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
if (!_menuCategories.ContainsKey(categoryId))
|
||||
{
|
||||
RegisterCategory(categoryId, categoryId); // Auto-create category if it doesn't exist
|
||||
}
|
||||
|
||||
_menuCategories[categoryId].MenuFactories[menuId] = menuFactory;
|
||||
_menuCategories[categoryId].MenuNames[menuId] = menuName;
|
||||
if (permission != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuPermissions[menuId] = permission;
|
||||
}
|
||||
if (commandName != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuCommandNames[menuId] = commandName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category containing the menu.</param>
|
||||
/// <param name="menuId">The menu to unregister.</param>
|
||||
public void UnregisterMenu(string categoryId, string menuId)
|
||||
{
|
||||
if (!_menuCategories.TryGetValue(categoryId, out var category)) return;
|
||||
category.MenuFactories.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuNames.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuPermissions.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuCommandNames.Remove(menuId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the main admin menu for a player with accessible categories.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to create the menu for.</param>
|
||||
/// <returns>A MenuBuilder instance for the main menu.</returns>
|
||||
public MenuBuilder CreateMainMenu(CCSPlayerController player)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mainMenu = new MenuBuilder(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
|
||||
foreach (var category in _menuCategories.Values)
|
||||
{
|
||||
if (category.MenuFactories.Count <= 0) continue;
|
||||
// Check category permissions
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, category.Permission))
|
||||
continue;
|
||||
|
||||
// Pass player to CreateCategoryMenu
|
||||
mainMenu.AddSubMenu(category.Name, () => CreateCategoryMenu(category, player),
|
||||
permission: category.Permission);
|
||||
}
|
||||
|
||||
return mainMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a category submenu containing all registered menus in that category.
|
||||
/// </summary>
|
||||
/// <param name="category">The menu category to create.</param>
|
||||
/// <param name="player">The player to create the menu for.</param>
|
||||
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
||||
private MenuBuilder CreateCategoryMenu(MenuCategory category, CCSPlayerController player)
|
||||
{
|
||||
var categoryMenu = new MenuBuilder(category.Name);
|
||||
|
||||
foreach (var kvp in category.MenuFactories)
|
||||
{
|
||||
var menuId = kvp.Key;
|
||||
var menuFactory = kvp.Value;
|
||||
var menuName = category.MenuNames.TryGetValue(menuId, out var name) ? name : menuId;
|
||||
var permission = category.MenuPermissions.TryGetValue(menuId, out var perm) ? perm : null;
|
||||
var commandName = category.MenuCommandNames.TryGetValue(menuId, out var cmd) ? cmd : null;
|
||||
|
||||
// Check permissions with command override support
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
|
||||
// If commandName is provided, check for permission overrides
|
||||
if (!string.IsNullOrEmpty(commandName))
|
||||
{
|
||||
bool hasPermission;
|
||||
|
||||
// Check if command has overridden permissions
|
||||
if (AdminManager.CommandIsOverriden(commandName))
|
||||
{
|
||||
var overriddenPermission = AdminManager.GetPermissionOverrides(commandName);
|
||||
hasPermission = AdminManager.PlayerHasPermissions(steamId, overriddenPermission);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(permission))
|
||||
{
|
||||
// Use default permission if no override exists
|
||||
hasPermission = AdminManager.PlayerHasPermissions(steamId, permission);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No permission required
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
if (!hasPermission)
|
||||
continue;
|
||||
}
|
||||
// Fallback to standard permission check if no commandName provided
|
||||
else if (!string.IsNullOrEmpty(permission))
|
||||
{
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, permission))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the actual factory with player parameter
|
||||
categoryMenu.AddSubMenu(menuName, () => menuFactory(player), permission: permission);
|
||||
}
|
||||
|
||||
return categoryMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the main admin menu for a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to open the menu for.</param>
|
||||
public void OpenMainMenu(CCSPlayerController player)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, "@css/generic"))
|
||||
{
|
||||
player.PrintToChat(localizer?["sa_prefix"] ?? "[SimpleAdmin] " +
|
||||
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command"));
|
||||
return;
|
||||
}
|
||||
|
||||
CreateMainMenu(player).OpenMenu(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes default menu categories (Players, Server, Admin).
|
||||
/// </summary>
|
||||
public void InitializeDefaultCategories()
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
RegisterCategory("players", localizer?["sa_menu_players_manage"] ?? "Manage Players", "@css/generic");
|
||||
RegisterCategory("server", localizer?["sa_menu_server_manage"] ?? "Server Management", "@css/generic");
|
||||
// RegisterCategory("fun", localizer?["sa_menu_fun_commands"] ?? "Fun Commands", "@css/generic");
|
||||
RegisterCategory("admin", localizer?["sa_menu_admins_manage"] ?? "Admin Management", "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method for creating category menus (for API usage).
|
||||
/// </summary>
|
||||
/// <param name="category">The menu category to create.</param>
|
||||
/// <param name="player">The player to create the menu for.</param>
|
||||
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
||||
public MenuBuilder CreateCategoryMenuPublic(MenuCategory category, CCSPlayerController player)
|
||||
{
|
||||
return CreateCategoryMenu(category, player);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a menu category containing multiple menus.
|
||||
/// </summary>
|
||||
public class MenuCategory
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Permission { get; set; } = "@css/generic";
|
||||
public Dictionary<string, Func<CCSPlayerController, MenuBuilder>> MenuFactories { get; set; } = [];
|
||||
public Dictionary<string, string> MenuNames { get; set; } = [];
|
||||
public Dictionary<string, string> MenuPermissions { get; set; } = [];
|
||||
public Dictionary<string, string> MenuCommandNames { get; set; } = [];
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public static class PlayersMenu
|
||||
{
|
||||
public static void OpenRealPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
|
||||
{
|
||||
OpenMenu(admin, menuName, onSelectAction, p => p.IsBot == false);
|
||||
OpenMenu(admin, menuName, onSelectAction, p => !p.IsBot);
|
||||
}
|
||||
|
||||
public static void OpenAdminPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController?, bool>? enableFilter = null)
|
||||
@@ -18,12 +18,12 @@ public static class PlayersMenu
|
||||
|
||||
public static void OpenAliveMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
|
||||
{
|
||||
OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive);
|
||||
OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE);
|
||||
}
|
||||
|
||||
public static void OpenDeadMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController?, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
|
||||
{
|
||||
OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive == false);
|
||||
OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE);
|
||||
}
|
||||
|
||||
public static void OpenMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
|
||||
@@ -37,7 +37,7 @@ public static class PlayersMenu
|
||||
var playerName = player != null && player.PlayerName.Length > 26 ? player.PlayerName[..26] : player?.PlayerName;
|
||||
|
||||
var optionName = HttpUtility.HtmlEncode(playerName);
|
||||
if (player != null && enableFilter != null && enableFilter(player) == false)
|
||||
if (player != null && enableFilter != null && !enableFilter(player))
|
||||
continue;
|
||||
|
||||
var enabled = admin.CanTarget(player);
|
||||
@@ -47,7 +47,7 @@ public static class PlayersMenu
|
||||
{
|
||||
if (player != null) onSelectAction.Invoke(admin, player);
|
||||
},
|
||||
enabled == false);
|
||||
!enabled);
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
|
||||
@@ -46,7 +46,7 @@ public static class ReasonMenu
|
||||
{
|
||||
menu?.AddMenuOption(reason, (_, _) => onSelectAction(admin, player, reason));
|
||||
}
|
||||
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
}
|
||||
39
CS2-SimpleAdmin/Models/BanRecord.cs
Normal file
39
CS2-SimpleAdmin/Models/BanRecord.cs
Normal file
@@ -0,0 +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; init; }
|
||||
|
||||
[Column("player_name")]
|
||||
public string? PlayerName { get; set; }
|
||||
|
||||
[Column("player_steamid")]
|
||||
public ulong? PlayerSteamId { get; set; }
|
||||
|
||||
[Column("player_ip")]
|
||||
public string? PlayerIp { get; set; }
|
||||
|
||||
[Column("status")]
|
||||
public required string Status { get; init; }
|
||||
|
||||
[NotMapped]
|
||||
public BanStatus StatusEnum => Status.ToUpper() switch
|
||||
{
|
||||
"ACTIVE" => BanStatus.ACTIVE,
|
||||
"UNBANNED" => BanStatus.UNBANNED,
|
||||
"EXPIRED" => BanStatus.EXPIRED,
|
||||
_ => BanStatus.UNKNOWN
|
||||
};
|
||||
}
|
||||
3
CS2-SimpleAdmin/Models/IpRecord.cs
Normal file
3
CS2-SimpleAdmin/Models/IpRecord.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace CS2_SimpleAdmin.Models;
|
||||
|
||||
public readonly record struct IpRecord(uint Ip, DateTime UsedAt, string PlayerName);
|
||||
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.6.8a
|
||||
1.7.8-beta-2
|
||||
@@ -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;
|
||||
@@ -32,16 +35,17 @@ public partial class CS2_SimpleAdmin
|
||||
// Command and Server Settings
|
||||
public static readonly bool UnlockedCommands = CoreConfig.UnlockConCommands;
|
||||
internal static string IpAddress = string.Empty;
|
||||
public static bool ServerLoaded;
|
||||
public static int? ServerId = null;
|
||||
internal static bool ServerLoaded;
|
||||
internal static int? ServerId = null;
|
||||
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
|
||||
|
||||
// Player Management
|
||||
private static readonly HashSet<int> GodPlayers = [];
|
||||
private static readonly HashSet<int> SilentPlayers = [];
|
||||
internal static readonly ConcurrentBag<string?> BannedPlayers = [];
|
||||
internal static readonly HashSet<int> SilentPlayers = [];
|
||||
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
|
||||
internal static readonly Dictionary<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
|
||||
@@ -49,24 +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
|
||||
private 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"];
|
||||
}
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nعقوبات اللاعبين لـ {lightred}{0}{default},\nعدد الحظر: {lightred}{1}{default}, عدد الصمت: {lightred}{2}{default}, عدد الكتم: {lightred}{3}{default}, عدد السكوت: {lightred}{4}{default}, عدد التحذيرات: {lightred}{5}{default}\nالعقوبات النشطة:\n{6}\nالتحذيرات النشطة:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}عقوبات اللاعبين لـ {lightred}{0}{grey}, حظر: {lightred}{1}{grey}, صمت: {lightred}{2}{grey}, كتم: {lightred}{3}{grey}, سكوت: {lightred}{4}{grey}, تحذيرات: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}الحسابات المرتبطة باللاعب {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "تم حظرك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "تم حظرك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "تم طردك لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} غيّر الجاذبية لـ {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} غيّر المال لـ {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} غيّر وضع الله لـ {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} قام بتغيير الحجم لـ {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} قتل {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} صفع {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} غيّر الخريطة إلى {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nSpielerstrafe für {lightred}{0}{default},\nAnzahl der Sperren: {lightred}{1}{default}, Anzahl der Mundtot: {lightred}{2}{default}, Anzahl der Stummschaltungen: {lightred}{3}{default}, Anzahl der Stille: {lightred}{4}{default}, Anzahl der Warnungen: {lightred}{5}{default}\nAktive Strafen:\n{6}\nAktive Warnungen:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Spielerstrafe für {lightred}{0}{grey}, Sperren: {lightred}{1}{grey}, Mundtot: {lightred}{2}{grey}, Stummschaltungen: {lightred}{3}{grey}, Stille: {lightred}{4}{grey}, Warnungen: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Verknüpfte Konten des Spielers {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} gebannt!",
|
||||
"sa_player_ban_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent gebannt!",
|
||||
"sa_player_kick_message": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} gekickt!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} hat die Schwerkraft von {lightred}{1}{default} geändert!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} hat das Geld von {lightred}{1}{default} geändert!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} hat den Gottmodus von {lightred}{1}{default} geändert!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} hat die Größe für {lightred}{1}{default} geändert!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} hat {lightred}{1}{default} getötet!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} hat {lightred}{1}{default} geschlagen!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} hat die Karte zu {lightred}{1}{default} geändert!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nPlayer penalties for {lightred}{0}{default},\nNumber of bans: {lightred}{1}{default}, Number of gags: {lightred}{2}{default}, Number of mutes: {lightred}{3}{default}, Number of silences: {lightred}{4}{default}, Number of warnings: {lightred}{5}{default}\nActive penalties:\n{6}\nActive warnings:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Player penalties for {lightred}{0}{grey}, Bans: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Warns: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Associated accounts of player {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "You have been banned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "You have been banned permanently for {lightred}{0}{default} by {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "You have been kicked for {lightred}{0}{default} by {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} changed gravity for {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} changed money for {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} changed size for {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} slayed {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} slapped {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} changed map to {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nPenalizaciones del jugador para {lightred}{0}{default},\nNúmero de prohibiciones: {lightred}{1}{default}, Número de boqueos: {lightred}{2}{default}, Número de silenciamientos: {lightred}{3}{default}, Número de silencios: {lightred}{4}{default}, Número de advertencias: {lightred}{5}{default}\nPenalizaciones activas:\n{6}\nAdvertencias activas:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Penalizaciones del jugador para {lightred}{0}{grey}, Prohibiciones: {lightred}{1}{grey}, Boqueos: {lightred}{2}{grey}, Silenciamientos: {lightred}{3}{grey}, Silencios: {lightred}{4}{grey}, Advertencias: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Cuentas asociadas del jugador {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "Has sido baneado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "Has sido baneado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "Has sido expulsado por {lightred}{0}{default} durante {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} cambió la gravedad de {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} cambió el dinero de {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} cambió el modo dios de {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} cambió el tamaño de {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} mató a {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} abofeteó a {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} cambió el mapa a {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nتنبیهات بازیکن برای {lightred}{0}{default},\nتعداد مسدودیتها: {lightred}{1}{default}, تعداد سکوتها: {lightred}{2}{default}, تعداد بیصدا کردنها: {lightred}{3}{default}, تعداد سکوتها: {lightred}{4}{default}, تعداد هشدارها: {lightred}{5}{default}\nتنبیهات فعال:\n{6}\nهشدارهای فعال:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}تنبیهات بازیکن برای {lightred}{0}{grey}, مسدودیتها: {lightred}{1}{grey}, سکوتها: {lightred}{2}{grey}, بیصدا کردنها: {lightred}{3}{grey}, سکوتها: {lightred}{4}{grey}, هشدارها: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}حسابهای مرتبط با بازیکن {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} مسدود شدهاید!",
|
||||
"sa_player_ban_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه مسدود شدهاید!",
|
||||
"sa_player_kick_message": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} اخراج شدهاید!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} جاذبه {lightred}{1}{default} را تغییر داد!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} پول {lightred}{1}{default} را تغییر داد!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} حالت خدا را برای {lightred}{1}{default} تغییر داد!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} اندازه {lightred}{1}{default} را تغییر داد!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default} را کشت!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} به {lightred}{1}{default} سیلی زد!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} نقشه را به {lightred}{1}{default} تغییر داد!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nPénalités du joueur pour {lightred}{0}{default},\nNombre de bannissements: {lightred}{1}{default}, Nombre de gag: {lightred}{2}{default}, Nombre de mutes: {lightred}{3}{default}, Nombre de silences: {lightred}{4}{default}, Nombre d’avertissements: {lightred}{5}{default}\nPénalités actives:\n{6}\nAvertissements actifs:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Pénalités du joueur pour {lightred}{0}{grey}, Bannissements: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Avertissements: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Comptes associés du joueur {lightred}{0}{grey} : {1}",
|
||||
"sa_player_ban_message_time": "Vous avez été banni pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "Vous avez été banni définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "Vous avez été expulsé pour {lightred}{0}{default} par {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} a modifié la gravité de {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} a modifié l'argent de {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} a modifié le mode dieu de {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} a changé la taille de {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} a tué {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} a giflé {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} a changé la carte pour {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nSpēlētāja sods priekš {lightred}{0}{default},\nAizliegumu skaits: {lightred}{1}{default}, Klusumu skaits: {lightred}{2}{default}, Izslēgšanas skaits: {lightred}{3}{default}, Klusēšanas skaits: {lightred}{4}{default}, Brīdinājumu skaits: {lightred}{5}{default}\nAktīvie sodi:\n{6}\nAktīvie brīdinājumi:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Spēlētāja sods priekš {lightred}{0}{grey}, Aizliegumi: {lightred}{1}{grey}, Klusumi: {lightred}{2}{grey}, Izslēgšana: {lightred}{3}{grey}, Klusēšana: {lightred}{4}{grey}, Brīdinājumi: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Spēlētāja {lightred}{0}{grey} saistītie konti: {1}",
|
||||
"sa_player_ban_message_time": "Tu esi nobanots uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "Tevis bans ir uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "Tu esi izmests, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} gravitāciju!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} naudu!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} mainīja dieva režīmu priekš {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} mainīja izmēru {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} nogalināja {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} pa seju!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} mainīja karti uz {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nBlokady gracza {lightred}{0}{default},\nIlość banów: {lightred}{1}{default}, Ilość zakneblowań: {lightred}{2}{default}, Ilość wyciszeń: {lightred}{3}{default}, Ilość uciszeń: {lightred}{4}{default}Ilość ostrzeżeń: {lightred}{5}{default}\nAktywne blokady:\n{6}\nAktywne ostrzeżenia:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Blokady gracza {lightred}{0}{grey} - bany: {lightred}{1}{grey}, zakneblowania: {lightred}{2}{grey}, wyciszenia: {lightred}{3}{grey}, uciszenia: {lightred}{4}{grey}, ostrzeżenia: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Powiązane konta gracza {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "Zostałeś zbanowany za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "Zostałeś zbanowany na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "Zostałeś wyrzucony za {lightred}{0}{default} przez {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} zmienił grawitacje dla {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} zmienił pieniądze dla {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} zmienił tryb Boga dla {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} zmienił rozmiar dla {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} zgładził {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} uderzył {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} zmienił mapę na {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "Você foi banido por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "Você foi banido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "Você foi expulso por {lightred}{0}{default} por {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} alterou o tamanho de {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} matou {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} deu um tapa em {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} mudou o mapa para {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "Foste banido pelo administrador {lightred}{0}{default} durante {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "Foste banido permanentemente pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "Foste expulso pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} alterou o tamanho de {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} matou {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} deu um tapa em {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} mudou o mapa para {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nШтрафы игрока для {lightred}{0}{default},\nКоличество банов: {lightred}{1}{default}, Количество гэгов: {lightred}{2}{default}, Количество мутов: {lightred}{3}{default}, Количество тишин: {lightred}{4}{default}, Количество предупреждений: {lightred}{5}{default}\nАктивные штрафы:\n{6}\nАктивные предупреждения:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Штрафы игрока для {lightred}{0}{grey}, Баны: {lightred}{1}{grey}, Гэги: {lightred}{2}{grey}, Муты: {lightred}{3}{grey}, Тишины: {lightred}{4}{grey}, Предупреждения: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}Связанные аккаунты игрока {lightred}{0}{grey}: {1}",
|
||||
"sa_player_ban_message_time": "Вы были забанены по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!",
|
||||
"sa_player_ban_message_perm": "Вас забанили навсегда по причине {lightred}{0}{default} администратором {lightred}{1}{default}!",
|
||||
"sa_player_kick_message": "Вы были выгнаны {lightred}{0}{default} администратором {lightred}{1}{default}!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} изменил гравитацию для {lightred}{1}{default}!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} изменил количество денег у {lightred}{1}{default}!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} изменил режим бога для {lightred}{1}{default}!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} изменил размер {lightred}{1}{default}!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} убил {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} дал пощечину {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} изменил карту на {lightred}{1}{default}!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
"sa_player_penalty_info": "===========================\nOyuncunun cezaları {lightred}{0}{default} için,\nBan sayısı: {lightred}{1}{default}, Gag sayısı: {lightred}{2}{default}, Mute sayısı: {lightred}{3}{default}, Sessizlik sayısı: {lightred}{4}{default}, Uyarı sayısı: {lightred}{5}{default}\nAktif cezalar:\n{6}\nAktif uyarılar:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}Oyuncunun cezaları {lightred}{0}{grey}, Banlar: {lightred}{1}{grey}, Gaglar: {lightred}{2}{grey}, Mute'lar: {lightred}{3}{grey}, Sessizlikler: {lightred}{4}{grey}, Uyarılar: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}{lightred}{0}{grey} oyuncusunun bağlı hesapları: {1}",
|
||||
"sa_player_ban_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından yasaklandınız!",
|
||||
"sa_player_ban_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından yasaklandınız!",
|
||||
"sa_player_kick_message": "Senaryo nedeniyle {lightred}{0}{default} tarafından atıldınız!",
|
||||
@@ -107,6 +108,7 @@
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} {lightred}{1}{default}'in yer çekimini değiştirdi!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} {lightred}{1}{default}'in parasını değiştirdi!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} {lightred}{1}{default}'in tanrı modunu değiştirdi!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} boyutu {lightred}{1}{default} için değiştirdi!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default}'i öldürdü!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} {lightred}{1}{default}'e tokat attı!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} haritayı {lightred}{1}{default} olarak değiştirdi!",
|
||||
@@ -124,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}",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sa_title": "SimpleAdmin",
|
||||
"sa_prefix": "{lightred}[SA] {default}",
|
||||
|
||||
|
||||
"sa_unknown": "未知",
|
||||
"sa_no_permission": "您没有权限使用此命令。",
|
||||
"sa_ban_max_duration_exceeded": "禁令持续时间不能超过{lightred}{0}{default}分钟。",
|
||||
"sa_ban_perm_restricted": "您没有永久封禁的权限。",
|
||||
"sa_ban_max_duration_exceeded": "禁止时长不能超过 {lightred}{0}{default} 分钟。",
|
||||
"sa_ban_perm_restricted": "您没有永久禁止权限。",
|
||||
|
||||
"sa_admin_add": "添加管理员",
|
||||
"sa_admin_remove": "移除管理员",
|
||||
@@ -15,7 +15,7 @@
|
||||
"sa_noclip": "穿墙模式",
|
||||
"sa_respawn": "重生",
|
||||
"sa_give_weapon": "给予武器",
|
||||
"sa_strip_weapons": "剥夺武器",
|
||||
"sa_strip_weapons": "移除武器",
|
||||
"sa_freeze": "冻结",
|
||||
"sa_set_hp": "设置生命值",
|
||||
"sa_set_speed": "设置速度",
|
||||
@@ -23,22 +23,22 @@
|
||||
"sa_set_money": "设置金钱",
|
||||
|
||||
"sa_changemap": "更换地图",
|
||||
"sa_restart_game": "重启游戏",
|
||||
"sa_restart_game": "重新开始游戏",
|
||||
|
||||
"sa_team_ct": "CT",
|
||||
"sa_team_t": "T",
|
||||
"sa_team_swap": "交换",
|
||||
"sa_team_spec": "观战",
|
||||
"sa_team_spec": "观察者",
|
||||
|
||||
"sa_slap": "掌掴",
|
||||
"sa_slay": "杀死",
|
||||
"sa_slay": "击杀",
|
||||
"sa_kick": "踢出",
|
||||
"sa_ban": "封禁",
|
||||
"sa_ban": "禁止",
|
||||
"sa_gag": "禁言",
|
||||
"sa_mute": "静音",
|
||||
"sa_silence": "沉默",
|
||||
"sa_warn": "警告",
|
||||
"sa_team_force": "强制队伍",
|
||||
"sa_team_force": "强制分队",
|
||||
|
||||
"sa_menu_custom_commands": "自定义命令",
|
||||
"sa_menu_server_manage": "服务器管理",
|
||||
@@ -47,87 +47,91 @@
|
||||
"sa_menu_players_manage": "玩家管理",
|
||||
"sa_menu_disconnected_title": "最近的玩家",
|
||||
"sa_menu_disconnected_action_title": "选择操作",
|
||||
"sa_menu_pluginsmanager_title": "管理插件",
|
||||
"sa_menu_pluginsmanager_title": "插件管理",
|
||||
|
||||
"sa_player": "玩家",
|
||||
"sa_console": "控制台",
|
||||
"sa_steamid": "SteamID",
|
||||
"sa_duration": "持续时间",
|
||||
"sa_duration": "时长",
|
||||
"sa_reason": "原因",
|
||||
"sa_admin": "管理员",
|
||||
"sa_permanent": "永久",
|
||||
|
||||
"sa_discord_penalty_ban": "封禁已记录",
|
||||
"sa_discord_penalty_mute": "禁言已记录",
|
||||
"sa_discord_penalty_gag": "禁言已记录",
|
||||
"sa_discord_penalty_silence": "禁声已记录",
|
||||
"sa_discord_penalty_warn": "警告已注册",
|
||||
"sa_discord_penalty_unknown": "未知已记录",
|
||||
"sa_discord_penalty_ban": "禁止记录",
|
||||
"sa_discord_penalty_mute": "静音记录",
|
||||
"sa_discord_penalty_gag": "禁言记录",
|
||||
"sa_discord_penalty_silence": "沉默记录",
|
||||
"sa_discord_penalty_warn": "警告记录",
|
||||
"sa_discord_penalty_unknown": "未知记录",
|
||||
|
||||
"sa_player_penalty_chat_active": "{lightred}您的聊天已被封锁到: {grey}{0}",
|
||||
|
||||
"sa_player_penalty_info_active_mute": "➔ 静音 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]",
|
||||
"sa_player_penalty_info_active_gag": "➔ 禁言 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]",
|
||||
"sa_player_penalty_info_active_silence": "➔ 沉默 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]",
|
||||
"sa_player_penalty_info_active_warn": "➔ 警告 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}] - 原因 [{lightred}{1}{default}]",
|
||||
"sa_player_penalty_info_active_mute": "➔ 静音 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
|
||||
"sa_player_penalty_info_active_gag": "➔ 禁言 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
|
||||
"sa_player_penalty_info_active_silence": "➔ 沉默 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
|
||||
"sa_player_penalty_info_active_warn": "➔ 警告 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期 - 原因 [{lightred}{1}{default}]",
|
||||
|
||||
"sa_player_penalty_info_no_active_mute": "➔ 静音 [{lime}✔{default}]",
|
||||
"sa_player_penalty_info_no_active_gag": "➔ 禁言 [{lime}✔{default}]",
|
||||
"sa_player_penalty_info_no_active_silence": "➔ 沉默 [{lime}✔{default}]",
|
||||
"sa_player_penalty_info_no_active_warn": "➔ 警告 [{lime}✔{default}]",
|
||||
|
||||
"sa_player_penalty_info": "===========================\n玩家处罚信息 {lightred}{0}{default},\n禁言次数: {lightred}{1}{default}, 禁言次数: {lightred}{2}{default}, 静音次数: {lightred}{3}{default}, 沉默次数: {lightred}{4}{default}, 警告次数: {lightred}{5}{default}\n当前处罚:\n{6}\n当前警告:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}玩家处罚信息 {lightred}{0}{grey}, 禁言: {lightred}{1}{grey}, 禁言: {lightred}{2}{grey}, 静音: {lightred}{3}{grey}, 沉默: {lightred}{4}{grey}, 警告: {lightred}{5}",
|
||||
"sa_player_ban_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}禁止{lightred}{2}{default}分钟!",
|
||||
"sa_player_ban_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁止!",
|
||||
"sa_player_kick_message": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}踢出!",
|
||||
"sa_player_gag_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁言{lightred}{1}{default}分钟!",
|
||||
"sa_player_gag_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁言!",
|
||||
"sa_player_mute_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁声{lightred}{1}{default}分钟!",
|
||||
"sa_player_mute_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁声!",
|
||||
"sa_player_silence_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁止发言{lightred}{1}{default}分钟!",
|
||||
"sa_player_silence_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁止发言!",
|
||||
"sa_player_warn_message_time": "您因 {lightred}{0}{default} 被 {lightred}{1}{default} 分钟内由 {lightred}{2}{default} 警告!",
|
||||
"sa_player_warn_message_perm": "您因 {lightred}{0}{default} 被 {lightred}{1}{default} 永久警告!",
|
||||
"sa_admin_ban_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_ban_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!",
|
||||
"sa_admin_kick_message": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被踢出!",
|
||||
"sa_admin_gag_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_gag_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!",
|
||||
"sa_admin_mute_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 的声音被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_mute_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 的声音被永久禁言 {lightred}{2}{default}!",
|
||||
"sa_admin_silence_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_silence_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!",
|
||||
"sa_admin_warn_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被警告 {lightred}{2}{default} {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_warn_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久警告 {lightred}{2}{default}!",
|
||||
"sa_player_penalty_info": "===========================\n玩家 {lightred}{0}{default} 的处罚信息,\n禁止次数: {lightred}{1}{default}, 禁言次数: {lightred}{2}{default}, 静音次数: {lightred}{3}{default}, 沉默次数: {lightred}{4}{default}, 警告次数: {lightred}{5}{default}\n活跃的处罚:\n{6}\n活跃的警告:\n{7}\n===========================",
|
||||
"sa_admin_penalty_info": "{grey}玩家 {lightred}{0}{grey} 的处罚信息, 禁止: {lightred}{1}{grey}, 禁言: {lightred}{2}{grey}, 静音: {lightred}{3}{grey}, 沉默: {lightred}{4}{grey}, 警告: {lightred}{5}",
|
||||
"sa_admin_associated_accounts": "{grey}玩家 {lightred}{0}{grey} 的关联账户:{1}",
|
||||
"sa_player_ban_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 禁止 {lightred}{1}{default} 分钟!",
|
||||
"sa_player_ban_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久禁止!",
|
||||
"sa_player_kick_message": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 踢出!",
|
||||
"sa_player_gag_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 禁言 {lightred}{1}{default} 分钟!",
|
||||
"sa_player_gag_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久禁言!",
|
||||
"sa_player_mute_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 静音 {lightred}{1}{default} 分钟!",
|
||||
"sa_player_mute_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久静音!",
|
||||
"sa_player_silence_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 沉默 {lightred}{1}{default} 分钟!",
|
||||
"sa_player_silence_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久沉默!",
|
||||
"sa_player_warn_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 警告 {lightred}{1}{default} 分钟!",
|
||||
"sa_player_warn_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久警告!",
|
||||
"sa_admin_ban_message_time": "{lightred}{0}{default} 禁止了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_ban_message_perm": "{lightred}{0}{default} 永久禁止了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
|
||||
"sa_admin_kick_message": "{lightred}{0}{default} 踢出了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
|
||||
"sa_admin_gag_message_time": "{lightred}{0}{default} 禁言了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_gag_message_perm": "{lightred}{0}{default} 永久禁言了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
|
||||
"sa_admin_mute_message_time": "{lightred}{0}{default} 静音了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_mute_message_perm": "{lightred}{0}{default} 永久静音了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
|
||||
"sa_admin_silence_message_time": "{lightred}{0}{default} 沉默了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_silence_message_perm": "{lightred}{0}{default} 永久沉默了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
|
||||
"sa_admin_warn_message_time": "{lightred}{0}{default} 警告了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
|
||||
"sa_admin_warn_message_perm": "{lightred}{0}{default} 永久警告了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
|
||||
"sa_admin_give_message": "{lightred}{0}{default} 给了 {lightred}{1}{default} {lightred}{2}{default}!",
|
||||
"sa_admin_strip_message": "{lightred}{0}{default} 拿走了 {lightred}{1}{default} 的所有武器!",
|
||||
"sa_admin_hp_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的生命值!",
|
||||
"sa_admin_speed_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的速度!",
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的重力!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的金钱!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的上帝模式!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} 杀死了 {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} 扇了 {lightred}{1}{default} 一巴掌!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} 更改了地图为 {lightred}{1}{default}!",
|
||||
"sa_admin_noclip_message": "{lightred}{0}{default} 为 {lightred}{1}{default} 切换了 noclip!",
|
||||
"sa_admin_strip_message": "{lightred}{0}{default} 移除了 {lightred}{1}{default} 的所有武器!",
|
||||
"sa_admin_hp_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的生命值!",
|
||||
"sa_admin_speed_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的速度!",
|
||||
"sa_admin_gravity_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的重力!",
|
||||
"sa_admin_money_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的金钱!",
|
||||
"sa_admin_god_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的上帝模式!",
|
||||
"sa_admin_resize_message": "{lightred}{0}{default} 更改了 {lightred}{1}{default} 的大小!",
|
||||
"sa_admin_slay_message": "{lightred}{0}{default} 击杀了 {lightred}{1}{default}!",
|
||||
"sa_admin_slap_message": "{lightred}{0}{default} 掌掴了 {lightred}{1}{default}!",
|
||||
"sa_admin_changemap_message": "{lightred}{0}{default} 将地图更换为 {lightred}{1}{default}!",
|
||||
"sa_admin_noclip_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的穿墙模式!",
|
||||
"sa_admin_freeze_message": "{lightred}{0}{default} 冻结了 {lightred}{1}{default}!",
|
||||
"sa_admin_unfreeze_message": "{lightred}{0}{default} 解冻了 {lightred}{1}{default}!",
|
||||
"sa_admin_rename_message": "{lightred}{0}{default} 更改了 {lightred}{1}{default} 的昵称为 {lightred}{2}{default}!",
|
||||
"sa_admin_respawn_message": "{lightred}{0}{default} 重新生成了 {lightred}{1}{default}!",
|
||||
"sa_admin_tp_message": "{lightred}{0}{default} 传送到了 {lightred}{1}{default}!",
|
||||
"sa_admin_bring_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 传送到了自己身边!",
|
||||
"sa_admin_team_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 转移到了 {lightred}{2}{default} 队伍!",
|
||||
"sa_admin_rename_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 的昵称改为 {lightred}{2}{default}!",
|
||||
"sa_admin_respawn_message": "{lightred}{0}{default} 复活了 {lightred}{1}{default}!",
|
||||
"sa_admin_tp_message": "{lightred}{0}{default} 传送到 {lightred}{1}{default}!",
|
||||
"sa_admin_bring_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 传送到自己!",
|
||||
"sa_admin_team_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 转移到 {lightred}{2}{default} 队伍!",
|
||||
"sa_admin_warns_menu_title": "{gold}{0} {lime}警告",
|
||||
"sa_admin_warns_unwarn": "{lime}成功{default} 取消了对 {gold}{0}{default} 的警告 {gold}{1}{default}!",
|
||||
"sa_admin_warns_unwarn": "{lime}成功移除警告{default} {gold}{0} 对于 {gold}{1}{default}!",
|
||||
"sa_admin_vote_menu_title": "{lime}投票 {gold}{0}",
|
||||
"sa_admin_vote_message": "{lightred}{0}{default} 开始为 {lightred}{1}{default} 进行投票",
|
||||
"sa_admin_vote_message": "{lightred}{0}{default} 发起了 [{lightred}{1}{default}] 的投票",
|
||||
"sa_admin_vote_message_results": "{lime}投票结果 {gold}{0}",
|
||||
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
|
||||
"sa_admin_voice_mute_all": "{Lightred}你已在讲话期间静音了{default}所有玩家",
|
||||
"sa_admin_voice_unmute_all": "{Lime}你已取消了{default}所有玩家的静音",
|
||||
"sa_admin_voice_listen_all": "{Default}你现在可以听到{lime}所有{default}玩家了",
|
||||
"sa_admin_voice_unlisten_all": "{Default}你现在不再听到{lime}所有{default}玩家了",
|
||||
"sa_adminsay_prefix": "{RED}管理员: {lightred}{0}{default}",
|
||||
"sa_adminchat_template_admin": "{LIME}(管理员) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_adminchat_template_player": "{SILVER}(玩家) {lightred}{0}{default}: {lightred}{1}{default}",
|
||||
"sa_discord_log_command": "**{0}** 在服务器 `HOSTNAME` 上发出了 `{1}` 命令",
|
||||
"sa_menu_pluginsmanager_loaded": "{lime}已启用 {default}插件 {lime}{0}",
|
||||
"sa_menu_pluginsmanager_unloaded": "{lightred}已禁用 {default}插件 {lightred}{0}"
|
||||
}
|
||||
"sa_discord_log_command": "**{0}** 在服务器 `HOSTNAME` 上执行了命令 `{1}`",
|
||||
"sa_menu_pluginsmanager_loaded": "{lime}激活了 {default}插件 {lime}{0}",
|
||||
"sa_menu_pluginsmanager_unloaded": "{lightred}停用了 {default}插件 {lightred}{0}"
|
||||
}
|
||||
|
||||
@@ -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.286" />
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdminApi;
|
||||
@@ -8,19 +9,175 @@ namespace CS2_SimpleAdminApi;
|
||||
public interface ICS2_SimpleAdminApi
|
||||
{
|
||||
public static readonly PluginCapability<ICS2_SimpleAdminApi?> PluginCapability = new("simpleadmin:api");
|
||||
|
||||
public event Action? OnSimpleAdminReady;
|
||||
|
||||
/// <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);
|
||||
|
||||
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltied;
|
||||
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?>? OnPlayerPenaltiedAdded;
|
||||
/// <summary>
|
||||
/// Event fired when a 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>
|
||||
/// Shows an admin activity message with a custom translated message (for modules with their own localizer).
|
||||
/// </summary>
|
||||
/// <param name="translatedMessage">Already translated message to display to players.</param>
|
||||
/// <param name="callerName">Name of the admin executing the action (optional).</param>
|
||||
/// <param name="dontPublish">If true, won't trigger publish events.</param>
|
||||
public void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false);
|
||||
|
||||
/// <summary>
|
||||
/// Shows an admin activity message using module's localizer for per-player language support.
|
||||
/// This method sends messages in each player's configured language.
|
||||
/// </summary>
|
||||
/// <param name="moduleLocalizer">The module's IStringLocalizer instance.</param>
|
||||
/// <param name="messageKey">The translation key from the module's lang files.</param>
|
||||
/// <param name="callerName">Name of the admin executing the action (optional).</param>
|
||||
/// <param name="dontPublish">If true, won't trigger publish events.</param>
|
||||
/// <param name="messageArgs">Arguments to format the localized message.</param>
|
||||
public void ShowAdminActivityLocalized(object moduleLocalizer, 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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets target players from command
|
||||
/// </summary>
|
||||
TargetResult? GetTarget(CommandInfo command);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of current valid players, available to call from other plugins.
|
||||
/// </summary>
|
||||
List<CCSPlayerController> GetValidPlayers();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu category.
|
||||
/// </summary>
|
||||
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu in a category.
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuName">Display name of the menu.</param>
|
||||
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
/// </summary>
|
||||
void UnregisterMenu(string categoryId, string menuId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a menu with an automatic back button.
|
||||
/// </summary>
|
||||
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a menu with a list of players with filter and action.
|
||||
/// </summary>
|
||||
object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an option to the menu (extension method helper).
|
||||
/// </summary>
|
||||
void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a submenu to the menu (extension method helper).
|
||||
/// </summary>
|
||||
void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory, bool disabled = false, string? permission = null);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a menu for a player.
|
||||
/// </summary>
|
||||
void OpenMenu(object menu, CCSPlayerController player);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Numerics;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CS2_SimpleAdminApi;
|
||||
|
||||
@@ -25,11 +25,16 @@ public class PlayerInfo(
|
||||
public int TotalGags { get; set; } = totalGags;
|
||||
public int TotalSilences { get; set; } = totalSilences;
|
||||
public int TotalWarns { get; set; } = totalWarns;
|
||||
public bool WaitingForKick { get; set; } = false;
|
||||
public List<(ulong SteamId, string PlayerName)> AccountsAssociated { get; set; } = [];
|
||||
public DiePosition? DiePosition { get; set; }
|
||||
public bool IsLoaded { get; set; }
|
||||
}
|
||||
|
||||
public struct DiePosition(Vector? position = null, QAngle? angle = null)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>AntiDLL_CS2_SimpleAdmin</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.305" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="AntiDLL.API">
|
||||
<HintPath>AntiDLL.API.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>CS2-SimpleAdminApi.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
16
Modules/AntiDLL-CS2-SimpleAdmin/AntiDLL-CS2-SimpleAdmin.sln
Normal file
16
Modules/AntiDLL-CS2-SimpleAdmin/AntiDLL-CS2-SimpleAdmin.sln
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntiDLL-CS2-SimpleAdmin", "AntiDLL-CS2-SimpleAdmin.csproj", "{21D8E512-1FA9-41DD-B955-709704CEC377}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{21D8E512-1FA9-41DD-B955-709704CEC377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{21D8E512-1FA9-41DD-B955-709704CEC377}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{21D8E512-1FA9-41DD-B955-709704CEC377}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{21D8E512-1FA9-41DD-B955-709704CEC377}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user