mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-18 10:43:23 +00:00
Compare commits
8 Commits
build-1.7.
...
build-1.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78318102fe | ||
|
|
2edacc2b3f | ||
|
|
e1e66441f2 | ||
|
|
cc54b9e879 | ||
|
|
640e618f3b | ||
|
|
23d174c4a5 | ||
|
|
b7371adf26 | ||
|
|
9154748ce6 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -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: |
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(CCSPlayerController player)
|
||||
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
|
||||
public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
|
||||
public int? GetServerId() => CS2_SimpleAdmin.ServerId;
|
||||
|
||||
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(
|
||||
CCSPlayerController player)
|
||||
{
|
||||
return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot);
|
||||
}
|
||||
|
||||
|
||||
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied;
|
||||
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded;
|
||||
public event Action<string, string?, bool, object>? OnAdminShowActivity;
|
||||
public event Action<int, bool>? OnAdminToggleSilent;
|
||||
|
||||
public void OnPlayerPenaltiedEvent(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
|
||||
int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
|
||||
|
||||
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<int> ListSilentAdminsSlots()
|
||||
{
|
||||
return CS2_SimpleAdmin.SilentPlayers;
|
||||
@@ -147,7 +158,7 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
list = new List<CommandDefinition>();
|
||||
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<CCSPlayerController, object> 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<CCSPlayerController> GetValidPlayers()
|
||||
{
|
||||
return Helper.GetValidPlayers();
|
||||
}
|
||||
|
||||
public object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||
{
|
||||
var menu = (MenuBuilder)CreateMenuWithBack(title, categoryId, admin);
|
||||
var players = Helper.GetValidPlayers().Where(filter);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
menu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
onSelect(admin, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
||||
string? permission = null)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.AddOption(name, action, disabled, permission);
|
||||
}
|
||||
|
||||
public void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory,
|
||||
bool disabled = false, string? permission = null)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.AddSubMenu(name, player =>
|
||||
{
|
||||
var subMenu = subMenuFactory(player);
|
||||
if (subMenu is not MenuBuilder builder)
|
||||
throw new InvalidOperationException("SubMenu factory must return MenuBuilder");
|
||||
return builder;
|
||||
}, disabled, permission);
|
||||
}
|
||||
|
||||
public void OpenMenu(object menu, CCSPlayerController player)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.OpenMenu(player);
|
||||
}
|
||||
}
|
||||
@@ -1,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
|
||||
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
||||
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
||||
public override string ModuleAuthor => "daffyy & Dliix66";
|
||||
public override string ModuleVersion => "1.7.7-alpha-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<CS2_SimpleAdmin
|
||||
PlayersTimer?.Kill();
|
||||
PlayersTimer = null;
|
||||
PlayerManager.CheckPlayersTimer();
|
||||
|
||||
Menus.MenuManager.Instance.InitializeDefaultCategories();
|
||||
BasicMenu.Initialize();
|
||||
}
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
@@ -83,7 +86,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
|
||||
AddTimer(6.0f, () => ReloadAdmins(null));
|
||||
RegisterEvents();
|
||||
RegisterCommands.InitializeCommands();
|
||||
AddTimer(0.5f, RegisterCommands.InitializeCommands);
|
||||
|
||||
if (!CoreConfig.UnlockConCommands)
|
||||
{
|
||||
@@ -232,7 +235,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
||||
WarnManager = new WarnManager(DatabaseProvider);
|
||||
}
|
||||
|
||||
private static TargetResult? GetTarget(CommandInfo command, int argument = 1)
|
||||
internal static TargetResult? GetTarget(CommandInfo command, int argument = 1)
|
||||
{
|
||||
var matches = command.GetArgTargetResult(argument);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<DebuggerSupport>false</DebuggerSupport>
|
||||
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||
<!-- <GenerateDependencyFile>false</GenerateDependencyFile>-->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -34,111 +34,105 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="ClearBuildFiles" AfterTargets="PostBuildEvent">
|
||||
<ItemGroup>
|
||||
<FilesToDelete Include="$(OutDir)Tomlyn.dll" />
|
||||
<FilesToDelete Include="$(OutDir)Serilog*.dll" />
|
||||
<FilesToDelete Include="$(OutDir)CS2-SimpleAdminApi.*" />
|
||||
<FilesToDelete Include="$(OutDir)McMaster*" />
|
||||
<FilesToDelete Include="$(OutDir)Scrutor.dll" />
|
||||
</ItemGroup>
|
||||
<Delete Files="@(FilesToDelete)" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="Database\Migrations\Mysql\001_CreateTables.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\002_CreateFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\003_ChangeColumnsPosition.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\004_MoveOldFlagsToFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\005_CreateUnbansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\006_ServerGroupsFeature.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\007_ServerGroupsGlobal.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\008_OnlineTimeInPenalties.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\009_BanAllUsedIpAddress.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\010_CreateWarnsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\011_AddRconColumnToServersTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\012_AddUpdatedAtColumnToSaBansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\013_AddNameColumnToSaPlayerIpsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\014_AddIndexesToMutesTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\015_steamidToBigInt.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\001_CreateTables.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\002_CreateFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\003_ChangeColumnsPosition.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\004_MoveOldFlagsToFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\005_CreateUnbansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\006_ServerGroupsFeature.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\007_ServerGroupsGlobal.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\008_OnlineTimeInPenalties.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\009_BanAllUsedIpAddress.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\010_CreateWarnsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\011_AddRconColumnToServersTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\012_AddUpdatedAtColumnToSaBansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\013_AddNameColumnToSaPlayerIpsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\014_AddIndexesToMutesTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\015_steamidToBigInt.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="Database\Migrations\Mysql\001_CreateTables.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\002_CreateFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\003_ChangeColumnsPosition.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\004_MoveOldFlagsToFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\005_CreateUnbansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\006_ServerGroupsFeature.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\007_ServerGroupsGlobal.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\008_OnlineTimeInPenalties.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\009_BanAllUsedIpAddress.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\010_CreateWarnsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\011_AddRconColumnToServersTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\012_AddUpdatedAtColumnToSaBansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\013_AddNameColumnToSaPlayerIpsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\014_AddIndexesToMutesTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\015_steamidToBigInt.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Mysql\016_OptimizeTablesAndIndexes.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\001_CreateTables.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\002_CreateFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\003_ChangeColumnsPosition.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\004_MoveOldFlagsToFlagsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\005_CreateUnbansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\006_ServerGroupsFeature.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\007_ServerGroupsGlobal.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\008_OnlineTimeInPenalties.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\009_BanAllUsedIpAddress.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\010_CreateWarnsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\011_AddRconColumnToServersTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\012_AddUpdatedAtColumnToSaBansTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\013_AddNameColumnToSaPlayerIpsTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\014_AddIndexesToMutesTable.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\015_steamidToBigInt.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Database\Migrations\Sqlite\016_OptimizeTablesAndIndexes.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Database\Migrations\*.sql" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -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<CommandsConfig>(json,
|
||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
|
||||
if (commandsConfig?.Commands == null) return;
|
||||
|
||||
foreach (var command in commandsConfig.Commands)
|
||||
if (commandsConfig?.Commands != null)
|
||||
{
|
||||
if (command.Value.Aliases == null) continue;
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogInformation(
|
||||
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
|
||||
|
||||
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
|
||||
if (mapping == null || command.Value.Aliases.Length == 0) continue;
|
||||
|
||||
foreach (var alias in command.Value.Aliases)
|
||||
foreach (var command in commandsConfig.Commands)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
|
||||
if (command.Value.Aliases == null) continue;
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogInformation(
|
||||
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
|
||||
|
||||
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
|
||||
if (mapping == null || command.Value.Aliases.Length == 0) continue;
|
||||
|
||||
foreach (var alias in command.Value.Aliases)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (name, definitions) in RegisterCommands._commandDefinitions)
|
||||
foreach (var (name, definitions) in _commandDefinitions)
|
||||
{
|
||||
foreach (var definition in definitions)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables or disables no-clip mode for specified player(s).
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">The command input containing targets.</param>
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player =>
|
||||
player.IsValid &&
|
||||
player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
NoClip(caller, player, callerName);
|
||||
}
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles no-clip mode for a player and shows admin activity messages.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin toggling no-clip.</param>
|
||||
/// <param name="player">The target player whose no-clip state changes.</param>
|
||||
/// <param name="callerName">Optional caller name for messages.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Toggle no-clip mode for the player
|
||||
player.Pawn.Value?.ToggleNoclip();
|
||||
|
||||
// Determine message keys and arguments for the no-clip notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_noclip_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables god mode for specified player(s).
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the command.</param>
|
||||
/// <param name="command">The command input containing targets.</param>
|
||||
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
God(caller, player, command);
|
||||
}
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles god mode for a player and notifies admins.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin toggling god mode.</param>
|
||||
/// <param name="player">The target player whose god mode changes.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Toggle god mode for the player
|
||||
if (!GodPlayers.Add(player.Slot))
|
||||
{
|
||||
GodPlayers.Remove(player.Slot);
|
||||
}
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
|
||||
// Determine message key and arguments for the god mode notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_god_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freezes target player(s) for an optional specified duration.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the freeze command.</param>
|
||||
/// <param name="command">The command input containing targets and duration.</param>
|
||||
[CommandHelper(1, "<#userid or name> [duration]")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
int.TryParse(command.GetArg(2), out var time);
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
Freeze(caller, player, time, callerName, command);
|
||||
}
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the target player(s) models to a specified scale.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the resize command.</param>
|
||||
/// <param name="command">The command input containing targets and scale factor.</param>
|
||||
[CommandHelper(1, "<#userid or name> [size]")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (!caller!.CanTarget(player)) return;
|
||||
|
||||
var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
|
||||
if (sceneNode == null) return;
|
||||
|
||||
sceneNode.GetSkeletonInstance().Scale = size;
|
||||
player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
|
||||
});
|
||||
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_resize_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freezes a single player and optionally schedules automatic unfreeze after a duration.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin freezing the player.</param>
|
||||
/// <param name="player">The player to freeze.</param>
|
||||
/// <param name="time">Duration of freeze in seconds.</param>
|
||||
/// <param name="callerName">Optional name for notifications.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Freeze player pawn
|
||||
player.Pawn.Value?.Freeze();
|
||||
|
||||
// Determine message keys and arguments for the freeze notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_freeze_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Schedule unfreeze for the player if time is specified
|
||||
if (time > 0)
|
||||
{
|
||||
Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
}
|
||||
|
||||
// Log the command and send Discord notification
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unfreezes target player(s).
|
||||
/// </summary>
|
||||
/// <param name="caller">The player issuing the unfreeze command.</param>
|
||||
/// <param name="command">The command input with targets.</param>
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
Unfreeze(caller, player, callerName, command);
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unfreezes a single player and notifies admins.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin unfreezing the player.</param>
|
||||
/// <param name="player">The player to unfreeze.</param>
|
||||
/// <param name="callerName">Optional name for notifications.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Unfreeze player pawn
|
||||
player.Pawn.Value?.Unfreeze();
|
||||
|
||||
// Determine message keys and arguments for the unfreeze notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_unfreeze_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Log the command and send Discord notification
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
}
|
||||
}
|
||||
// using System.Globalization;
|
||||
// using CounterStrikeSharp.API;
|
||||
// using CounterStrikeSharp.API.Core;
|
||||
// using CounterStrikeSharp.API.Modules.Admin;
|
||||
// using CounterStrikeSharp.API.Modules.Commands;
|
||||
//
|
||||
// namespace CS2_SimpleAdmin;
|
||||
//
|
||||
// public partial class CS2_SimpleAdmin
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Enables or disables no-clip mode for specified player(s).
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player issuing the command.</param>
|
||||
// /// <param name="command">The command input containing targets.</param>
|
||||
// [CommandHelper(1, "<#userid or name>")]
|
||||
// [RequiresPermissions("@css/cheats")]
|
||||
// public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player =>
|
||||
// player.IsValid &&
|
||||
// player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (caller!.CanTarget(player))
|
||||
// {
|
||||
// NoClip(caller, player, callerName);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Toggles no-clip mode for a player and shows admin activity messages.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin toggling no-clip.</param>
|
||||
// /// <param name="player">The target player whose no-clip state changes.</param>
|
||||
// /// <param name="callerName">Optional caller name for messages.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!player.IsValid) return;
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Toggle no-clip mode for the player
|
||||
// player.Pawn.Value?.ToggleNoclip();
|
||||
//
|
||||
// // Determine message keys and arguments for the no-clip notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_noclip_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
//
|
||||
// // Log the command
|
||||
// if (command == null)
|
||||
// Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Enables or disables god mode for specified player(s).
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player issuing the command.</param>
|
||||
// /// <param name="command">The command input containing targets.</param>
|
||||
//
|
||||
// [RequiresPermissions("@css/cheats")]
|
||||
// [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
// public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
//
|
||||
// var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
// return;
|
||||
//
|
||||
// if (caller!.CanTarget(player))
|
||||
// {
|
||||
// God(caller, player, command);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Toggles god mode for a player and notifies admins.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin toggling god mode.</param>
|
||||
// /// <param name="player">The target player whose god mode changes.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Toggle god mode for the player
|
||||
// if (!GodPlayers.Add(player.Slot))
|
||||
// {
|
||||
// GodPlayers.Remove(player.Slot);
|
||||
// }
|
||||
//
|
||||
// // Log the command
|
||||
// if (command == null)
|
||||
// Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
//
|
||||
// // Determine message key and arguments for the god mode notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_god_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Freezes target player(s) for an optional specified duration.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player issuing the freeze command.</param>
|
||||
// /// <param name="command">The command input containing targets and duration.</param>
|
||||
// [CommandHelper(1, "<#userid or name> [duration]")]
|
||||
// [RequiresPermissions("@css/slay")]
|
||||
// public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
// int.TryParse(command.GetArg(2), out var time);
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (caller!.CanTarget(player))
|
||||
// {
|
||||
// Freeze(caller, player, time, callerName, command);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Resizes the target player(s) models to a specified scale.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player issuing the resize command.</param>
|
||||
// /// <param name="command">The command input containing targets and scale factor.</param>
|
||||
// [CommandHelper(1, "<#userid or name> [size]")]
|
||||
// [RequiresPermissions("@css/slay")]
|
||||
// public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
// float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// if (!caller!.CanTarget(player)) return;
|
||||
//
|
||||
// var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
|
||||
// if (sceneNode == null) return;
|
||||
//
|
||||
// sceneNode.GetSkeletonInstance().Scale = size;
|
||||
// player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
|
||||
//
|
||||
// Server.NextWorldUpdate(() =>
|
||||
// {
|
||||
// Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
|
||||
// });
|
||||
//
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_resize_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Freezes a single player and optionally schedules automatic unfreeze after a duration.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin freezing the player.</param>
|
||||
// /// <param name="player">The player to freeze.</param>
|
||||
// /// <param name="time">Duration of freeze in seconds.</param>
|
||||
// /// <param name="callerName">Optional name for notifications.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!player.IsValid) return;
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Freeze player pawn
|
||||
// player.Pawn.Value?.Freeze();
|
||||
//
|
||||
// // Determine message keys and arguments for the freeze notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_freeze_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
//
|
||||
// // Schedule unfreeze for the player if time is specified
|
||||
// if (time > 0)
|
||||
// {
|
||||
// Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||
// }
|
||||
//
|
||||
// // Log the command and send Discord notification
|
||||
// if (command == null)
|
||||
// Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Unfreezes target player(s).
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player issuing the unfreeze command.</param>
|
||||
// /// <param name="command">The command input with targets.</param>
|
||||
// [CommandHelper(1, "<#userid or name>")]
|
||||
// [RequiresPermissions("@css/slay")]
|
||||
// public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
// {
|
||||
// var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
//
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null) return;
|
||||
// var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
//
|
||||
// playersToTarget.ForEach(player =>
|
||||
// {
|
||||
// Unfreeze(caller, player, callerName, command);
|
||||
// });
|
||||
//
|
||||
// Helper.LogCommand(caller, command);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Unfreezes a single player and notifies admins.
|
||||
// /// </summary>
|
||||
// /// <param name="caller">The player/admin unfreezing the player.</param>
|
||||
// /// <param name="player">The player to unfreeze.</param>
|
||||
// /// <param name="callerName">Optional name for notifications.</param>
|
||||
// /// <param name="command">Optional command info for logging.</param>
|
||||
// internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
// {
|
||||
// if (!player.IsValid) return;
|
||||
// if (!caller.CanTarget(player)) return;
|
||||
//
|
||||
// // Set default caller name if not provided
|
||||
// callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
//
|
||||
// // Unfreeze player pawn
|
||||
// player.Pawn.Value?.Unfreeze();
|
||||
//
|
||||
// // Determine message keys and arguments for the unfreeze notification
|
||||
// var (activityMessageKey, adminActivityArgs) =
|
||||
// ("sa_admin_unfreeze_message",
|
||||
// new object[] { "CALLER", player.PlayerName });
|
||||
//
|
||||
// // Display admin activity message to other players
|
||||
// if (caller == null || !SilentPlayers.Contains(caller.Slot))
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
// }
|
||||
//
|
||||
// // Log the command and send Discord notification
|
||||
// if (command == null)
|
||||
// Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
// }
|
||||
// }
|
||||
@@ -11,9 +11,6 @@ namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
internal static readonly Dictionary<CCSPlayerController, float> SpeedPlayers = [];
|
||||
internal static readonly Dictionary<CCSPlayerController, float> GravityPlayers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Executes the 'slay' command, forcing the targeted players to commit suicide.
|
||||
/// Checks player validity and permissions.
|
||||
@@ -72,451 +69,6 @@ public partial class CS2_SimpleAdmin
|
||||
Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the 'give' command to provide a specified weapon to targeted players.
|
||||
/// Enforces server rules for prohibited weapons.
|
||||
/// </summary>
|
||||
/// <param name="caller">Player or console issuing the command.</param>
|
||||
/// <param name="command">Command details, including targets and weapon name.</param>
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
[CommandHelper(minArgs: 2, usage: "<#userid or name> <weapon>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnGiveCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gives a weapon identified by name to a player, handling ambiguous matches and logging.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin issuing the command.</param>
|
||||
/// <param name="player">Target player to receive the weapon.</param>
|
||||
/// <param name="weaponName">Weapon name or partial name.</param>
|
||||
/// <param name="callerName">Optional name to display in notifications.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
private static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, string weaponName, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives a specific weapon to a player, with notifications and logging.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin issuing the command.</param>
|
||||
/// <param name="player">Target player.</param>
|
||||
/// <param name="weapon">Weapon item object.</param>
|
||||
/// <param name="callerName">Optional caller name for notifications.</param>
|
||||
/// <param name="command">Optional command info.</param>
|
||||
internal static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, CsItem weapon, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the 'strip' command, removing all weapons from targeted players.
|
||||
/// Checks player validity and permissions.
|
||||
/// </summary>
|
||||
/// <param name="caller">Player or console issuing the command.</param>
|
||||
/// <param name="command">Command details including targets.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnStripCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all weapons from a player, with notifications and logging.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console issuing the strip command.</param>
|
||||
/// <param name="player">Target player.</param>
|
||||
/// <param name="callerName">Optional caller name.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void StripWeapons(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets health value on targeted players.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console issuing the command.</param>
|
||||
/// <param name="command">Command details including targets and health value.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <health>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnHpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes health of a player and logs the action.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console calling the method.</param>
|
||||
/// <param name="player">Target player.</param>
|
||||
/// <param name="health">Health value to set.</param>
|
||||
/// <param name="command">Optional command info.</param>
|
||||
internal static void SetHp(CCSPlayerController? caller, CCSPlayerController player, int health, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid || player.IsHLTV) return;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets movement speed on targeted players.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console issuing the command.</param>
|
||||
/// <param name="command">Command details including targets and speed.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <speed>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnSpeedCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes speed of a player and logs the action.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console calling the method.</param>
|
||||
/// <param name="player">Target player.</param>
|
||||
/// <param name="speed">Speed value to set.</param>
|
||||
/// <param name="command">Optional command info.</param>
|
||||
internal static void SetSpeed(CCSPlayerController? caller, CCSPlayerController player, float speed, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets gravity on targeted players.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console issuing the command.</param>
|
||||
/// <param name="command">Command details including targets and gravity value.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <gravity>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnGravityCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes gravity of a player and logs the action.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console calling the method.</param>
|
||||
/// <param name="player">Target player.</param>
|
||||
/// <param name="gravity">Gravity value to set.</param>
|
||||
/// <param name="command">Optional command info.</param>
|
||||
internal static void SetGravity(CCSPlayerController? caller, CCSPlayerController player, float gravity, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the money amount for the targeted players.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin executing the command.</param>
|
||||
/// <param name="command">The command containing target player and money value.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> <money>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnMoneyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies money value to a single targeted player and logs the operation.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin setting the money.</param>
|
||||
/// <param name="player">The player whose money will be set.</param>
|
||||
/// <param name="money">The value of money to set.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void SetMoney(CCSPlayerController? caller, CCSPlayerController player, int money, CommandInfo? command = null)
|
||||
{
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies damage as a slap effect to the targeted players.
|
||||
/// </summary>
|
||||
@@ -802,75 +354,6 @@ public partial class CS2_SimpleAdmin
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respawns targeted players, restoring their state.
|
||||
/// </summary>
|
||||
/// <param name="caller">The admin or player issuing respawn.</param>
|
||||
/// <param name="command">The command including target players.</param>
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
public void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respawns a specified player and updates admin notifications.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or player executing respawn.</param>
|
||||
/// <param name="player">Player to respawn.</param>
|
||||
/// <param name="callerName">Optional admin name.</param>
|
||||
/// <param name="command">Optional command info.</param>
|
||||
internal static void Respawn(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
// Check if the caller can target the player
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
// Ensure the player's pawn is valid before attempting to respawn
|
||||
if (_cBasePlayerControllerSetPawnFunc == null || player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) return;
|
||||
|
||||
// Perform the respawn operation
|
||||
var playerPawn = player.PlayerPawn.Value;
|
||||
_cBasePlayerControllerSetPawnFunc.Invoke(player, playerPawn, true, false);
|
||||
VirtualFunction.CreateVoid<CCSPlayerController>(player.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(player);
|
||||
|
||||
if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports targeted player(s) to another player's location.
|
||||
/// </summary>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
ALTER TABLE `sa_admins` ADD `group_id` INT NULL AFTER `created`;
|
||||
|
||||
ALTER TABLE `sa_groups_flags` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `sa_groups_servers` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `sa_admins` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE SET NULL;
|
||||
@@ -1,8 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`steamid` bigint(20) NOT NULL,
|
||||
`address` varchar(64) NOT NULL,
|
||||
`used_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `steamid` (`steamid`,`address`)
|
||||
PRIMARY KEY (`steamid`, `address`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`);
|
||||
@@ -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`)
|
||||
);
|
||||
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`steamid`, `address`)
|
||||
);
|
||||
@@ -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`);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows admin activity with a custom translated message (for modules with their own localizer).
|
||||
/// </summary>
|
||||
public static void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false)
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
|
||||
if (CS2_SimpleAdmin._localizer == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(callerName))
|
||||
callerName = CS2_SimpleAdmin._localizer["sa_console"];
|
||||
|
||||
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
|
||||
|
||||
if (!validPlayers.Any())
|
||||
return;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
|
||||
{
|
||||
validPlayers = validPlayers.Where(c =>
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
|
||||
}
|
||||
|
||||
foreach (var controller in validPlayers.ToList())
|
||||
{
|
||||
// Replace "CALLER" placeholder based on showActivityType
|
||||
var message = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||
{
|
||||
1 => translatedMessage.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
_ => translatedMessage.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
|
||||
};
|
||||
|
||||
// Send the pre-translated message to the player
|
||||
controller.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows admin activity using module's localizer for per-player language support.
|
||||
/// Each player receives the message in their configured language using SendLocalizedMessage.
|
||||
/// </summary>
|
||||
public static void ShowAdminActivityLocalized(IStringLocalizer moduleLocalizer, string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
|
||||
if (CS2_SimpleAdmin._localizer == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(callerName))
|
||||
callerName = CS2_SimpleAdmin._localizer["sa_console"];
|
||||
|
||||
var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();
|
||||
|
||||
var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });
|
||||
|
||||
if (!validPlayers.Any())
|
||||
return;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
|
||||
{
|
||||
validPlayers = validPlayers.Where(c =>
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
|
||||
AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
|
||||
}
|
||||
|
||||
foreach (var controller in validPlayers.ToList())
|
||||
{
|
||||
var currentMessageArgs = (string[])formattedMessageArgs.Clone();
|
||||
|
||||
// Replace "CALLER" placeholder based on showActivityType
|
||||
for (var i = 0; i < currentMessageArgs.Length; i++)
|
||||
{
|
||||
var arg = currentMessageArgs[i];
|
||||
currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||
{
|
||||
1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||
_ => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
|
||||
};
|
||||
}
|
||||
|
||||
// Send the localized message to each player using their language
|
||||
controller.SendLocalizedMessage(moduleLocalizer, messageKey, currentMessageArgs.Cast<object>().ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisplayCenterMessage(
|
||||
CCSPlayerController player,
|
||||
string messageKey,
|
||||
@@ -794,7 +877,7 @@ internal static class Helper
|
||||
if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || CS2_SimpleAdmin._localizer == null)
|
||||
return;
|
||||
|
||||
if (caller != null && caller.IsValid == false)
|
||||
if (caller != null && !caller.IsValid)
|
||||
caller = null;
|
||||
|
||||
var callerName = caller == null ? CS2_SimpleAdmin._localizer["sa_console"] : caller.PlayerName;
|
||||
@@ -806,32 +889,38 @@ internal static class Helper
|
||||
commandString]));
|
||||
}
|
||||
|
||||
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
|
||||
#pragma warning disable CS8604
|
||||
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null, Action<CCSPlayerController>? resetAction = null)
|
||||
{
|
||||
if (CS2_SimpleAdmin.MenuApi == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var menuType = CS2_SimpleAdmin.Instance.Config.MenuConfigs.MenuType.ToLower();
|
||||
|
||||
var menu = menuType switch
|
||||
{
|
||||
_ when menuType.Equals("selectable", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.GetMenu(title),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("dynamic", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ButtonMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ButtonMenu, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("center", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.CenterMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.CenterMenu, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("chat", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ChatMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ChatMenu, backAction, resetAction),
|
||||
|
||||
_ when menuType.Equals("console", StringComparison.CurrentCultureIgnoreCase) =>
|
||||
CS2_SimpleAdmin.MenuApi?.GetMenuForcetype(title, MenuType.ConsoleMenu),
|
||||
CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ConsoleMenu, backAction, resetAction),
|
||||
|
||||
_ => CS2_SimpleAdmin.MenuApi?.GetMenu(title)
|
||||
_ => CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction)
|
||||
};
|
||||
|
||||
return menu;
|
||||
}
|
||||
#pragma warning restore CS8604
|
||||
|
||||
internal static IPluginManager? GetPluginManager()
|
||||
{
|
||||
|
||||
@@ -29,7 +29,6 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
||||
try
|
||||
{
|
||||
var sql = databaseProvider.GetAddBanQuery();
|
||||
|
||||
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = player.SteamId.SteamId64,
|
||||
|
||||
@@ -70,39 +70,44 @@ internal class CacheManager: IDisposable
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
var ipHistory =
|
||||
(await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
|
||||
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC")).ToList();
|
||||
|
||||
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
|
||||
{
|
||||
var ipSet = group
|
||||
.GroupBy(x => x.address)
|
||||
.Select(g =>
|
||||
{
|
||||
var latest = g.MaxBy(x => x.used_at);
|
||||
return new IpRecord(
|
||||
g.Key,
|
||||
latest.used_at,
|
||||
string.IsNullOrEmpty(latest.name)
|
||||
? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
|
||||
: latest.name
|
||||
);
|
||||
})
|
||||
.ToHashSet(new IpRecordComparer());
|
||||
// Optimization: Load IP history and build cache in single pass
|
||||
var ipHistory = await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
|
||||
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY steamid, address, used_at DESC");
|
||||
|
||||
_playerIpsCache.AddOrUpdate(
|
||||
group.Key,
|
||||
_ => ipSet,
|
||||
(_, existingSet) =>
|
||||
{
|
||||
foreach (var ip in ipSet)
|
||||
{
|
||||
existingSet.Remove(ip);
|
||||
existingSet.Add(ip);
|
||||
}
|
||||
return existingSet;
|
||||
});
|
||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var currentSteamId = 0UL;
|
||||
var currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
|
||||
var latestIpTimestamps = new Dictionary<uint, DateTime>();
|
||||
|
||||
foreach (var record in ipHistory)
|
||||
{
|
||||
// When we encounter a new steamid, save the previous one
|
||||
if (record.steamid != currentSteamId && currentSteamId != 0)
|
||||
{
|
||||
_playerIpsCache[currentSteamId] = currentIpSet;
|
||||
currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
|
||||
latestIpTimestamps.Clear();
|
||||
}
|
||||
|
||||
currentSteamId = record.steamid;
|
||||
|
||||
// Only keep the latest timestamp for each IP
|
||||
if (!latestIpTimestamps.TryGetValue(record.address, out var existingTimestamp) ||
|
||||
record.used_at > existingTimestamp)
|
||||
{
|
||||
latestIpTimestamps[record.address] = record.used_at;
|
||||
currentIpSet.Add(new IpRecord(
|
||||
record.address,
|
||||
record.used_at,
|
||||
string.IsNullOrEmpty(record.name) ? unknownName : record.name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Don't forget the last steamid
|
||||
if (currentSteamId != 0)
|
||||
{
|
||||
_playerIpsCache[currentSteamId] = currentIpSet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,8 +155,10 @@ internal class CacheManager: IDisposable
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
IEnumerable<BanRecord> updatedBans;
|
||||
|
||||
var allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
||||
|
||||
// Optimization: Only get IDs for comparison if we need to check for deletions
|
||||
// Most of the time bans are just added/updated, not deleted
|
||||
HashSet<int>? allIds = null;
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
{
|
||||
@@ -161,11 +168,19 @@ internal class CacheManager: IDisposable
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
status AS Status
|
||||
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
||||
""",
|
||||
new { lastUpdate = _lastUpdateTime }
|
||||
));
|
||||
|
||||
// Optimization: Only fetch all IDs if there were updates
|
||||
var updatedList = updatedBans.ToList();
|
||||
if (updatedList.Count > 0)
|
||||
{
|
||||
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
||||
}
|
||||
updatedBans = updatedList;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -175,32 +190,47 @@ internal class CacheManager: IDisposable
|
||||
player_name AS PlayerName,
|
||||
player_steamid AS PlayerSteamId,
|
||||
player_ip AS PlayerIp,
|
||||
status AS Status
|
||||
status AS Status
|
||||
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
|
||||
""",
|
||||
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
|
||||
));
|
||||
}
|
||||
|
||||
foreach (var id in _banCache.Keys)
|
||||
{
|
||||
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
|
||||
|
||||
if (ban.PlayerSteamId != null &&
|
||||
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
|
||||
{
|
||||
steamBans.RemoveAll(b => b.Id == id);
|
||||
if (steamBans.Count == 0)
|
||||
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
|
||||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
|
||||
// Optimization: Only fetch all IDs if there were updates
|
||||
var updatedList = updatedBans.ToList();
|
||||
if (updatedList.Count > 0)
|
||||
{
|
||||
ipBans.RemoveAll(b => b.Id == id);
|
||||
if (ipBans.Count == 0)
|
||||
_ipIndex.TryRemove(ipUInt, out _);
|
||||
allIds = (await connection.QueryAsync<int>(
|
||||
"SELECT id FROM sa_bans WHERE server_id = @serverId",
|
||||
new { serverId = CS2_SimpleAdmin.ServerId }
|
||||
)).ToHashSet();
|
||||
}
|
||||
updatedBans = updatedList;
|
||||
}
|
||||
|
||||
// Optimization: Only process deletions if we have the full ID list
|
||||
if (allIds != null)
|
||||
{
|
||||
foreach (var id in _banCache.Keys)
|
||||
{
|
||||
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
|
||||
|
||||
if (ban.PlayerSteamId != null &&
|
||||
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
|
||||
{
|
||||
steamBans.RemoveAll(b => b.Id == id);
|
||||
if (steamBans.Count == 0)
|
||||
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
|
||||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
|
||||
{
|
||||
ipBans.RemoveAll(b => b.Id == id);
|
||||
if (ipBans.Count == 0)
|
||||
_ipIndex.TryRemove(ipUInt, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,12 +275,21 @@ internal class CacheManager: IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache with new/modified bans
|
||||
var hasUpdates = false;
|
||||
foreach (var ban in updatedBans)
|
||||
{
|
||||
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
||||
hasUpdates = true;
|
||||
}
|
||||
|
||||
RebuildIndexes();
|
||||
|
||||
// Always rebuild indexes if there were any updates
|
||||
// This ensures status changes (ACTIVE -> UNBANNED) are reflected
|
||||
if (hasUpdates)
|
||||
{
|
||||
RebuildIndexes();
|
||||
}
|
||||
|
||||
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -266,38 +305,38 @@ internal class CacheManager: IDisposable
|
||||
{
|
||||
_steamIdIndex.Clear();
|
||||
_ipIndex.Clear();
|
||||
|
||||
foreach (var ban in _banCache.Values)
|
||||
{
|
||||
if (ban.StatusEnum != BanStatus.ACTIVE)
|
||||
continue;
|
||||
|
||||
if (ban.PlayerSteamId != null)
|
||||
// Optimization: Cache config value to avoid repeated property access
|
||||
var banType = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType;
|
||||
var checkIpBans = banType != 0;
|
||||
|
||||
// Optimization: Pre-filter only ACTIVE bans to avoid checking status in loop
|
||||
var activeBans = _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
|
||||
foreach (var ban in activeBans)
|
||||
{
|
||||
// Index by Steam ID
|
||||
if (ban.PlayerSteamId.HasValue)
|
||||
{
|
||||
var steamId = ban.PlayerSteamId;
|
||||
_steamIdIndex.AddOrUpdate(
|
||||
steamId.Value,
|
||||
key => [ban],
|
||||
(key, list) =>
|
||||
{
|
||||
list.Add(ban);
|
||||
return list;
|
||||
});
|
||||
var steamId = ban.PlayerSteamId.Value;
|
||||
if (!_steamIdIndex.TryGetValue(steamId, out var steamList))
|
||||
{
|
||||
steamList = new List<BanRecord>();
|
||||
_steamIdIndex[steamId] = steamList;
|
||||
}
|
||||
steamList.Add(ban);
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) continue;
|
||||
|
||||
if (ban.PlayerIp != null &&
|
||||
|
||||
// Index by IP (only if IP bans are enabled)
|
||||
if (checkIpBans && !string.IsNullOrEmpty(ban.PlayerIp) &&
|
||||
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
|
||||
{
|
||||
_ipIndex.AddOrUpdate(
|
||||
ipUInt,
|
||||
key => [ban],
|
||||
(key, list) =>
|
||||
{
|
||||
list.Add(ban);
|
||||
return list;
|
||||
});
|
||||
if (!_ipIndex.TryGetValue(ipUInt, out var ipList))
|
||||
{
|
||||
ipList = new List<BanRecord>();
|
||||
_ipIndex[ipUInt] = ipList;
|
||||
}
|
||||
ipList.Add(ban);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,16 +371,16 @@ internal class CacheManager: IDisposable
|
||||
{
|
||||
var ipAsUint = IpHelper.IpToUint(ipAddress);
|
||||
var results = new List<(ulong, DateTime, string)>();
|
||||
var comparer = _playerIpsCache.Comparer;
|
||||
|
||||
// Optimization: Direct lookup using HashSet.Contains instead of TryGetValue
|
||||
var searchRecord = new IpRecord(ipAsUint, default, null!);
|
||||
|
||||
foreach (var (steamId, ipSet) in _playerIpsCache)
|
||||
{
|
||||
if (!ipSet.TryGetValue(new IpRecord(ipAsUint, Time.ActualDateTime(), "Unknown"), out var actualEntry)) continue;
|
||||
results.Add((steamId, actualEntry.UsedAt, actualEntry.PlayerName));
|
||||
|
||||
// Optimization: Single pass through the set
|
||||
foreach (var entry in ipSet)
|
||||
{
|
||||
if (entry.Ip == ipAsUint && !Equals(entry, actualEntry))
|
||||
if (entry.Ip == ipAsUint)
|
||||
{
|
||||
results.Add((steamId, entry.UsedAt, entry.PlayerName));
|
||||
}
|
||||
|
||||
@@ -95,66 +95,36 @@ internal class PlayerManager
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(
|
||||
steamId, ipAddress))
|
||||
{
|
||||
const string updateQuery = """
|
||||
UPDATE `sa_players_ips`
|
||||
SET used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName
|
||||
WHERE steamid = @SteamID AND address = @IPAddress;
|
||||
""";
|
||||
await connection.ExecuteAsync(updateQuery, new
|
||||
{
|
||||
playerName,
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const string selectQuery =
|
||||
"SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
|
||||
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
// Eliminates the need for SELECT COUNT and duplicate UPDATE queries
|
||||
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
|
||||
var ipUint = IpHelper.IpToUint(ipAddress);
|
||||
|
||||
if (recordExists > 0)
|
||||
{
|
||||
const string updateQuery = """
|
||||
UPDATE `sa_players_ips`
|
||||
SET used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName
|
||||
WHERE steamid = @SteamID AND address = @IPAddress;
|
||||
""";
|
||||
await connection.ExecuteAsync(updateQuery, new
|
||||
{
|
||||
playerName,
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const string insertQuery = """
|
||||
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP);
|
||||
""";
|
||||
await connection.ExecuteAsync(insertQuery, new
|
||||
{
|
||||
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
|
||||
playerName,
|
||||
IPAddress = IpHelper.IpToUint(ipAddress)
|
||||
});
|
||||
}
|
||||
}
|
||||
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE pattern
|
||||
const string upsertQuery = """
|
||||
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
|
||||
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
used_at = CURRENT_TIMESTAMP,
|
||||
name = @playerName;
|
||||
""";
|
||||
|
||||
await connection.ExecuteAsync(upsertQuery, new
|
||||
{
|
||||
SteamID = steamId64,
|
||||
playerName,
|
||||
IPAddress = ipUint
|
||||
});
|
||||
|
||||
// // Cache will be updated on next refresh cycle
|
||||
// if (!CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(steamId, ipAddress))
|
||||
// {
|
||||
// // IP association will be reflected after cache refresh
|
||||
// }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(
|
||||
$"Unable to save ip address for {playerInfo.Name} ({ipAddress}) {ex.Message}");
|
||||
$"Unable to save ip address for {playerInfo.Name} ({ipAddress}): {ex.Message}");
|
||||
}
|
||||
|
||||
playerInfo.AccountsAssociated =
|
||||
@@ -313,31 +283,6 @@ internal class PlayerManager
|
||||
/// </remarks>
|
||||
public void CheckPlayersTimer()
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.AddTimer(0.12f, () =>
|
||||
{
|
||||
if (CS2_SimpleAdmin.SpeedPlayers.Count > 0)
|
||||
{
|
||||
foreach (var (player, speed) in CS2_SimpleAdmin.SpeedPlayers)
|
||||
{
|
||||
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
||||
{
|
||||
player.SetSpeed(speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.GravityPlayers.Count > 0)
|
||||
{
|
||||
foreach (var (player, gravity) in CS2_SimpleAdmin.GravityPlayers)
|
||||
{
|
||||
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
||||
{
|
||||
player.SetGravity(gravity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, TimerFlags.REPEAT);
|
||||
|
||||
CS2_SimpleAdmin.Instance.PlayersTimer = CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -346,18 +291,26 @@ internal class PlayerManager
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
return;
|
||||
|
||||
var tempPlayers = Helper.GetValidPlayers()
|
||||
.Select(p => new
|
||||
{
|
||||
p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot,
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Optimization: Get players once and avoid allocating anonymous types
|
||||
var validPlayers = Helper.GetValidPlayers();
|
||||
if (validPlayers.Count == 0)
|
||||
return;
|
||||
|
||||
// Use ValueTuple instead of anonymous type - better performance and less allocations
|
||||
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
||||
foreach (var p in validPlayers)
|
||||
{
|
||||
tempPlayers.Add((p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot));
|
||||
}
|
||||
|
||||
var pluginInstance = CS2_SimpleAdmin.Instance;
|
||||
var config = _config.OtherSettings; // Cache config access
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Run all expire tasks in parallel
|
||||
var expireTasks = new[]
|
||||
{
|
||||
pluginInstance.BanManager.ExpireOldBans(),
|
||||
@@ -384,22 +337,33 @@ internal class PlayerManager
|
||||
|
||||
if (pluginInstance.CacheManager == null)
|
||||
return;
|
||||
|
||||
var bannedPlayers = tempPlayers.AsValueEnumerable()
|
||||
.Where(player =>
|
||||
{
|
||||
var playerName = player.PlayerName;
|
||||
var steamId = player.SteamID;
|
||||
var ip = player.IpAddress?.Split(':')[0];
|
||||
|
||||
return CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
{
|
||||
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
||||
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
|
||||
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
|
||||
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
|
||||
};
|
||||
}).ToList();
|
||||
// Optimization: Cache ban type and multi-account check to avoid repeated config access
|
||||
var banType = config.BanType;
|
||||
var checkMultiAccounts = config.CheckMultiAccountsByIp;
|
||||
|
||||
var bannedPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>();
|
||||
|
||||
// Manual loop instead of LINQ - better performance
|
||||
foreach (var player in tempPlayers)
|
||||
{
|
||||
var playerName = player.PlayerName;
|
||||
var steamId = player.SteamID;
|
||||
var ip = player.IpAddress?.Split(':')[0];
|
||||
|
||||
bool isBanned = banType switch
|
||||
{
|
||||
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
||||
_ => checkMultiAccounts
|
||||
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
|
||||
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
|
||||
};
|
||||
|
||||
if (isBanned)
|
||||
{
|
||||
bannedPlayers.Add(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (bannedPlayers.Count > 0)
|
||||
{
|
||||
@@ -410,32 +374,38 @@ internal class PlayerManager
|
||||
}
|
||||
}
|
||||
|
||||
if (_config.OtherSettings.TimeMode == 0)
|
||||
if (config.TimeMode == 0)
|
||||
{
|
||||
var onlinePlayers = tempPlayers.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList();
|
||||
if (tempPlayers.Count == 0 || onlinePlayers.Count == 0) return;
|
||||
// Optimization: Manual projection instead of LINQ
|
||||
var onlinePlayers = new List<(ulong, int?, int)>(tempPlayers.Count);
|
||||
foreach (var player in tempPlayers)
|
||||
{
|
||||
onlinePlayers.Add((player.SteamID, player.UserId, player.Slot));
|
||||
}
|
||||
|
||||
if (onlinePlayers.Count > 0)
|
||||
{
|
||||
await pluginInstance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Optimization: Process penalties without LINQ allocations
|
||||
var players = Helper.GetValidPlayers();
|
||||
var penalizedSlots = players
|
||||
.Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
|
||||
.Select(player => new
|
||||
{
|
||||
Player = player,
|
||||
IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _),
|
||||
IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _),
|
||||
IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _)
|
||||
});
|
||||
|
||||
foreach (var entry in penalizedSlots)
|
||||
foreach (var player in players)
|
||||
{
|
||||
if (!entry.IsMuted && !entry.IsSilenced)
|
||||
if (!PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
|
||||
continue;
|
||||
|
||||
var isMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _);
|
||||
var isSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _);
|
||||
|
||||
// Only reset voice flags if not muted or silenced
|
||||
if (!isMuted && !isSilenced)
|
||||
{
|
||||
entry.Player.VoiceFlags = VoiceFlags.Normal;
|
||||
player.VoiceFlags = VoiceFlags.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,29 +35,43 @@ public class ServerManager
|
||||
{
|
||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
|
||||
if (_getIpTryCount > 32 && Helper.GetServerIp().StartsWith("0.0.0.0") || string.IsNullOrEmpty(Helper.GetServerIp()))
|
||||
// Optimization: Get server IP once and reuse
|
||||
var serverIp = Helper.GetServerIp();
|
||||
var isInvalidIp = string.IsNullOrEmpty(serverIp) || serverIp.StartsWith("0.0.0");
|
||||
|
||||
// Check if we've exceeded retry limit with invalid IP
|
||||
if (_getIpTryCount > 32 && isInvalidIp)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load server data - can't fetch ip address!");
|
||||
return;
|
||||
}
|
||||
|
||||
var ipAddress = ConVar.Find("ip")?.StringValue;
|
||||
// Optimization: Cache ConVar lookups
|
||||
var ipConVar = ConVar.Find("ip");
|
||||
var ipAddress = ipConVar?.StringValue;
|
||||
|
||||
// Use Helper IP if ConVar IP is invalid
|
||||
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
|
||||
{
|
||||
ipAddress = Helper.GetServerIp();
|
||||
ipAddress = serverIp;
|
||||
|
||||
if (_getIpTryCount <= 32 && (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")))
|
||||
// Retry if still invalid and under retry limit
|
||||
if (_getIpTryCount <= 32 && isInvalidIp)
|
||||
{
|
||||
_getIpTryCount++;
|
||||
|
||||
LoadServerData();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var address = $"{ipAddress}:{ConVar.Find("hostport")?.GetPrimitiveValue<int>()}";
|
||||
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var rconPassword = ConVar.Find("rcon_password")?.StringValue ?? "";
|
||||
// Optimization: Cache remaining ConVar lookups
|
||||
var hostportConVar = ConVar.Find("hostport");
|
||||
var hostnameConVar = ConVar.Find("hostname");
|
||||
var rconPasswordConVar = ConVar.Find("rcon_password");
|
||||
|
||||
var address = $"{ipAddress}:{hostportConVar?.GetPrimitiveValue<int>()}";
|
||||
var hostname = hostnameConVar?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var rconPassword = rconPasswordConVar?.StringValue ?? "";
|
||||
CS2_SimpleAdmin.IpAddress = address;
|
||||
|
||||
Task.Run(async () =>
|
||||
@@ -118,6 +132,8 @@ public class ServerManager
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CS2_SimpleAdmin.SimpleAdminApi?.OnSimpleAdminReadyEvent();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,11 @@ namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public static class AdminMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
MenuManager.Instance.OpenMainMenu(admin);
|
||||
}
|
||||
|
||||
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
|
||||
{
|
||||
return Helper.CreateMenu(title, backAction);
|
||||
@@ -27,44 +32,44 @@ public static class AdminMenu
|
||||
// }
|
||||
}
|
||||
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
List<ChatMenuOptionData> options =
|
||||
[
|
||||
new(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
|
||||
new(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
|
||||
new(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
|
||||
];
|
||||
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
if (customCommands.Count > 0)
|
||||
{
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
|
||||
}
|
||||
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
{
|
||||
var menuName = menuOptionData.Name;
|
||||
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
|
||||
}
|
||||
|
||||
if (menu != null) OpenMenu(admin, menu);
|
||||
}
|
||||
// public static void OpenMenu(CCSPlayerController admin)
|
||||
// {
|
||||
// if (admin.IsValid == false)
|
||||
// return;
|
||||
//
|
||||
// var localizer = CS2_SimpleAdmin._localizer;
|
||||
// if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
|
||||
// {
|
||||
// admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
// "[SimpleAdmin] " +
|
||||
// (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
// List<ChatMenuOptionData> options =
|
||||
// [
|
||||
// new(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
|
||||
// new(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
|
||||
// new(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
|
||||
// ];
|
||||
//
|
||||
// var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
// if (customCommands.Count > 0)
|
||||
// {
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
|
||||
// }
|
||||
//
|
||||
// if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
|
||||
//
|
||||
// foreach (var menuOptionData in options)
|
||||
// {
|
||||
// var menuName = menuOptionData.Name;
|
||||
// menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
|
||||
// }
|
||||
//
|
||||
// if (menu != null) OpenMenu(admin, menu);
|
||||
// }
|
||||
}
|
||||
602
CS2-SimpleAdmin/Menus/BasicMenu.cs
Normal file
602
CS2-SimpleAdmin/Menus/BasicMenu.cs
Normal file
@@ -0,0 +1,602 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public abstract class BasicMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes all menus in the system by registering them with the MenuManager.
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
var manager = MenuManager.Instance;
|
||||
|
||||
// Players category menus
|
||||
manager.RegisterMenu("players", "slap", "Slap Player", CreateSlapMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "slay", "Slay Player", CreateSlayMenu, "@css/slay");
|
||||
manager.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "warn", "Warn Player", CreateWarnMenu, "@css/kick");
|
||||
manager.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
|
||||
manager.RegisterMenu("players", "gag", "Gag Player", CreateGagMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "mute", "Mute Player", CreateMuteMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "silence", "Silence Player", CreateSilenceMenu, "@css/chat");
|
||||
manager.RegisterMenu("players", "team", "Force Team", CreateForceTeamMenu, "@css/kick");
|
||||
|
||||
// Server category menus
|
||||
manager.RegisterMenu("server", "plugins", "Manage Plugins", CreatePluginsMenu, "@css/root");
|
||||
manager.RegisterMenu("server", "changemap", "Change Map", CreateChangeMapMenu, "@css/changemap");
|
||||
manager.RegisterMenu("server", "restart", "Restart Game", CreateRestartGameMenu, "@css/generic");
|
||||
manager.RegisterMenu("server", "custom", "Custom Commands", CreateCustomCommandsMenu, "@css/generic");
|
||||
|
||||
// Admin category menus
|
||||
manager.RegisterMenu("admin", "add", "Add Admin", CreateAddAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "remove", "Remove Admin", CreateRemoveAdminMenu, "@css/root");
|
||||
manager.RegisterMenu("admin", "reload", "Reload Admins", CreateReloadAdminsMenu, "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for slapping players with selectable damage amounts.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the slap menu.</returns>
|
||||
|
||||
private static MenuBuilder CreateSlapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slapMenu = new MenuBuilder(localizer?["sa_slap"] ?? "Slap Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, player));
|
||||
}
|
||||
|
||||
return slapMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates damage selection submenu for slapping a specific player.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player executing the slap.</param>
|
||||
/// <param name="target">The target player to be slapped.</param>
|
||||
/// <returns>A MenuBuilder instance for the slap damage menu.</returns>
|
||||
private static MenuBuilder CreateSlapDamageMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var slapDamageMenu = new MenuBuilder($"Slap: {target.PlayerName}");
|
||||
var damages = new[] { 0, 1, 5, 10, 50, 100 };
|
||||
|
||||
foreach (var damage in damages)
|
||||
{
|
||||
slapDamageMenu.AddOption($"{damage} HP", _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Slap(admin, target, damage);
|
||||
// Keep menu open for consecutive slaps
|
||||
CreateSlapDamageMenu(admin, target).OpenMenu(admin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return slapDamageMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for slaying (killing) players.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the slay menu.</returns>
|
||||
private static MenuBuilder CreateSlayMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var slayMenu = new MenuBuilder(localizer?["sa_slay"] ?? "Slay Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
slayMenu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Slay(admin, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return slayMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for kicking players with reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the kick menu.</returns>
|
||||
private static MenuBuilder CreateKickMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var kickMenu = new MenuBuilder(localizer?["sa_kick"] ?? "Kick Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return kickMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for warning players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the warn menu.</returns>
|
||||
private static MenuBuilder CreateWarnMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var warnMenu = new MenuBuilder(localizer?["sa_warn"] ?? "Warn Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Warn",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Warn", PenaltyType.Warn,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return warnMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for banning players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the ban menu.</returns>
|
||||
private static MenuBuilder CreateBanMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var banMenu = new MenuBuilder(localizer?["sa_ban"] ?? "Ban Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Ban",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Ban", PenaltyType.Ban,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return banMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for gagging (text chat muting) players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the gag menu.</returns>
|
||||
private static MenuBuilder CreateGagMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var gagMenu = new MenuBuilder(localizer?["sa_gag"] ?? "Gag Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Gag",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Gag", PenaltyType.Gag,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return gagMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for muting (voice chat muting) players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the mute menu.</returns>
|
||||
private static MenuBuilder CreateMuteMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var muteMenu = new MenuBuilder(localizer?["sa_mute"] ?? "Mute Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Mute",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Mute", PenaltyType.Mute,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return muteMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for silencing (both text and voice chat muting) players with duration and reason selection.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the silence menu.</returns>
|
||||
private static MenuBuilder CreateSilenceMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var silenceMenu = new MenuBuilder(localizer?["sa_silence"] ?? "Silence Player");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Silence",
|
||||
(_, _, duration) => CreateReasonMenu(admin, player, "Silence", PenaltyType.Silence,
|
||||
(_, _, reason) =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
return silenceMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for forcing players to switch teams.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the force team menu.</returns>
|
||||
private static MenuBuilder CreateForceTeamMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamMenu = new MenuBuilder(localizer?["sa_team_force"] ?? "Force Team");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return teamMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates team selection submenu for forcing a specific player to a team.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player executing the team change.</param>
|
||||
/// <param name="target">The target player to be moved.</param>
|
||||
/// <returns>A MenuBuilder instance for the team selection menu.</returns>
|
||||
private static MenuBuilder CreateTeamSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var teamSelectionMenu = new MenuBuilder($"Force Team: {target.PlayerName}");
|
||||
|
||||
var teams = new[]
|
||||
{
|
||||
(localizer?["sa_team_ct"] ?? "CT", "ct", CsTeam.CounterTerrorist),
|
||||
(localizer?["sa_team_t"] ?? "T", "t", CsTeam.Terrorist),
|
||||
(localizer?["sa_team_swap"] ?? "Swap", "swap", CsTeam.Spectator),
|
||||
(localizer?["sa_team_spec"] ?? "Spec", "spec", CsTeam.Spectator)
|
||||
};
|
||||
|
||||
foreach (var (name, teamName, teamNum) in teams)
|
||||
{
|
||||
teamSelectionMenu.AddOption(name, _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.ChangeTeam(admin, target, teamName, teamNum, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return teamSelectionMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for managing server plugins.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the plugins menu.</returns>
|
||||
private static MenuBuilder CreatePluginsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var pluginsMenu = new MenuBuilder(localizer?["sa_menu_pluginsmanager_title"] ?? "Manage Plugins");
|
||||
|
||||
pluginsMenu.AddOption("Open Plugins Manager", _ =>
|
||||
{
|
||||
admin.ExecuteClientCommandFromServer("css_pluginsmanager");
|
||||
});
|
||||
|
||||
return pluginsMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for changing the current map (includes default and workshop maps).
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the change map menu.</returns>
|
||||
private static MenuBuilder CreateChangeMapMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mapMenu = new MenuBuilder(localizer?["sa_changemap"] ?? "Change Map");
|
||||
|
||||
// Add default maps
|
||||
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
|
||||
foreach (var map in maps)
|
||||
{
|
||||
mapMenu.AddOption(map, _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ChangeMap(admin, map);
|
||||
});
|
||||
}
|
||||
|
||||
// Add workshop maps
|
||||
var wsMaps = CS2_SimpleAdmin.Instance.Config.WorkshopMaps;
|
||||
foreach (var wsMap in wsMaps)
|
||||
{
|
||||
mapMenu.AddOption($"{wsMap.Key} (WS)", _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ChangeWorkshopMap(admin, wsMap.Value?.ToString() ?? wsMap.Key);
|
||||
});
|
||||
}
|
||||
|
||||
return mapMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for restarting the current game/round.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the restart game menu.</returns>
|
||||
private static MenuBuilder CreateRestartGameMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var restartMenu = new MenuBuilder(localizer?["sa_restart_game"] ?? "Restart Game");
|
||||
|
||||
restartMenu.AddOption("Restart Round", _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.RestartGame(admin);
|
||||
});
|
||||
|
||||
return restartMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for executing custom server commands defined in configuration.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the custom commands menu.</returns>
|
||||
private static MenuBuilder CreateCustomCommandsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var customMenu = new MenuBuilder(localizer?["sa_menu_custom_commands"] ?? "Custom Commands");
|
||||
|
||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||
|
||||
foreach (var customCommand in customCommands)
|
||||
{
|
||||
if (string.IsNullOrEmpty(customCommand.DisplayName) || string.IsNullOrEmpty(customCommand.Command))
|
||||
continue;
|
||||
|
||||
var steamId = new SteamID(admin.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, customCommand.Flag))
|
||||
continue;
|
||||
|
||||
customMenu.AddOption(customCommand.DisplayName, _ =>
|
||||
{
|
||||
Helper.TryLogCommandOnDiscord(admin, customCommand.Command);
|
||||
|
||||
if (customCommand.ExecuteOnClient)
|
||||
admin.ExecuteClientCommandFromServer(customCommand.Command);
|
||||
else
|
||||
Server.ExecuteCommand(customCommand.Command);
|
||||
});
|
||||
}
|
||||
|
||||
return customMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for adding admin privileges to players.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the add admin menu.</returns>
|
||||
private static MenuBuilder CreateAddAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var addAdminMenu = new MenuBuilder(localizer?["sa_admin_add"] ?? "Add Admin");
|
||||
|
||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, player));
|
||||
}
|
||||
|
||||
return addAdminMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates admin flags selection submenu for granting specific permissions to a player.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player granting permissions.</param>
|
||||
/// <param name="target">The target player to receive admin privileges.</param>
|
||||
/// <returns>A MenuBuilder instance for the admin flags menu.</returns>
|
||||
private static MenuBuilder CreateAdminFlagsMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var flagsMenu = new MenuBuilder($"Add Admin: {target.PlayerName}");
|
||||
|
||||
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
|
||||
{
|
||||
var hasFlag = AdminManager.PlayerHasPermissions(target, adminFlag.Flag);
|
||||
flagsMenu.AddOption(adminFlag.Name, _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.AddAdmin(admin, target.SteamID.ToString(), target.PlayerName, adminFlag.Flag, 10);
|
||||
}
|
||||
}, hasFlag); // Disabled if player already has this flag
|
||||
}
|
||||
|
||||
return flagsMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for removing admin privileges from players.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the remove admin menu.</returns>
|
||||
private static MenuBuilder CreateRemoveAdminMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var removeAdminMenu = new MenuBuilder(localizer?["sa_admin_remove"] ?? "Remove Admin");
|
||||
|
||||
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
||||
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
||||
p != admin &&
|
||||
admin.CanTarget(p));
|
||||
|
||||
foreach (var player in adminPlayers)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
removeAdminMenu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return removeAdminMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates menu for reloading admin list from database.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player opening the menu.</param>
|
||||
/// <returns>A MenuBuilder instance for the reload admins menu.</returns>
|
||||
private static MenuBuilder CreateReloadAdminsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var reloadMenu = new MenuBuilder(localizer?["sa_admin_reload"] ?? "Reload Admins");
|
||||
|
||||
reloadMenu.AddOption("Reload Admins", _ =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(admin);
|
||||
});
|
||||
|
||||
return reloadMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates duration selection submenu for time-based penalties (ban, mute, gag, etc.).
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting duration.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
||||
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||
{
|
||||
var durationMenu = new MenuBuilder($"{actionName} Duration: {player.PlayerName}");
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
durationMenu.AddOption(durationItem.Name, _ =>
|
||||
{
|
||||
onSelectAction(admin, player, durationItem.Duration);
|
||||
});
|
||||
}
|
||||
|
||||
return durationMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates reason selection submenu for penalties with predefined reasons from configuration.
|
||||
/// </summary>
|
||||
/// <param name="admin">The admin player selecting reason.</param>
|
||||
/// <param name="player">The target player for the penalty.</param>
|
||||
/// <param name="actionName">The name of the penalty action.</param>
|
||||
/// <param name="penaltyType">The type of penalty to determine which reason list to use.</param>
|
||||
/// <param name="onSelectAction">Callback action executed when reason is selected.</param>
|
||||
/// <returns>A MenuBuilder instance for the reason menu.</returns>
|
||||
private static MenuBuilder CreateReasonMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||
PenaltyType penaltyType, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
|
||||
{
|
||||
var reasonMenu = new MenuBuilder($"{actionName} Reason: {player.PlayerName}");
|
||||
|
||||
var reasons = penaltyType switch
|
||||
{
|
||||
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons,
|
||||
PenaltyType.Kick => CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons,
|
||||
PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
|
||||
PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons,
|
||||
PenaltyType.Gag or PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
|
||||
_ => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons
|
||||
};
|
||||
|
||||
foreach (var reason in reasons)
|
||||
{
|
||||
reasonMenu.AddOption(reason, _ =>
|
||||
{
|
||||
onSelectAction(admin, player, reason);
|
||||
});
|
||||
}
|
||||
|
||||
return reasonMenu.WithBackButton();
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ public static class CustomCommandsMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
|
||||
@@ -8,7 +8,6 @@ public static class DurationMenu
|
||||
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||
{
|
||||
var menu = AdminMenu.CreateMenu(menuName);
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
|
||||
@@ -20,7 +19,6 @@ public static class DurationMenu
|
||||
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
|
||||
{
|
||||
var menu = AdminMenu.CreateMenu(menuName);
|
||||
|
||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||
{
|
||||
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
|
||||
|
||||
@@ -1,267 +1,267 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public static class FunActionsMenu
|
||||
{
|
||||
private static Dictionary<int, CsItem>? _weaponsCache;
|
||||
|
||||
private static Dictionary<int, CsItem> GetWeaponsCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_weaponsCache != null) return _weaponsCache;
|
||||
|
||||
var weaponsArray = Enum.GetValues(typeof(CsItem));
|
||||
|
||||
// avoid duplicates in the menu
|
||||
_weaponsCache = new Dictionary<int, CsItem>();
|
||||
foreach (CsItem item in weaponsArray)
|
||||
{
|
||||
if (item == CsItem.Tablet)
|
||||
continue;
|
||||
|
||||
_weaponsCache[(int)item] = item;
|
||||
}
|
||||
|
||||
return _weaponsCache;
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands");
|
||||
List<ChatMenuOptionData> options = [];
|
||||
|
||||
//var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats");
|
||||
//var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay");
|
||||
|
||||
// options added in order
|
||||
|
||||
if (AdminManager.CommandIsOverriden("css_god")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_god"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
|
||||
if (AdminManager.CommandIsOverriden("css_noclip")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_noclip"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
|
||||
if (AdminManager.CommandIsOverriden("css_respawn")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_respawn"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
|
||||
if (AdminManager.CommandIsOverriden("css_give")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_give"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
|
||||
|
||||
if (AdminManager.CommandIsOverriden("css_strip")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_strip"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
|
||||
if (AdminManager.CommandIsOverriden("css_freeze")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_freeze"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
|
||||
if (AdminManager.CommandIsOverriden("css_hp")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_hp"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
|
||||
if (AdminManager.CommandIsOverriden("css_speed")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_speed"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
|
||||
if (AdminManager.CommandIsOverriden("css_gravity")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gravity"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
|
||||
if (AdminManager.CommandIsOverriden("css_money")
|
||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_money"))
|
||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
|
||||
|
||||
foreach (var menuOptionData in options)
|
||||
{
|
||||
var menuName = menuOptionData.Name;
|
||||
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void GodMode(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.God(admin, player);
|
||||
}
|
||||
|
||||
private static void NoClip(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.NoClip(admin, player);
|
||||
}
|
||||
|
||||
private static void Respawn(CCSPlayerController? admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.Respawn(admin, player);
|
||||
}
|
||||
|
||||
private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}");
|
||||
|
||||
foreach (var weapon in GetWeaponsCache)
|
||||
{
|
||||
menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue)
|
||||
{
|
||||
CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue);
|
||||
}
|
||||
|
||||
private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
CS2_SimpleAdmin.StripWeapons(admin, player);
|
||||
}
|
||||
|
||||
private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
if (!(player.PlayerPawn.Value?.IsValid ?? false))
|
||||
return;
|
||||
|
||||
if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_INVALID)
|
||||
CS2_SimpleAdmin.Freeze(admin, player, -1);
|
||||
else
|
||||
CS2_SimpleAdmin.Unfreeze(admin, player);
|
||||
}
|
||||
|
||||
private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var hpArray = new[]
|
||||
{
|
||||
new Tuple<string, int>("1", 1),
|
||||
new Tuple<string, int>("10", 10),
|
||||
new Tuple<string, int>("25", 25),
|
||||
new Tuple<string, int>("50", 50),
|
||||
new Tuple<string, int>("100", 100),
|
||||
new Tuple<string, int>("200", 200),
|
||||
new Tuple<string, int>("500", 500),
|
||||
new Tuple<string, int>("999", 999)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in hpArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp)
|
||||
{
|
||||
CS2_SimpleAdmin.SetHp(admin, player, hp);
|
||||
}
|
||||
|
||||
private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var speedArray = new[]
|
||||
{
|
||||
new Tuple<string, float>("0.1", .1f),
|
||||
new Tuple<string, float>("0.25", .25f),
|
||||
new Tuple<string, float>("0.5", .5f),
|
||||
new Tuple<string, float>("0.75", .75f),
|
||||
new Tuple<string, float>("1", 1),
|
||||
new Tuple<string, float>("2", 2),
|
||||
new Tuple<string, float>("3", 3),
|
||||
new Tuple<string, float>("4", 4)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in speedArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed)
|
||||
{
|
||||
CS2_SimpleAdmin.SetSpeed(admin, player, speed);
|
||||
}
|
||||
|
||||
private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var gravityArray = new[]
|
||||
{
|
||||
new Tuple<string, float>("0.1", .1f),
|
||||
new Tuple<string, float>("0.25", .25f),
|
||||
new Tuple<string, float>("0.5", .5f),
|
||||
new Tuple<string, float>("0.75", .75f),
|
||||
new Tuple<string, float>("1", 1),
|
||||
new Tuple<string, float>("2", 2)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in gravityArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity)
|
||||
{
|
||||
CS2_SimpleAdmin.SetGravity(admin, player, gravity);
|
||||
}
|
||||
|
||||
private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
{
|
||||
var moneyArray = new[]
|
||||
{
|
||||
new Tuple<string, int>("$0", 0),
|
||||
new Tuple<string, int>("$1000", 1000),
|
||||
new Tuple<string, int>("$2500", 2500),
|
||||
new Tuple<string, int>("$5000", 5000),
|
||||
new Tuple<string, int>("$10000", 10000),
|
||||
new Tuple<string, int>("$16000", 16000)
|
||||
};
|
||||
|
||||
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}");
|
||||
|
||||
foreach (var (optionName, value) in moneyArray)
|
||||
{
|
||||
menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); });
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
}
|
||||
|
||||
private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money)
|
||||
{
|
||||
CS2_SimpleAdmin.SetMoney(admin, player, money);
|
||||
}
|
||||
}
|
||||
// using CounterStrikeSharp.API.Core;
|
||||
// using CounterStrikeSharp.API.Modules.Admin;
|
||||
// using CounterStrikeSharp.API.Modules.Entities;
|
||||
// using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
//
|
||||
// namespace CS2_SimpleAdmin.Menus;
|
||||
//
|
||||
// public static class FunActionsMenu
|
||||
// {
|
||||
// private static Dictionary<int, CsItem>? _weaponsCache;
|
||||
//
|
||||
// private static Dictionary<int, CsItem> GetWeaponsCache
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (_weaponsCache != null) return _weaponsCache;
|
||||
//
|
||||
// var weaponsArray = Enum.GetValues(typeof(CsItem));
|
||||
//
|
||||
// // avoid duplicates in the menu
|
||||
// _weaponsCache = new Dictionary<int, CsItem>();
|
||||
// foreach (CsItem item in weaponsArray)
|
||||
// {
|
||||
// if (item == CsItem.Tablet)
|
||||
// continue;
|
||||
//
|
||||
// _weaponsCache[(int)item] = item;
|
||||
// }
|
||||
//
|
||||
// return _weaponsCache;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void OpenMenu(CCSPlayerController admin)
|
||||
// {
|
||||
// if (!admin.IsValid)
|
||||
// return;
|
||||
//
|
||||
// var localizer = CS2_SimpleAdmin._localizer;
|
||||
// if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
// {
|
||||
// admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
// "[SimpleAdmin] " +
|
||||
// (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands");
|
||||
// List<ChatMenuOptionData> options = [];
|
||||
//
|
||||
// //var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats");
|
||||
// //var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay");
|
||||
//
|
||||
// // options added in order
|
||||
//
|
||||
// if (AdminManager.CommandIsOverriden("css_god")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_god"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
|
||||
// if (AdminManager.CommandIsOverriden("css_noclip")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_noclip"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
|
||||
// if (AdminManager.CommandIsOverriden("css_respawn")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_respawn"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
|
||||
// if (AdminManager.CommandIsOverriden("css_give")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_give"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
|
||||
//
|
||||
// if (AdminManager.CommandIsOverriden("css_strip")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_strip"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
|
||||
// if (AdminManager.CommandIsOverriden("css_freeze")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_freeze"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
|
||||
// if (AdminManager.CommandIsOverriden("css_hp")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_hp"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
|
||||
// if (AdminManager.CommandIsOverriden("css_speed")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_speed"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
|
||||
// if (AdminManager.CommandIsOverriden("css_gravity")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gravity"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
|
||||
// if (AdminManager.CommandIsOverriden("css_money")
|
||||
// ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_money"))
|
||||
// : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
|
||||
// options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
|
||||
//
|
||||
// foreach (var menuOptionData in options)
|
||||
// {
|
||||
// var menuName = menuOptionData.Name;
|
||||
// menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void GodMode(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.God(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void NoClip(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.NoClip(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void Respawn(CCSPlayerController? admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.Respawn(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var weapon in GetWeaponsCache)
|
||||
// {
|
||||
// menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue)
|
||||
// {
|
||||
// CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue);
|
||||
// }
|
||||
//
|
||||
// private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// CS2_SimpleAdmin.StripWeapons(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// if (!(player.PlayerPawn.Value?.IsValid ?? false))
|
||||
// return;
|
||||
//
|
||||
// if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_INVALID)
|
||||
// CS2_SimpleAdmin.Freeze(admin, player, -1);
|
||||
// else
|
||||
// CS2_SimpleAdmin.Unfreeze(admin, player);
|
||||
// }
|
||||
//
|
||||
// private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var hpArray = new[]
|
||||
// {
|
||||
// new Tuple<string, int>("1", 1),
|
||||
// new Tuple<string, int>("10", 10),
|
||||
// new Tuple<string, int>("25", 25),
|
||||
// new Tuple<string, int>("50", 50),
|
||||
// new Tuple<string, int>("100", 100),
|
||||
// new Tuple<string, int>("200", 200),
|
||||
// new Tuple<string, int>("500", 500),
|
||||
// new Tuple<string, int>("999", 999)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in hpArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetHp(admin, player, hp);
|
||||
// }
|
||||
//
|
||||
// private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var speedArray = new[]
|
||||
// {
|
||||
// new Tuple<string, float>("0.1", .1f),
|
||||
// new Tuple<string, float>("0.25", .25f),
|
||||
// new Tuple<string, float>("0.5", .5f),
|
||||
// new Tuple<string, float>("0.75", .75f),
|
||||
// new Tuple<string, float>("1", 1),
|
||||
// new Tuple<string, float>("2", 2),
|
||||
// new Tuple<string, float>("3", 3),
|
||||
// new Tuple<string, float>("4", 4)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in speedArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetSpeed(admin, player, speed);
|
||||
// }
|
||||
//
|
||||
// private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var gravityArray = new[]
|
||||
// {
|
||||
// new Tuple<string, float>("0.1", .1f),
|
||||
// new Tuple<string, float>("0.25", .25f),
|
||||
// new Tuple<string, float>("0.5", .5f),
|
||||
// new Tuple<string, float>("0.75", .75f),
|
||||
// new Tuple<string, float>("1", 1),
|
||||
// new Tuple<string, float>("2", 2)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in gravityArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetGravity(admin, player, gravity);
|
||||
// }
|
||||
//
|
||||
// private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player)
|
||||
// {
|
||||
// var moneyArray = new[]
|
||||
// {
|
||||
// new Tuple<string, int>("$0", 0),
|
||||
// new Tuple<string, int>("$1000", 1000),
|
||||
// new Tuple<string, int>("$2500", 2500),
|
||||
// new Tuple<string, int>("$5000", 5000),
|
||||
// new Tuple<string, int>("$10000", 10000),
|
||||
// new Tuple<string, int>("$16000", 16000)
|
||||
// };
|
||||
//
|
||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}");
|
||||
//
|
||||
// foreach (var (optionName, value) in moneyArray)
|
||||
// {
|
||||
// menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); });
|
||||
// }
|
||||
//
|
||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
// }
|
||||
//
|
||||
// private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money)
|
||||
// {
|
||||
// CS2_SimpleAdmin.SetMoney(admin, player, money);
|
||||
// }
|
||||
// }
|
||||
@@ -8,11 +8,11 @@ public static class ManageAdminsMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
|
||||
@@ -10,11 +10,11 @@ public static class ManagePlayersMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
|
||||
@@ -8,11 +8,11 @@ public static class ManageServerMenu
|
||||
{
|
||||
public static void OpenMenu(CCSPlayerController admin)
|
||||
{
|
||||
if (admin.IsValid == false)
|
||||
if (!admin.IsValid)
|
||||
return;
|
||||
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
|
||||
if (!AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic"))
|
||||
{
|
||||
admin.PrintToChat(localizer?["sa_prefix"] ??
|
||||
"[SimpleAdmin] " +
|
||||
|
||||
170
CS2-SimpleAdmin/Menus/MenuBuilder.cs
Normal file
170
CS2-SimpleAdmin/Menus/MenuBuilder.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
public class MenuBuilder(string title)
|
||||
{
|
||||
private readonly List<MenuOption> _options = [];
|
||||
private MenuBuilder? _parentMenu;
|
||||
private Action<CCSPlayerController>? _backAction;
|
||||
private Action<CCSPlayerController>? _resetAction;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option with an action.
|
||||
/// </summary>
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = action,
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option that opens a submenu.
|
||||
/// </summary>
|
||||
public MenuBuilder AddSubMenu(string name, Func<MenuBuilder> subMenuFactory, bool disabled = false, string? permission = null)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = player =>
|
||||
{
|
||||
var subMenu = subMenuFactory();
|
||||
subMenu.SetParent(this);
|
||||
// Automatically add back button to submenu
|
||||
subMenu.WithBackButton();
|
||||
subMenu.OpenMenu(player);
|
||||
},
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu option that opens a submenu (with player parameter in factory).
|
||||
/// </summary>
|
||||
public MenuBuilder AddSubMenu(string name, Func<CCSPlayerController, MenuBuilder> subMenuFactory, bool disabled = false, string? permission = null)
|
||||
{
|
||||
_options.Add(new MenuOption
|
||||
{
|
||||
Name = name,
|
||||
Action = player =>
|
||||
{
|
||||
var subMenu = subMenuFactory(player);
|
||||
subMenu.SetParent(this);
|
||||
// Automatically add back button to submenu
|
||||
subMenu.WithBackButton();
|
||||
subMenu.OpenMenu(player);
|
||||
},
|
||||
Disabled = disabled,
|
||||
Permission = permission
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a back button to return to the previous menu.
|
||||
/// </summary>
|
||||
public MenuBuilder WithBackButton()
|
||||
{
|
||||
if (_parentMenu != null)
|
||||
{
|
||||
_backAction = player => _parentMenu.OpenMenu(player);
|
||||
|
||||
// Add back option at the end of menu
|
||||
// AddOption(backButtonText, _backAction);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the parent menu (for navigation).
|
||||
/// </summary>
|
||||
private void SetParent(MenuBuilder parent)
|
||||
{
|
||||
_parentMenu = parent;
|
||||
_backAction = player => _parentMenu.OpenMenu(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the menu for a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to open the menu for.</param>
|
||||
public void OpenMenu(CCSPlayerController player)
|
||||
{
|
||||
if (!player.IsValid) return;
|
||||
|
||||
// Use MenuManager dependency
|
||||
var menu = Helper.CreateMenu(title, _backAction);
|
||||
if (menu == null) return;
|
||||
|
||||
foreach (var option in _options)
|
||||
{
|
||||
// Check permissions if required
|
||||
if (!string.IsNullOrEmpty(option.Permission))
|
||||
{
|
||||
var steamId = new CounterStrikeSharp.API.Modules.Entities.SteamID(player.SteamID);
|
||||
if (!CounterStrikeSharp.API.Modules.Admin.AdminManager.PlayerHasPermissions(steamId, option.Permission))
|
||||
{
|
||||
continue; // Skip option if player doesn't have permission
|
||||
}
|
||||
}
|
||||
|
||||
menu.AddMenuOption(option.Name, (menuPlayer, menuOption) =>
|
||||
{
|
||||
option.Action?.Invoke(menuPlayer);
|
||||
}, option.Disabled);
|
||||
}
|
||||
|
||||
menu.Open(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all menu options.
|
||||
/// </summary>
|
||||
/// <returns>This MenuBuilder instance for chaining.</returns>
|
||||
public MenuBuilder Clear()
|
||||
{
|
||||
_options.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a reset action for the menu.
|
||||
/// </summary>
|
||||
/// <param name="resetAction">The action to execute on reset.</param>
|
||||
/// <returns>This MenuBuilder instance for chaining.</returns>
|
||||
public MenuBuilder WithResetAction(Action<CCSPlayerController> resetAction)
|
||||
{
|
||||
_resetAction = resetAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom back action for the menu.
|
||||
/// </summary>
|
||||
/// <param name="backAction">The action to execute when going back (nullable).</param>
|
||||
/// <returns>This MenuBuilder instance for chaining.</returns>
|
||||
public MenuBuilder WithBackAction(Action<CCSPlayerController>? backAction)
|
||||
{
|
||||
_backAction = backAction;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an option within a menu.
|
||||
/// </summary>
|
||||
public class MenuOption
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public Action<CCSPlayerController>? Action { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string? Permission { get; set; }
|
||||
}
|
||||
|
||||
190
CS2-SimpleAdmin/Menus/MenuManager.cs
Normal file
190
CS2-SimpleAdmin/Menus/MenuManager.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdmin.Menus;
|
||||
|
||||
public class MenuManager
|
||||
{
|
||||
private static MenuManager? _instance;
|
||||
public static MenuManager Instance => _instance ??= new MenuManager();
|
||||
|
||||
private readonly Dictionary<string, Func<CCSPlayerController, MenuBuilder>> _menuFactories = [];
|
||||
private readonly Dictionary<string, MenuCategory> _menuCategories = [];
|
||||
|
||||
/// <summary>
|
||||
/// Provides public access to menu categories (for API usage).
|
||||
/// </summary>
|
||||
/// <returns>Dictionary of menu categories keyed by category ID.</returns>
|
||||
public Dictionary<string, MenuCategory> GetMenuCategories()
|
||||
{
|
||||
return _menuCategories;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new menu category with specified permissions.
|
||||
/// </summary>
|
||||
/// <param name="categoryId">Unique identifier for the category.</param>
|
||||
/// <param name="categoryName">Display name of the category.</param>
|
||||
/// <param name="permission">Required permission to access this category (default: @css/generic).</param>
|
||||
public void RegisterCategory(string categoryId, string categoryName, string permission = "@css/generic")
|
||||
{
|
||||
_menuCategories[categoryId] = new MenuCategory
|
||||
{
|
||||
Id = categoryId,
|
||||
Name = categoryName,
|
||||
Permission = permission,
|
||||
MenuFactories = new Dictionary<string, Func<CCSPlayerController, MenuBuilder>>()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu within a category (API for other plugins).
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category to add this menu to.</param>
|
||||
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||
/// <param name="menuName">Display name of the menu.</param>
|
||||
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
|
||||
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission = null)
|
||||
{
|
||||
if (!_menuCategories.ContainsKey(categoryId))
|
||||
{
|
||||
RegisterCategory(categoryId, categoryId); // Auto-create category if it doesn't exist
|
||||
}
|
||||
|
||||
_menuCategories[categoryId].MenuFactories[menuId] = menuFactory;
|
||||
_menuCategories[categoryId].MenuNames[menuId] = menuName;
|
||||
if (permission != null)
|
||||
{
|
||||
_menuCategories[categoryId].MenuPermissions[menuId] = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
/// </summary>
|
||||
/// <param name="categoryId">The category containing the menu.</param>
|
||||
/// <param name="menuId">The menu to unregister.</param>
|
||||
public void UnregisterMenu(string categoryId, string menuId)
|
||||
{
|
||||
if (!_menuCategories.TryGetValue(categoryId, out var category)) return;
|
||||
category.MenuFactories.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuNames.Remove(menuId);
|
||||
_menuCategories[categoryId].MenuPermissions.Remove(menuId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the main admin menu for a player with accessible categories.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to create the menu for.</param>
|
||||
/// <returns>A MenuBuilder instance for the main menu.</returns>
|
||||
public MenuBuilder CreateMainMenu(CCSPlayerController player)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
var mainMenu = new MenuBuilder(localizer?["sa_title"] ?? "SimpleAdmin");
|
||||
|
||||
foreach (var category in _menuCategories.Values)
|
||||
{
|
||||
if (category.MenuFactories.Count <= 0) continue;
|
||||
// Check category permissions
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, category.Permission))
|
||||
continue;
|
||||
|
||||
// Pass player to CreateCategoryMenu
|
||||
mainMenu.AddSubMenu(category.Name, () => CreateCategoryMenu(category, player),
|
||||
permission: category.Permission);
|
||||
}
|
||||
|
||||
return mainMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a category submenu containing all registered menus in that category.
|
||||
/// </summary>
|
||||
/// <param name="category">The menu category to create.</param>
|
||||
/// <param name="player">The player to create the menu for.</param>
|
||||
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
||||
private MenuBuilder CreateCategoryMenu(MenuCategory category, CCSPlayerController player)
|
||||
{
|
||||
var categoryMenu = new MenuBuilder(category.Name);
|
||||
|
||||
foreach (var kvp in category.MenuFactories)
|
||||
{
|
||||
var menuId = kvp.Key;
|
||||
var menuFactory = kvp.Value;
|
||||
var menuName = category.MenuNames.TryGetValue(menuId, out var name) ? name : menuId;
|
||||
var permission = category.MenuPermissions.TryGetValue(menuId, out var perm) ? perm : null;
|
||||
|
||||
// Check permissions
|
||||
if (!string.IsNullOrEmpty(permission))
|
||||
{
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, permission))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the actual factory with player parameter
|
||||
categoryMenu.AddSubMenu(menuName, () => menuFactory(player), permission: permission);
|
||||
}
|
||||
|
||||
return categoryMenu.WithBackButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the main admin menu for a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to open the menu for.</param>
|
||||
public void OpenMainMenu(CCSPlayerController player)
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
var steamId = new SteamID(player.SteamID);
|
||||
if (!AdminManager.PlayerHasPermissions(steamId, "@css/generic"))
|
||||
{
|
||||
player.PrintToChat(localizer?["sa_prefix"] ?? "[SimpleAdmin] " +
|
||||
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command"));
|
||||
return;
|
||||
}
|
||||
|
||||
CreateMainMenu(player).OpenMenu(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes default menu categories (Players, Server, Admin).
|
||||
/// </summary>
|
||||
public void InitializeDefaultCategories()
|
||||
{
|
||||
var localizer = CS2_SimpleAdmin._localizer;
|
||||
|
||||
RegisterCategory("players", localizer?["sa_menu_players_manage"] ?? "Manage Players", "@css/generic");
|
||||
RegisterCategory("server", localizer?["sa_menu_server_manage"] ?? "Server Management", "@css/generic");
|
||||
// RegisterCategory("fun", localizer?["sa_menu_fun_commands"] ?? "Fun Commands", "@css/generic");
|
||||
RegisterCategory("admin", localizer?["sa_menu_admins_manage"] ?? "Admin Management", "@css/root");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method for creating category menus (for API usage).
|
||||
/// </summary>
|
||||
/// <param name="category">The menu category to create.</param>
|
||||
/// <param name="player">The player to create the menu for.</param>
|
||||
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
||||
public MenuBuilder CreateCategoryMenuPublic(MenuCategory category, CCSPlayerController player)
|
||||
{
|
||||
return CreateCategoryMenu(category, player);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a menu category containing multiple menus.
|
||||
/// </summary>
|
||||
public class MenuCategory
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Permission { get; set; } = "@css/generic";
|
||||
public Dictionary<string, Func<CCSPlayerController, MenuBuilder>> MenuFactories { get; set; } = [];
|
||||
public Dictionary<string, string> MenuNames { get; set; } = [];
|
||||
public Dictionary<string, string> MenuPermissions { get; set; } = [];
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public static class PlayersMenu
|
||||
{
|
||||
public static void OpenRealPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
|
||||
{
|
||||
OpenMenu(admin, menuName, onSelectAction, p => p.IsBot == false);
|
||||
OpenMenu(admin, menuName, onSelectAction, p => !p.IsBot);
|
||||
}
|
||||
|
||||
public static void OpenAdminPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController?, bool>? enableFilter = null)
|
||||
@@ -37,7 +37,7 @@ public static class PlayersMenu
|
||||
var playerName = player != null && player.PlayerName.Length > 26 ? player.PlayerName[..26] : player?.PlayerName;
|
||||
|
||||
var optionName = HttpUtility.HtmlEncode(playerName);
|
||||
if (player != null && enableFilter != null && enableFilter(player) == false)
|
||||
if (player != null && enableFilter != null && !enableFilter(player))
|
||||
continue;
|
||||
|
||||
var enabled = admin.CanTarget(player);
|
||||
@@ -47,7 +47,7 @@ public static class PlayersMenu
|
||||
{
|
||||
if (player != null) onSelectAction.Invoke(admin, player);
|
||||
},
|
||||
enabled == false);
|
||||
!enabled);
|
||||
}
|
||||
|
||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.7-alpha-10
|
||||
1.7.8-beta-1
|
||||
@@ -1,6 +1,7 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
|
||||
namespace CS2_SimpleAdminApi;
|
||||
@@ -8,6 +9,8 @@ namespace CS2_SimpleAdminApi;
|
||||
public interface ICS2_SimpleAdminApi
|
||||
{
|
||||
public static readonly PluginCapability<ICS2_SimpleAdminApi?> PluginCapability = new("simpleadmin:api");
|
||||
|
||||
public event Action? OnSimpleAdminReady;
|
||||
|
||||
/// <summary>
|
||||
/// Gets player information associated with the specified player controller.
|
||||
@@ -83,6 +86,25 @@ public interface ICS2_SimpleAdminApi
|
||||
/// </summary>
|
||||
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs);
|
||||
|
||||
/// <summary>
|
||||
/// Shows an admin activity message with a custom translated message (for modules with their own localizer).
|
||||
/// </summary>
|
||||
/// <param name="translatedMessage">Already translated message to display to players.</param>
|
||||
/// <param name="callerName">Name of the admin executing the action (optional).</param>
|
||||
/// <param name="dontPublish">If true, won't trigger publish events.</param>
|
||||
public void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false);
|
||||
|
||||
/// <summary>
|
||||
/// Shows an admin activity message using module's localizer for per-player language support.
|
||||
/// This method sends messages in each player's configured language.
|
||||
/// </summary>
|
||||
/// <param name="moduleLocalizer">The module's IStringLocalizer instance.</param>
|
||||
/// <param name="messageKey">The translation key from the module's lang files.</param>
|
||||
/// <param name="callerName">Name of the admin executing the action (optional).</param>
|
||||
/// <param name="dontPublish">If true, won't trigger publish events.</param>
|
||||
/// <param name="messageArgs">Arguments to format the localized message.</param>
|
||||
public void ShowAdminActivityLocalized(object moduleLocalizer, string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified admin player is in silent mode (not broadcasting activity).
|
||||
/// </summary>
|
||||
@@ -102,4 +124,54 @@ public interface ICS2_SimpleAdminApi
|
||||
/// Unregisters an existing command by its name.
|
||||
/// </summary>
|
||||
public void UnRegisterCommand(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets target players from command
|
||||
/// </summary>
|
||||
TargetResult? GetTarget(CommandInfo command);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of current valid players, available to call from other plugins.
|
||||
/// </summary>
|
||||
List<CCSPlayerController> GetValidPlayers();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu category.
|
||||
/// </summary>
|
||||
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
|
||||
|
||||
/// <summary>
|
||||
/// Registers a menu in a category.
|
||||
/// </summary>
|
||||
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a menu from a category.
|
||||
/// </summary>
|
||||
void UnregisterMenu(string categoryId, string menuId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a menu with an automatic back button.
|
||||
/// </summary>
|
||||
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a menu with a list of players with filter and action.
|
||||
/// </summary>
|
||||
object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an option to the menu (extension method helper).
|
||||
/// </summary>
|
||||
void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a submenu to the menu (extension method helper).
|
||||
/// </summary>
|
||||
void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory, bool disabled = false, string? permission = null);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a menu for a player.
|
||||
/// </summary>
|
||||
void OpenMenu(object menu, CCSPlayerController player);
|
||||
}
|
||||
BIN
Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdminApi.dll
Normal file
BIN
Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdminApi.dll
Normal file
Binary file not shown.
@@ -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
|
||||
@@ -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)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respawns a player and teleports them to their last death position if available.
|
||||
/// This demonstrates using the GetPlayerInfo API to access player data.
|
||||
/// </summary>
|
||||
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)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes a player's model to the specified scale.
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public Config Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// BEST PRACTICE: Cache expensive operations
|
||||
/// Weapons enum values don't change, so we cache them on first access
|
||||
/// </summary>
|
||||
private static Dictionary<int, CsItem>? _weaponsCache;
|
||||
private static Dictionary<int, CsItem> GetWeaponsCache()
|
||||
{
|
||||
if (_weaponsCache != null) return _weaponsCache;
|
||||
|
||||
var weaponsArray = Enum.GetValues(typeof(CsItem));
|
||||
_weaponsCache = new Dictionary<int, CsItem>();
|
||||
|
||||
foreach (CsItem item in weaponsArray)
|
||||
{
|
||||
if (item == CsItem.Tablet) continue; // Skip tablet (invalid weapon)
|
||||
_weaponsCache[(int)item] = item;
|
||||
}
|
||||
|
||||
return _weaponsCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track players with god mode enabled
|
||||
/// HashSet for O(1) lookup performance
|
||||
/// </summary>
|
||||
private static readonly HashSet<int> GodPlayers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Track players with modified speed
|
||||
/// Dictionary for storing speed values per player
|
||||
/// </summary>
|
||||
private static readonly Dictionary<CCSPlayerController, float> SpeedPlayers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Track players with modified gravity
|
||||
/// Dictionary for storing gravity values per player
|
||||
/// </summary>
|
||||
private static readonly Dictionary<CCSPlayerController, float> GravityPlayers = [];
|
||||
|
||||
/// <summary>
|
||||
/// BEST PRACTICE: Use capability system to get SimpleAdmin API
|
||||
/// This ensures your module works even if SimpleAdmin loads after your module
|
||||
/// </summary>
|
||||
private ICS2_SimpleAdminApi? _sharedApi;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
/// <summary>
|
||||
/// BEST PRACTICE: Track menu registration state to prevent duplicate registrations
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// BEST PRACTICE: Initialize plugin after all plugins are loaded
|
||||
/// This ensures SimpleAdmin API is available
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>CS2_SimpleAdmin_FunCommands</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>..\CS2-SimpleAdminApi.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="lang\**\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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> <weapon>")]
|
||||
[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> <hp>")]
|
||||
[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> <speed>")]
|
||||
[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> <gravity>")]
|
||||
[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> <money>")]
|
||||
[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> <size>")]
|
||||
[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<object> { "CALLER", target.PlayerName };
|
||||
args.AddRange(extraArgs);
|
||||
|
||||
// Show admin activity using module's own localizer with per-player language support
|
||||
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
|
||||
{
|
||||
// Use module's own translations with automatic per-player language support
|
||||
if (Localizer != null)
|
||||
{
|
||||
// This will send the message in each player's configured language
|
||||
_sharedApi!.ShowAdminActivityLocalized(Localizer, messageKey, callerName, false, args.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to old method if localizer is not available
|
||||
_sharedApi!.ShowAdminActivity(messageKey, callerName, false, args.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
// Build and log command using API string method
|
||||
var logCommand = $"{baseCommand} {(string.IsNullOrEmpty(target.PlayerName) ? target.SteamID.ToString() : target.PlayerName)}";
|
||||
if (extraArgs.Length > 0)
|
||||
{
|
||||
logCommand += $" {string.Join(" ", extraArgs)}";
|
||||
}
|
||||
|
||||
_sharedApi!.LogCommand(caller, logCommand);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace CS2_SimpleAdmin_FunCommands;
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
public List<string> NoclipCommands { get; set; } = ["css_noclip"];
|
||||
public List<string> GodCommands { get; set; } = ["css_god"];
|
||||
public List<string> FreezeCommands { get; set; } = ["css_freeze"];
|
||||
public List<string> UnfreezeCommands { get; set; } = ["css_unfreeze"];
|
||||
public List<string> RespawnCommands { get; set; } = ["css_respawn"];
|
||||
public List<string> GiveCommands { get; set; } = ["css_give"];
|
||||
public List<string> StripCommands { get; set; } = ["css_strip"];
|
||||
public List<string> HpCommands { get; set; } = ["css_hp"];
|
||||
public List<string> SpeedCommands { get; set; } = ["css_speed"];
|
||||
public List<string> GravityCommands { get; set; } = ["css_gravity"];
|
||||
public List<string> MoneyCommands { get; set; } = ["css_money"];
|
||||
public List<string> ResizeCommands { get; set; } = ["css_resize"];
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
|
||||
namespace CS2_SimpleAdmin_FunCommands;
|
||||
|
||||
public partial class CS2_SimpleAdmin_FunCommands
|
||||
{
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
|
||||
// Check if player has god mode (similar to main plugin)
|
||||
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
|
||||
|
||||
// Cancel damage
|
||||
@event.DmgHealth = 0;
|
||||
@event.DmgArmor = 0;
|
||||
|
||||
// Reset health to full
|
||||
if (player.PlayerPawn?.Value == null) return HookResult.Continue;
|
||||
|
||||
player.PlayerPawn.Value.Health = player.PlayerPawn.Value.MaxHealth;
|
||||
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_iHealth");
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
|
||||
// Remove player from god mode, speed, and gravity tracking on death
|
||||
GodPlayers.Remove(player.Slot);
|
||||
SpeedPlayers.Remove(player);
|
||||
GravityPlayers.Remove(player);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
// Clear all fun command modifications at round start
|
||||
GodPlayers.Clear();
|
||||
SpeedPlayers.Clear();
|
||||
GravityPlayers.Clear();
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
|
||||
// Clean up player from all tracking when they disconnect
|
||||
GodPlayers.Remove(player.Slot);
|
||||
SpeedPlayers.Remove(player);
|
||||
GravityPlayers.Remove(player);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
using System.Globalization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace CS2_SimpleAdmin_FunCommands;
|
||||
|
||||
/// <summary>
|
||||
/// Menu creation methods for Fun Commands module.
|
||||
/// This file demonstrates different menu patterns using SimpleAdmin API.
|
||||
/// </summary>
|
||||
public partial class CS2_SimpleAdmin_FunCommands
|
||||
{
|
||||
// =================================
|
||||
// SIMPLE PLAYER SELECTION MENUS
|
||||
// =================================
|
||||
// Pattern: Direct player selection with immediate action
|
||||
// Use CreateMenuWithPlayers when you just need to select a player and execute an action
|
||||
|
||||
/// <summary>
|
||||
/// Creates a simple player selection menu for god mode.
|
||||
/// PATTERN: CreateMenuWithPlayers with method reference
|
||||
/// </summary>
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_god"] ?? "God Mode", // Menu title from translation
|
||||
"fun", // Category ID (for back button navigation)
|
||||
admin, // Admin opening the menu
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player), // Filter: only alive, targetable players
|
||||
God); // Action to execute (method reference)
|
||||
}
|
||||
|
||||
private object CreateNoClipMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_noclip"] ?? "No Clip",
|
||||
"fun",
|
||||
admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
NoClip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a player selection menu for respawn command.
|
||||
/// PATTERN: CreateMenuWithPlayers with method reference
|
||||
/// </summary>
|
||||
private object CreateRespawnMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_respawn"] ?? "Respawn", // Menu title from translation
|
||||
"fun", // Category ID
|
||||
admin, // Admin
|
||||
admin.CanTarget, // Filter: only targetable players (no LifeState check - can respawn dead players)
|
||||
Respawn); // Use the Respawn method which includes death position teleport
|
||||
}
|
||||
|
||||
// =================================
|
||||
// NESTED MENUS - PLAYER → VALUE SELECTION
|
||||
// =================================
|
||||
// Pattern: First select player, then select a value/option for that player
|
||||
// Use CreateMenuWithBack + AddSubMenu for multi-level menus
|
||||
|
||||
/// <summary>
|
||||
/// Creates a nested menu: Player selection → Weapon selection.
|
||||
/// PATTERN: CreateMenuWithBack + foreach + AddSubMenu
|
||||
/// </summary>
|
||||
private object CreateGiveWeaponMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_give"] ?? "Give Weapon",
|
||||
"fun",
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
|
||||
// AddSubMenu automatically adds a "Back" button to the submenu
|
||||
// The lambda receives 'p' but we use captured 'player' variable (closure)
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateWeaponSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates weapon selection submenu for a specific player.
|
||||
/// PATTERN: CreateMenuWithBack + foreach + AddMenuOption
|
||||
/// </summary>
|
||||
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var weaponMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}",
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
// Loop through cached weapons (performance optimization)
|
||||
foreach (var weapon in GetWeaponsCache())
|
||||
{
|
||||
// AddMenuOption for each selectable option
|
||||
// IMPORTANT: Always validate target.IsValid before executing action
|
||||
_sharedApi.AddMenuOption(weaponMenu, weapon.Value.ToString(), _ =>
|
||||
{
|
||||
if (target.IsValid) // Player might disconnect before selection
|
||||
{
|
||||
target.GiveNamedItem(weapon.Value);
|
||||
LogAndShowActivity(admin, target, "fun_admin_give_message", $"css_give", weapon.Value.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return weaponMenu;
|
||||
}
|
||||
|
||||
private object CreateStripWeaponsMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
|
||||
"fun",
|
||||
admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) =>
|
||||
{
|
||||
targetPlayer.RemoveWeapons();
|
||||
LogAndShowActivity(adminPlayer, targetPlayer, "fun_admin_strip_message", "css_strip");
|
||||
});
|
||||
}
|
||||
|
||||
private object CreateFreezeMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _sharedApi!.CreateMenuWithPlayers(
|
||||
Localizer?["fun_menu_freeze"] ?? "Freeze",
|
||||
"fun",
|
||||
admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) => { Freeze(adminPlayer, targetPlayer, -1); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a nested menu for setting player HP with predefined values.
|
||||
/// PATTERN: Same as Give Weapon (player selection → value selection)
|
||||
/// This is a common pattern you'll use frequently!
|
||||
/// </summary>
|
||||
private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_hp"] ?? "Set HP",
|
||||
"fun",
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateHpSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates HP value selection submenu.
|
||||
/// TIP: Use arrays for predefined values - easy to modify and maintain
|
||||
/// </summary>
|
||||
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var hpSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}",
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
// Predefined HP values - easy to customize
|
||||
var hpValues = new[] { 1, 10, 25, 50, 100, 200, 500, 999 };
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
_sharedApi.AddMenuOption(hpSelectionMenu,
|
||||
Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP",
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
target.SetHp(hp);
|
||||
LogAndShowActivity(admin, target, "fun_admin_hp_message", "css_hp", hp.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hpSelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetSpeedMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_speed"] ?? "Set Speed",
|
||||
"fun",
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateSpeedSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates speed value selection submenu.
|
||||
/// TIP: Use tuples (value, display) when you need different internal value vs display text
|
||||
/// Example: (0.5f, "0.5") - float value for code, string for display
|
||||
/// </summary>
|
||||
private object CreateSpeedSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var speedSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}",
|
||||
"fun",
|
||||
admin);
|
||||
|
||||
// Tuple pattern: (actualValue, displayText)
|
||||
// Useful when display text differs from actual value
|
||||
var speedValues = new[]
|
||||
{
|
||||
(0.1f, "0.1"), (0.25f, "0.25"), (0.5f, "0.5"), (0.75f, "0.75"), (1f, "1"), (2f, "2"), (3f, "3"), (4f, "4")
|
||||
};
|
||||
|
||||
foreach (var (speed, display) in speedValues)
|
||||
{
|
||||
_sharedApi.AddMenuOption(speedSelectionMenu,
|
||||
Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}",
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
target.SetSpeed(speed);
|
||||
|
||||
// Track speed modification for timer
|
||||
if (speed == 1f)
|
||||
SpeedPlayers.Remove(target);
|
||||
else
|
||||
SpeedPlayers[target] = speed;
|
||||
|
||||
LogAndShowActivity(admin, target, "fun_admin_speed_message", "css_speed", speed.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return speedSelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetGravityMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
|
||||
"fun",
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateGravitySelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateGravitySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var gravitySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}",
|
||||
"fun",
|
||||
admin);
|
||||
var gravityValues = new[]
|
||||
{ (0.1f, "0.1"), (0.25f, "0.25"), (0.5f, "0.5"), (0.75f, "0.75"), (1f, "1"), (2f, "2") };
|
||||
|
||||
foreach (var (gravity, display) in gravityValues)
|
||||
{
|
||||
_sharedApi.AddMenuOption(gravitySelectionMenu,
|
||||
Localizer?["fun_menu_gravity_value", display] ?? $"Gravity {display}",
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
target.SetGravity(Convert.ToSingle(gravity, CultureInfo.InvariantCulture));
|
||||
|
||||
// Track gravity modification for timer
|
||||
if (gravity == 1f)
|
||||
GravityPlayers.Remove(target);
|
||||
else
|
||||
GravityPlayers[target] = gravity;
|
||||
|
||||
LogAndShowActivity(admin, target, "fun_admin_gravity_message", "css_gravity", gravity.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return gravitySelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetMoneyMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_money"] ?? "Set Money",
|
||||
"fun",
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateMoneySelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateMoneySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var moneySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}",
|
||||
"fun",
|
||||
admin);
|
||||
var moneyValues = new[] { 0, 1000, 2500, 5000, 10000, 16000 };
|
||||
|
||||
foreach (var money in moneyValues)
|
||||
{
|
||||
_sharedApi.AddMenuOption(moneySelectionMenu,
|
||||
Localizer?["fun_menu_money_value", money] ?? $"${money}",
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
target.SetMoney(money);
|
||||
LogAndShowActivity(admin, target, "fun_admin_money_message", "css_money", money.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return moneySelectionMenu;
|
||||
}
|
||||
|
||||
private object CreateSetResizeMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_resize"] ?? "Resize Player",
|
||||
"fun",
|
||||
admin);
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateResizeSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateResizeSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var resizeSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||
Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}",
|
||||
"fun",
|
||||
admin);
|
||||
var resizeValues = new[]
|
||||
{ (0.5f, "0.5"), (0.75f, "0.75"), (1f, "1"), (1.25f, "1.25"), (1.5f, "1.5"), (2f, "2"), (3f, "3") };
|
||||
|
||||
foreach (var (resize, display) in resizeValues)
|
||||
{
|
||||
_sharedApi.AddMenuOption(resizeSelectionMenu,
|
||||
Localizer?["fun_menu_resize_value", display] ?? $"Size {display}",
|
||||
_ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
Resize(admin, target, resize);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return resizeSelectionMenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.UserMessages;
|
||||
|
||||
namespace CS2_SimpleAdmin_FunCommands;
|
||||
|
||||
public static class PlayerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Slaps the player pawn by applying optional damage and adding a random velocity knockback.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn to slap.</param>
|
||||
/// <param name="damage">The amount of damage to apply (default is 0).</param>
|
||||
public static void Slap(this CBasePlayerPawn pawn, int damage = 0)
|
||||
{
|
||||
PerformSlap(pawn, damage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the player controller can target another player controller, respecting admin permissions and immunity.
|
||||
/// </summary>
|
||||
/// <param name="controller">The player controller who wants to target.</param>
|
||||
/// <param name="target">The player controller being targeted.</param>
|
||||
/// <returns>True if targeting is allowed, false otherwise.</returns>
|
||||
public static bool CanTarget(this CCSPlayerController? controller, CCSPlayerController? target)
|
||||
{
|
||||
if (controller is null || target is null) return true;
|
||||
if (target.IsBot) return true;
|
||||
|
||||
return AdminManager.CanPlayerTarget(controller, target) ||
|
||||
AdminManager.CanPlayerTarget(new SteamID(controller.SteamID),
|
||||
new SteamID(target.SteamID)) ||
|
||||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the controller can target a player by SteamID, considering targeting permissions and immunities.
|
||||
/// </summary>
|
||||
/// <param name="controller">The attacker player controller.</param>
|
||||
/// <param name="steamId">The SteamID of the target player.</param>
|
||||
/// <returns>True if targeting is permitted, false otherwise.</returns>
|
||||
public static bool CanTarget(this CCSPlayerController? controller, SteamID steamId)
|
||||
{
|
||||
if (controller is null) return true;
|
||||
|
||||
return AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), steamId) ||
|
||||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(steamId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the movement speed modifier of the player controller.
|
||||
/// </summary>
|
||||
/// <param name="controller">The player controller.</param>
|
||||
/// <param name="speed">The speed modifier value.</param>
|
||||
public static void SetSpeed(this CCSPlayerController? controller, float speed)
|
||||
{
|
||||
var playerPawnValue = controller?.PlayerPawn.Value;
|
||||
if (playerPawnValue == null) return;
|
||||
|
||||
playerPawnValue.VelocityModifier = speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the gravity scale for the player controller.
|
||||
/// </summary>
|
||||
/// <param name="controller">The player controller.</param>
|
||||
/// <param name="gravity">The gravity scale.</param>
|
||||
public static void SetGravity(this CCSPlayerController? controller, float gravity)
|
||||
{
|
||||
var playerPawnValue = controller?.PlayerPawn.Value;
|
||||
if (playerPawnValue == null) return;
|
||||
|
||||
playerPawnValue.ActualGravityScale = gravity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the player's in-game money amount.
|
||||
/// </summary>
|
||||
/// <param name="controller">The player controller.</param>
|
||||
/// <param name="money">The amount of money to set.</param>
|
||||
public static void SetMoney(this CCSPlayerController? controller, int money)
|
||||
{
|
||||
var moneyServices = controller?.InGameMoneyServices;
|
||||
if (moneyServices == null) return;
|
||||
|
||||
moneyServices.Account = money;
|
||||
|
||||
if (controller != null) Utilities.SetStateChanged(controller, "CCSPlayerController", "m_pInGameMoneyServices");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the player's health points.
|
||||
/// </summary>
|
||||
/// <param name="controller">The player controller.</param>
|
||||
/// <param name="health">The health value, default is 100.</param>
|
||||
public static void SetHp(this CCSPlayerController? controller, int health = 100)
|
||||
{
|
||||
if (controller == null) return;
|
||||
if (health <= 0 || controller.PlayerPawn.Value == null || controller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return;
|
||||
|
||||
controller.PlayerPawn.Value.Health = health;
|
||||
|
||||
if (health > 100)
|
||||
{
|
||||
controller.PlayerPawn.Value.MaxHealth = health;
|
||||
}
|
||||
|
||||
Utilities.SetStateChanged(controller.PlayerPawn.Value, "CBaseEntity", "m_iHealth");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buries the player pawn by moving it down by a depth offset.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn to bury.</param>
|
||||
/// <param name="depth">The depth offset (default 10 units).</param>
|
||||
public static void Bury(this CBasePlayerPawn pawn, float depth = 10f)
|
||||
{
|
||||
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
|
||||
pawn.AbsOrigin!.Z - depth);
|
||||
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
|
||||
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
|
||||
|
||||
pawn.Teleport(newPos, newRotation, newVelocity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unburies the player pawn by moving it up by a depth offset.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn to unbury.</param>
|
||||
/// <param name="depth">The depth offset (default 15 units).</param>
|
||||
public static void Unbury(this CBasePlayerPawn pawn, float depth = 15f)
|
||||
{
|
||||
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
|
||||
pawn.AbsOrigin!.Z + depth);
|
||||
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
|
||||
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
|
||||
|
||||
pawn.Teleport(newPos, newRotation, newVelocity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freezes the player pawn, disabling movement.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn to freeze.</param>
|
||||
public static void Freeze(this CBasePlayerPawn pawn)
|
||||
{
|
||||
pawn.MoveType = MoveType_t.MOVETYPE_INVALID;
|
||||
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 11); // invalid
|
||||
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unfreezes the player pawn, enabling movement.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn to unfreeze.</param>
|
||||
public static void Unfreeze(this CBasePlayerPawn pawn)
|
||||
{
|
||||
pawn.MoveType = MoveType_t.MOVETYPE_WALK;
|
||||
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk
|
||||
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the player's color tint to specified RGBA values.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The pawn to colorize.</param>
|
||||
/// <param name="r">Red component (0-255).</param>
|
||||
/// <param name="g">Green component (0-255).</param>
|
||||
/// <param name="b">Blue component (0-255).</param>
|
||||
/// <param name="a">Alpha (transparency) component (0-255).</param>
|
||||
public static void Colorize(this CBasePlayerPawn pawn, int r = 255, int g = 255, int b = 255, int a = 255)
|
||||
{
|
||||
pawn.Render = Color.FromArgb(a, r, g, b);
|
||||
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles noclip mode for the player pawn.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn.</param>
|
||||
public static void ToggleNoclip(this CBasePlayerPawn pawn)
|
||||
{
|
||||
if (pawn.MoveType == MoveType_t.MOVETYPE_NOCLIP)
|
||||
{
|
||||
pawn.MoveType = MoveType_t.MOVETYPE_WALK;
|
||||
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk
|
||||
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
|
||||
}
|
||||
else
|
||||
{
|
||||
pawn.MoveType = MoveType_t.MOVETYPE_NOCLIP;
|
||||
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 8); // noclip
|
||||
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports a player controller to the position, rotation, and velocity of another player controller.
|
||||
/// </summary>
|
||||
/// <param name="controller">The controller to teleport.</param>
|
||||
/// <param name="target">The target controller whose position to copy.</param>
|
||||
public static void TeleportPlayer(this CCSPlayerController? controller, CCSPlayerController? target)
|
||||
{
|
||||
if (controller?.PlayerPawn.Value == null && target?.PlayerPawn.Value == null)
|
||||
return;
|
||||
|
||||
if (
|
||||
controller?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null } &&
|
||||
target?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null }
|
||||
)
|
||||
{
|
||||
controller.PlayerPawn.Value.Teleport(
|
||||
target.PlayerPawn.Value.AbsOrigin,
|
||||
target.PlayerPawn.Value.AbsRotation,
|
||||
target.PlayerPawn.Value.AbsVelocity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a slap effect to the given player pawn, optionally inflicting damage and adding velocity knockback.
|
||||
/// </summary>
|
||||
/// <param name="pawn">The player pawn to slap.</param>
|
||||
/// <param name="damage">The amount of damage to deal (default is 0).</param>
|
||||
private static void PerformSlap(CBasePlayerPawn pawn, int damage = 0)
|
||||
{
|
||||
if (pawn.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
return;
|
||||
|
||||
var controller = pawn.Controller.Value?.As<CCSPlayerController>();
|
||||
|
||||
/* Teleport in a random direction - thank you, Mani!*/
|
||||
/* Thank you AM & al!*/
|
||||
var random = new Random();
|
||||
var vel = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
|
||||
|
||||
vel.X += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
|
||||
vel.Y += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
|
||||
vel.Z += random.Next(200) + 100;
|
||||
|
||||
pawn.AbsVelocity.X = vel.X;
|
||||
pawn.AbsVelocity.Y = vel.Y;
|
||||
pawn.AbsVelocity.Z = vel.Z;
|
||||
|
||||
if (controller != null && controller.IsValid)
|
||||
{
|
||||
var shakeMessage = UserMessage.FromPartialName("Shake");
|
||||
shakeMessage.SetFloat("duration", 1);
|
||||
shakeMessage.SetFloat("amplitude", 10);
|
||||
shakeMessage.SetFloat("frequency", 1f);
|
||||
shakeMessage.SetInt("command", 0);
|
||||
shakeMessage.Recipients.Add(controller);
|
||||
shakeMessage.Send();
|
||||
}
|
||||
|
||||
if (damage <= 0)
|
||||
return;
|
||||
|
||||
pawn.Health -= damage;
|
||||
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
||||
|
||||
if (pawn.Health <= 0)
|
||||
pawn.CommitSuicide(true, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "أوامر المرح",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} أعطى {lightred}{1}{default} {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} أخذ جميع أسلحة اللاعب {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} غيّر عدد نقاط الحياة لـ {lightred}{1}{default}!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} غيّر السرعة لـ {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} غيّر الجاذبية لـ {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} غيّر المال لـ {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} غيّر وضع الله لـ {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} فعّل/ألغى نمط اللا تصادم لـ {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} جمد {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} أذاب {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} أحيى {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "وضع الله",
|
||||
"fun_menu_noclip": "اللا تصادم",
|
||||
"fun_menu_respawn": "إحياء",
|
||||
"fun_menu_give": "إعطاء سلاح",
|
||||
"fun_menu_give_player": "إعطاء سلاح: {0}",
|
||||
"fun_menu_strip": "نزع الأسلحة",
|
||||
"fun_menu_freeze": "تجميد",
|
||||
"fun_menu_hp": "ضبط الحياة",
|
||||
"fun_menu_hp_player": "ضبط الحياة: {0}",
|
||||
"fun_menu_speed": "ضبط السرعة",
|
||||
"fun_menu_speed_player": "ضبط السرعة: {0}",
|
||||
"fun_menu_gravity": "ضبط الجاذبية",
|
||||
"fun_menu_gravity_player": "ضبط الجاذبية: {0}",
|
||||
"fun_menu_money": "ضبط المال",
|
||||
"fun_menu_money_player": "ضبط المال: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "السرعة {0}",
|
||||
"fun_menu_gravity_value": "الجاذبية {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} غيّر حجم {lightred}{1}{default} إلى {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "تغيير الحجم",
|
||||
"fun_menu_resize_player": "تغيير الحجم: {0}",
|
||||
"fun_menu_resize_value": "الحجم {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Spaß-Befehle",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} hat {lightred}{1}{default} ein {lightred}{2}{default} gegeben!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} hat alle Waffen von Spieler {lightred}{1}{default} entfernt!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} hat die Lebenspunkte von {lightred}{1}{default} geändert!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} hat die Geschwindigkeit von {lightred}{1}{default} geändert!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} hat die Schwerkraft von {lightred}{1}{default} geändert!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} hat das Geld von {lightred}{1}{default} geändert!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} hat den Gottmodus von {lightred}{1}{default} geändert!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} hat den Noclip-Modus für {lightred}{1}{default} aktiviert/deaktiviert!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} eingefroren!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} aufgetaut!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} hat {lightred}{1}{default} wiederbelebt!",
|
||||
|
||||
"fun_menu_god": "Gottmodus",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Wiederbeleben",
|
||||
"fun_menu_give": "Waffe Geben",
|
||||
"fun_menu_give_player": "Waffe Geben: {0}",
|
||||
"fun_menu_strip": "Waffen Entfernen",
|
||||
"fun_menu_freeze": "Einfrieren",
|
||||
"fun_menu_hp": "HP Festlegen",
|
||||
"fun_menu_hp_player": "HP Festlegen: {0}",
|
||||
"fun_menu_speed": "Geschwindigkeit Festlegen",
|
||||
"fun_menu_speed_player": "Geschwindigkeit Festlegen: {0}",
|
||||
"fun_menu_gravity": "Schwerkraft Festlegen",
|
||||
"fun_menu_gravity_player": "Schwerkraft Festlegen: {0}",
|
||||
"fun_menu_money": "Geld Festlegen",
|
||||
"fun_menu_money_player": "Geld Festlegen: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Geschwindigkeit {0}",
|
||||
"fun_menu_gravity_value": "Schwerkraft {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} hat die Größe von {lightred}{1}{default} auf {lightred}{2}{default} geändert!",
|
||||
"fun_menu_resize": "Größe Ändern",
|
||||
"fun_menu_resize_player": "Größe Ändern: {0}",
|
||||
"fun_menu_resize_value": "Größe {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Fun Commands",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} gave {lightred}{1}{default} a {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} took all of player {lightred}{1}{default} weapons!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} changed {lightred}{1}{default} hp amount!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} changed speed for {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} changed gravity for {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} changed money for {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} toggled noclip for {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} froze {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} unfroze {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} respawned {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "God Mode",
|
||||
"fun_menu_noclip": "No Clip",
|
||||
"fun_menu_respawn": "Respawn",
|
||||
"fun_menu_give": "Give Weapon",
|
||||
"fun_menu_give_player": "Give Weapon: {0}",
|
||||
"fun_menu_strip": "Strip Weapons",
|
||||
"fun_menu_freeze": "Freeze",
|
||||
"fun_menu_hp": "Set HP",
|
||||
"fun_menu_hp_player": "Set HP: {0}",
|
||||
"fun_menu_speed": "Set Speed",
|
||||
"fun_menu_speed_player": "Set Speed: {0}",
|
||||
"fun_menu_gravity": "Set Gravity",
|
||||
"fun_menu_gravity_player": "Set Gravity: {0}",
|
||||
"fun_menu_money": "Set Money",
|
||||
"fun_menu_money_player": "Set Money: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Speed {0}",
|
||||
"fun_menu_gravity_value": "Gravity {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} resized {lightred}{1}{default} to {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Resize Player",
|
||||
"fun_menu_resize_player": "Resize: {0}",
|
||||
"fun_menu_resize_value": "Size {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Comandos Divertidos",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} dio {lightred}{1}{default} un {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} quitó todas las armas del jugador {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} cambió la cantidad de HP de {lightred}{1}{default}!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} cambió la velocidad de {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} cambió la gravedad de {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} cambió el dinero de {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} cambió el modo dios de {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} alternó noclip para {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} congeló a {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} descongeló a {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} reapareció a {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "Modo Dios",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Reaparecer",
|
||||
"fun_menu_give": "Dar Arma",
|
||||
"fun_menu_give_player": "Dar Arma: {0}",
|
||||
"fun_menu_strip": "Quitar Armas",
|
||||
"fun_menu_freeze": "Congelar",
|
||||
"fun_menu_hp": "Establecer HP",
|
||||
"fun_menu_hp_player": "Establecer HP: {0}",
|
||||
"fun_menu_speed": "Establecer Velocidad",
|
||||
"fun_menu_speed_player": "Establecer Velocidad: {0}",
|
||||
"fun_menu_gravity": "Establecer Gravedad",
|
||||
"fun_menu_gravity_player": "Establecer Gravedad: {0}",
|
||||
"fun_menu_money": "Establecer Dinero",
|
||||
"fun_menu_money_player": "Establecer Dinero: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Velocidad {0}",
|
||||
"fun_menu_gravity_value": "Gravedad {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} cambió el tamaño de {lightred}{1}{default} a {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Cambiar Tamaño",
|
||||
"fun_menu_resize_player": "Cambiar Tamaño: {0}",
|
||||
"fun_menu_resize_value": "Tamaño {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "دستورات سرگرمی",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} {lightred}{2}{default} را به {lightred}{1}{default} داد!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} تمام سلاحهای بازیکن {lightred}{1}{default} را گرفت!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} مقدار سلامت {lightred}{1}{default} را تغییر داد!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} سرعت {lightred}{1}{default} را تغییر داد!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} جاذبه {lightred}{1}{default} را تغییر داد!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} پول {lightred}{1}{default} را تغییر داد!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} حالت خدا را برای {lightred}{1}{default} تغییر داد!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} ناپدیدی را برای {lightred}{1}{default} فعال/غیرفعال کرد!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default} را یخزده کرد!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default} را از حالت یخ خارج کرد!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default} را دوباره زنده کرد!",
|
||||
|
||||
"fun_menu_god": "حالت خدا",
|
||||
"fun_menu_noclip": "ناپدیدی",
|
||||
"fun_menu_respawn": "احیا",
|
||||
"fun_menu_give": "دادن سلاح",
|
||||
"fun_menu_give_player": "دادن سلاح: {0}",
|
||||
"fun_menu_strip": "گرفتن سلاحها",
|
||||
"fun_menu_freeze": "یخ زدن",
|
||||
"fun_menu_hp": "تنظیم سلامت",
|
||||
"fun_menu_hp_player": "تنظیم سلامت: {0}",
|
||||
"fun_menu_speed": "تنظیم سرعت",
|
||||
"fun_menu_speed_player": "تنظیم سرعت: {0}",
|
||||
"fun_menu_gravity": "تنظیم جاذبه",
|
||||
"fun_menu_gravity_player": "تنظیم جاذبه: {0}",
|
||||
"fun_menu_money": "تنظیم پول",
|
||||
"fun_menu_money_player": "تنظیم پول: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "سرعت {0}",
|
||||
"fun_menu_gravity_value": "جاذبه {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} اندازه {lightred}{1}{default} را به {lightred}{2}{default} تغییر داد!",
|
||||
"fun_menu_resize": "تغییر اندازه",
|
||||
"fun_menu_resize_player": "تغییر اندازه: {0}",
|
||||
"fun_menu_resize_value": "اندازه {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Commandes Amusantes",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} a donné {lightred}{2}{default} à {lightred}{1}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} a retiré toutes les armes de {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} a modifié la quantité de HP de {lightred}{1}{default}!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} a modifié la vitesse de {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} a modifié la gravité de {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} a modifié l'argent de {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} a modifié le mode dieu de {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} a activé/désactivé le noclip pour {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} a gelé {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} a dégivré {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} a réapparu {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "Mode Dieu",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Réapparition",
|
||||
"fun_menu_give": "Donner Arme",
|
||||
"fun_menu_give_player": "Donner Arme: {0}",
|
||||
"fun_menu_strip": "Retirer Armes",
|
||||
"fun_menu_freeze": "Geler",
|
||||
"fun_menu_hp": "Définir HP",
|
||||
"fun_menu_hp_player": "Définir HP: {0}",
|
||||
"fun_menu_speed": "Définir Vitesse",
|
||||
"fun_menu_speed_player": "Définir Vitesse: {0}",
|
||||
"fun_menu_gravity": "Définir Gravité",
|
||||
"fun_menu_gravity_player": "Définir Gravité: {0}",
|
||||
"fun_menu_money": "Définir Argent",
|
||||
"fun_menu_money_player": "Définir Argent: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Vitesse {0}",
|
||||
"fun_menu_gravity_value": "Gravité {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} a redimensionné {lightred}{1}{default} à {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Redimensionner",
|
||||
"fun_menu_resize_player": "Redimensionner: {0}",
|
||||
"fun_menu_resize_value": "Taille {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Izklaidējošas Komandas",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} noņēma visus {lightred}{1}{default} ieročus!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} HP daudzumu!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} ātrumu!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} gravitāciju!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} naudu!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} mainīja dieva režīmu priekš {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} aktivizēja/deaktivizēja noclip priekš {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} sasaldēja {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} atkausēja {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} atdzīvināja {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "Dieva Režīms",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Atdzīvināt",
|
||||
"fun_menu_give": "Dot Ieroci",
|
||||
"fun_menu_give_player": "Dot Ieroci: {0}",
|
||||
"fun_menu_strip": "Noņemt Ieročus",
|
||||
"fun_menu_freeze": "Sasaldēt",
|
||||
"fun_menu_hp": "Uzstādīt HP",
|
||||
"fun_menu_hp_player": "Uzstādīt HP: {0}",
|
||||
"fun_menu_speed": "Uzstādīt Ātrumu",
|
||||
"fun_menu_speed_player": "Uzstādīt Ātrumu: {0}",
|
||||
"fun_menu_gravity": "Uzstādīt Gravitāciju",
|
||||
"fun_menu_gravity_player": "Uzstādīt Gravitāciju: {0}",
|
||||
"fun_menu_money": "Uzstādīt Naudu",
|
||||
"fun_menu_money_player": "Uzstādīt Naudu: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Ātrums {0}",
|
||||
"fun_menu_gravity_value": "Gravitācija {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} izmēru uz {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Mainīt Izmēru",
|
||||
"fun_menu_resize_player": "Mainīt Izmēru: {0}",
|
||||
"fun_menu_resize_value": "Izmērs {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Komendy Rozrywkowe",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} dał {lightred}{1}{default} przedmiot {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} zabrał wszystkie bronie {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} zmienił ilość hp dla {lightred}{1}{default}!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} zmienił prędkość dla {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} zmienił grawitację dla {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} zmienił pieniądze dla {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} zmienił tryb Boga dla {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} ustawił latanie dla {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} zamroził {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} odmroził {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} odrodził {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "Tryb Boga",
|
||||
"fun_menu_noclip": "Latanie",
|
||||
"fun_menu_respawn": "Odrodzenie",
|
||||
"fun_menu_give": "Daj Broń",
|
||||
"fun_menu_give_player": "Daj Broń: {0}",
|
||||
"fun_menu_strip": "Zabierz Bronie",
|
||||
"fun_menu_freeze": "Zamrożenie",
|
||||
"fun_menu_hp": "Ustaw HP",
|
||||
"fun_menu_hp_player": "Ustaw HP: {0}",
|
||||
"fun_menu_speed": "Ustaw Prędkość",
|
||||
"fun_menu_speed_player": "Ustaw Prędkość: {0}",
|
||||
"fun_menu_gravity": "Ustaw Grawitację",
|
||||
"fun_menu_gravity_player": "Ustaw Grawitację: {0}",
|
||||
"fun_menu_money": "Ustaw Pieniądze",
|
||||
"fun_menu_money_player": "Ustaw Pieniądze: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Prędkość {0}",
|
||||
"fun_menu_gravity_value": "Grawitacja {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} zmienił rozmiar {lightred}{1}{default} na {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Zmień Rozmiar",
|
||||
"fun_menu_resize_player": "Zmień Rozmiar: {0}",
|
||||
"fun_menu_resize_value": "Rozmiar {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Comandos Divertidos",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} deu {lightred}{1}{default} um(a) {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} tirou todas as armas de {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} mudou a quantidade de HP de {lightred}{1}{default}!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} mudou a velocidade de {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} ativou/desativou o noclip para {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} congelou {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} descongelou {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} reanimou {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "Modo Deus",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Reanimar",
|
||||
"fun_menu_give": "Dar Arma",
|
||||
"fun_menu_give_player": "Dar Arma: {0}",
|
||||
"fun_menu_strip": "Remover Armas",
|
||||
"fun_menu_freeze": "Congelar",
|
||||
"fun_menu_hp": "Definir HP",
|
||||
"fun_menu_hp_player": "Definir HP: {0}",
|
||||
"fun_menu_speed": "Definir Velocidade",
|
||||
"fun_menu_speed_player": "Definir Velocidade: {0}",
|
||||
"fun_menu_gravity": "Definir Gravidade",
|
||||
"fun_menu_gravity_player": "Definir Gravidade: {0}",
|
||||
"fun_menu_money": "Definir Dinheiro",
|
||||
"fun_menu_money_player": "Definir Dinheiro: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Velocidade {0}",
|
||||
"fun_menu_gravity_value": "Gravidade {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} redimensionou {lightred}{1}{default} para {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Redimensionar",
|
||||
"fun_menu_resize_player": "Redimensionar: {0}",
|
||||
"fun_menu_resize_value": "Tamanho {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Comandos Divertidos",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} deu {lightred}{1}{default} um(a) {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} tirou todas as armas de {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} mudou a quantidade de HP de {lightred}{1}{default}!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} mudou a velocidade de {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} ativou/desativou o noclip para {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} congelou {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} descongelou {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} reanimou {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "Modo Deus",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Reanimar",
|
||||
"fun_menu_give": "Dar Arma",
|
||||
"fun_menu_give_player": "Dar Arma: {0}",
|
||||
"fun_menu_strip": "Remover Armas",
|
||||
"fun_menu_freeze": "Congelar",
|
||||
"fun_menu_hp": "Definir HP",
|
||||
"fun_menu_hp_player": "Definir HP: {0}",
|
||||
"fun_menu_speed": "Definir Velocidade",
|
||||
"fun_menu_speed_player": "Definir Velocidade: {0}",
|
||||
"fun_menu_gravity": "Definir Gravidade",
|
||||
"fun_menu_gravity_player": "Definir Gravidade: {0}",
|
||||
"fun_menu_money": "Definir Dinheiro",
|
||||
"fun_menu_money_player": "Definir Dinheiro: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Velocidade {0}",
|
||||
"fun_menu_gravity_value": "Gravidade {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} redimensionou {lightred}{1}{default} para {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Redimensionar",
|
||||
"fun_menu_resize_player": "Redimensionar: {0}",
|
||||
"fun_menu_resize_value": "Tamanho {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Развлекательные Команды",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} дал {lightred}{1}{default} {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} забрал все оружие у {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} изменил количество HP у {lightred}{1}{default}!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} изменил скорость {lightred}{1}{default}!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} изменил гравитацию для {lightred}{1}{default}!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} изменил количество денег у {lightred}{1}{default}!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} изменил режим бога для {lightred}{1}{default}!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} включил/выключил noclip для {lightred}{1}{default}!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} заморозил {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} разморозил {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} возродил {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "Режим Бога",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Возрождение",
|
||||
"fun_menu_give": "Выдать Оружие",
|
||||
"fun_menu_give_player": "Выдать Оружие: {0}",
|
||||
"fun_menu_strip": "Забрать Оружие",
|
||||
"fun_menu_freeze": "Заморозить",
|
||||
"fun_menu_hp": "Установить HP",
|
||||
"fun_menu_hp_player": "Установить HP: {0}",
|
||||
"fun_menu_speed": "Установить Скорость",
|
||||
"fun_menu_speed_player": "Установить Скорость: {0}",
|
||||
"fun_menu_gravity": "Установить Гравитацию",
|
||||
"fun_menu_gravity_player": "Установить Гравитацию: {0}",
|
||||
"fun_menu_money": "Установить Деньги",
|
||||
"fun_menu_money_player": "Установить Деньги: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Скорость {0}",
|
||||
"fun_menu_gravity_value": "Гравитация {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} изменил размер {lightred}{1}{default} на {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "Изменить Размер",
|
||||
"fun_menu_resize_player": "Изменить Размер: {0}",
|
||||
"fun_menu_resize_value": "Размер {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "Eğlence Komutları",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} {lightred}{1}{default}'e {lightred}{2}{default} verdi!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} {lightred}{1}{default}'in tüm silahlarını aldı!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} {lightred}{1}{default}'in HP miktarını değiştirdi!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} {lightred}{1}{default}'in hızını değiştirdi!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} {lightred}{1}{default}'in yer çekimini değiştirdi!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} {lightred}{1}{default}'in parasını değiştirdi!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} {lightred}{1}{default}'in tanrı modunu değiştirdi!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} {lightred}{1}{default} için noclip'i açtı/kapatı!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default}'i dondurdu!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default}'in dondurmasını çözdü!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default}'i yeniden doğurdu!",
|
||||
|
||||
"fun_menu_god": "Tanrı Modu",
|
||||
"fun_menu_noclip": "Noclip",
|
||||
"fun_menu_respawn": "Yeniden Doğma",
|
||||
"fun_menu_give": "Silah Ver",
|
||||
"fun_menu_give_player": "Silah Ver: {0}",
|
||||
"fun_menu_strip": "Silahları Al",
|
||||
"fun_menu_freeze": "Dondur",
|
||||
"fun_menu_hp": "HP Ayarla",
|
||||
"fun_menu_hp_player": "HP Ayarla: {0}",
|
||||
"fun_menu_speed": "Hız Ayarla",
|
||||
"fun_menu_speed_player": "Hız Ayarla: {0}",
|
||||
"fun_menu_gravity": "Yer Çekimi Ayarla",
|
||||
"fun_menu_gravity_player": "Yer Çekimi Ayarla: {0}",
|
||||
"fun_menu_money": "Para Ayarla",
|
||||
"fun_menu_money_player": "Para Ayarla: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "Hız {0}",
|
||||
"fun_menu_gravity_value": "Yer Çekimi {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} {lightred}{1}{default}'in boyutunu {lightred}{2}{default} olarak değiştirdi!",
|
||||
"fun_menu_resize": "Boyut Değiştir",
|
||||
"fun_menu_resize_player": "Boyut Değiştir: {0}",
|
||||
"fun_menu_resize_value": "Boyut {0}"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fun_category_name": "趣味命令",
|
||||
|
||||
"fun_admin_give_message": "{lightred}{0}{default} 给了 {lightred}{1}{default} {lightred}{2}{default}!",
|
||||
"fun_admin_strip_message": "{lightred}{0}{default} 移除了 {lightred}{1}{default} 的所有武器!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的生命值!",
|
||||
"fun_admin_speed_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的速度!",
|
||||
"fun_admin_gravity_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的重力!",
|
||||
"fun_admin_money_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的金钱!",
|
||||
"fun_admin_god_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的上帝模式!",
|
||||
"fun_admin_noclip_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的穿墙模式!",
|
||||
"fun_admin_freeze_message": "{lightred}{0}{default} 冻结了 {lightred}{1}{default}!",
|
||||
"fun_admin_unfreeze_message": "{lightred}{0}{default} 解冻了 {lightred}{1}{default}!",
|
||||
"fun_admin_respawn_message": "{lightred}{0}{default} 复活了 {lightred}{1}{default}!",
|
||||
|
||||
"fun_menu_god": "上帝模式",
|
||||
"fun_menu_noclip": "穿墙模式",
|
||||
"fun_menu_respawn": "复活",
|
||||
"fun_menu_give": "给予武器",
|
||||
"fun_menu_give_player": "给予武器: {0}",
|
||||
"fun_menu_strip": "移除武器",
|
||||
"fun_menu_freeze": "冻结",
|
||||
"fun_menu_hp": "设置生命值",
|
||||
"fun_menu_hp_player": "设置生命值: {0}",
|
||||
"fun_menu_speed": "设置速度",
|
||||
"fun_menu_speed_player": "设置速度: {0}",
|
||||
"fun_menu_gravity": "设置重力",
|
||||
"fun_menu_gravity_player": "设置重力: {0}",
|
||||
"fun_menu_money": "设置金钱",
|
||||
"fun_menu_money_player": "设置金钱: {0}",
|
||||
"fun_menu_hp_value": "{0} HP",
|
||||
"fun_menu_speed_value": "速度 {0}",
|
||||
"fun_menu_gravity_value": "重力 {0}",
|
||||
"fun_menu_money_value": "${0}",
|
||||
|
||||
"fun_admin_resize_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 的大小改为 {lightred}{2}{default}!",
|
||||
"fun_menu_resize": "调整大小",
|
||||
"fun_menu_resize_player": "调整大小: {0}",
|
||||
"fun_menu_resize_value": "大小 {0}"
|
||||
}
|
||||
384
Modules/CS2-SimpleAdmin_FunCommands/README.md
Normal file
384
Modules/CS2-SimpleAdmin_FunCommands/README.md
Normal file
@@ -0,0 +1,384 @@
|
||||
# CS2-SimpleAdmin Fun Commands Module
|
||||
|
||||
This module serves as a **reference implementation** for creating CS2-SimpleAdmin modules. It demonstrates best practices for menu creation, command registration, translation support, and API usage.
|
||||
|
||||
## 📚 What This Module Teaches
|
||||
|
||||
This module is designed to be educational and shows you how to:
|
||||
|
||||
1. ✅ **Register commands dynamically** from configuration
|
||||
2. ✅ **Create menu categories** and menu items
|
||||
3. ✅ **Use per-player translations** with `ShowAdminActivityLocalized`
|
||||
4. ✅ **Handle player targeting** and validation
|
||||
5. ✅ **Implement proper cleanup** on module unload
|
||||
6. ✅ **Structure code** using partial classes for organization
|
||||
7. ✅ **Cache data** for performance (weapons cache)
|
||||
8. ✅ **Use configuration** to enable/disable features
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
This module provides fun admin commands:
|
||||
|
||||
- **God Mode** (`css_god`) - Toggle god mode for players
|
||||
- **No Clip** (`css_noclip`) - Enable no-clip mode
|
||||
- **Freeze/Unfreeze** (`css_freeze`, `css_unfreeze`) - Freeze players in place
|
||||
- **Respawn** (`css_respawn`) - Respawn dead players
|
||||
- **Give Weapon** (`css_give`) - Give weapons to players
|
||||
- **Strip Weapons** (`css_strip`) - Remove all player weapons
|
||||
- **Set HP** (`css_hp`) - Set player health
|
||||
- **Set Speed** (`css_speed`) - Modify player movement speed
|
||||
- **Set Gravity** (`css_gravity`) - Change player gravity
|
||||
- **Set Money** (`css_money`) - Set player money
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
```
|
||||
CS2-SimpleAdmin_FunCommands/
|
||||
├── CS2-SimpleAdmin_FunCommands.cs # Main plugin file - initialization, registration
|
||||
├── Commands.cs # Command handlers
|
||||
├── Actions.cs # Action methods (God, NoClip, Freeze, etc.)
|
||||
├── Menus.cs # Menu creation using SimpleAdmin API
|
||||
├── Config.cs # Configuration with command lists
|
||||
└── lang/ # Translation files (13 languages)
|
||||
├── en.json
|
||||
├── pl.json
|
||||
├── ru.json
|
||||
└── ... (10 more languages)
|
||||
```
|
||||
|
||||
## 🔍 Code Organization Explained
|
||||
|
||||
### 1. Main Plugin File (`CS2-SimpleAdmin_FunCommands.cs`)
|
||||
|
||||
**Key Concepts Demonstrated:**
|
||||
|
||||
```csharp
|
||||
public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
// ✅ BEST PRACTICE: Use capability system to get API
|
||||
private ICS2_SimpleAdminApi? _sharedApi;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
// ✅ BEST PRACTICE: Cache expensive data
|
||||
private static Dictionary<int, CsItem>? _weaponsCache;
|
||||
|
||||
// ✅ BEST PRACTICE: Track menu registration state
|
||||
private bool _menusRegistered = false;
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
// Get the API
|
||||
_sharedApi = _pluginCapability.Get();
|
||||
|
||||
// Register commands
|
||||
RegisterFunCommands();
|
||||
|
||||
// ✅ BEST PRACTICE: Wait for SimpleAdmin to be ready before registering menus
|
||||
_sharedApi.OnSimpleAdminReady += RegisterFunMenus;
|
||||
RegisterFunMenus(); // Fallback for hot reload
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why partial classes?**
|
||||
- Separates concerns (commands, actions, menus)
|
||||
- Makes code easier to navigate
|
||||
- Each file has a specific purpose
|
||||
|
||||
### 2. Configuration (`Config.cs`)
|
||||
|
||||
**Key Concept:** Command lists for flexibility
|
||||
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
// ✅ BEST PRACTICE: Allow multiple command aliases
|
||||
public List<string> NoclipCommands { get; set; } = ["css_noclip"];
|
||||
public List<string> GodCommands { get; set; } = ["css_god"];
|
||||
// ... more command lists
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Users can disable features by emptying the list
|
||||
- Users can add command aliases (e.g., `["css_god", "css_godmode"]`)
|
||||
- Menus only register if commands exist
|
||||
|
||||
### 3. Commands (`Commands.cs`)
|
||||
|
||||
**Key Concepts Demonstrated:**
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/cheats")]
|
||||
private void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// ✅ BEST PRACTICE: Use API to get targets (handles target syntax)
|
||||
var targets = _sharedApi!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// ✅ BEST PRACTICE: Filter for alive players
|
||||
var playersToTarget = targets.Players.Where(player =>
|
||||
player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
|
||||
|
||||
// ✅ BEST PRACTICE: Check targeting permissions
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
God(caller, player);
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ BEST PRACTICE: Always log commands
|
||||
_sharedApi.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Actions (`Actions.cs`)
|
||||
|
||||
**Key Concepts Demonstrated:**
|
||||
|
||||
```csharp
|
||||
private void God(CCSPlayerController? caller, CCSPlayerController player)
|
||||
{
|
||||
// Perform the action
|
||||
if (!GodPlayers.Add(player.Slot))
|
||||
{
|
||||
GodPlayers.Remove(player.Slot);
|
||||
}
|
||||
|
||||
// ✅ BEST PRACTICE: Use per-player language support
|
||||
var activityArgs = new object[] { "CALLER", player.PlayerName };
|
||||
if (caller == null || !_sharedApi!.IsAdminSilent(caller))
|
||||
{
|
||||
if (Localizer != null)
|
||||
{
|
||||
// Each player sees message in their configured language!
|
||||
_sharedApi!.ShowAdminActivityLocalized(Localizer, "fun_admin_god_message", callerName, false, activityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ BEST PRACTICE: Log the action
|
||||
_sharedApi!.LogCommand(caller, $"css_god {player.PlayerName}");
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Menus (`Menus.cs`)
|
||||
|
||||
**Key Concepts Demonstrated:**
|
||||
|
||||
#### Simple Player Selection Menu
|
||||
```csharp
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
{
|
||||
// ✅ BEST PRACTICE: Use CreateMenuWithPlayers for simple player selection
|
||||
return _sharedApi!.CreateMenuWithPlayers("God Mode", "fun", admin,
|
||||
player => player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(player),
|
||||
God); // Direct method reference
|
||||
}
|
||||
```
|
||||
|
||||
#### Nested Menu with Value Selection
|
||||
```csharp
|
||||
private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
{
|
||||
// ✅ BEST PRACTICE: Use CreateMenuWithBack for menus with back button
|
||||
var menu = _sharedApi!.CreateMenuWithBack("Set HP", "fun", admin);
|
||||
|
||||
var players = _sharedApi.GetValidPlayers().Where(p =>
|
||||
p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// ✅ BEST PRACTICE: AddSubMenu automatically adds back button to submenu
|
||||
_sharedApi.AddSubMenu(menu, playerName, p => CreateHpSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var hpMenu = _sharedApi!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "fun", admin);
|
||||
var hpValues = new[] { 1, 10, 25, 50, 100, 200, 500, 999 };
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
// ✅ BEST PRACTICE: AddMenuOption for simple actions
|
||||
_sharedApi.AddMenuOption(hpMenu, $"{hp} HP", _ =>
|
||||
{
|
||||
// ✅ BEST PRACTICE: Always validate before executing
|
||||
if (target.IsValid)
|
||||
{
|
||||
target.SetHp(hp);
|
||||
LogAndShowActivity(admin, target, "fun_admin_hp_message", "css_hp", hp.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hpMenu;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Translations
|
||||
|
||||
**Key Concept:** Module-specific translations
|
||||
|
||||
```json
|
||||
// lang/en.json
|
||||
{
|
||||
"fun_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!",
|
||||
"fun_admin_hp_message": "{lightred}{0}{default} changed {lightred}{1}{default} hp amount!"
|
||||
}
|
||||
```
|
||||
|
||||
**Why module translations?**
|
||||
- Your module is independent from SimpleAdmin
|
||||
- You can update translations without affecting main plugin
|
||||
- Each player sees messages in their language automatically
|
||||
|
||||
## 🛠️ How to Use This as a Template
|
||||
|
||||
### Step 1: Copy the Module
|
||||
```bash
|
||||
cp -r CS2-SimpleAdmin_FunCommands YourModuleName
|
||||
```
|
||||
|
||||
### Step 2: Rename Files
|
||||
- Rename `.csproj` file
|
||||
- Rename all `.cs` files to match your module name
|
||||
- Update namespace in all files
|
||||
|
||||
### Step 3: Update References
|
||||
- Change `namespace CS2_SimpleAdmin_FunCommands` to `namespace YourModuleName`
|
||||
- Update plugin metadata (name, version, author, description)
|
||||
|
||||
### Step 4: Modify Config
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
// Add your own command lists
|
||||
public List<string> YourCommands { get; set; } = ["css_yourcommand"];
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Add Your Commands
|
||||
Look at `Commands.cs` for examples of command handlers
|
||||
|
||||
### Step 6: Add Your Menus
|
||||
Look at `Menus.cs` for examples of menu creation
|
||||
|
||||
### Step 7: Add Translations
|
||||
Create language files in `lang/{language}.json` (e.g., `lang/en.json`, `lang/pl.json`)
|
||||
|
||||
## 📖 Learning Path
|
||||
|
||||
If you're new to module development, study files in this order:
|
||||
|
||||
1. **Config.cs** - Understand configuration structure
|
||||
2. **CS2-SimpleAdmin_FunCommands.cs** - See initialization and API acquisition
|
||||
3. **Commands.cs** - Learn command registration and handling
|
||||
4. **Actions.cs** - Understand action methods and translations
|
||||
5. **Menus.cs** - Study menu creation patterns
|
||||
|
||||
## 🎓 Best Practices Demonstrated
|
||||
|
||||
### ✅ Command Registration
|
||||
```csharp
|
||||
// Dynamic registration based on config
|
||||
if (Config.GodCommands.Count > 0)
|
||||
{
|
||||
foreach (var command in Config.GodCommands)
|
||||
{
|
||||
_sharedApi.RegisterCommand(command, "Enable god mode", OnGodCommand);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Target Validation
|
||||
```csharp
|
||||
// Always check if player can be targeted
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Always validate player state
|
||||
if (!player.IsValid) return;
|
||||
```
|
||||
|
||||
### ✅ Translation Usage
|
||||
```csharp
|
||||
// Use module's localizer for per-player language support
|
||||
if (Localizer != null)
|
||||
{
|
||||
_sharedApi.ShowAdminActivityLocalized(Localizer, "translation_key", callerName, false, args);
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Cleanup on Unload
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
// Unregister all commands
|
||||
if (Config.GodCommands.Count > 0)
|
||||
{
|
||||
foreach (var command in Config.GodCommands)
|
||||
{
|
||||
_sharedApi.UnRegisterCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister all menus
|
||||
if (Config.GodCommands.Count > 0)
|
||||
_sharedApi.UnregisterMenu("fun", "god");
|
||||
|
||||
// Remove event handlers
|
||||
_sharedApi.OnSimpleAdminReady -= RegisterFunMenus;
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Data Caching
|
||||
```csharp
|
||||
// Cache expensive operations
|
||||
private static Dictionary<int, CsItem> GetWeaponsCache()
|
||||
{
|
||||
if (_weaponsCache != null) return _weaponsCache;
|
||||
|
||||
var weaponsArray = Enum.GetValues(typeof(CsItem));
|
||||
_weaponsCache = new Dictionary<int, CsItem>();
|
||||
// ... populate cache
|
||||
return _weaponsCache;
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- **[MODULE_DEVELOPMENT.md](../MODULE_DEVELOPMENT.md)** - Complete API reference
|
||||
- **[TRANSLATION_EXAMPLE.md](../TRANSLATION_EXAMPLE.md)** - Translation usage guide
|
||||
- **[CS2-SimpleAdminApi](../../CS2-SimpleAdminApi/)** - API interface definitions
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
1. **Start Simple** - Begin with one command and one menu, then expand
|
||||
2. **Test Thoroughly** - Test with multiple players, different permissions, and languages
|
||||
3. **Handle Errors** - Always validate player state before actions
|
||||
4. **Log Everything** - Use `_sharedApi.LogCommand()` for all admin actions
|
||||
5. **Use Partial Classes** - Split your code into logical files
|
||||
6. **Comment Your Code** - Especially if others will use it as reference
|
||||
|
||||
## 🐛 Common Mistakes to Avoid
|
||||
|
||||
- ❌ **Don't** forget to check `IsValid` before accessing player properties
|
||||
- ❌ **Don't** skip permission checks (`CanTarget`, `RequiresPermissions`)
|
||||
- ❌ **Don't** forget to unregister commands/menus in `Unload()`
|
||||
- ❌ **Don't** use SimpleAdmin's translations - create your own
|
||||
- ❌ **Don't** hardcode command names - use config lists instead
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
If you improve this module or find better patterns, please contribute back so others can learn!
|
||||
|
||||
## 📄 License
|
||||
|
||||
This module is provided as-is for educational purposes. Feel free to use it as a template for your own modules.
|
||||
7
Modules/CS2-SimpleAdmin_FunCommands/global.json
Normal file
7
Modules/CS2-SimpleAdmin_FunCommands/global.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
602
Modules/MODULE_DEVELOPMENT.md
Normal file
602
Modules/MODULE_DEVELOPMENT.md
Normal file
@@ -0,0 +1,602 @@
|
||||
# Module Development Guide - CS2-SimpleAdmin
|
||||
|
||||
> **🎓 New to module development?** Start with the [Fun Commands Module](./CS2-SimpleAdmin_FunCommands/) - it's a fully documented reference implementation showing all best practices!
|
||||
|
||||
This guide explains how to create modules for CS2-SimpleAdmin with custom commands, menus, and translations.
|
||||
|
||||
## 📖 Table of Contents
|
||||
|
||||
1. [Quick Start](#quick-start)
|
||||
2. [Learning Resources](#learning-resources)
|
||||
3. [API Methods Reference](#api-methods)
|
||||
4. [Menu Patterns](#menu-patterns)
|
||||
5. [Best Practices](#best-practices)
|
||||
6. [Common Patterns](#common-patterns)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Step 1: Study the Example Module
|
||||
|
||||
The **[CS2-SimpleAdmin_FunCommands](./CS2-SimpleAdmin_FunCommands/)** module is your best learning resource. It demonstrates:
|
||||
|
||||
✅ Command registration from config
|
||||
✅ Dynamic menu creation
|
||||
✅ Per-player translation support
|
||||
✅ Proper cleanup on unload
|
||||
✅ Code organization with partial classes
|
||||
✅ All menu patterns you'll need
|
||||
|
||||
**Start here:** Read [`CS2-SimpleAdmin_FunCommands/README.md`](./CS2-SimpleAdmin_FunCommands/README.md)
|
||||
|
||||
### Step 2: Create Your Module Structure
|
||||
|
||||
```
|
||||
YourModule/
|
||||
├── YourModule.csproj # Project file
|
||||
├── YourModule.cs # Main plugin class
|
||||
├── Commands.cs # Command handlers (optional)
|
||||
├── Menus.cs # Menu creation (optional)
|
||||
├── Config.cs # Configuration
|
||||
└── lang/ # Translations
|
||||
├── en.json
|
||||
├── pl.json
|
||||
└── ru.json
|
||||
```
|
||||
|
||||
### Step 3: Minimal Working Example
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
public class MyModule : BasePlugin
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register menus after API is ready
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Fallback for hot reload
|
||||
}
|
||||
|
||||
private void RegisterMenus()
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// 1. Register a new category
|
||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||
|
||||
// 2. Register menu items in the category
|
||||
_api.RegisterMenu("mymodule", "action1", "Action 1", CreateAction1Menu, "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "action2", "Action 2", CreateAction2Menu, "@css/kick");
|
||||
}
|
||||
|
||||
private object CreateAction1Menu(CCSPlayerController admin)
|
||||
{
|
||||
// Create a menu with automatic back button
|
||||
var menu = _api!.CreateMenuWithBack("Action 1 Menu", "mymodule", admin);
|
||||
|
||||
// Add menu options
|
||||
_api.AddMenuOption(menu, "Option 1", player =>
|
||||
{
|
||||
player.PrintToChat("You selected Option 1");
|
||||
});
|
||||
|
||||
_api.AddMenuOption(menu, "Option 2", player =>
|
||||
{
|
||||
player.PrintToChat("You selected Option 2");
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateAction2Menu(CCSPlayerController admin)
|
||||
{
|
||||
// Use the built-in player selection menu
|
||||
return _api!.CreateMenuWithPlayers("Select Player", "mymodule", admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) =>
|
||||
{
|
||||
adminPlayer.PrintToChat($"You selected {targetPlayer.PlayerName}");
|
||||
});
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Clean up registered menus
|
||||
_api.UnregisterMenu("mymodule", "action1");
|
||||
_api.UnregisterMenu("mymodule", "action2");
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 Learning Resources
|
||||
|
||||
### For Beginners
|
||||
|
||||
1. **Start Here:** [`CS2-SimpleAdmin_FunCommands/README.md`](./CS2-SimpleAdmin_FunCommands/README.md)
|
||||
- Explains every file and pattern
|
||||
- Shows code organization
|
||||
- Demonstrates all menu types
|
||||
|
||||
2. **Read the Code:** Study these files in order:
|
||||
- `Config.cs` - Simple configuration
|
||||
- `CS2-SimpleAdmin_FunCommands.cs` - Plugin initialization
|
||||
- `Commands.cs` - Command registration
|
||||
- `Menus.cs` - Menu creation patterns
|
||||
|
||||
3. **Translations:** [`TRANSLATION_EXAMPLE.md`](./TRANSLATION_EXAMPLE.md)
|
||||
- How to use module translations
|
||||
- Per-player language support
|
||||
- Best practices
|
||||
|
||||
### Key Concepts
|
||||
|
||||
| Concept | What It Does | Example File |
|
||||
|---------|-------------|--------------|
|
||||
| **API Capability** | Get access to SimpleAdmin API | `CS2-SimpleAdmin_FunCommands.cs:37` |
|
||||
| **Command Registration** | Register console commands | `Commands.cs:15-34` |
|
||||
| **Menu Registration** | Add menus to admin menu | `CS2-SimpleAdmin_FunCommands.cs:130-141` |
|
||||
| **Translations** | Per-player language support | `Actions.cs:20-31` |
|
||||
| **Cleanup** | Unregister on plugin unload | `CS2-SimpleAdmin_FunCommands.cs:63-97` |
|
||||
|
||||
## 🎯 Menu Patterns
|
||||
|
||||
The FunCommands module demonstrates **3 essential menu patterns** you'll use in every module:
|
||||
|
||||
### Pattern 1: Simple Player Selection
|
||||
**When to use:** Select a player and immediately execute an action
|
||||
|
||||
```csharp
|
||||
// Example: God Mode menu
|
||||
private object CreateGodModeMenu(CCSPlayerController admin)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(
|
||||
"God Mode", // Title
|
||||
"yourmodule", // Category ID
|
||||
admin, // Admin
|
||||
player => player.IsValid && admin.CanTarget(player), // Filter
|
||||
(adminPlayer, target) => // Action
|
||||
{
|
||||
// Execute action immediately
|
||||
ToggleGodMode(target);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:21-29`
|
||||
|
||||
### Pattern 2: Nested Menu (Player → Value)
|
||||
**When to use:** Select a player, then select a value/option for that player
|
||||
|
||||
```csharp
|
||||
// Example: Set HP menu (player selection)
|
||||
private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack("Set HP", "yourmodule", admin);
|
||||
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// AddSubMenu automatically adds back button to submenu
|
||||
_api.AddSubMenu(menu, player.PlayerName,
|
||||
p => CreateHpValueMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
// Example: Set HP menu (value selection)
|
||||
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack($"HP for {target.PlayerName}", "yourmodule", admin);
|
||||
var values = new[] { 50, 100, 200, 500 };
|
||||
|
||||
foreach (var hp in values)
|
||||
{
|
||||
_api.AddMenuOption(menu, $"{hp} HP", _ =>
|
||||
{
|
||||
if (target.IsValid) // Always validate!
|
||||
{
|
||||
target.PlayerPawn.Value.Health = hp;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:134-173`
|
||||
|
||||
### Pattern 3: Nested Menu with Complex Data
|
||||
**When to use:** Need to display more complex options (like weapons with icons, items with descriptions)
|
||||
|
||||
```csharp
|
||||
// Example: Give Weapon menu
|
||||
private object CreateGiveWeaponMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack("Give Weapon", "yourmodule", admin);
|
||||
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName,
|
||||
p => CreateWeaponSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack($"Weapons for {target.PlayerName}", "yourmodule", admin);
|
||||
|
||||
// Use cached data for performance
|
||||
foreach (var weapon in GetWeaponsCache())
|
||||
{
|
||||
_api.AddMenuOption(menu, weapon.Value.ToString(), _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
target.GiveNamedItem(weapon.Value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
**See:** `CS2-SimpleAdmin_FunCommands/Menus.cs:67-109`
|
||||
|
||||
## 📋 API Methods Reference
|
||||
|
||||
### 1. Category Management
|
||||
|
||||
#### `RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")`
|
||||
|
||||
Registers a new menu category that appears in the main admin menu.
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Unique identifier for the category (e.g., "fun", "vip", "economy")
|
||||
- `categoryName` - Display name shown in menu (e.g., "Fun Commands")
|
||||
- `permission` - Required permission to see the category (default: "@css/generic")
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenuCategory("vip", "VIP Features", "@css/vip");
|
||||
```
|
||||
|
||||
### 2. Menu Registration
|
||||
|
||||
#### `RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, object> menuFactory, string? permission = null)`
|
||||
|
||||
Registers a menu item within a category.
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - The category to add this menu to
|
||||
- `menuId` - Unique identifier for the menu
|
||||
- `menuName` - Display name in the category menu
|
||||
- `menuFactory` - Function that creates the menu when selected (receives admin player)
|
||||
- `permission` - Optional permission required to see this menu item
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenu("fun", "godmode", "God Mode", CreateGodModeMenu, "@css/cheats");
|
||||
```
|
||||
|
||||
#### `UnregisterMenu(string categoryId, string menuId)`
|
||||
|
||||
Removes a menu item from a category.
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.UnregisterMenu("fun", "godmode");
|
||||
```
|
||||
|
||||
### 3. Menu Creation
|
||||
|
||||
#### `CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)`
|
||||
|
||||
Creates a menu with an automatic "Back" button that returns to the category menu.
|
||||
|
||||
**Parameters:**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category this menu belongs to (for back navigation)
|
||||
- `player` - The admin player viewing the menu
|
||||
|
||||
**Returns:** `object` (MenuBuilder instance)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api.CreateMenuWithBack("Weapon Selection", "fun", admin);
|
||||
```
|
||||
|
||||
#### `CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin, Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)`
|
||||
|
||||
Creates a menu with a list of players, filtered and with automatic back button.
|
||||
|
||||
**Parameters:**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category for back navigation
|
||||
- `admin` - The admin player viewing the menu
|
||||
- `filter` - Function to filter which players appear in the menu
|
||||
- `onSelect` - Action to execute when a player is selected (receives admin and target)
|
||||
|
||||
**Returns:** `object` (MenuBuilder instance)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
return _api.CreateMenuWithPlayers("Select Player to Kick", "admin", admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(adminPlayer, targetPlayer) =>
|
||||
{
|
||||
// Kick the selected player
|
||||
Server.ExecuteCommand($"css_kick {targetPlayer.UserId}");
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Menu Manipulation
|
||||
|
||||
#### `AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)`
|
||||
|
||||
Adds a clickable option to a menu.
|
||||
|
||||
**Parameters:**
|
||||
- `menu` - The menu object (from CreateMenuWithBack)
|
||||
- `name` - Display name of the option
|
||||
- `action` - Action to execute when clicked (receives the player who clicked)
|
||||
- `disabled` - Whether the option is disabled (grayed out)
|
||||
- `permission` - Optional permission required to see this option
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.AddMenuOption(menu, "Give AK-47", player =>
|
||||
{
|
||||
player.GiveNamedItem("weapon_ak47");
|
||||
}, permission: "@css/cheats");
|
||||
```
|
||||
|
||||
#### `AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory, bool disabled = false, string? permission = null)`
|
||||
|
||||
Adds a submenu option that opens another menu. **Automatically adds a back button to the submenu.**
|
||||
|
||||
**Parameters:**
|
||||
- `menu` - The parent menu
|
||||
- `name` - Display name of the submenu option
|
||||
- `subMenuFactory` - Function that creates the submenu (receives the player)
|
||||
- `disabled` - Whether the option is disabled
|
||||
- `permission` - Optional permission required
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.AddSubMenu(menu, "Weapon Category", player =>
|
||||
{
|
||||
var weaponMenu = _api.CreateMenuWithBack("Weapons", "fun", player);
|
||||
_api.AddMenuOption(weaponMenu, "AK-47", p => p.GiveNamedItem("weapon_ak47"));
|
||||
_api.AddMenuOption(weaponMenu, "AWP", p => p.GiveNamedItem("weapon_awp"));
|
||||
return weaponMenu;
|
||||
});
|
||||
```
|
||||
|
||||
#### `OpenMenu(object menu, CCSPlayerController player)`
|
||||
|
||||
Opens a menu for a specific player.
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api.CreateMenuWithBack("Custom Menu", "fun", player);
|
||||
_api.AddMenuOption(menu, "Test", p => p.PrintToChat("Test!"));
|
||||
_api.OpenMenu(menu, player);
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Nested Menus with Player Selection
|
||||
|
||||
```csharp
|
||||
private object CreateGiveWeaponMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack("Give Weapon", "fun", admin);
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// Add submenu for each player - automatic back button will be added
|
||||
_api.AddSubMenu(menu, player.PlayerName, p => CreateWeaponSelectionMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var weaponMenu = _api.CreateMenuWithBack($"Weapons for {target.PlayerName}", "fun", admin);
|
||||
|
||||
var weapons = new[] { "weapon_ak47", "weapon_m4a1", "weapon_awp", "weapon_deagle" };
|
||||
foreach (var weapon in weapons)
|
||||
{
|
||||
_api.AddMenuOption(weaponMenu, weapon, _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
target.GiveNamedItem(weapon);
|
||||
admin.PrintToChat($"Gave {weapon} to {target.PlayerName}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return weaponMenu;
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Menu with Value Selection
|
||||
|
||||
```csharp
|
||||
private object CreateSetHpMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack("Set HP", "admin", admin);
|
||||
var players = _api.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, p => CreateHpValueMenu(admin, player));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var hpMenu = _api.CreateMenuWithBack($"HP for {target.PlayerName}", "admin", admin);
|
||||
var hpValues = new[] { 1, 50, 100, 200, 500, 1000 };
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
_api.AddMenuOption(hpMenu, $"{hp} HP", _ =>
|
||||
{
|
||||
if (target.IsValid && target.PlayerPawn?.Value != null)
|
||||
{
|
||||
target.PlayerPawn.Value.Health = hp;
|
||||
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hpMenu;
|
||||
}
|
||||
```
|
||||
|
||||
### Permission-Based Menu Options
|
||||
|
||||
```csharp
|
||||
private object CreateAdminToolsMenu(CCSPlayerController admin)
|
||||
{
|
||||
var menu = _api.CreateMenuWithBack("Admin Tools", "admin", admin);
|
||||
|
||||
// Only root admins can see this
|
||||
_api.AddMenuOption(menu, "Dangerous Action", player =>
|
||||
{
|
||||
player.PrintToChat("Executing dangerous action...");
|
||||
}, permission: "@css/root");
|
||||
|
||||
// All admins can see this
|
||||
_api.AddMenuOption(menu, "Safe Action", player =>
|
||||
{
|
||||
player.PrintToChat("Executing safe action...");
|
||||
}, permission: "@css/generic");
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always check for API availability**
|
||||
```csharp
|
||||
if (_api == null) return;
|
||||
```
|
||||
|
||||
2. **Validate player state before actions**
|
||||
```csharp
|
||||
if (target.IsValid && target.PlayerPawn?.Value != null)
|
||||
{
|
||||
// Safe to perform action
|
||||
}
|
||||
```
|
||||
|
||||
3. **Use descriptive category and menu IDs**
|
||||
- Good: `"economy"`, `"vip_features"`, `"fun_commands"`
|
||||
- Bad: `"cat1"`, `"menu"`, `"test"`
|
||||
|
||||
4. **Clean up on unload**
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_api?.UnregisterMenu("mymodule", "mymenu");
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
}
|
||||
```
|
||||
|
||||
5. **Use appropriate permissions**
|
||||
- `@css/generic` - All admins
|
||||
- `@css/ban` - Admins who can ban
|
||||
- `@css/kick` - Admins who can kick
|
||||
- `@css/root` - Root admins only
|
||||
- Custom permissions from your module
|
||||
|
||||
6. **Handle hot reload**
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Fallback for hot reload case
|
||||
```
|
||||
|
||||
## Automatic Back Button
|
||||
|
||||
The menu system **automatically adds a "Back" button** to all submenus created with:
|
||||
- `CreateMenuWithBack()` - Returns to the category menu
|
||||
- `AddSubMenu()` - Returns to the parent menu
|
||||
|
||||
You don't need to manually add back buttons - the system handles navigation automatically!
|
||||
|
||||
## Getting Valid Players
|
||||
|
||||
Use the API helper method to get valid, connected players:
|
||||
|
||||
```csharp
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
// With filtering
|
||||
var alivePlayers = _api.GetValidPlayers()
|
||||
.Where(p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE);
|
||||
|
||||
var targetablePlayers = _api.GetValidPlayers()
|
||||
.Where(p => admin.CanTarget(p));
|
||||
```
|
||||
|
||||
## Complete Module Example
|
||||
|
||||
See the `CS2-SimpleAdmin_FunCommands` module in the `Modules/` directory for a complete, production-ready example of:
|
||||
- Category registration
|
||||
- Multiple menu types
|
||||
- Nested menus with automatic back buttons
|
||||
- Player selection menus
|
||||
- Permission-based access
|
||||
- Proper cleanup on unload
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Q: My category doesn't appear in the admin menu**
|
||||
- Ensure you're calling `RegisterMenuCategory()` after the API is ready
|
||||
- Check that the player has the required permission
|
||||
- Verify the category has at least one menu registered with `RegisterMenu()`
|
||||
|
||||
**Q: Back button doesn't work**
|
||||
- Make sure you're using `CreateMenuWithBack()` instead of creating menus manually
|
||||
- The `categoryId` parameter must match the category you registered
|
||||
- Use `AddSubMenu()` for nested menus - it handles back navigation automatically
|
||||
|
||||
**Q: Menu appears but is empty**
|
||||
- Check that you're adding options with `AddMenuOption()` or `AddSubMenu()`
|
||||
- Verify permission checks aren't filtering out all options
|
||||
- Ensure player validation in filters isn't too restrictive
|
||||
|
||||
**Q: API is null in OnAllPluginsLoaded**
|
||||
- Wait for the `OnSimpleAdminReady` event instead of immediate registration
|
||||
- Make sure CS2-SimpleAdmin is loaded before your module
|
||||
268
Modules/TRANSLATION_EXAMPLE.md
Normal file
268
Modules/TRANSLATION_EXAMPLE.md
Normal file
@@ -0,0 +1,268 @@
|
||||
# Module Translation Guide
|
||||
|
||||
> **🎓 New to translations?** This guide shows you how to add multi-language support to your module!
|
||||
|
||||
## Why Use Module Translations?
|
||||
|
||||
When you use SimpleAdmin API's translation system, **each player automatically sees messages in their preferred language**!
|
||||
|
||||
**Example:**
|
||||
- 🇬🇧 **Player with English:** "Admin gave PlayerName a weapon!"
|
||||
- 🇵🇱 **Player with Polish:** "Admin dał PlayerName broń!"
|
||||
- 🇷🇺 **Player with Russian:** "Admin дал PlayerName оружие!"
|
||||
|
||||
**All from ONE line of code!**
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Create Your Translation Files
|
||||
|
||||
Create a `lang` folder in your module with translation files for each language:
|
||||
|
||||
```
|
||||
YourModule/
|
||||
└── lang/
|
||||
├── en.json
|
||||
├── pl.json
|
||||
└── ru.json
|
||||
```
|
||||
|
||||
**Example: `lang/en.json`**
|
||||
```json
|
||||
{
|
||||
"yourmod_admin_action": "{lightred}{0}{default} performed action on {lightred}{1}{default}!"
|
||||
}
|
||||
```
|
||||
|
||||
**Example: `lang/pl.json`**
|
||||
```json
|
||||
{
|
||||
"yourmod_admin_action": "{lightred}{0}{default} wykonał akcję na {lightred}{1}{default}!"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Use in Your Code
|
||||
|
||||
**✅ RECOMMENDED METHOD:** `ShowAdminActivityLocalized`
|
||||
|
||||
```csharp
|
||||
// Show activity with per-player language support
|
||||
var args = new object[] { "CALLER", targetPlayer.PlayerName };
|
||||
if (admin == null || !_api.IsAdminSilent(admin))
|
||||
{
|
||||
if (Localizer != null)
|
||||
{
|
||||
// Each player sees this in their language!
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer, // Your module's localizer
|
||||
"yourmod_admin_action", // Translation key
|
||||
admin.PlayerName, // Caller name
|
||||
false, // dontPublish
|
||||
args); // Message arguments
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That's it! SimpleAdmin handles the rest.
|
||||
|
||||
## Complete Example
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
public partial class MyModule : BasePlugin
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
|
||||
private void GiveWeaponToPlayer(CCSPlayerController admin, CCSPlayerController target, string weaponName)
|
||||
{
|
||||
// Give the weapon
|
||||
target.GiveNamedItem(weaponName);
|
||||
|
||||
var callerName = admin.PlayerName;
|
||||
|
||||
// Show activity using module's localizer - each player sees it in their language!
|
||||
if (admin == null || !_api!.IsAdminSilent(admin))
|
||||
{
|
||||
var args = new object[] { "CALLER", target.PlayerName, weaponName };
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(Localizer, "yourmod_admin_give_message", callerName, false, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the command
|
||||
_api!.LogCommand(admin, $"css_give {target.PlayerName} {weaponName}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔑 Important: The "CALLER" Placeholder
|
||||
|
||||
**Always use `"CALLER"` as the first argument** in your translation messages!
|
||||
|
||||
The API automatically replaces `"CALLER"` based on the server's `ShowActivityType` configuration:
|
||||
|
||||
| ShowActivityType | What Players See |
|
||||
|-----------------|-----------------|
|
||||
| `1` | Non-admins see "Admin", admins see real name |
|
||||
| `2+` | Everyone sees real admin name |
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"yourmod_message": "{0} did something to {1}"
|
||||
↑ This will be replaced with "Admin" or admin's name
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
var args = new object[] { "CALLER", targetPlayer.PlayerName };
|
||||
// ↑ API replaces this automatically
|
||||
```
|
||||
|
||||
## 💡 Pro Tips
|
||||
|
||||
### Tip 1: Use a Helper Method
|
||||
|
||||
Create a reusable helper to reduce code duplication:
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Helper method to show activity and log command
|
||||
/// Copy this to your module!
|
||||
/// </summary>
|
||||
private void LogAndShowActivity(
|
||||
CCSPlayerController? caller,
|
||||
CCSPlayerController target,
|
||||
string translationKey,
|
||||
string baseCommand,
|
||||
params string[] extraArgs)
|
||||
{
|
||||
var callerName = caller?.PlayerName ?? "Console";
|
||||
|
||||
// Build args: "CALLER" + target name + any extra args
|
||||
var args = new List<object> { "CALLER", target.PlayerName };
|
||||
args.AddRange(extraArgs);
|
||||
|
||||
// Show activity with per-player language support
|
||||
if (caller == null || !_api.IsAdminSilent(caller))
|
||||
{
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
translationKey,
|
||||
callerName,
|
||||
false,
|
||||
args.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
// Build and log command
|
||||
var logCommand = $"{baseCommand} {target.PlayerName}";
|
||||
if (extraArgs.Length > 0)
|
||||
{
|
||||
logCommand += $" {string.Join(" ", extraArgs)}";
|
||||
}
|
||||
_api.LogCommand(caller, logCommand);
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```csharp
|
||||
// Simple action
|
||||
LogAndShowActivity(admin, target, "yourmod_kick_message", "css_kick");
|
||||
|
||||
// Action with parameters
|
||||
LogAndShowActivity(admin, target, "yourmod_hp_message", "css_hp", "100");
|
||||
```
|
||||
|
||||
### Tip 2: Translation Key Naming Convention
|
||||
|
||||
Use a consistent prefix for your module:
|
||||
|
||||
```json
|
||||
{
|
||||
"yourmod_admin_action1": "...",
|
||||
"yourmod_admin_action2": "...",
|
||||
"yourmod_error_notarget": "..."
|
||||
}
|
||||
```
|
||||
|
||||
This prevents conflicts with other modules and makes it clear which module owns the translation.
|
||||
|
||||
### Tip 3: Color Formatting
|
||||
|
||||
Use CounterStrikeSharp color tags in your translations:
|
||||
|
||||
```json
|
||||
{
|
||||
"yourmod_message": "{lightred}{0}{default} gave {green}{1}{default} a {yellow}{2}{default}!"
|
||||
}
|
||||
```
|
||||
|
||||
**Available colors:**
|
||||
- `{default}`, `{white}`, `{darkred}`, `{green}`, `{lightyellow}`
|
||||
- `{lightblue}`, `{olive}`, `{lime}`, `{red}`, `{purple}`
|
||||
- `{grey}`, `{yellow}`, `{gold}`, `{silver}`, `{blue}`
|
||||
- `{darkblue}`, `{bluegrey}`, `{magenta}`, `{lightred}`, `{orange}`
|
||||
|
||||
## 📖 Real Example: Fun Commands Module
|
||||
|
||||
The **[CS2-SimpleAdmin_FunCommands](./CS2-SimpleAdmin_FunCommands/)** module is a perfect reference:
|
||||
|
||||
**Translation files:** `Modules/CS2-SimpleAdmin_FunCommands/lang/`
|
||||
- Has 13 languages (en, pl, ru, de, fr, es, etc.)
|
||||
- Shows proper key naming (`fun_admin_*`)
|
||||
- Demonstrates color usage
|
||||
|
||||
**Code examples:** `Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Actions.cs`
|
||||
- Lines 20-31: God mode with translations
|
||||
- Lines 48-59: NoClip with translations
|
||||
- Lines 76-86: Freeze with translations
|
||||
|
||||
**Helper method:** `Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/Commands.cs:274-306`
|
||||
|
||||
## ❌ Common Mistakes
|
||||
|
||||
### Mistake 1: Forgetting "CALLER"
|
||||
```csharp
|
||||
// ❌ WRONG
|
||||
var args = new object[] { admin.PlayerName, target.PlayerName };
|
||||
|
||||
// ✅ CORRECT
|
||||
var args = new object[] { "CALLER", target.PlayerName };
|
||||
```
|
||||
|
||||
### Mistake 2: Using SimpleAdmin's Translations
|
||||
```csharp
|
||||
// ❌ WRONG - Uses SimpleAdmin's keys
|
||||
_api.ShowAdminActivity("sa_admin_kick", ...)
|
||||
|
||||
// ✅ CORRECT - Uses YOUR module's keys
|
||||
_api.ShowAdminActivityLocalized(Localizer, "yourmod_kick", ...)
|
||||
```
|
||||
|
||||
### Mistake 3: Not Checking Localizer
|
||||
```csharp
|
||||
// ❌ WRONG - Will crash if Localizer is null
|
||||
_api.ShowAdminActivityLocalized(Localizer, "key", ...)
|
||||
|
||||
// ✅ CORRECT - Check first
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api.ShowAdminActivityLocalized(Localizer, "key", ...)
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 See Also
|
||||
|
||||
- **[MODULE_DEVELOPMENT.md](./MODULE_DEVELOPMENT.md)** - Complete module development guide
|
||||
- **[CS2-SimpleAdmin_FunCommands/README.md](./CS2-SimpleAdmin_FunCommands/README.md)** - Reference implementation
|
||||
- **[CounterStrikeSharp Localization](https://docs.cssharp.dev/guides/localization/)** - Official CSS localization docs
|
||||
|
||||
---
|
||||
|
||||
**Need help?** Study the FunCommands module - it demonstrates all these patterns correctly!
|
||||
@@ -8,8 +8,8 @@
|
||||
<img src="https://img.shields.io/badge/Made_with-a_lot_of_tea_%F0%9F%8D%B5-red" alt="Made with a lot of tea 🍵" />
|
||||
</p>
|
||||
|
||||
# [](https://discord.com/channels/1160907911501991946/1180627186865156126)
|
||||
CS2-SimpleAdmin
|
||||
[](https://discord.com/channels/1160907911501991946/1180627186865156126)
|
||||
# CS2-SimpleAdmin
|
||||
|
||||
---
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user