From 9154748ce6eef5f7e9fbbebcb58907203a44fe1a Mon Sep 17 00:00:00 2001
From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com>
Date: Fri, 3 Oct 2025 02:19:05 +0200
Subject: [PATCH 01/40] Update build.yml
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 45bc423..69c76c7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -80,7 +80,7 @@ jobs:
with:
name: CS2-SimpleAdmin-Build-Artifacts
path: |
- CS2-SimpleAdmin${{ steps.get_version.outputs.VERSION }}.zip
+ CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip
StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip
StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip
From b7371adf26d8a106954506d0714dbdeedd47106e Mon Sep 17 00:00:00 2001
From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com>
Date: Fri, 3 Oct 2025 02:23:07 +0200
Subject: [PATCH 02/40] Update build.yml
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 69c76c7..e34b240 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -97,7 +97,7 @@ jobs:
name: CS2-SimpleAdmin-Build-Artifacts
path: .
- name: Unzip main build artifact
- run: unzip CS2-SimpleAdmin${{ needs.build.outputs.build_version }}.zip -d ./counterstrikesharp
+ run: unzip CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip -d ./counterstrikesharp
- name: Publish combined release
uses: ncipollo/release-action@v1.14.0
with:
From 23d174c4a5370895ecb9111ac84d06e01f7b1d59 Mon Sep 17 00:00:00 2001
From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com>
Date: Fri, 3 Oct 2025 02:26:07 +0200
Subject: [PATCH 03/40] Update build.yml
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e34b240..39f323b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -59,7 +59,7 @@ jobs:
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 }}
+ run: zip -r CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip ${{ env.OUTPUT_PATH }}
- name: Extract & Zip StatusBlocker Linux
run: |
From 640e618f3b9f78909fad2370928a6531cd564cc6 Mon Sep 17 00:00:00 2001
From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com>
Date: Fri, 3 Oct 2025 02:34:03 +0200
Subject: [PATCH 04/40] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 19e53e7..19cd89f 100644
--- a/README.md
+++ b/README.md
@@ -103,4 +103,4 @@ If you find CS2-SimpleAdmin useful, consider supporting the ongoing development:
---
## 📄 License
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+This project is licensed under the GPL-3.0 license - see the [LICENSE](LICENSE) file for details.
From cc54b9e879b62d9a166f3a7677474a86dc535780 Mon Sep 17 00:00:00 2001
From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com>
Date: Fri, 3 Oct 2025 02:35:45 +0200
Subject: [PATCH 05/40] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 19cd89f..2a73b8e 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,8 @@
-# [](https://discord.com/channels/1160907911501991946/1180627186865156126)
- CS2-SimpleAdmin
+[](https://discord.com/channels/1160907911501991946/1180627186865156126)
+# CS2-SimpleAdmin
---
From e1e66441f2e8144b337e857952b9c670206fd27e Mon Sep 17 00:00:00 2001
From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com>
Date: Fri, 3 Oct 2025 12:10:50 +0200
Subject: [PATCH 06/40] Remove custom ClearBuildFiles target and cleanup csproj
Commented out the GenerateDependencyFile property and removed the ClearBuildFiles target from the project file. Also reformatted and cleaned up the ItemGroup for migration scripts, improving maintainability.
---
CS2-SimpleAdmin/CS2-SimpleAdmin.csproj | 200 ++++++++++++-------------
1 file changed, 94 insertions(+), 106 deletions(-)
diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj
index e0e6c88..395249f 100644
--- a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj
+++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj
@@ -15,7 +15,7 @@
false
true
false
- false
+
@@ -34,111 +34,99 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
From 78318102fe6d96fc58f1f271da54dabb70899d48 Mon Sep 17 00:00:00 2001
From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com>
Date: Sun, 19 Oct 2025 03:12:58 +0200
Subject: [PATCH 07/40] Refactor fun commands to external module
Commented out fun command implementations (noclip, godmode, freeze, unfreeze, resize) in funcommands.cs and removed their registration from RegisterCommands.cs. These commands are now intended to be provided by the new CS2-SimpleAdmin_FunCommands external module, improving modularity and maintainability.
---
.gitignore | 1 +
CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs | 197 +++++-
CS2-SimpleAdmin/CS2-SimpleAdmin.cs | 13 +-
CS2-SimpleAdmin/CS2-SimpleAdmin.csproj | 6 +
CS2-SimpleAdmin/Commands/RegisterCommands.cs | 55 +-
CS2-SimpleAdmin/Commands/basebans.cs | 2 +-
CS2-SimpleAdmin/Commands/basecommands.cs | 6 +-
CS2-SimpleAdmin/Commands/basecomms.cs | 2 +-
CS2-SimpleAdmin/Commands/funcommands.cs | 614 +++++++++---------
CS2-SimpleAdmin/Commands/playercommands.cs | 517 ---------------
CS2-SimpleAdmin/Database/Migration.cs | 27 +-
.../Mysql/006_ServerGroupsFeature.sql | 1 -
.../Mysql/009_BanAllUsedIpAddress.sql | 4 +-
.../013_AddNameColumnToSaPlayerIpsTable.sql | 8 -
.../Mysql/016_OptimizeTablesAndIndexes.sql | 33 +
.../Sqlite/009_BanAllUsedIpAddress.sql | 7 +-
.../Sqlite/016_OptimizeTablesAndIndexes.sql | 33 +
.../Database/MysqlDatabaseProvider.cs | 9 +
CS2-SimpleAdmin/Events.cs | 19 +-
.../Extensions/PlayerExtensions.cs | 4 +-
CS2-SimpleAdmin/Helper.cs | 119 +++-
CS2-SimpleAdmin/Managers/BanManager.cs | 1 -
CS2-SimpleAdmin/Managers/CacheManager.cs | 217 ++++---
CS2-SimpleAdmin/Managers/PlayerManager.cs | 208 +++---
CS2-SimpleAdmin/Managers/ServerManager.cs | 32 +-
CS2-SimpleAdmin/Menus/AdminMenu.cs | 85 +--
CS2-SimpleAdmin/Menus/BasicMenu.cs | 602 +++++++++++++++++
CS2-SimpleAdmin/Menus/CustomCommandsMenu.cs | 4 +-
CS2-SimpleAdmin/Menus/DurationMenu.cs | 2 -
CS2-SimpleAdmin/Menus/FunActionsMenu.cs | 534 +++++++--------
CS2-SimpleAdmin/Menus/ManageAdminsMenu.cs | 4 +-
CS2-SimpleAdmin/Menus/ManagePlayersMenu.cs | 4 +-
CS2-SimpleAdmin/Menus/ManageServerMenu.cs | 4 +-
CS2-SimpleAdmin/Menus/MenuBuilder.cs | 170 +++++
CS2-SimpleAdmin/Menus/MenuManager.cs | 190 ++++++
CS2-SimpleAdmin/Menus/PlayersMenu.cs | 6 +-
CS2-SimpleAdmin/VERSION | 2 +-
CS2-SimpleAdminApi/ICS2-SimpleAdminApi.cs | 72 ++
.../CS2-SimpleAdminApi.dll | Bin 0 -> 11264 bytes
.../CS2-SimpleAdmin_FunCommands.sln | 16 +
.../CS2-SimpleAdmin_FunCommands/Actions.cs | 237 +++++++
.../CS2-SimpleAdmin_FunCommands.cs | 492 ++++++++++++++
.../CS2-SimpleAdmin_FunCommands.csproj | 24 +
.../CS2-SimpleAdmin_FunCommands/Commands.cs | 320 +++++++++
.../CS2-SimpleAdmin_FunCommands/Config.cs | 21 +
.../CS2-SimpleAdmin_FunCommands/Events.cs | 69 ++
.../CS2-SimpleAdmin_FunCommands/Menus.cs | 391 +++++++++++
.../PlayerExtensions.cs | 271 ++++++++
.../CS2-SimpleAdmin_FunCommands/lang/ar.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/de.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/en.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/es.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/fa.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/fr.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/lv.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/pl.json | 40 ++
.../lang/pt-BR.json | 40 ++
.../lang/pt-PT.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/ru.json | 40 ++
.../CS2-SimpleAdmin_FunCommands/lang/tr.json | 40 ++
.../lang/zh-Hans.json | 40 ++
Modules/CS2-SimpleAdmin_FunCommands/README.md | 384 +++++++++++
.../CS2-SimpleAdmin_FunCommands/global.json | 7 +
.../StatusBlocker-v1.0.3-linux.tar.gz | Bin 393195 -> 0 bytes
.../StatusBlocker-v1.0.3-windows.tar.gz | Bin 190719 -> 0 bytes
.../StatusBlocker-v1.0.7-linux.tar.gz | Bin 0 -> 292291 bytes
.../StatusBlocker-v1.0.7-windows.tar.gz | Bin 0 -> 195834 bytes
Modules/MODULE_DEVELOPMENT.md | 602 +++++++++++++++++
Modules/TRANSLATION_EXAMPLE.md | 268 ++++++++
69 files changed, 5943 insertions(+), 1493 deletions(-)
create mode 100644 CS2-SimpleAdmin/Database/Migrations/Mysql/016_OptimizeTablesAndIndexes.sql
create mode 100644 CS2-SimpleAdmin/Database/Migrations/Sqlite/016_OptimizeTablesAndIndexes.sql
create mode 100644 CS2-SimpleAdmin/Menus/BasicMenu.cs
create mode 100644 CS2-SimpleAdmin/Menus/MenuBuilder.cs
create mode 100644 CS2-SimpleAdmin/Menus/MenuManager.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdminApi.dll
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.sln
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Actions.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Commands.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Config.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Events.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Menus.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/PlayerExtensions.cs
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/ar.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/de.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/en.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/es.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/fa.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/fr.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/lv.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/pl.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/pt-BR.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/pt-PT.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/ru.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/tr.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/lang/zh-Hans.json
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/README.md
create mode 100644 Modules/CS2-SimpleAdmin_FunCommands/global.json
delete mode 100644 Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.0.3-linux.tar.gz
delete mode 100644 Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.0.3-windows.tar.gz
create mode 100644 Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.0.7-linux.tar.gz
create mode 100644 Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN/StatusBlocker-v1.0.7-windows.tar.gz
create mode 100644 Modules/MODULE_DEVELOPMENT.md
create mode 100644 Modules/TRANSLATION_EXAMPLE.md
diff --git a/.gitignore b/.gitignore
index 8eebf6c..28e335d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ CS2-SimpleAdmin.sln.DotSettings.user
Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user
CS2-SimpleAdmin_BanSoundModule — kopia
*.user
+CLAUDE.md
diff --git a/CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs b/CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs
index 56c1b7f..95c27db 100644
--- a/CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs
+++ b/CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs
@@ -1,47 +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.SteamID];
+ 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> GetPlayerMuteStatus(CCSPlayerController player)
+ public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
+ public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
+ public int? GetServerId() => CS2_SimpleAdmin.ServerId;
+
+ public Dictionary> GetPlayerMuteStatus(
+ CCSPlayerController player)
{
return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot);
}
-
+
public event Action? OnPlayerPenaltied;
public event Action? OnPlayerPenaltiedAdded;
public event Action? OnAdminShowActivity;
public event Action? OnAdminToggleSilent;
public void OnPlayerPenaltiedEvent(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
- int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
-
+ int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration,
+ penaltyId, CS2_SimpleAdmin.ServerId);
+
public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
- int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
-
- public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
+ int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration,
+ penaltyId, CS2_SimpleAdmin.ServerId);
- public void OnAdminToggleSilentEvent(int slot, bool status) => OnAdminToggleSilent?.Invoke(slot, status);
+ public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false,
+ params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
- public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
+ 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)
{
@@ -79,8 +89,9 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
}
-
- public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
+
+ public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason,
+ int duration = -1)
{
switch (penaltyType)
{
@@ -123,12 +134,12 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
{
Helper.LogCommand(caller, command);
}
-
+
public bool IsAdminSilent(CCSPlayerController player)
{
return CS2_SimpleAdmin.SilentPlayers.Contains(player.Slot);
- }
-
+ }
+
public HashSet ListSilentAdminsSlots()
{
return CS2_SimpleAdmin.SilentPlayers;
@@ -147,7 +158,7 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
list = new List();
RegisterCommands._commandDefinitions[name] = list;
}
-
+
list.Add(definition);
}
@@ -162,9 +173,145 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
CS2_SimpleAdmin.Instance.RemoveCommand(commandName, definition.Callback);
}
}
-
- public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
+
+ 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 menuFactory, string? permission = null)
+ {
+ Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission);
+ 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 GetValidPlayers()
+ {
+ return Helper.GetValidPlayers();
+ }
+
+ public object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin,
+ Func filter, Action 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 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 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);
+ }
}
\ No newline at end of file
diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs
index b1d53f7..825eb09 100644
--- a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs
+++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs
@@ -1,5 +1,4 @@
-using System.Reflection;
-using CounterStrikeSharp.API;
+using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Capabilities;
@@ -8,6 +7,7 @@ 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;
@@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
public override string ModuleAuthor => "daffyy & Dliix66";
- public override string ModuleVersion => "1.7.7-alpha-10";
+ public override string ModuleVersion => "1.7.8-beta-1";
public override void Load(bool hotReload)
{
@@ -67,6 +67,9 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig ReloadAdmins(null));
RegisterEvents();
- RegisterCommands.InitializeCommands();
+ AddTimer(0.5f, RegisterCommands.InitializeCommands);
if (!CoreConfig.UnlockConCommands)
{
@@ -232,7 +235,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -126,6 +129,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/CS2-SimpleAdmin/Commands/RegisterCommands.cs b/CS2-SimpleAdmin/Commands/RegisterCommands.cs
index d323e29..584f3ec 100644
--- a/CS2-SimpleAdmin/Commands/RegisterCommands.cs
+++ b/CS2-SimpleAdmin/Commands/RegisterCommands.cs
@@ -65,24 +65,11 @@ public static class RegisterCommands
new("css_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
- new("css_noclip", CS2_SimpleAdmin.Instance.OnNoclipCommand),
- new("css_freeze", CS2_SimpleAdmin.Instance.OnFreezeCommand),
- new("css_unfreeze", CS2_SimpleAdmin.Instance.OnUnfreezeCommand),
- new("css_godmode", CS2_SimpleAdmin.Instance.OnGodCommand),
-
new("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
new("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
- new("css_give", CS2_SimpleAdmin.Instance.OnGiveCommand),
- new("css_strip", CS2_SimpleAdmin.Instance.OnStripCommand),
- new("css_hp", CS2_SimpleAdmin.Instance.OnHpCommand),
- new("css_speed", CS2_SimpleAdmin.Instance.OnSpeedCommand),
- new("css_gravity", CS2_SimpleAdmin.Instance.OnGravityCommand),
- new("css_resize", CS2_SimpleAdmin.Instance.OnResizeCommand),
- new("css_money", CS2_SimpleAdmin.Instance.OnMoneyCommand),
new("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
new("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
new("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
- new("css_respawn", CS2_SimpleAdmin.Instance.OnRespawnCommand),
new("css_tp", CS2_SimpleAdmin.Instance.OnGotoCommand),
new("css_bring", CS2_SimpleAdmin.Instance.OnBringCommand),
new("css_pluginsmanager", CS2_SimpleAdmin.Instance.OnPluginManagerCommand),
@@ -160,23 +147,12 @@ 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_resize", new Command { Aliases = ["css_resize", "css_size"] } },
- { "css_money", new Command { Aliases = ["css_money"] } },
{ "css_team", new Command { Aliases = ["css_team"] } },
{ "css_rename", new Command { Aliases = ["css_rename"] } },
{ "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"] } },
@@ -205,25 +181,26 @@ public static class RegisterCommands
var commandsConfig = JsonSerializer.Deserialize(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 RegisterCommands._commandDefinitions)
+ foreach (var (name, definitions) in _commandDefinitions)
{
foreach (var definition in definitions)
{
diff --git a/CS2-SimpleAdmin/Commands/basebans.cs b/CS2-SimpleAdmin/Commands/basebans.cs
index 7b15b55..761ecbd 100644
--- a/CS2-SimpleAdmin/Commands/basebans.cs
+++ b/CS2-SimpleAdmin/Commands/basebans.cs
@@ -316,7 +316,7 @@ public partial class CS2_SimpleAdmin
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;
diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs
index 290bbd0..8d95c33 100644
--- a/CS2-SimpleAdmin/Commands/basecommands.cs
+++ b/CS2-SimpleAdmin/Commands/basecommands.cs
@@ -33,7 +33,7 @@ public partial class CS2_SimpleAdmin
[CommandHelper(usage: "[#userid or name]", whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void OnPenaltiesCommand(CCSPlayerController? caller, CommandInfo command)
{
- if (caller == null || caller.IsValid == false || !caller.UserId.HasValue || DatabaseProvider == null)
+ if (caller == null || !caller.IsValid || !caller.UserId.HasValue || DatabaseProvider == null)
return;
var userId = caller.UserId.Value;
@@ -160,7 +160,7 @@ public partial class CS2_SimpleAdmin
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void OnAdminVoiceCommand(CCSPlayerController? caller, CommandInfo command)
{
- if (caller == null || caller.IsValid == false)
+ if (caller == null || !caller.IsValid)
return;
if (command.ArgCount > 1)
@@ -205,7 +205,7 @@ public partial class CS2_SimpleAdmin
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)]
public void OnAdminCommand(CCSPlayerController? caller, CommandInfo command)
{
- if (caller == null || caller.IsValid == false)
+ if (caller == null || !caller.IsValid)
return;
AdminMenu.OpenMenu(caller);
diff --git a/CS2-SimpleAdmin/Commands/basecomms.cs b/CS2-SimpleAdmin/Commands/basecomms.cs
index 0cc9a8c..e202c1e 100644
--- a/CS2-SimpleAdmin/Commands/basecomms.cs
+++ b/CS2-SimpleAdmin/Commands/basecomms.cs
@@ -959,7 +959,7 @@ public partial class CS2_SimpleAdmin
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;
diff --git a/CS2-SimpleAdmin/Commands/funcommands.cs b/CS2-SimpleAdmin/Commands/funcommands.cs
index 5f4b64f..0eb040c 100644
--- a/CS2-SimpleAdmin/Commands/funcommands.cs
+++ b/CS2-SimpleAdmin/Commands/funcommands.cs
@@ -1,307 +1,307 @@
-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
-{
- ///
- /// Enables or disables no-clip mode for specified player(s).
- ///
- /// The player issuing the command.
- /// The command input containing targets.
- [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);
- }
-
- ///
- /// Toggles no-clip mode for a player and shows admin activity messages.
- ///
- /// The player/admin toggling no-clip.
- /// The target player whose no-clip state changes.
- /// Optional caller name for messages.
- /// Optional command info for logging.
- 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)}");
- }
-
- ///
- /// Enables or disables god mode for specified player(s).
- ///
- /// The player issuing the command.
- /// The command input containing targets.
-
- [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);
- }
-
- ///
- /// Toggles god mode for a player and notifies admins.
- ///
- /// The player/admin toggling god mode.
- /// The target player whose god mode changes.
- /// Optional command info for logging.
- 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);
- }
- }
-
- ///
- /// Freezes target player(s) for an optional specified duration.
- ///
- /// The player issuing the freeze command.
- /// The command input containing targets and duration.
- [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);
- }
-
- ///
- /// Resizes the target player(s) models to a specified scale.
- ///
- /// The player issuing the resize command.
- /// The command input containing targets and scale factor.
- [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);
- }
-
- ///
- /// Freezes a single player and optionally schedules automatic unfreeze after a duration.
- ///
- /// The player/admin freezing the player.
- /// The player to freeze.
- /// Duration of freeze in seconds.
- /// Optional name for notifications.
- /// Optional command info for logging.
- 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}");
- }
-
- ///
- /// Unfreezes target player(s).
- ///
- /// The player issuing the unfreeze command.
- /// The command input with targets.
- [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);
- }
-
- ///
- /// Unfreezes a single player and notifies admins.
- ///
- /// The player/admin unfreezing the player.
- /// The player to unfreeze.
- /// Optional name for notifications.
- /// Optional command info for logging.
- 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)}");
- }
-}
\ No newline at end of file
+// 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
+// {
+// ///
+// /// Enables or disables no-clip mode for specified player(s).
+// ///
+// /// The player issuing the command.
+// /// The command input containing targets.
+// [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);
+// }
+//
+// ///
+// /// Toggles no-clip mode for a player and shows admin activity messages.
+// ///
+// /// The player/admin toggling no-clip.
+// /// The target player whose no-clip state changes.
+// /// Optional caller name for messages.
+// /// Optional command info for logging.
+// 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)}");
+// }
+//
+// ///
+// /// Enables or disables god mode for specified player(s).
+// ///
+// /// The player issuing the command.
+// /// The command input containing targets.
+//
+// [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);
+// }
+//
+// ///
+// /// Toggles god mode for a player and notifies admins.
+// ///
+// /// The player/admin toggling god mode.
+// /// The target player whose god mode changes.
+// /// Optional command info for logging.
+// 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);
+// }
+// }
+//
+// ///
+// /// Freezes target player(s) for an optional specified duration.
+// ///
+// /// The player issuing the freeze command.
+// /// The command input containing targets and duration.
+// [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);
+// }
+//
+// ///
+// /// Resizes the target player(s) models to a specified scale.
+// ///
+// /// The player issuing the resize command.
+// /// The command input containing targets and scale factor.
+// [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);
+// }
+//
+// ///
+// /// Freezes a single player and optionally schedules automatic unfreeze after a duration.
+// ///
+// /// The player/admin freezing the player.
+// /// The player to freeze.
+// /// Duration of freeze in seconds.
+// /// Optional name for notifications.
+// /// Optional command info for logging.
+// 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}");
+// }
+//
+// ///
+// /// Unfreezes target player(s).
+// ///
+// /// The player issuing the unfreeze command.
+// /// The command input with targets.
+// [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);
+// }
+//
+// ///
+// /// Unfreezes a single player and notifies admins.
+// ///
+// /// The player/admin unfreezing the player.
+// /// The player to unfreeze.
+// /// Optional name for notifications.
+// /// Optional command info for logging.
+// 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)}");
+// }
+// }
\ No newline at end of file
diff --git a/CS2-SimpleAdmin/Commands/playercommands.cs b/CS2-SimpleAdmin/Commands/playercommands.cs
index b66adf2..25385b5 100644
--- a/CS2-SimpleAdmin/Commands/playercommands.cs
+++ b/CS2-SimpleAdmin/Commands/playercommands.cs
@@ -11,9 +11,6 @@ namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
- internal static readonly Dictionary SpeedPlayers = [];
- internal static readonly Dictionary GravityPlayers = [];
-
///
/// Executes the 'slay' command, forcing the targeted players to commit suicide.
/// Checks player validity and permissions.
@@ -72,451 +69,6 @@ public partial class CS2_SimpleAdmin
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
- ///
- /// Executes the 'give' command to provide a specified weapon to targeted players.
- /// Enforces server rules for prohibited weapons.
- ///
- /// Player or console issuing the command.
- /// Command details, including targets and weapon name.
- [RequiresPermissions("@css/cheats")]
- [CommandHelper(minArgs: 2, usage: "<#userid or name> ", 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 { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
- var weaponName = command.GetArg(2);
-
- // 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);
- });
-
- Helper.LogCommand(caller, command);
- }
-
-
- ///
- /// Gives a weapon identified by name to a player, handling ambiguous matches and logging.
- ///
- /// Admin issuing the command.
- /// Target player to receive the weapon.
- /// Weapon name or partial name.
- /// Optional name to display in notifications.
- /// Optional command info for logging.
- 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}");
-
- // 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, false, adminActivityArgs);
- }
- }
-
- ///
- /// Gives a specific weapon to a player, with notifications and logging.
- ///
- /// Admin issuing the command.
- /// Target player.
- /// Weapon item object.
- /// Optional caller name for notifications.
- /// Optional command info.
- 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()}");
-
- // 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, false, adminActivityArgs);
- }
- }
-
- ///
- /// Executes the 'strip' command, removing all weapons from targeted players.
- /// Checks player validity and permissions.
- ///
- /// Player or console issuing the command.
- /// Command details including targets.
- [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 { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
-
- playersToTarget.ForEach(player =>
- {
- if (caller!.CanTarget(player))
- {
- StripWeapons(caller, player, callerName, command);
- }
- });
-
- Helper.LogCommand(caller, command);
- }
-
- ///
- /// Removes all weapons from a player, with notifications and logging.
- ///
- /// Admin or console issuing the strip command.
- /// Target player.
- /// Optional caller name.
- /// Optional command info for logging.
- 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.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || 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)}");
-
- // 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, false, adminActivityArgs);
- }
- }
-
- ///
- /// Sets health value on targeted players.
- ///
- /// Admin or console issuing the command.
- /// Command details including targets and health value.
- [RequiresPermissions("@css/slay")]
- [CommandHelper(minArgs: 1, usage: "<#userid or name> ", 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 { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
-
- playersToTarget.ForEach(player =>
- {
- if (caller!.CanTarget(player))
- {
- SetHp(caller, player, health, command);
- }
- });
-
- Helper.LogCommand(caller, command);
- }
-
- ///
- /// Changes health of a player and logs the action.
- ///
- /// Admin or console calling the method.
- /// Target player.
- /// Health value to set.
- /// Optional command info.
- 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}");
-
- // 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, false, adminActivityArgs);
- }
- }
-
- ///
- /// Sets movement speed on targeted players.
- ///
- /// Admin or console issuing the command.
- /// Command details including targets and speed.
- [RequiresPermissions("@css/slay")]
- [CommandHelper(minArgs: 1, usage: "<#userid or name> ", 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 { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
-
- playersToTarget.ForEach(player =>
- {
- if (player.Connected != PlayerConnectedState.PlayerConnected)
- return;
-
- if (caller!.CanTarget(player))
- {
- SetSpeed(caller, player, speed, command);
- }
- });
-
- Helper.LogCommand(caller, command);
- }
-
- ///
- /// Changes speed of a player and logs the action.
- ///
- /// Admin or console calling the method.
- /// Target player.
- /// Speed value to set.
- /// Optional command info.
- 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);
- else
- SpeedPlayers[player] = speed;
-
- // Log the command
- if (command == null)
- Helper.LogCommand(caller, $"css_speed {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {speed}");
-
- // 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, false, adminActivityArgs);
- }
- }
-
- ///
- /// Sets gravity on targeted players.
- ///
- /// Admin or console issuing the command.
- /// Command details including targets and gravity value.
- [RequiresPermissions("@css/slay")]
- [CommandHelper(minArgs: 1, usage: "<#userid or name> ", 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 { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
-
- playersToTarget.ForEach(player =>
- {
- if (player.Connected != PlayerConnectedState.PlayerConnected)
- return;
-
- if (caller!.CanTarget(player))
- {
- SetGravity(caller, player, gravity, command);
- }
- });
-
- Helper.LogCommand(caller, command);
- }
-
- ///
- /// Changes gravity of a player and logs the action.
- ///
- /// Admin or console calling the method.
- /// Target player.
- /// Gravity value to set.
- /// Optional command info.
- 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}");
-
- // 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, false, adminActivityArgs);
- }
- }
-
- ///
- /// Sets the money amount for the targeted players.
- ///
- /// The player/admin executing the command.
- /// The command containing target player and money value.
- [RequiresPermissions("@css/slay")]
- [CommandHelper(minArgs: 1, usage: "<#userid or name> ", 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 { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
-
- playersToTarget.ForEach(player =>
- {
- if (player.Connected != PlayerConnectedState.PlayerConnected)
- return;
-
- if (caller!.CanTarget(player))
- {
- SetMoney(caller, player, money, command);
- }
- });
-
- Helper.LogCommand(caller, command);
- }
-
- ///
- /// Applies money value to a single targeted player and logs the operation.
- ///
- /// The player/admin setting the money.
- /// The player whose money will be set.
- /// The value of money to set.
- /// Optional command info for logging.
- 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}");
-
- // 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, false, adminActivityArgs);
- }
- }
-
///
/// Applies damage as a slap effect to the targeted players.
///
@@ -802,75 +354,6 @@ public partial class CS2_SimpleAdmin
});
}
- ///
- /// Respawns targeted players, restoring their state.
- ///
- /// The admin or player issuing respawn.
- /// The command including target players.
- [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);
- }
- });
-
- Helper.LogCommand(caller, command);
- }
-
- ///
- /// Respawns a specified player and updates admin notifications.
- ///
- /// Admin or player executing respawn.
- /// Player to respawn.
- /// Optional admin name.
- /// Optional command info.
- 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(player.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(player);
-
- if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.SteamID, out var value) && value.DiePosition != null)
- playerPawn.Teleport(value.DiePosition?.Position, value.DiePosition?.Angle);
-
- // Log the command
- if (command == null)
- Helper.LogCommand(caller, $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
-
- // 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, false, adminActivityArgs);
- }
-
///
/// Teleports targeted player(s) to another player's location.
///
diff --git a/CS2-SimpleAdmin/Database/Migration.cs b/CS2-SimpleAdmin/Database/Migration.cs
index 7a25747..5b97b9d 100644
--- a/CS2-SimpleAdmin/Database/Migration.cs
+++ b/CS2-SimpleAdmin/Database/Migration.cs
@@ -17,16 +17,27 @@ public class Migration(string migrationsPath)
if (files.Count == 0) return;
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
-
await using (var cmd = connection.CreateCommand())
{
- cmd.CommandText = """
- CREATE TABLE IF NOT EXISTS sa_migrations (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- version TEXT NOT NULL
- );
-
- """;
+ 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
+ );
+ """;
+ }
await cmd.ExecuteNonQueryAsync();
}
diff --git a/CS2-SimpleAdmin/Database/Migrations/Mysql/006_ServerGroupsFeature.sql b/CS2-SimpleAdmin/Database/Migrations/Mysql/006_ServerGroupsFeature.sql
index 4424189..4256cec 100644
--- a/CS2-SimpleAdmin/Database/Migrations/Mysql/006_ServerGroupsFeature.sql
+++ b/CS2-SimpleAdmin/Database/Migrations/Mysql/006_ServerGroupsFeature.sql
@@ -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;
\ No newline at end of file
diff --git a/CS2-SimpleAdmin/Database/Migrations/Mysql/009_BanAllUsedIpAddress.sql b/CS2-SimpleAdmin/Database/Migrations/Mysql/009_BanAllUsedIpAddress.sql
index 2fa6688..d05bec7 100644
--- a/CS2-SimpleAdmin/Database/Migrations/Mysql/009_BanAllUsedIpAddress.sql
+++ b/CS2-SimpleAdmin/Database/Migrations/Mysql/009_BanAllUsedIpAddress.sql
@@ -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;
diff --git a/CS2-SimpleAdmin/Database/Migrations/Mysql/013_AddNameColumnToSaPlayerIpsTable.sql b/CS2-SimpleAdmin/Database/Migrations/Mysql/013_AddNameColumnToSaPlayerIpsTable.sql
index 81a26a6..aa455a1 100644
--- a/CS2-SimpleAdmin/Database/Migrations/Mysql/013_AddNameColumnToSaPlayerIpsTable.sql
+++ b/CS2-SimpleAdmin/Database/Migrations/Mysql/013_AddNameColumnToSaPlayerIpsTable.sql
@@ -1,11 +1,3 @@
-DELETE FROM `sa_players_ips`
-WHERE `id` NOT IN (
- SELECT * FROM (
- SELECT MIN(`id`)
- FROM `sa_players_ips`
- GROUP BY `steamid`
- ) AS `keep_ids`
-);
DELETE FROM sa_players_ips WHERE INET_ATON(address) IS NULL AND address IS NOT NULL;
UPDATE `sa_players_ips` SET `address` = INET_ATON(address);
ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL;
diff --git a/CS2-SimpleAdmin/Database/Migrations/Mysql/016_OptimizeTablesAndIndexes.sql b/CS2-SimpleAdmin/Database/Migrations/Mysql/016_OptimizeTablesAndIndexes.sql
new file mode 100644
index 0000000..c674d89
--- /dev/null
+++ b/CS2-SimpleAdmin/Database/Migrations/Mysql/016_OptimizeTablesAndIndexes.sql
@@ -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`);
diff --git a/CS2-SimpleAdmin/Database/Migrations/Sqlite/009_BanAllUsedIpAddress.sql b/CS2-SimpleAdmin/Database/Migrations/Sqlite/009_BanAllUsedIpAddress.sql
index e28a4b3..7a141c5 100644
--- a/CS2-SimpleAdmin/Database/Migrations/Sqlite/009_BanAllUsedIpAddress.sql
+++ b/CS2-SimpleAdmin/Database/Migrations/Sqlite/009_BanAllUsedIpAddress.sql
@@ -1,7 +1,6 @@
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
- `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`steamid` INTEGER NOT NULL,
`address` VARCHAR(64) NOT NULL,
- `used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (`steamid`, `address`)
- );
\ No newline at end of file
+ `used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`steamid`, `address`)
+);
\ No newline at end of file
diff --git a/CS2-SimpleAdmin/Database/Migrations/Sqlite/016_OptimizeTablesAndIndexes.sql b/CS2-SimpleAdmin/Database/Migrations/Sqlite/016_OptimizeTablesAndIndexes.sql
new file mode 100644
index 0000000..749229a
--- /dev/null
+++ b/CS2-SimpleAdmin/Database/Migrations/Sqlite/016_OptimizeTablesAndIndexes.sql
@@ -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`);
diff --git a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs
index 91de7bb..9e79d93 100644
--- a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs
+++ b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs
@@ -9,6 +9,15 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
{
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;
}
diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs
index 68847f4..4a3755a 100644
--- a/CS2-SimpleAdmin/Events.cs
+++ b/CS2-SimpleAdmin/Events.cs
@@ -96,9 +96,6 @@ public partial class CS2_SimpleAdmin
CachedPlayers.Remove(player);
SilentPlayers.Remove(player.Slot);
- GodPlayers.Remove(player.Slot);
- SpeedPlayers.Remove(player);
- GravityPlayers.Remove(player);
if (player.IsBot)
return HookResult.Continue;
@@ -247,10 +244,6 @@ public partial class CS2_SimpleAdmin
Logger.LogCritical("[OnRoundStart]");
#endif
- GodPlayers.Clear();
- SpeedPlayers.Clear();
- GravityPlayers.Clear();
-
foreach (var player in PlayersInfo.Values)
{
player.DiePosition = null;
@@ -467,8 +460,6 @@ public partial class CS2_SimpleAdmin
GodPlayers.Clear();
SilentPlayers.Clear();
- SpeedPlayers.Clear();
- GravityPlayers.Clear();
PlayerPenaltyManager.RemoveAllPenalties();
}
@@ -494,13 +485,9 @@ public partial class CS2_SimpleAdmin
{
var player = @event.Userid;
- if (player?.UserId == null || !player.IsValid || player.IsHLTV || player.Connected != PlayerConnectedState.PlayerConnected)
- return HookResult.Continue;
-
- SpeedPlayers.Remove(player);
- GravityPlayers.Remove(player);
-
- if (!PlayersInfo.ContainsKey(player.SteamID) || @event.Attacker == null)
+ if (player?.UserId == null || !player.IsValid || player.IsHLTV ||
+ player.Connected != PlayerConnectedState.PlayerConnected || !PlayersInfo.ContainsKey(player.SteamID) ||
+ @event.Attacker == null)
return HookResult.Continue;
var playerPosition = player.PlayerPawn.Value?.AbsOrigin;
diff --git a/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs b/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs
index 0477263..c2d8e07 100644
--- a/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs
+++ b/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs
@@ -335,10 +335,10 @@ 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);
}
}
diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs
index 84b7041..089e083 100644
--- a/CS2-SimpleAdmin/Helper.cs
+++ b/CS2-SimpleAdmin/Helper.cs
@@ -441,20 +441,20 @@ internal static class Helper
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;
-
+
if (string.IsNullOrWhiteSpace(callerName))
callerName = CS2_SimpleAdmin._localizer["sa_console"];
var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();
-
- if (dontPublish == false && publishActions.Any(messageKey.Contains))
+
+ 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++)
// {
@@ -478,7 +478,7 @@ internal static class Helper
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();
@@ -499,6 +499,89 @@ internal static class Helper
}
}
+ ///
+ /// Shows admin activity with a custom translated message (for modules with their own localizer).
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Shows admin activity using module's localizer for per-player language support.
+ /// Each player receives the message in their configured language using SendLocalizedMessage.
+ ///
+ 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
public void UnRegisterCommand(string name);
+
+ ///
+ /// Gets target players from command
+ ///
+ TargetResult? GetTarget(CommandInfo command);
+
+ ///
+ /// Returns the list of current valid players, available to call from other plugins.
+ ///
+ List GetValidPlayers();
+
+ ///
+ /// Registers a menu category.
+ ///
+ void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
+
+ ///
+ /// Registers a menu in a category.
+ ///
+ void RegisterMenu(string categoryId, string menuId, string menuName, Func menuFactory, string? permission = null);
+
+ ///
+ /// Unregisters a menu from a category.
+ ///
+ void UnregisterMenu(string categoryId, string menuId);
+
+ ///
+ /// Creates a menu with an automatic back button.
+ ///
+ object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player);
+
+ ///
+ /// Creates a menu with a list of players with filter and action.
+ ///
+ object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func filter, Action onSelect);
+
+ ///
+ /// Adds an option to the menu (extension method helper).
+ ///
+ void AddMenuOption(object menu, string name, Action action, bool disabled = false, string? permission = null);
+
+ ///
+ /// Adds a submenu to the menu (extension method helper).
+ ///
+ void AddSubMenu(object menu, string name, Func subMenuFactory, bool disabled = false, string? permission = null);
+
+ ///
+ /// Opens a menu for a player.
+ ///
+ void OpenMenu(object menu, CCSPlayerController player);
}
\ No newline at end of file
diff --git a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdminApi.dll b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdminApi.dll
new file mode 100644
index 0000000000000000000000000000000000000000..45fa7565ff64acfb265259725fa1269af40b6ee8
GIT binary patch
literal 11264
zcmeHNeQ+G*d4G0q?{0M`9jCK}O)+r7#u#HR`mkdR7<86wqhQ&>C)wE8NUOV*y!L%{
z?@qSJ2+kPn8rlpr5GQq7Ovg~5kkSm(At@ao2xNhrDaG`m`T49G86iH
zp547WNftxXzdDn(_qWg2`@GNlyq~+LfxQotkBBtfpMFa85}thZ2!1wHp^jYf-3Yzd
z^hWzj%CP%jQt@0NV=7$y2UPQ=`+O4JP5P!jiC-DmM}BE3PcgTJ+bPn69gW$-Ut34l)8>c$OD
zt~jDf*d;p+PGTDc!3|xByW#3VcO`5yTYw?QN>$wKYc=kMtA_}!oqe5jB^;YdetM~$
z=$ls%DYMN!H@C?~$zo|SQK$+^;}8~gs5IhwP<_))5UbjBh#&sxFh3&NGy=!dKiwkF
zz;r~O!Rd?SsZXO1p>LXQm1k%=#?Mk4>X}Amn!4&@kgG3Ozs&CW$^QAt74VZQ;3rqW
zPp)9~m54_O2)HH@2mt|CDFPuN;JQU11Oyy}2!w!OktYxW0**~Agn$4MI|3mfK-`W%
zh+PP12_#6+QhwB$#X=67rAX?g)R#6XZZC14Pp(aLB)U7gJ2!Bm{gefsMN^mGgYk|Q
z9)Z#FRN1!j6D79zt>18l-16ZRJqMbn$nvej{e5`84E{XE+44=jL?c0_S^Ad$O+7$6
z1B`#8J*rep_4A42k$`
z7N6%e_WX2klKl{)0u0etX-`vtj)>&PA(jt_d?j^2ehQ^Rv{|3j0`wBRMQ9g&mm+ip
zFhFT3wMAn;EA&5t|EmI%!3ecMdxGLY3{3KepXT_*vRQCiV(0-Kqaxiei^YSI1@6};
zkg1;sK0%t8Q;?p!v`+$0Acrf#N2y!?B(R^FT<*glb4%!E`XZ^#2bDnJ+hi-ds`mve
zz`cUMt1&$pVEn+xc!ge3+G({q1$@qD16$Q9@RWK#@DbnrsCF;p3N07BMNkv02tKc#
zfOenyAn*hA6mSzH?evCv8dwENgJ`DZYvD7{dffBicr@@ed&nwQT
zVBHI$M(!8%rwzJyLf@%#Em`IJio02$zWcx-@p8?ybSpnryw!N7Wji<-$I#BStZT+l
zsaXzy!-^r-OiL`>s`BjOYo=v2I7}(IX8Z@5FI6$IYTVT-=Df>&&)2Q;jNog=|2^Lg
zDyEcNGydhxz3Q?W_XA~{8n1C5DJgY%joTdFudb?buZ7a;H8t){U$%jJ!xyDBHQg^l
z74^Crw>NZ;`h^gTrfn>&iF!Y>yQ%(2F;5ju}
zXsUp4ga%x8x6);@W|3>{YUBmWrRDv^sxRBxHlb6{}8>oo8ED`R9NxtrXRZ8
z+vErLfy*t?o`>bfF86heiQV+yF87a3T<%ks`%#lZyGd^+T*vjJ(A_~%mwOnxJLnRZ
zdltGq)b4UGLAQriyWGdR;@eAIE~h}Zmu_^q&XD4}lX_jQC#2Aww9Vx<1Vg@kG~{yI
zf)Q~0UGA*1$hV(Nm-}<2-FFw|UGD8*hi`-?UG86k>%iUXa+fF}-zc4Mxs^%;+`|rs
zeBS6Y=qXqCdT_HZP2X}kMPrWFkD{z14vu=$0CJ{hYa1;WqPdhx$1%BlbPy9&qosN@
z>ZenXDAa^$y|DIXDf>fAc7+=0zY0gAD#f*E)JOk-Xf^sYf|5AE3qyX`NdB15HqAAH4bK
zJJo2k{3lwZQdn!E!#dgu
z3}Amhj2-bBV3axqHwY#L`vq?o+#_fK+vqoe0h$6T*nJP{m(s}^#_9W_eGizSR)sCs
zfR55yk$*=}#|ig1>DU1u0xqV9flKKz;4=C>V4NNY_S0G5eqf9$uvby%GoT+3`lQg0
z3H=Sw5&BE|V`$$MNvpz^Z3>sVO6Y4q!>8bY;O&CLf_s2*D$!0Yj+h>zb~;9PXjkJ@
z@DN>1hv;n_*S-vGg2FWIOVA=Z4!o3}1TLp<0k5T>0J}+p&5amc3F@WGfwze~1?&<3
zJ>tI?^h4U0P|NGuLBV4pIfhyu(eB4d<`nQ4y{tVb+GnKpX9drRI9g3%KXIYk
z1y_iqN9d$rpGZc9HUu*wsS15m@TBrpN^2(}zgIgWk|z{y^%FurBlN2ZXX#auye4>F
zBGPw;)QR8+~NU{cT!JgRanM^&!ns7UTtr4|>t)N3MtPf+nm8+~lkCwNxyecx$V
zuFzP1R`9&WmhWpUCqL71!5+arKU5gZk)3Z4;s
zRq$MZJ)akvf>KJbC&+d62t6wFsL)lxGlJ&@N#{~$b!lT0o~EV!`N0Z;a?2*15e7iX;|S?&`>jaR>k_!0`J!WRZ2jk
z&=-Izbzl`!ux_=2?!t^zF#8sPUW+rkLhFDkPSQ(2uLr7hJ=Qw~r}s-h-$0jwz7eR>
zO>`OPjX;%d!P|gJJ(&M1AsNEza}evDR^=U_@%mTnZ`d4a*EH>r-HEM+YNn?}Kilqd
z?w~nkt0IGVdnKDS#wn;bbGSlUP-R+sK^@#O!(?B6p$(lD)s%#oL
zyioQQD*3W$^ULQUGj+hQi;3i5KTUFtBO{cWDwWM#qPLLE!Y2YRCAOM*)3(wyVU|Z6
zFByUnyk$f-TrzEt73ZF+Qbaj3Ux6lVl+B5PJ>}Z>m$ns*j0q=jSYwP#W@JZx
zFl$Vi_Mn+JvSrK6Br|Mho4LZIxsdjP1s5zs8LPw{H^}_
zfx_WrCTHcjJ15auL$;AGWu?eG-L^v7$Xa)^H_nrlp_|;q-F#?dWRsCTgctuUmYIdZ
z$=-aqmzepQ`O#g#CO?5rpI6y-BX+lxzUTS!gWobIaZmNV0m5UEpK%RW{{a1%T5h(L+j!p!$zaG-~+UI
zxL_ZuD?Ixxh-a5+moU8MEBo`~1v_W(*fX;8tWsvWVq+!_ns&}AUD!i!AwOE#Nc
z@K@%#V$eWZlD2J3)n%R+uFc3dDBQjmdD(LIi+V9xy$KU$&QM9XNyF?WEX)WY@2u+1
zx5ouGgM_nZlrVB+29y#`H?o%uS}|A|%UY!a)SK$ME@kDgOF1iJ(V~9HM&`(RZ4+Z{
z0ONeORX)J09}h&HTD_PCW6bk>5Q1mO?Bz&yi8-SA6(=&;tlM_loUh~vD+C$5USo!M
z!-F#JKp=1EID0c0veVUaXO6pjpG;V9ffJ@-!IQum`J%f_5>^^+?T#W3T*{cGv~4*w
zGEstv*C;OsJjS_ioSjcNXLX?7-jpNckz@0QY}2@v#X3Yq*(M1;-(dWDyNyi=J_P{f
z;Vv56D>>f1OJ3{xEMp>HD3w{Xz=AQi`@N#>8029g%LwoD7S?vj%&gU3ax+^}ZrlyX
zSyV8fP!}955D2eaiQ4(dTG-HwcrzAAoEsVAeU4#@*f0_EMNZYP|W35At_
zm-VC#n_3Cpgz6=PlM+I?+4lxoqP|GgdZOWsRd*`eSKX__id%=oNWtOI$~YqfnVU7a
zJn5Xi$1=b}!il}M6O!fai9G(CLz}xv5}#0IcPq=(F7X~&st;4nU8-!9D<#@7b`Z{~
zkWZOBYuLtFEy#K7G)t9i8FP`3cj9cQFoBKH@m@ct%~$o8MCKZD+Bpg;F(A%VWsGMT
z?Z6f>tozGn}{GORB}Stg7t%0
zuEe9~p6$Ik@Er$p4)2W&FEMpK8ZB0S+xg%=>~1WM3NfEv(!f!5|gK=SAd
z#D>*CL=C9T(AP2p@nS8|T78Cd7wZqi6=Xk$Zi)osBn6kmm9|(#ZEbc+P5WA_M|Cxp
zy95O;+*>uxybcRX8K=*hgywDw$Nfqy7t3*<3q9=6fw&5JM3+ab`hXtS6!i9G%K~v9
z6HO?$OaurX5$T9HR{b4C*F_(zKBA*J)yJeLR8#Qg6pdVjqLD}h=HEcUWf+Jm_+Z=z
zf3e(>xL=;GTIJEj0Y;=kNPi!mRB^Jb=0lBeyvvY0y!^-FA&fl?Up+P)iRgSGr#ikC
z#)d(tJml3Dj@+ct{Z3;E4Q(xx04h7Q<;!zBfFJS~VeViqK}Zyw^UF+O?)J-jVL?>gpWtS~rgGND?f5is6v?m&HpR
zGU&bphn#InUUVp|;2RTvmBHLcK?fmN;mE_A^BM0wNbhscZ-GGruNxVv73OsVZQ%7n
zRa9`um-o9NWORe}&eup8803JC<9<4cdpT>Ii_7!xg;(QFY{I?5k$cF$$2VR(Q+=t8
z%2gY;7XEt6@mC(Y>+6p_`E&NM?S_3@`-g5F-n4H{mgCo57tb39^XSXF&G
z6fI+}S+Z#DD~ui7=k~?Eyje!CUBD_)%#2a$)@0YZFJeeYAd_3%K+xEW*MH@ym9M{b
z&z%QP-2Cb^8suH4n2vkg?5Fp~CysOkBdjl*9WZc&&Xv*_Zl)Zv-7=qE1vhikGTYy;
zx&LDZ_%93OZ#k;79my=gS5ulNXS-W*-!Y26G2vW=wMSr1v{7asT&dr6HCVWkGE}391?LGQHDDY}vFW!RPm`@)4*qg_xASNqh
zS`I;LKw5&{pfc|CCg=caAz2A_NxeHPv$j!*2=Wkfia<;J1VgqU*wM3magw9=|1|L9c~{0lN-xoha1>
y90#rg@^!uRWm3+bCh{?c3{4?>3&+8I`zu^M&fg3^=lvPTui)a>MgIS0;C}!UmbgCv
literal 0
HcmV?d00001
diff --git a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.sln b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.sln
new file mode 100644
index 0000000..c2fa528
--- /dev/null
+++ b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-SimpleAdmin_FunCommands", "CS2-SimpleAdmin_FunCommands\CS2-SimpleAdmin_FunCommands.csproj", "{72713A40-688F-401F-8211-3D28B068C791}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {72713A40-688F-401F-8211-3D28B068C791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72713A40-688F-401F-8211-3D28B068C791}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72713A40-688F-401F-8211-3D28B068C791}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72713A40-688F-401F-8211-3D28B068C791}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Actions.cs b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Actions.cs
new file mode 100644
index 0000000..e6521e9
--- /dev/null
+++ b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Actions.cs
@@ -0,0 +1,237 @@
+using System.Globalization;
+using CounterStrikeSharp.API;
+using CounterStrikeSharp.API.Core;
+using Microsoft.Extensions.Logging;
+
+namespace CS2_SimpleAdmin_FunCommands;
+
+public partial class CS2_SimpleAdmin_FunCommands
+{
+ private void God(CCSPlayerController? caller, CCSPlayerController player)
+ {
+ if (!caller.CanTarget(player)) return;
+
+ var callerName = caller?.PlayerName ?? "Console";
+
+ // Toggle god mode for the player (like in main plugin)
+ if (!GodPlayers.Add(player.Slot))
+ {
+ GodPlayers.Remove(player.Slot);
+ }
+
+ // Show admin activity using module's own localizer with per-player language support
+ var activityArgs = new object[] { "CALLER", player.PlayerName };
+ if (caller == null || !_sharedApi!.IsAdminSilent(caller))
+ {
+ if (Localizer != null)
+ {
+ _sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_god_message", callerName, false,
+ activityArgs);
+ }
+ else
+ {
+ _sharedApi!.ShowAdminActivity("fun_admin_god_message", callerName, false, activityArgs);
+ }
+ }
+
+ // Log command using API
+ _sharedApi!.LogCommand(caller,
+ $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
+ }
+
+ private void NoClip(CCSPlayerController? caller, CCSPlayerController player)
+ {
+ if (!player.IsValid) return;
+ if (!caller.CanTarget(player)) return;
+
+ var callerName = caller?.PlayerName ?? "Console";
+
+ // Toggle no-clip mode using PlayerExtensions
+ player.Pawn.Value?.ToggleNoclip();
+
+ // Show admin activity using module's own localizer with per-player language support
+ var activityArgs = new object[] { "CALLER", player.PlayerName };
+ if (caller == null || !_sharedApi!.IsAdminSilent(caller))
+ {
+ if (Localizer != null)
+ {
+ _sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_noclip_message", callerName, false,
+ activityArgs);
+ }
+ else
+ {
+ _sharedApi!.ShowAdminActivity("fun_admin_noclip_message", callerName, false, activityArgs);
+ }
+ }
+
+ // Log command using API
+ _sharedApi!.LogCommand(caller,
+ $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
+ }
+
+ private void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time)
+ {
+ if (!player.IsValid) return;
+ if (!caller.CanTarget(player)) return;
+
+ var callerName = caller?.PlayerName ?? "Console";
+
+ // Freeze player using PlayerExtensions
+ player.Pawn.Value?.Freeze();
+
+ // Show admin activity using module's own localizer with per-player language support
+ var activityArgs = new object[] { "CALLER", player.PlayerName };
+ if (caller == null || !_sharedApi!.IsAdminSilent(caller))
+ {
+ if (Localizer != null)
+ {
+ _sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_freeze_message", callerName, false,
+ activityArgs);
+ }
+ else
+ {
+ _sharedApi!.ShowAdminActivity("fun_admin_freeze_message", callerName, false, activityArgs);
+ }
+ }
+
+ // Auto-unfreeze after duration
+ if (time > 0)
+ {
+ AddTimer(time, () => player.Pawn.Value?.Unfreeze(),
+ CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
+ }
+
+ // Log command using API
+ _sharedApi!.LogCommand(caller,
+ $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
+ }
+
+ private void Unfreeze(CCSPlayerController? caller, CCSPlayerController player)
+ {
+ if (!player.IsValid) return;
+ if (!caller.CanTarget(player)) return;
+
+ var callerName = caller?.PlayerName ?? "Console";
+
+ // Unfreeze player using PlayerExtensions
+ player.Pawn.Value?.Unfreeze();
+
+ // Show admin activity using module's own localizer with per-player language support
+ var activityArgs = new object[] { "CALLER", player.PlayerName };
+ if (caller == null || !_sharedApi!.IsAdminSilent(caller))
+ {
+ if (Localizer != null)
+ {
+ _sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_unfreeze_message", callerName, false,
+ activityArgs);
+ }
+ else
+ {
+ _sharedApi!.ShowAdminActivity("fun_admin_unfreeze_message", callerName, false, activityArgs);
+ }
+ }
+
+ // Log command using API
+ _sharedApi!.LogCommand(caller,
+ $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
+ }
+
+ ///
+ /// Respawns a player and teleports them to their last death position if available.
+ /// This demonstrates using the GetPlayerInfo API to access player data.
+ ///
+ private void Respawn(CCSPlayerController? caller, CCSPlayerController player)
+ {
+ if (!player.IsValid) return;
+ if (!caller.CanTarget(player)) return;
+
+ var callerName = caller?.PlayerName ?? "Console";
+
+ // Respawn the player
+ player.Respawn();
+
+ // Get death position from API and teleport player to it
+ // BEST PRACTICE: Use API to access player data like death position
+ if (_sharedApi != null && player.UserId.HasValue)
+ {
+ try
+ {
+ var playerInfo = _sharedApi.GetPlayerInfo(player);
+
+ // Teleport to death position if available
+ if (playerInfo?.DiePosition != null && player.PlayerPawn?.Value != null)
+ {
+ player.PlayerPawn.Value.Teleport(
+ playerInfo.DiePosition.Position,
+ playerInfo.DiePosition.Angle);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"Failed to get player info for respawn: {ex.Message}");
+ }
+ }
+
+ // Show admin activity using module's own localizer with per-player language support
+ var activityArgs = new object[] { "CALLER", player.PlayerName };
+ if (caller == null || !_sharedApi!.IsAdminSilent(caller))
+ {
+ if (Localizer != null)
+ {
+ _sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_respawn_message", callerName, false,
+ activityArgs);
+ }
+ else
+ {
+ _sharedApi!.ShowAdminActivity("fun_admin_respawn_message", callerName, false, activityArgs);
+ }
+ }
+
+ // Log command using API
+ _sharedApi!.LogCommand(caller,
+ $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
+ }
+
+ ///
+ /// Resizes a player's model to the specified scale.
+ ///
+ private void Resize(CCSPlayerController? caller, CCSPlayerController player, float size)
+ {
+ if (!player.IsValid) return;
+ if (!caller.CanTarget(player)) return;
+
+ var callerName = caller?.PlayerName ?? "Console";
+
+ // Resize the player
+ var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
+ if (sceneNode != null)
+ {
+ 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");
+ });
+ }
+
+ // Show admin activity using module's own localizer with per-player language support
+ var activityArgs = new object[] { "CALLER", player.PlayerName, size.ToString(CultureInfo.InvariantCulture) };
+ if (caller == null || !_sharedApi!.IsAdminSilent(caller))
+ {
+ if (Localizer != null)
+ {
+ _sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_resize_message", callerName, false,
+ activityArgs);
+ }
+ else
+ {
+ _sharedApi!.ShowAdminActivity("fun_admin_resize_message", callerName, false, activityArgs);
+ }
+ }
+
+ // Log command using API
+ _sharedApi!.LogCommand(caller,
+ $"css_resize {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {size}");
+ }
+}
\ No newline at end of file
diff --git a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.cs b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.cs
new file mode 100644
index 0000000..d7d6238
--- /dev/null
+++ b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.cs
@@ -0,0 +1,492 @@
+using CounterStrikeSharp.API;
+using CounterStrikeSharp.API.Core;
+using CounterStrikeSharp.API.Core.Capabilities;
+using CounterStrikeSharp.API.Modules.Admin;
+using CounterStrikeSharp.API.Modules.Commands;
+using CounterStrikeSharp.API.Modules.Entities;
+using CounterStrikeSharp.API.Modules.Entities.Constants;
+using CounterStrikeSharp.API.Modules.Utils;
+using CS2_SimpleAdminApi;
+using Microsoft.Extensions.Logging;
+using System.Globalization;
+
+namespace CS2_SimpleAdmin_FunCommands;
+
+///
+/// CS2-SimpleAdmin Fun Commands Module
+///
+/// This module serves as a REFERENCE IMPLEMENTATION for creating CS2-SimpleAdmin modules.
+/// Study this code to learn best practices for:
+/// - Command registration from configuration
+/// - Menu creation with SimpleAdmin API
+/// - Per-player translation support
+/// - Proper cleanup on module unload
+/// - Code organization using partial classes
+///
+/// File Structure:
+/// - CS2-SimpleAdmin_FunCommands.cs (this file) - Plugin initialization and registration
+/// - Commands.cs - Command handlers
+/// - Actions.cs - Action methods (God, NoClip, Freeze, etc.)
+/// - Menus.cs - Menu creation
+/// - Config.cs - Configuration with command lists
+/// - lang/ - Translation files (13 languages)
+///
+/// See README.md for detailed explanation of all patterns demonstrated here.
+///
+public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig
+{
+ public Config Config { get; set; }
+
+ ///
+ /// BEST PRACTICE: Cache expensive operations
+ /// Weapons enum values don't change, so we cache them on first access
+ ///
+ private static Dictionary? _weaponsCache;
+ private static Dictionary GetWeaponsCache()
+ {
+ if (_weaponsCache != null) return _weaponsCache;
+
+ var weaponsArray = Enum.GetValues(typeof(CsItem));
+ _weaponsCache = new Dictionary();
+
+ foreach (CsItem item in weaponsArray)
+ {
+ if (item == CsItem.Tablet) continue; // Skip tablet (invalid weapon)
+ _weaponsCache[(int)item] = item;
+ }
+
+ return _weaponsCache;
+ }
+
+ ///
+ /// Track players with god mode enabled
+ /// HashSet for O(1) lookup performance
+ ///
+ private static readonly HashSet GodPlayers = [];
+
+ ///
+ /// Track players with modified speed
+ /// Dictionary for storing speed values per player
+ ///
+ private static readonly Dictionary SpeedPlayers = [];
+
+ ///
+ /// Track players with modified gravity
+ /// Dictionary for storing gravity values per player
+ ///
+ private static readonly Dictionary GravityPlayers = [];
+
+ ///
+ /// BEST PRACTICE: Use capability system to get SimpleAdmin API
+ /// This ensures your module works even if SimpleAdmin loads after your module
+ ///
+ private ICS2_SimpleAdminApi? _sharedApi;
+ private readonly PluginCapability _pluginCapability = new("simpleadmin:api");
+
+ ///
+ /// BEST PRACTICE: Track menu registration state to prevent duplicate registrations
+ ///
+ private bool _menusRegistered = false;
+
+ // Plugin metadata
+ public override string ModuleName => "CS2-SimpleAdmin Fun Commands";
+ public override string ModuleVersion => "1.0.0";
+ public override string ModuleAuthor => "Your Name";
+ public override string ModuleDescription => "Fun commands extension for CS2-SimpleAdmin";
+
+ ///
+ /// BEST PRACTICE: Initialize plugin after all plugins are loaded
+ /// This ensures SimpleAdmin API is available
+ ///
+ public override void OnAllPluginsLoaded(bool hotReload)
+ {
+ // STEP 1: Get SimpleAdmin API using capability system
+ _sharedApi = _pluginCapability.Get();
+ if (_sharedApi == null)
+ {
+ Logger.LogError("CS2-SimpleAdmin API not found - make sure CS2-SimpleAdmin is loaded!");
+ Unload(false);
+ return;
+ }
+
+ // STEP 2: Register commands (can be done immediately)
+ RegisterFunCommands();
+
+ // STEP 3: Register menus (wait for SimpleAdmin to be ready)
+ // BEST PRACTICE: Use event + fallback to handle both normal load and hot reload
+ _sharedApi.OnSimpleAdminReady += RegisterFunMenus;
+ RegisterFunMenus(); // Fallback for hot reload case
+
+ // STEP 4: Start timer to maintain speed and gravity modifications
+ StartSpeedGravityTimer();
+ }
+
+ public override void Unload(bool hotReload)
+ {
+ if (_sharedApi == null) return;
+
+ // Unregister commands
+ if (Config.NoclipCommands.Count > 0)
+ {
+ foreach (var command in Config.NoclipCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.GodCommands.Count > 0)
+ {
+ foreach (var command in Config.GodCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.FreezeCommands.Count > 0)
+ {
+ foreach (var command in Config.FreezeCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.UnfreezeCommands.Count > 0)
+ {
+ foreach (var command in Config.UnfreezeCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.RespawnCommands.Count > 0)
+ {
+ foreach (var command in Config.RespawnCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.GiveCommands.Count > 0)
+ {
+ foreach (var command in Config.GiveCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.StripCommands.Count > 0)
+ {
+ foreach (var command in Config.StripCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.HpCommands.Count > 0)
+ {
+ foreach (var command in Config.HpCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.SpeedCommands.Count > 0)
+ {
+ foreach (var command in Config.SpeedCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.GravityCommands.Count > 0)
+ {
+ foreach (var command in Config.GravityCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.MoneyCommands.Count > 0)
+ {
+ foreach (var command in Config.MoneyCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ if (Config.ResizeCommands.Count > 0)
+ {
+ foreach (var command in Config.ResizeCommands)
+ {
+ _sharedApi.UnRegisterCommand(command);
+ }
+ }
+
+ // Unregister menus
+ if (Config.NoclipCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "noclip");
+
+ if (Config.GodCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "god");
+
+ if (Config.RespawnCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "respawn");
+
+ if (Config.GiveCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "give");
+
+ if (Config.StripCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "strip");
+
+ if (Config.FreezeCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "freeze");
+
+ if (Config.HpCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "hp");
+
+ if (Config.SpeedCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "speed");
+
+ if (Config.GravityCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "gravity");
+
+ if (Config.MoneyCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "money");
+
+ if (Config.ResizeCommands.Count > 0)
+ _sharedApi.UnregisterMenu("fun", "resize");
+
+ _sharedApi.OnSimpleAdminReady -= RegisterFunMenus;
+ }
+
+ private void RegisterFunCommands()
+ {
+ if (_sharedApi == null) return;
+
+ if (Config.NoclipCommands.Count > 0)
+ {
+ foreach (var command in Config.NoclipCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Enable noclip", OnNoclipCommand);
+ }
+ }
+
+ if (Config.GodCommands.Count > 0)
+ {
+ foreach (var command in Config.GodCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Enable god mode", OnGodCommand);
+ }
+ }
+
+ if (Config.FreezeCommands.Count > 0)
+ {
+ foreach (var command in Config.FreezeCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Freeze player", OnFreezeCommand);
+ }
+ }
+
+ if (Config.UnfreezeCommands.Count > 0)
+ {
+ foreach (var command in Config.UnfreezeCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Unfreeze player", OnUnfreezeCommand);
+ }
+ }
+
+ if (Config.RespawnCommands.Count > 0)
+ {
+ foreach (var command in Config.RespawnCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Respawn player", OnRespawnCommand);
+ }
+ }
+
+ if (Config.GiveCommands.Count > 0)
+ {
+ foreach (var command in Config.GiveCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Give weapon", OnGiveWeaponCommand);
+ }
+ }
+
+ if (Config.StripCommands.Count > 0)
+ {
+ foreach (var command in Config.StripCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Strip weapons", OnStripWeaponsCommand);
+ }
+ }
+
+ if (Config.HpCommands.Count > 0)
+ {
+ foreach (var command in Config.HpCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Set HP", OnSetHpCommand);
+ }
+ }
+
+ if (Config.SpeedCommands.Count > 0)
+ {
+ foreach (var command in Config.SpeedCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Set speed", OnSetSpeedCommand);
+ }
+ }
+
+ if (Config.GravityCommands.Count > 0)
+ {
+ foreach (var command in Config.GravityCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Set gravity", OnSetGravityCommand);
+ }
+ }
+
+ if (Config.MoneyCommands.Count > 0)
+ {
+ foreach (var command in Config.MoneyCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Set money", OnSetMoneyCommand);
+ }
+ }
+
+ if (Config.ResizeCommands.Count > 0)
+ {
+ foreach (var command in Config.ResizeCommands)
+ {
+ _sharedApi.RegisterCommand(command, "Resize player", OnSetResizeCommand);
+ }
+ }
+ }
+
+ private void RegisterFunMenus()
+ {
+ if (_sharedApi == null || _menusRegistered) return;
+
+ try
+ {
+ _sharedApi.RegisterMenuCategory("fun", Localizer?["fun_category_name"] ?? "Fun Commands", "@css/generic");
+
+ if (Config.GodCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "god",
+ Localizer?["fun_menu_god"] ?? "God Mode",
+ CreateGodModeMenu, "@css/cheats");
+
+ if (Config.NoclipCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "noclip",
+ Localizer?["fun_menu_noclip"] ?? "No Clip",
+ CreateNoClipMenu, "@css/cheats");
+
+ if (Config.RespawnCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "respawn",
+ Localizer?["fun_menu_respawn"] ?? "Respawn",
+ CreateRespawnMenu, "@css/cheats");
+
+ if (Config.GiveCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "give",
+ Localizer?["fun_menu_give"] ?? "Give Weapon",
+ CreateGiveWeaponMenu, "@css/cheats");
+
+ if (Config.StripCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "strip",
+ Localizer?["fun_menu_strip"] ?? "Strip Weapons",
+ CreateStripWeaponsMenu, "@css/slay");
+
+ if (Config.FreezeCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "freeze",
+ Localizer?["fun_menu_freeze"] ?? "Freeze",
+ CreateFreezeMenu, "@css/slay");
+
+ if (Config.HpCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "hp",
+ Localizer?["fun_menu_hp"] ?? "Set HP",
+ CreateSetHpMenu, "@css/slay");
+
+ if (Config.SpeedCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "speed",
+ Localizer?["fun_menu_speed"] ?? "Set Speed",
+ CreateSetSpeedMenu, "@css/slay");
+
+ if (Config.GravityCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "gravity",
+ Localizer?["fun_menu_gravity"] ?? "Set Gravity",
+ CreateSetGravityMenu, "@css/slay");
+
+ if (Config.MoneyCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "money",
+ Localizer?["fun_menu_money"] ?? "Set Money",
+ CreateSetMoneyMenu, "@css/slay");
+
+ if (Config.ResizeCommands.Count > 0)
+ _sharedApi.RegisterMenu("fun", "resize",
+ Localizer?["fun_menu_resize"] ?? "Resize Player",
+ CreateSetResizeMenu, "@css/slay");
+
+ _menusRegistered = true;
+ Logger.LogInformation("Fun menus registered successfully!");
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"Failed to register Fun menus: {ex.Message}");
+ }
+ }
+
+ public void OnConfigParsed(Config config)
+ {
+ Config = config;
+ }
+
+ ///
+ /// Starts a repeating timer to maintain speed and gravity modifications for players.
+ /// This ensures that speed/gravity changes persist even after respawns or round changes.
+ ///
+ private void StartSpeedGravityTimer()
+ {
+ AddTimer(0.12f, () =>
+ {
+ // Early exit if no players have modified speed or gravity
+ var hasSpeedPlayers = SpeedPlayers.Count > 0;
+ var hasGravityPlayers = GravityPlayers.Count > 0;
+
+ if (!hasSpeedPlayers && !hasGravityPlayers)
+ return;
+
+ if (hasSpeedPlayers)
+ {
+ // Iterate through players with modified speed
+ foreach (var kvp in SpeedPlayers)
+ {
+ var player = kvp.Key;
+ // Early validation check - avoid accessing PlayerPawn if player is invalid
+ if (player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected)
+ {
+ var pawn = player.PlayerPawn?.Value;
+ if (pawn != null && pawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
+ {
+ player.SetSpeed(kvp.Value);
+ }
+ }
+ }
+ }
+
+ if (hasGravityPlayers)
+ {
+ // Iterate through players with modified gravity
+ foreach (var kvp in GravityPlayers)
+ {
+ var player = kvp.Key;
+ if (player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected)
+ {
+ var pawn = player.PlayerPawn?.Value;
+ if (pawn != null && pawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
+ {
+ player.SetGravity(kvp.Value);
+ }
+ }
+ }
+ }
+ }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT);
+ }
+
+}
\ No newline at end of file
diff --git a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj
new file mode 100644
index 0000000..3990512
--- /dev/null
+++ b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net8.0
+ CS2_SimpleAdmin_FunCommands
+ enable
+ enable
+
+
+
+
+ ..\CS2-SimpleAdminApi.dll
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Commands.cs b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Commands.cs
new file mode 100644
index 0000000..47778e5
--- /dev/null
+++ b/Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Commands.cs
@@ -0,0 +1,320 @@
+using System.Globalization;
+using CounterStrikeSharp.API.Core;
+using CounterStrikeSharp.API.Modules.Admin;
+using CounterStrikeSharp.API.Modules.Commands;
+using CounterStrikeSharp.API.Modules.Entities.Constants;
+
+namespace CS2_SimpleAdmin_FunCommands;
+
+public partial class CS2_SimpleAdmin_FunCommands
+{
+ // =================================
+ // COMMAND HANDLERS
+ // =================================
+
+ [CommandHelper(1, "<#userid or name>")]
+ [RequiresPermissions("@css/cheats")]
+ private void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ var targets = _sharedApi!.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))
+ {
+ NoClip(caller, player);
+ }
+ });
+ }
+
+ [RequiresPermissions("@css/cheats")]
+ [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
+ private void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ var targets = _sharedApi!.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))
+ {
+ God(caller, player);
+ }
+ });
+ }
+
+ [CommandHelper(1, "<#userid or name> [duration]")]
+ [RequiresPermissions("@css/slay")]
+ private void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ int.TryParse(command.GetArg(2), out var time);
+ var targets = _sharedApi!.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);
+ }
+ });
+ }
+
+ [CommandHelper(1, "<#userid or name>")]
+ [RequiresPermissions("@css/slay")]
+ private void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ var targets = _sharedApi!.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))
+ {
+ Unfreeze(caller, player);
+ }
+ });
+ }
+
+ [CommandHelper(1, "<#userid or name>")]
+ [RequiresPermissions("@css/cheats")]
+ private void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ var targets = _sharedApi!.GetTarget(command);
+ if (targets == null) return;
+
+ var playersToTarget = targets.Players.Where(player =>
+ player is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList();
+
+ playersToTarget.ForEach(player =>
+ {
+ if (caller!.CanTarget(player))
+ {
+ Respawn(caller, player);
+ }
+ });
+ }
+
+ [CommandHelper(2, "<#userid or name> ")]
+ [RequiresPermissions("@css/cheats")]
+ private void OnGiveWeaponCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ var weaponName = command.GetArg(2);
+ if (Enum.TryParse(weaponName, true, out CsItem weapon))
+ {
+ var targets = _sharedApi!.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))
+ {
+ player.GiveNamedItem(weapon);
+ LogAndShowActivity(caller, player, "fun_admin_give_message", "css_give", weapon.ToString());
+ }
+ });
+ }
+ }
+
+ [CommandHelper(1, "<#userid or name>")]
+ [RequiresPermissions("@css/slay")]
+ private void OnStripWeaponsCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ var targets = _sharedApi!.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))
+ {
+ player.RemoveWeapons();
+ LogAndShowActivity(caller, player, "fun_admin_strip_message", "css_strip");
+ }
+ });
+ }
+
+ [CommandHelper(2, "<#userid or name> ")]
+ [RequiresPermissions("@css/slay")]
+ private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ if (int.TryParse(command.GetArg(2), out var hp))
+ {
+ var targets = _sharedApi!.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))
+ {
+ player.SetHp(hp);
+ LogAndShowActivity(caller, player, "fun_admin_hp_message", "css_hp", hp.ToString());
+ }
+ });
+ }
+ }
+
+ [CommandHelper(2, "<#userid or name> ")]
+ [RequiresPermissions("@css/slay")]
+ private void OnSetSpeedCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ if (float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var speed))
+ {
+ var targets = _sharedApi!.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))
+ {
+ player.SetSpeed(speed);
+
+ // Track speed modification for timer
+ if (speed == 1f)
+ SpeedPlayers.Remove(player);
+ else
+ SpeedPlayers[player] = speed;
+
+ LogAndShowActivity(caller, player, "fun_admin_speed_message", "css_speed", speed.ToString(CultureInfo.InvariantCulture));
+ }
+ });
+ }
+ }
+
+ [CommandHelper(2, "<#userid or name> ")]
+ [RequiresPermissions("@css/slay")]
+ private void OnSetGravityCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ if (float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var gravity))
+ {
+ var targets = _sharedApi!.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))
+ {
+ player.SetGravity(gravity);
+
+ // Track gravity modification for timer
+ if (gravity == 1f)
+ GravityPlayers.Remove(player);
+ else
+ GravityPlayers[player] = gravity;
+
+ LogAndShowActivity(caller, player, "fun_admin_gravity_message", "css_gravity", gravity.ToString(CultureInfo.InvariantCulture));
+ }
+ });
+ }
+ }
+
+ [CommandHelper(2, "<#userid or name> ")]
+ [RequiresPermissions("@css/slay")]
+ private void OnSetMoneyCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ if (int.TryParse(command.GetArg(2), out var money))
+ {
+ var targets = _sharedApi!.GetTarget(command);
+ if (targets == null) return;
+
+ var playersToTarget = targets.Players.Where(player =>
+ player is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList();
+
+ playersToTarget.ForEach(player =>
+ {
+ if (caller!.CanTarget(player))
+ {
+ player.SetMoney(money);
+ LogAndShowActivity(caller, player, "fun_admin_money_message", "css_money", money.ToString());
+ }
+ });
+ }
+ }
+
+ [CommandHelper(2, "<#userid or name> ")]
+ [RequiresPermissions("@css/slay")]
+ private void OnSetResizeCommand(CCSPlayerController? caller, CommandInfo command)
+ {
+ if (float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size))
+ {
+ var targets = _sharedApi!.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))
+ {
+ Resize(caller, player, size);
+ }
+ });
+ }
+ }
+
+ // =================================
+ // HELPER METHOD FOR ACTIVITIES WITH INDIVIDUAL COMMAND LOGGING
+ // =================================
+
+ private void LogAndShowActivity(CCSPlayerController? caller, CCSPlayerController target, string messageKey, string baseCommand, params string[] extraArgs)
+ {
+ var callerName = caller?.PlayerName ?? "Console";
+
+ // Build activity args
+ var args = new List