mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-02-18 18:49:23 +00:00
Compare commits
7 Commits
build-1.7.
...
build-1.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
665962565e | ||
|
|
c2e8b4a898 | ||
|
|
9723a4faee | ||
|
|
4865b76262 | ||
|
|
0dded66e5d | ||
|
|
038641dbdf | ||
|
|
a03964c08a |
@@ -1,5 +1,6 @@
|
|||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
using CounterStrikeSharp.API.Core.Commands;
|
using CounterStrikeSharp.API.Core.Commands;
|
||||||
|
using CounterStrikeSharp.API.Core.Translations;
|
||||||
using CounterStrikeSharp.API.Modules.Commands;
|
using CounterStrikeSharp.API.Modules.Commands;
|
||||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
using CounterStrikeSharp.API.Modules.Entities;
|
||||||
@@ -205,6 +206,14 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
|||||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
|
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer)
|
||||||
|
{
|
||||||
|
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||||
|
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||||
|
|
||||||
|
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryNameKey, permission, localizer);
|
||||||
|
}
|
||||||
|
|
||||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||||
Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null)
|
Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null)
|
||||||
{
|
{
|
||||||
@@ -263,6 +272,39 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegisterMenu(string categoryId, string menuId, string menuNameKey,
|
||||||
|
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer)
|
||||||
|
{
|
||||||
|
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||||
|
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||||
|
|
||||||
|
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuNameKey, BuilderFactory, permission, commandName, localizer);
|
||||||
|
return;
|
||||||
|
|
||||||
|
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||||
|
{
|
||||||
|
var context = new MenuContext(categoryId, menuId, menuNameKey, permission, commandName);
|
||||||
|
|
||||||
|
if (menuFactory(player, context) 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)
|
public void UnregisterMenu(string categoryId, string menuId)
|
||||||
{
|
{
|
||||||
@@ -289,7 +331,30 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
|||||||
|
|
||||||
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
||||||
{
|
{
|
||||||
return CreateMenuWithBack(context.MenuTitle, context.CategoryId, player);
|
// Get translated title if module has localizer
|
||||||
|
string title = context.MenuTitle;
|
||||||
|
|
||||||
|
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||||
|
{
|
||||||
|
// Check if this specific menu has a localizer
|
||||||
|
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||||
|
{
|
||||||
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
|
{
|
||||||
|
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to category localizer
|
||||||
|
else if (category.ModuleLocalizer != null)
|
||||||
|
{
|
||||||
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
|
{
|
||||||
|
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateMenuWithBack(title, context.CategoryId, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CCSPlayerController> GetValidPlayers()
|
public List<CCSPlayerController> GetValidPlayers()
|
||||||
@@ -321,7 +386,30 @@ public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
|||||||
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
|
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
|
||||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||||
{
|
{
|
||||||
return CreateMenuWithPlayers(context.MenuTitle, context.CategoryId, admin, filter, onSelect);
|
// Get translated title if module has localizer
|
||||||
|
string title = context.MenuTitle;
|
||||||
|
|
||||||
|
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||||
|
{
|
||||||
|
// Check if this specific menu has a localizer
|
||||||
|
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||||
|
{
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to category localizer
|
||||||
|
else if (category.ModuleLocalizer != null)
|
||||||
|
{
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateMenuWithPlayers(title, context.CategoryId, admin, filter, onSelect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
||||||
|
|||||||
@@ -21,13 +21,12 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
|
|
||||||
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
||||||
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
||||||
public override string ModuleAuthor => "daffyy & Dliix66";
|
public override string ModuleAuthor => "daffyy";
|
||||||
public override string ModuleVersion => "1.7.8-beta-3";
|
public override string ModuleVersion => "1.7.8-beta-8";
|
||||||
|
|
||||||
public override void Load(bool hotReload)
|
public override void Load(bool hotReload)
|
||||||
{
|
{
|
||||||
Instance = this;
|
Instance = this;
|
||||||
|
|
||||||
if (hotReload)
|
if (hotReload)
|
||||||
{
|
{
|
||||||
ServerLoaded = false;
|
ServerLoaded = false;
|
||||||
@@ -47,7 +46,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
CachedPlayers.Clear();
|
CachedPlayers.Clear();
|
||||||
BotPlayers.Clear();
|
BotPlayers.Clear();
|
||||||
|
|
||||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
|
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected && !p.IsHLTV).ToArray())
|
||||||
{
|
{
|
||||||
if (!player.IsBot)
|
if (!player.IsBot)
|
||||||
PlayerManager.LoadPlayerData(player, true);
|
PlayerManager.LoadPlayerData(player, true);
|
||||||
@@ -261,6 +260,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
CacheManager = null;
|
CacheManager = null;
|
||||||
PlayersTimer?.Kill();
|
PlayersTimer?.Kill();
|
||||||
PlayersTimer = null;
|
PlayersTimer = null;
|
||||||
|
|
||||||
UnregisterEvents();
|
UnregisterEvents();
|
||||||
|
|
||||||
if (hotReload)
|
if (hotReload)
|
||||||
|
|||||||
@@ -19,16 +19,16 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340">
|
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346">
|
||||||
<PrivateAssets>none</PrivateAssets>
|
<PrivateAssets>none</PrivateAssets>
|
||||||
<ExcludeAssets>runtime</ExcludeAssets>
|
<ExcludeAssets>runtime</ExcludeAssets>
|
||||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||||
<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
<PackageReference Include="MySqlConnector" Version="2.5.0-beta.1" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
|
<PackageReference Include="System.Linq.Async" Version="7.0.0-preview.1.g24680b5469" />
|
||||||
<PackageReference Include="ZLinq" Version="1.5.2" />
|
<PackageReference Include="ZLinq" Version="1.5.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ public interface IDatabaseProvider
|
|||||||
string GetIpHistoryQuery();
|
string GetIpHistoryQuery();
|
||||||
string GetBanUpdateQuery(bool multiServer);
|
string GetBanUpdateQuery(bool multiServer);
|
||||||
|
|
||||||
|
// PlayerManager
|
||||||
|
string GetUpsertPlayerIpQuery();
|
||||||
|
|
||||||
// PermissionManager
|
// PermissionManager
|
||||||
string GetAdminsQuery();
|
string GetAdminsQuery();
|
||||||
string GetDeleteAdminQuery(bool globalDelete);
|
string GetDeleteAdminQuery(bool globalDelete);
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
-- Migration 016: Optimize tables and indexes
|
-- -- Migration 016: Optimize tables and indexes
|
||||||
-- Add proper indexes for all tables to improve query performance
|
-- -- Add proper indexes for all tables to improve query performance
|
||||||
|
|
||||||
-- Optimize sa_players_ips table indexes
|
-- -- Optimize sa_players_ips table indexes
|
||||||
-- Add index on used_at for efficient date-based queries
|
-- -- 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);
|
-- ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
||||||
|
|
||||||
-- Optimize sa_bans table indexes
|
-- -- Optimize sa_bans table indexes
|
||||||
-- Add composite indexes for common query patterns
|
-- -- 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_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_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_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_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
-- CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||||
|
|
||||||
-- Optimize sa_admins table indexes
|
-- -- 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_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_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||||
|
|
||||||
-- Optimize sa_mutes table indexes (in addition to migration 014)
|
-- -- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||||
-- Add index for expire queries
|
-- -- 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_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_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
-- CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||||
|
|
||||||
-- Optimize sa_warns table indexes (if exists)
|
-- -- 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_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_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `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
|
-- -- Add index on sa_servers for faster lookups
|
||||||
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
-- CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
||||||
`steamid` INTEGER NOT NULL,
|
`steamid` INTEGER NOT NULL,
|
||||||
`address` INTEGER NOT NULL
|
`address` INTEGER NOT NULL,
|
||||||
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`steamid`, `address`)
|
PRIMARY KEY (`steamid`, `address`)
|
||||||
);
|
);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
TRUNCATE TABLE `sa_players_ips`;
|
DELETE FROM sa_players_ips;
|
||||||
|
|
||||||
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL;
|
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL;
|
||||||
CREATE INDEX IF NOT EXISTS `idx_sa_players_ips_used_at` ON `sa_players_ips` (`used_at` DESC);
|
CREATE INDEX IF NOT EXISTS `idx_sa_players_ips_used_at` ON `sa_players_ips` (`used_at` DESC);
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
-- 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`);
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
|
|||||||
cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
||||||
await cmd.ExecuteNonQueryAsync();
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
cmd.CommandText = "SET time_zone = '+00:00';";
|
// cmd.CommandText = "SET time_zone = '+00:00';";
|
||||||
await cmd.ExecuteNonQueryAsync();
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
@@ -86,6 +86,17 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
|
|||||||
return "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
return "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetUpsertPlayerIpQuery()
|
||||||
|
{
|
||||||
|
return """
|
||||||
|
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;
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
public string GetBanUpdateQuery(bool multiServer)
|
public string GetBanUpdateQuery(bool multiServer)
|
||||||
{
|
{
|
||||||
return multiServer ? """
|
return multiServer ? """
|
||||||
|
|||||||
@@ -83,6 +83,17 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider
|
|||||||
public string GetIpHistoryQuery() =>
|
public string GetIpHistoryQuery() =>
|
||||||
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
|
||||||
|
|
||||||
|
public string GetUpsertPlayerIpQuery()
|
||||||
|
{
|
||||||
|
return """
|
||||||
|
INSERT INTO sa_players_ips (steamid, name, address, used_at)
|
||||||
|
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(steamid, address) DO UPDATE SET
|
||||||
|
used_at = CURRENT_TIMESTAMP,
|
||||||
|
name = @playerName;
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
public string GetBanUpdateQuery(bool multiServer) =>
|
public string GetBanUpdateQuery(bool multiServer) =>
|
||||||
multiServer
|
multiServer
|
||||||
? """
|
? """
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
{
|
{
|
||||||
RegisterListener<Listeners.OnMapStart>(OnMapStart);
|
RegisterListener<Listeners.OnMapStart>(OnMapStart);
|
||||||
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||||
RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||||
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
|
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
|
||||||
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
|
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
|
||||||
if (Config.OtherSettings.UserMessageGagChatType)
|
if (Config.OtherSettings.UserMessageGagChatType)
|
||||||
@@ -77,7 +77,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
new ServerManager().LoadServerData();
|
new ServerManager().LoadServerData();
|
||||||
}
|
}
|
||||||
|
|
||||||
[GameEventHandler(HookMode.Pre)]
|
[GameEventHandler]
|
||||||
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||||
{
|
{
|
||||||
if (@event.Reason is 149 or 6)
|
if (@event.Reason is 149 or 6)
|
||||||
@@ -92,13 +92,14 @@ public partial class CS2_SimpleAdmin
|
|||||||
if (player == null || !player.IsValid || player.IsHLTV)
|
if (player == null || !player.IsValid || player.IsHLTV)
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
|
||||||
BotPlayers.Remove(player);
|
|
||||||
CachedPlayers.Remove(player);
|
CachedPlayers.Remove(player);
|
||||||
|
BotPlayers.Remove(player);
|
||||||
SilentPlayers.Remove(player.Slot);
|
SilentPlayers.Remove(player.Slot);
|
||||||
|
|
||||||
if (player.IsBot)
|
if (player.IsBot)
|
||||||
|
{
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Logger.LogCritical("[OnClientDisconnect] After Check");
|
Logger.LogCritical("[OnClientDisconnect] After Check");
|
||||||
@@ -176,6 +177,9 @@ public partial class CS2_SimpleAdmin
|
|||||||
if (player == null || !player.IsValid || player.IsBot)
|
if (player == null || !player.IsValid || player.IsBot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!CachedPlayers.Contains(player))
|
||||||
|
CachedPlayers.Add(player);
|
||||||
|
|
||||||
PlayerManager.LoadPlayerData(player);
|
PlayerManager.LoadPlayerData(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,28 +462,11 @@ public partial class CS2_SimpleAdmin
|
|||||||
// OnGameServerSteamAPIActivated();
|
// OnGameServerSteamAPIActivated();
|
||||||
// });
|
// });
|
||||||
|
|
||||||
GodPlayers.Clear();
|
|
||||||
SilentPlayers.Clear();
|
SilentPlayers.Clear();
|
||||||
|
|
||||||
PlayerPenaltyManager.RemoveAllPenalties();
|
PlayerPenaltyManager.RemoveAllPenalties();
|
||||||
}
|
}
|
||||||
|
|
||||||
[GameEventHandler]
|
|
||||||
public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
|
||||||
{
|
|
||||||
var player = @event.Userid;
|
|
||||||
|
|
||||||
if (player is null || @event.Attacker is null || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.PlayerPawn.Value == null)
|
|
||||||
return HookResult.Continue;
|
|
||||||
|
|
||||||
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
|
|
||||||
|
|
||||||
player.PlayerPawn.Value.Health = player.PlayerPawn.Value.MaxHealth;
|
|
||||||
player.PlayerPawn.Value.ArmorValue = 100;
|
|
||||||
|
|
||||||
return HookResult.Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[GameEventHandler]
|
[GameEventHandler]
|
||||||
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||||
{
|
{
|
||||||
@@ -512,17 +499,13 @@ public partial class CS2_SimpleAdmin
|
|||||||
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
|
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
|
||||||
{
|
{
|
||||||
var player = @event.Userid;
|
var player = @event.Userid;
|
||||||
if (player == null || !player.IsValid || player.IsBot)
|
if (player == null || !player.IsValid || player.IsBot || !SilentPlayers.Contains(player.Slot))
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
|
||||||
if (!SilentPlayers.Contains(player.Slot))
|
if (@event is not { Oldteam: <= 1, Team: >= 1 }) return HookResult.Continue;
|
||||||
return HookResult.Continue;
|
|
||||||
|
|
||||||
if (@event is { Oldteam: <= 1, Team: >= 1 })
|
SilentPlayers.Remove(player.Slot);
|
||||||
{
|
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
|
||||||
SilentPlayers.Remove(player.Slot);
|
|
||||||
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ internal static class Helper
|
|||||||
|
|
||||||
public static List<CCSPlayerController> GetValidPlayers()
|
public static List<CCSPlayerController> GetValidPlayers()
|
||||||
{
|
{
|
||||||
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().ToList();
|
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<CCSPlayerController> GetValidPlayersWithBots()
|
public static List<CCSPlayerController> GetValidPlayersWithBots()
|
||||||
@@ -426,7 +426,7 @@ internal static class Helper
|
|||||||
|
|
||||||
var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : "<https://steamcommunity.com/profiles/0>";
|
var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : "<https://steamcommunity.com/profiles/0>";
|
||||||
var callerName = caller != null ? caller.PlayerName : CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
|
var callerName = caller != null ? caller.PlayerName : CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
|
||||||
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(Helper.GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString]));
|
_ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SendDiscordLogMessage(CCSPlayerController? caller, string command, IStringLocalizer? localizer)
|
private static void SendDiscordLogMessage(CCSPlayerController? caller, string command, IStringLocalizer? localizer)
|
||||||
@@ -590,20 +590,25 @@ internal static class Helper
|
|||||||
{
|
{
|
||||||
if (CS2_SimpleAdmin._localizer == null) return;
|
if (CS2_SimpleAdmin._localizer == null) return;
|
||||||
|
|
||||||
|
// Determine the localized message key
|
||||||
var localizedMessageKey = $"{messageKey}";
|
var localizedMessageKey = $"{messageKey}";
|
||||||
|
|
||||||
var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();
|
var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();
|
||||||
|
|
||||||
|
// Replace placeholder based on showActivityType
|
||||||
for (var i = 0; i < formattedMessageArgs.Length; i++)
|
for (var i = 0; i < formattedMessageArgs.Length; i++)
|
||||||
{
|
{
|
||||||
var arg = formattedMessageArgs[i];
|
var arg = formattedMessageArgs[i]; // Convert argument to string if not null
|
||||||
|
// Replace "CALLER" placeholder in the argument string
|
||||||
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||||
{
|
{
|
||||||
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
|
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||||
|
2 => arg.Replace("CALLER", callerName ?? "Console"),
|
||||||
_ => arg
|
_ => arg
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print the localized message to the center of the screen for the player
|
||||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
{
|
{
|
||||||
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
|
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
|
||||||
@@ -1026,7 +1031,9 @@ public static class Time
|
|||||||
{
|
{
|
||||||
public static DateTime ActualDateTime()
|
public static DateTime ActualDateTime()
|
||||||
{
|
{
|
||||||
return DateTime.UtcNow;
|
if (CS2_SimpleAdmin.Instance.Config.DatabaseConfig.DatabaseType.ToLower().Equals("sqlite"))
|
||||||
|
return DateTime.UtcNow;
|
||||||
|
|
||||||
string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone;
|
string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone;
|
||||||
DateTime utcNow = DateTime.UtcNow;
|
DateTime utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
|
|||||||
using CS2_SimpleAdmin.Database;
|
using CS2_SimpleAdmin.Database;
|
||||||
using CS2_SimpleAdmin.Models;
|
using CS2_SimpleAdmin.Models;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using ZLinq;
|
using ZLinq;
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin.Managers;
|
namespace CS2_SimpleAdmin.Managers;
|
||||||
@@ -16,6 +17,7 @@ internal class CacheManager: IDisposable
|
|||||||
private HashSet<uint> _cachedIgnoredIps = [];
|
private HashSet<uint> _cachedIgnoredIps = [];
|
||||||
|
|
||||||
private DateTime _lastUpdateTime = DateTime.MinValue;
|
private DateTime _lastUpdateTime = DateTime.MinValue;
|
||||||
|
private DateTime? _lastDatabaseTime = null; // Track actual time from database
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
@@ -156,13 +158,20 @@ internal class CacheManager: IDisposable
|
|||||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||||
IEnumerable<BanRecord> updatedBans;
|
IEnumerable<BanRecord> updatedBans;
|
||||||
|
|
||||||
|
// Get current time from database in local timezone (CURRENT_TIMESTAMP uses session timezone, not UTC)
|
||||||
|
var currentDatabaseTime = await connection.QueryFirstAsync<DateTime>("SELECT CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
// Optimization: Only get IDs for comparison if we need to check for deletions
|
// Optimization: Only get IDs for comparison if we need to check for deletions
|
||||||
// Most of the time bans are just added/updated, not deleted
|
// Most of the time bans are just added/updated, not deleted
|
||||||
HashSet<int>? allIds = null;
|
HashSet<int>? allIds = null;
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||||
{
|
{
|
||||||
updatedBans = (await connection.QueryAsync<BanRecord>(
|
// Use previous database time or start from far past if first run
|
||||||
|
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||||
|
|
||||||
|
// Get recently updated bans by timestamp (using database time to avoid timezone issues)
|
||||||
|
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||||
"""
|
"""
|
||||||
SELECT id AS Id,
|
SELECT id AS Id,
|
||||||
player_name AS PlayerName,
|
player_name AS PlayerName,
|
||||||
@@ -171,33 +180,68 @@ internal class CacheManager: IDisposable
|
|||||||
status AS Status
|
status AS Status
|
||||||
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
||||||
""",
|
""",
|
||||||
new { lastUpdate = _lastUpdateTime }
|
new { lastUpdate = lastCheckTime }
|
||||||
));
|
)).ToList();
|
||||||
|
|
||||||
|
// Detect changes: new bans or status changes
|
||||||
|
var updatedList = new List<BanRecord>();
|
||||||
|
foreach (var ban in updatedBans_Query)
|
||||||
|
{
|
||||||
|
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||||
|
{
|
||||||
|
// New ban
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
else if (cachedBan.Status != ban.Status)
|
||||||
|
{
|
||||||
|
// Status changed
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optimization: Only fetch all IDs if there were updates
|
|
||||||
var updatedList = updatedBans.ToList();
|
|
||||||
if (updatedList.Count > 0)
|
if (updatedList.Count > 0)
|
||||||
{
|
{
|
||||||
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
||||||
}
|
}
|
||||||
updatedBans = updatedList;
|
updatedBans = updatedList;
|
||||||
|
|
||||||
|
// Update last check time to current database time
|
||||||
|
_lastDatabaseTime = currentDatabaseTime;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
updatedBans = (await connection.QueryAsync<BanRecord>(
|
// Use previous database time or start from far past if first run
|
||||||
|
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||||
|
|
||||||
|
// Get recently updated bans for this server by timestamp (using database time to avoid timezone issues)
|
||||||
|
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||||
"""
|
"""
|
||||||
SELECT id AS Id,
|
SELECT id AS Id,
|
||||||
player_name AS PlayerName,
|
player_name AS PlayerName,
|
||||||
player_steamid AS PlayerSteamId,
|
player_steamid AS PlayerSteamId,
|
||||||
player_ip AS PlayerIp,
|
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
|
FROM `sa_bans` WHERE server_id = @serverId AND (updated_at > @lastUpdate OR created > @lastUpdate) ORDER BY updated_at DESC
|
||||||
""",
|
""",
|
||||||
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
|
new { serverId = CS2_SimpleAdmin.ServerId, lastUpdate = lastCheckTime }
|
||||||
));
|
)).ToList();
|
||||||
|
|
||||||
|
// Detect changes: new bans or status changes
|
||||||
|
var updatedList = new List<BanRecord>();
|
||||||
|
foreach (var ban in updatedBans_Query)
|
||||||
|
{
|
||||||
|
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||||
|
{
|
||||||
|
// New ban
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
else if (cachedBan.Status != ban.Status)
|
||||||
|
{
|
||||||
|
// Status changed
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optimization: Only fetch all IDs if there were updates
|
|
||||||
var updatedList = updatedBans.ToList();
|
|
||||||
if (updatedList.Count > 0)
|
if (updatedList.Count > 0)
|
||||||
{
|
{
|
||||||
allIds = (await connection.QueryAsync<int>(
|
allIds = (await connection.QueryAsync<int>(
|
||||||
@@ -206,6 +250,9 @@ internal class CacheManager: IDisposable
|
|||||||
)).ToHashSet();
|
)).ToHashSet();
|
||||||
}
|
}
|
||||||
updatedBans = updatedList;
|
updatedBans = updatedList;
|
||||||
|
|
||||||
|
// Update last check time to current database time
|
||||||
|
_lastDatabaseTime = currentDatabaseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization: Only process deletions if we have the full ID list
|
// Optimization: Only process deletions if we have the full ID list
|
||||||
@@ -276,16 +323,19 @@ internal class CacheManager: IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update cache with new/modified bans
|
// Update cache with new/modified bans
|
||||||
var hasUpdates = false;
|
var needsRebuild = false;
|
||||||
foreach (var ban in updatedBans)
|
foreach (var ban in updatedBans)
|
||||||
{
|
{
|
||||||
|
if (_banCache.TryGetValue(ban.Id, out var oldBan) && oldBan.Status != ban.Status)
|
||||||
|
{
|
||||||
|
// Ban status changed (e.g., ACTIVE -> EXPIRED/UNBANNED), need to rebuild indexes
|
||||||
|
needsRebuild = true;
|
||||||
|
}
|
||||||
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
||||||
hasUpdates = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always rebuild indexes if there were any updates
|
// Rebuild indexes if there were updates or status changes
|
||||||
// This ensures status changes (ACTIVE -> UNBANNED) are reflected
|
if (updatedBans.Any() || needsRebuild)
|
||||||
if (hasUpdates)
|
|
||||||
{
|
{
|
||||||
RebuildIndexes();
|
RebuildIndexes();
|
||||||
}
|
}
|
||||||
@@ -436,17 +486,21 @@ internal class CacheManager: IDisposable
|
|||||||
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||||
if (record != null)
|
if (record != null)
|
||||||
{
|
{
|
||||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
// Double-check the ban is still active in cache (handle race conditions)
|
||||||
(!record.PlayerSteamId.HasValue))
|
if (_banCache.TryGetValue(record.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||||
{
|
{
|
||||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||||
}
|
(!record.PlayerSteamId.HasValue))
|
||||||
|
{
|
||||||
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(ipAddress) ||
|
if (string.IsNullOrEmpty(ipAddress) ||
|
||||||
@@ -456,6 +510,11 @@ internal class CacheManager: IDisposable
|
|||||||
|
|
||||||
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||||
if (record == null) return false;
|
if (record == null) return false;
|
||||||
|
|
||||||
|
// Double-check the ban is still active in cache (handle race conditions)
|
||||||
|
if (!_banCache.TryGetValue(record.Id, out var cachedBanIp) || cachedBanIp.StatusEnum != BanStatus.ACTIVE)
|
||||||
|
return false;
|
||||||
|
|
||||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||||
(!record.PlayerSteamId.HasValue && steamId.HasValue))
|
(!record.PlayerSteamId.HasValue && steamId.HasValue))
|
||||||
{
|
{
|
||||||
@@ -547,53 +606,54 @@ internal class CacheManager: IDisposable
|
|||||||
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||||
if (activeBan != null)
|
if (activeBan != null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp))
|
// Double-check the ban is still active in cache (handle race conditions)
|
||||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
if (_banCache.TryGetValue(activeBan.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
|
||||||
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
|
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
if (_cachedIgnoredIps.Contains(ipUInt))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_ipIndex.TryGetValue(ipUInt, out var ipBanRecords))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||||
|
if (ipBan == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_banCache.TryGetValue(ipBan.Id, out var cachedIpBan) || cachedIpBan.StatusEnum != BanStatus.ACTIVE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans;
|
||||||
|
if (expireOldIpBans > 0)
|
||||||
|
{
|
||||||
|
var cutoff = Time.ActualDateTime().AddDays(-expireOldIpBans);
|
||||||
|
if (ipBan.Created < cutoff)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||||
|
|
||||||
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
|
if (string.IsNullOrEmpty(ipBan.PlayerName))
|
||||||
{
|
ipBan.PlayerName = playerName;
|
||||||
if (!_cachedIgnoredIps.Contains(ipAsUint))
|
|
||||||
{
|
|
||||||
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var ipRecord in ipData)
|
ipBan.PlayerSteamId ??= steamId;
|
||||||
{
|
|
||||||
if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords))
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
continue;
|
|
||||||
|
|
||||||
var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
return true;
|
||||||
if (activeBan == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(activeBan.PlayerName))
|
|
||||||
activeBan.PlayerName = unknownName;
|
|
||||||
|
|
||||||
activeBan.PlayerSteamId ??= steamId;
|
|
||||||
|
|
||||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace CS2_SimpleAdmin.Managers;
|
|||||||
|
|
||||||
internal class PlayerManager
|
internal class PlayerManager
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _loadPlayerSemaphore = new(5);
|
private readonly SemaphoreSlim _loadPlayerSemaphore = new(10);
|
||||||
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
|
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -51,7 +51,6 @@ internal class PlayerManager
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _loadPlayerSemaphore.WaitAsync();
|
await _loadPlayerSemaphore.WaitAsync();
|
||||||
|
|
||||||
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
||||||
{
|
{
|
||||||
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||||
@@ -82,12 +81,6 @@ internal class PlayerManager
|
|||||||
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
||||||
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
||||||
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
if (!CS2_SimpleAdmin.CachedPlayers.Contains(player))
|
|
||||||
CS2_SimpleAdmin.CachedPlayers.Add(player);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null &&
|
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null &&
|
||||||
CS2_SimpleAdmin.PlayersInfo[steamId] != null)
|
CS2_SimpleAdmin.PlayersInfo[steamId] != null)
|
||||||
{
|
{
|
||||||
@@ -99,14 +92,8 @@ internal class PlayerManager
|
|||||||
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
|
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
|
||||||
var ipUint = IpHelper.IpToUint(ipAddress);
|
var ipUint = IpHelper.IpToUint(ipAddress);
|
||||||
|
|
||||||
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE pattern
|
// Use database-specific UPSERT query (handles MySQL vs SQLite syntax differences)
|
||||||
const string upsertQuery = """
|
var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery();
|
||||||
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
|
await connection.ExecuteAsync(upsertQuery, new
|
||||||
{
|
{
|
||||||
@@ -260,6 +247,7 @@ internal class PlayerManager
|
|||||||
_loadPlayerSemaphore.Release();
|
_loadPlayerSemaphore.Release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
|
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
|
||||||
{
|
{
|
||||||
player.Rename(name);
|
player.Rename(name);
|
||||||
@@ -293,9 +281,6 @@ internal class PlayerManager
|
|||||||
|
|
||||||
// Optimization: Get players once and avoid allocating anonymous types
|
// Optimization: Get players once and avoid allocating anonymous types
|
||||||
var validPlayers = Helper.GetValidPlayers();
|
var validPlayers = Helper.GetValidPlayers();
|
||||||
if (validPlayers.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Use ValueTuple instead of anonymous type - better performance and less allocations
|
// 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);
|
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
||||||
foreach (var p in validPlayers)
|
foreach (var p in validPlayers)
|
||||||
@@ -370,7 +355,12 @@ internal class PlayerManager
|
|||||||
foreach (var player in bannedPlayers)
|
foreach (var player in bannedPlayers)
|
||||||
{
|
{
|
||||||
if (!player.UserId.HasValue) continue;
|
if (!player.UserId.HasValue) continue;
|
||||||
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
|
await Server.NextWorldUpdateAsync(() =>
|
||||||
|
{
|
||||||
|
if (Helper.GetPlayerFromSteamid64(player.SteamID) != null)
|
||||||
|
Helper.KickPlayer((int)player.UserId,
|
||||||
|
NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||||||
playerName = player.Name,
|
playerName = player.Name,
|
||||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||||
muteReason = reason,
|
warnReason = reason,
|
||||||
duration = time,
|
duration = time,
|
||||||
ends = futureTime,
|
ends = futureTime,
|
||||||
created = now,
|
created = now,
|
||||||
@@ -42,7 +42,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||||||
|
|
||||||
return warnId;
|
return warnId;
|
||||||
}
|
}
|
||||||
catch
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||||||
playerSteamid = playerSteamId,
|
playerSteamid = playerSteamId,
|
||||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||||
muteReason = reason,
|
warnReason = reason,
|
||||||
duration = time,
|
duration = time,
|
||||||
ends = futureTime,
|
ends = futureTime,
|
||||||
created = now,
|
created = now,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using CounterStrikeSharp.API;
|
using CounterStrikeSharp.API;
|
||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Core.Translations;
|
||||||
using CounterStrikeSharp.API.Modules.Admin;
|
using CounterStrikeSharp.API.Modules.Admin;
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
using CounterStrikeSharp.API.Modules.Entities;
|
||||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||||
@@ -12,32 +13,33 @@ public abstract class BasicMenu
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes all menus in the system by registering them with the MenuManager.
|
/// Initializes all menus in the system by registering them with the MenuManager.
|
||||||
|
/// Register with translation keys instead of static names - translation happens per-player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
var manager = MenuManager.Instance;
|
var manager = MenuManager.Instance;
|
||||||
|
|
||||||
// Players category menus
|
// Players category menus - using translation keys
|
||||||
manager.RegisterMenu("players", "slap", "Slap Player", CreateSlapMenu, "@css/slay");
|
manager.RegisterMenu("players", "slap", "sa_slap", CreateSlapMenu, "@css/slay");
|
||||||
manager.RegisterMenu("players", "slay", "Slay Player", CreateSlayMenu, "@css/slay");
|
manager.RegisterMenu("players", "slay", "sa_slay", CreateSlayMenu, "@css/slay");
|
||||||
manager.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
|
manager.RegisterMenu("players", "kick", "sa_kick", CreateKickMenu, "@css/kick");
|
||||||
manager.RegisterMenu("players", "warn", "Warn Player", CreateWarnMenu, "@css/kick");
|
manager.RegisterMenu("players", "warn", "sa_warn", CreateWarnMenu, "@css/kick");
|
||||||
manager.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
|
manager.RegisterMenu("players", "ban", "sa_ban", CreateBanMenu, "@css/ban");
|
||||||
manager.RegisterMenu("players", "gag", "Gag Player", CreateGagMenu, "@css/chat");
|
manager.RegisterMenu("players", "gag", "sa_gag", CreateGagMenu, "@css/chat");
|
||||||
manager.RegisterMenu("players", "mute", "Mute Player", CreateMuteMenu, "@css/chat");
|
manager.RegisterMenu("players", "mute", "sa_mute", CreateMuteMenu, "@css/chat");
|
||||||
manager.RegisterMenu("players", "silence", "Silence Player", CreateSilenceMenu, "@css/chat");
|
manager.RegisterMenu("players", "silence", "sa_silence", CreateSilenceMenu, "@css/chat");
|
||||||
manager.RegisterMenu("players", "team", "Force Team", CreateForceTeamMenu, "@css/kick");
|
manager.RegisterMenu("players", "team", "sa_team_force", CreateForceTeamMenu, "@css/kick");
|
||||||
|
|
||||||
// Server category menus
|
// Server category menus - using translation keys
|
||||||
manager.RegisterMenu("server", "plugins", "Manage Plugins", CreatePluginsMenu, "@css/root");
|
manager.RegisterMenu("server", "plugins", "sa_menu_pluginsmanager_title", CreatePluginsMenu, "@css/root");
|
||||||
manager.RegisterMenu("server", "changemap", "Change Map", CreateChangeMapMenu, "@css/changemap");
|
manager.RegisterMenu("server", "changemap", "sa_changemap", CreateChangeMapMenu, "@css/changemap");
|
||||||
manager.RegisterMenu("server", "restart", "Restart Game", CreateRestartGameMenu, "@css/generic");
|
manager.RegisterMenu("server", "restart", "sa_restart_game", CreateRestartGameMenu, "@css/generic");
|
||||||
manager.RegisterMenu("server", "custom", "Custom Commands", CreateCustomCommandsMenu, "@css/generic");
|
manager.RegisterMenu("server", "custom", "sa_menu_custom_commands", CreateCustomCommandsMenu, "@css/generic");
|
||||||
|
|
||||||
// Admin category menus
|
// Admin category menus - using translation keys
|
||||||
manager.RegisterMenu("admin", "add", "Add Admin", CreateAddAdminMenu, "@css/root");
|
manager.RegisterMenu("admin", "add", "sa_admin_add", CreateAddAdminMenu, "@css/root");
|
||||||
manager.RegisterMenu("admin", "remove", "Remove Admin", CreateRemoveAdminMenu, "@css/root");
|
manager.RegisterMenu("admin", "remove", "sa_admin_remove", CreateRemoveAdminMenu, "@css/root");
|
||||||
manager.RegisterMenu("admin", "reload", "Reload Admins", CreateReloadAdminsMenu, "@css/root");
|
manager.RegisterMenu("admin", "reload", "sa_admin_reload", CreateReloadAdminsMenu, "@css/root");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,14 +51,15 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateSlapMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateSlapMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var slapMenu = new MenuBuilder(localizer?["sa_slap"] ?? "Slap Player");
|
var slapMenu = new MenuBuilder("sa_slap", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, player));
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, capturedPlayer));
|
||||||
}
|
}
|
||||||
|
|
||||||
return slapMenu.WithBackButton();
|
return slapMenu.WithBackButton();
|
||||||
@@ -70,18 +73,25 @@ public abstract class BasicMenu
|
|||||||
/// <returns>A MenuBuilder instance for the slap damage menu.</returns>
|
/// <returns>A MenuBuilder instance for the slap damage menu.</returns>
|
||||||
private static MenuBuilder CreateSlapDamageMenu(CCSPlayerController admin, CCSPlayerController target)
|
private static MenuBuilder CreateSlapDamageMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
var slapDamageMenu = new MenuBuilder($"Slap: {target.PlayerName}");
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
|
string localizedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
localizedTitle = $"{localizer?["sa_slap"] ?? "Slap"}: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var slapDamageMenu = new MenuBuilder(localizedTitle);
|
||||||
var damages = new[] { 0, 1, 5, 10, 50, 100 };
|
var damages = new[] { 0, 1, 5, 10, 50, 100 };
|
||||||
|
|
||||||
foreach (var damage in damages)
|
foreach (var damage in damages)
|
||||||
{
|
{
|
||||||
slapDamageMenu.AddOption($"{damage} HP", _ =>
|
slapDamageMenu.AddOption($"{damage} HP", currentAdmin =>
|
||||||
{
|
{
|
||||||
if (target.IsValid)
|
if (target.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Slap(admin, target, damage);
|
CS2_SimpleAdmin.Slap(currentAdmin, target, damage);
|
||||||
// Keep menu open for consecutive slaps
|
// Reopen the same menu (not create new one) to keep back button working
|
||||||
CreateSlapDamageMenu(admin, target).OpenMenu(admin);
|
slapDamageMenu.OpenMenu(currentAdmin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -97,18 +107,19 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateSlayMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateSlayMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var slayMenu = new MenuBuilder(localizer?["sa_slay"] ?? "Slay Player");
|
var slayMenu = new MenuBuilder("sa_slay", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
var players = Helper.GetValidPlayers().Where(admin.CanTarget);
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
slayMenu.AddOption(playerName, _ =>
|
slayMenu.AddOption(playerName, _ =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Slay(admin, player);
|
CS2_SimpleAdmin.Slay(admin, capturedPlayer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -124,19 +135,20 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateKickMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateKickMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var kickMenu = new MenuBuilder(localizer?["sa_kick"] ?? "Kick Player");
|
var kickMenu = new MenuBuilder("sa_kick", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, capturedPlayer, "Kick", PenaltyType.Kick,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
|
CS2_SimpleAdmin.Instance.Kick(admin, capturedPlayer, reason, admin.PlayerName);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -152,20 +164,21 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateWarnMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateWarnMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var warnMenu = new MenuBuilder(localizer?["sa_warn"] ?? "Warn Player");
|
var warnMenu = new MenuBuilder("sa_warn", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Warn",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Warn", PenaltyType.Warn,
|
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Warn",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Warn", PenaltyType.Warn,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
|
CS2_SimpleAdmin.Instance.Warn(admin, capturedPlayer, duration, reason, admin.PlayerName);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -181,20 +194,21 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateBanMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateBanMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var banMenu = new MenuBuilder(localizer?["sa_ban"] ?? "Ban Player");
|
var banMenu = new MenuBuilder("sa_ban", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Ban",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Ban", PenaltyType.Ban,
|
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Ban",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Ban", PenaltyType.Ban,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
|
CS2_SimpleAdmin.Instance.Ban(admin, capturedPlayer, duration, reason, admin.PlayerName);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -210,20 +224,21 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateGagMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateGagMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var gagMenu = new MenuBuilder(localizer?["sa_gag"] ?? "Gag Player");
|
var gagMenu = new MenuBuilder("sa_gag", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Gag",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Gag", PenaltyType.Gag,
|
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Gag",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Gag", PenaltyType.Gag,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
|
CS2_SimpleAdmin.Instance.Gag(admin, capturedPlayer, duration, reason);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -239,20 +254,21 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateMuteMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateMuteMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var muteMenu = new MenuBuilder(localizer?["sa_mute"] ?? "Mute Player");
|
var muteMenu = new MenuBuilder("sa_mute", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Mute",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Mute", PenaltyType.Mute,
|
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Mute",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Mute", PenaltyType.Mute,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
|
CS2_SimpleAdmin.Instance.Mute(admin, capturedPlayer, duration, reason);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -268,20 +284,21 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateSilenceMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateSilenceMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var silenceMenu = new MenuBuilder(localizer?["sa_silence"] ?? "Silence Player");
|
var silenceMenu = new MenuBuilder("sa_silence", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Silence",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Silence", PenaltyType.Silence,
|
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Silence",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Silence", PenaltyType.Silence,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
|
CS2_SimpleAdmin.Instance.Silence(admin, capturedPlayer, duration, reason);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -297,14 +314,15 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateForceTeamMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateForceTeamMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var teamMenu = new MenuBuilder(localizer?["sa_team_force"] ?? "Force Team");
|
var teamMenu = new MenuBuilder("sa_team_force", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, player));
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, capturedPlayer));
|
||||||
}
|
}
|
||||||
|
|
||||||
return teamMenu.WithBackButton();
|
return teamMenu.WithBackButton();
|
||||||
@@ -319,14 +337,32 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateTeamSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
private static MenuBuilder CreateTeamSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var teamSelectionMenu = new MenuBuilder($"Force Team: {target.PlayerName}");
|
|
||||||
|
// Localize title for admin's language
|
||||||
|
string localizedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
localizedTitle = $"{localizer?["sa_team_force"] ?? "Force Team"}: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var teamSelectionMenu = new MenuBuilder(localizedTitle);
|
||||||
|
|
||||||
|
// Localize team options for admin's language
|
||||||
|
string ctName, tName, swapName, specName;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
ctName = localizer?["sa_team_ct"] ?? "CT";
|
||||||
|
tName = localizer?["sa_team_t"] ?? "T";
|
||||||
|
swapName = localizer?["sa_team_swap"] ?? "Swap";
|
||||||
|
specName = localizer?["sa_team_spec"] ?? "Spec";
|
||||||
|
}
|
||||||
|
|
||||||
var teams = new[]
|
var teams = new[]
|
||||||
{
|
{
|
||||||
(localizer?["sa_team_ct"] ?? "CT", "ct", CsTeam.CounterTerrorist),
|
(ctName, "ct", CsTeam.CounterTerrorist),
|
||||||
(localizer?["sa_team_t"] ?? "T", "t", CsTeam.Terrorist),
|
(tName, "t", CsTeam.Terrorist),
|
||||||
(localizer?["sa_team_swap"] ?? "Swap", "swap", CsTeam.Spectator),
|
(swapName, "swap", CsTeam.Spectator),
|
||||||
(localizer?["sa_team_spec"] ?? "Spec", "spec", CsTeam.Spectator)
|
(specName, "spec", CsTeam.Spectator)
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var (name, teamName, teamNum) in teams)
|
foreach (var (name, teamName, teamNum) in teams)
|
||||||
@@ -351,7 +387,7 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreatePluginsMenu(CCSPlayerController admin)
|
private static MenuBuilder CreatePluginsMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var pluginsMenu = new MenuBuilder(localizer?["sa_menu_pluginsmanager_title"] ?? "Manage Plugins");
|
var pluginsMenu = new MenuBuilder("sa_menu_pluginsmanager_title", admin, localizer);
|
||||||
|
|
||||||
pluginsMenu.AddOption("Open Plugins Manager", _ =>
|
pluginsMenu.AddOption("Open Plugins Manager", _ =>
|
||||||
{
|
{
|
||||||
@@ -369,7 +405,7 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateChangeMapMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateChangeMapMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var mapMenu = new MenuBuilder(localizer?["sa_changemap"] ?? "Change Map");
|
var mapMenu = new MenuBuilder("sa_changemap", admin, localizer);
|
||||||
|
|
||||||
// Add default maps
|
// Add default maps
|
||||||
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
|
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
|
||||||
@@ -402,7 +438,7 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateRestartGameMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateRestartGameMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var restartMenu = new MenuBuilder(localizer?["sa_restart_game"] ?? "Restart Game");
|
var restartMenu = new MenuBuilder("sa_restart_game", admin, localizer);
|
||||||
|
|
||||||
restartMenu.AddOption("Restart Round", _ =>
|
restartMenu.AddOption("Restart Round", _ =>
|
||||||
{
|
{
|
||||||
@@ -420,7 +456,7 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateCustomCommandsMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateCustomCommandsMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var customMenu = new MenuBuilder(localizer?["sa_menu_custom_commands"] ?? "Custom Commands");
|
var customMenu = new MenuBuilder("sa_menu_custom_commands", admin, localizer);
|
||||||
|
|
||||||
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
|
||||||
|
|
||||||
@@ -455,14 +491,15 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateAddAdminMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateAddAdminMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var addAdminMenu = new MenuBuilder(localizer?["sa_admin_add"] ?? "Add Admin");
|
var addAdminMenu = new MenuBuilder("sa_admin_add", admin, localizer);
|
||||||
|
|
||||||
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
var players = Helper.GetValidPlayers().Where(p => !p.IsBot && admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, player));
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, capturedPlayer));
|
||||||
}
|
}
|
||||||
|
|
||||||
return addAdminMenu.WithBackButton();
|
return addAdminMenu.WithBackButton();
|
||||||
@@ -476,7 +513,16 @@ public abstract class BasicMenu
|
|||||||
/// <returns>A MenuBuilder instance for the admin flags menu.</returns>
|
/// <returns>A MenuBuilder instance for the admin flags menu.</returns>
|
||||||
private static MenuBuilder CreateAdminFlagsMenu(CCSPlayerController admin, CCSPlayerController target)
|
private static MenuBuilder CreateAdminFlagsMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
var flagsMenu = new MenuBuilder($"Add Admin: {target.PlayerName}");
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
|
|
||||||
|
// Localize title for admin's language
|
||||||
|
string localizedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
localizedTitle = $"{localizer?["sa_admin_add"] ?? "Add Admin"}: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var flagsMenu = new MenuBuilder(localizedTitle);
|
||||||
|
|
||||||
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
|
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
|
||||||
{
|
{
|
||||||
@@ -501,7 +547,7 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateRemoveAdminMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateRemoveAdminMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var removeAdminMenu = new MenuBuilder(localizer?["sa_admin_remove"] ?? "Remove Admin");
|
var removeAdminMenu = new MenuBuilder("sa_admin_remove", admin, localizer);
|
||||||
|
|
||||||
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
||||||
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
||||||
@@ -510,12 +556,13 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in adminPlayers)
|
foreach (var player in adminPlayers)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
removeAdminMenu.AddOption(playerName, _ =>
|
removeAdminMenu.AddOption(playerName, _ =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
|
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, capturedPlayer.SteamID.ToString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -531,7 +578,7 @@ public abstract class BasicMenu
|
|||||||
private static MenuBuilder CreateReloadAdminsMenu(CCSPlayerController admin)
|
private static MenuBuilder CreateReloadAdminsMenu(CCSPlayerController admin)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var reloadMenu = new MenuBuilder(localizer?["sa_admin_reload"] ?? "Reload Admins");
|
var reloadMenu = new MenuBuilder("sa_admin_reload", admin, localizer);
|
||||||
|
|
||||||
reloadMenu.AddOption("Reload Admins", _ =>
|
reloadMenu.AddOption("Reload Admins", _ =>
|
||||||
{
|
{
|
||||||
@@ -546,20 +593,40 @@ public abstract class BasicMenu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="admin">The admin player selecting duration.</param>
|
/// <param name="admin">The admin player selecting duration.</param>
|
||||||
/// <param name="player">The target player for the penalty.</param>
|
/// <param name="player">The target player for the penalty.</param>
|
||||||
/// <param name="actionName">The name of the penalty action.</param>
|
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
||||||
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
|
/// <param name="onSelectAction">Callback function that returns the next menu when duration is selected.</param>
|
||||||
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
||||||
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||||
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
Func<CCSPlayerController, CCSPlayerController, int, MenuBuilder> onSelectAction)
|
||||||
{
|
{
|
||||||
var durationMenu = new MenuBuilder($"{actionName} Duration: {player.PlayerName}");
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
|
|
||||||
|
// Convert action name to translation key (e.g., "Ban" -> "sa_ban")
|
||||||
|
var actionKey = actionName.ToLower() switch
|
||||||
|
{
|
||||||
|
"kick" => "sa_kick",
|
||||||
|
"ban" => "sa_ban",
|
||||||
|
"warn" => "sa_warn",
|
||||||
|
"gag" => "sa_gag",
|
||||||
|
"mute" => "sa_mute",
|
||||||
|
"silence" => "sa_silence",
|
||||||
|
_ => actionName
|
||||||
|
};
|
||||||
|
|
||||||
|
// Localize title for admin's language
|
||||||
|
string localizedAction, durationText;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
localizedAction = localizer?[actionKey] ?? actionName;
|
||||||
|
durationText = localizer?["sa_duration"] ?? "Duration";
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationMenu = new MenuBuilder($"{localizedAction} {durationText}: {player.PlayerName}");
|
||||||
|
|
||||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||||
{
|
{
|
||||||
durationMenu.AddOption(durationItem.Name, _ =>
|
var capturedDuration = durationItem.Duration; // Capture to avoid closure issue
|
||||||
{
|
durationMenu.AddSubMenu(durationItem.Name, () => onSelectAction(admin, player, capturedDuration));
|
||||||
onSelectAction(admin, player, durationItem.Duration);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return durationMenu.WithBackButton();
|
return durationMenu.WithBackButton();
|
||||||
@@ -570,14 +637,36 @@ public abstract class BasicMenu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="admin">The admin player selecting reason.</param>
|
/// <param name="admin">The admin player selecting reason.</param>
|
||||||
/// <param name="player">The target player for the penalty.</param>
|
/// <param name="player">The target player for the penalty.</param>
|
||||||
/// <param name="actionName">The name of the penalty action.</param>
|
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
||||||
/// <param name="penaltyType">The type of penalty to determine which reason list to use.</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>
|
/// <param name="onSelectAction">Callback action executed when reason is selected.</param>
|
||||||
/// <returns>A MenuBuilder instance for the reason menu.</returns>
|
/// <returns>A MenuBuilder instance for the reason menu.</returns>
|
||||||
private static MenuBuilder CreateReasonMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
private static MenuBuilder CreateReasonMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||||
PenaltyType penaltyType, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
|
PenaltyType penaltyType, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
|
||||||
{
|
{
|
||||||
var reasonMenu = new MenuBuilder($"{actionName} Reason: {player.PlayerName}");
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
|
|
||||||
|
// Convert action name to translation key
|
||||||
|
var actionKey = actionName.ToLower() switch
|
||||||
|
{
|
||||||
|
"kick" => "sa_kick",
|
||||||
|
"ban" => "sa_ban",
|
||||||
|
"warn" => "sa_warn",
|
||||||
|
"gag" => "sa_gag",
|
||||||
|
"mute" => "sa_mute",
|
||||||
|
"silence" => "sa_silence",
|
||||||
|
_ => actionName
|
||||||
|
};
|
||||||
|
|
||||||
|
// Localize title for admin's language
|
||||||
|
string localizedAction, reasonText;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
localizedAction = localizer?[actionKey] ?? actionName;
|
||||||
|
reasonText = localizer?["sa_reason"] ?? "Reason";
|
||||||
|
}
|
||||||
|
|
||||||
|
var reasonMenu = new MenuBuilder($"{localizedAction} {reasonText}: {player.PlayerName}");
|
||||||
|
|
||||||
var reasons = penaltyType switch
|
var reasons = penaltyType switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,12 +8,28 @@ public static class DurationMenu
|
|||||||
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||||
{
|
{
|
||||||
var menu = AdminMenu.CreateMenu(menuName);
|
var menu = AdminMenu.CreateMenu(menuName);
|
||||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
if (menu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var durations = CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations;
|
||||||
|
|
||||||
|
// Capture admin and player to avoid closure issues
|
||||||
|
var capturedAdmin = admin;
|
||||||
|
var capturedPlayer = player;
|
||||||
|
var capturedAction = onSelectAction;
|
||||||
|
|
||||||
|
foreach (var durationItem in durations)
|
||||||
{
|
{
|
||||||
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
|
var duration = durationItem.Duration; // Capture in local variable
|
||||||
|
var name = durationItem.Name;
|
||||||
|
|
||||||
|
menu.AddMenuOption(name, (controller, option) =>
|
||||||
|
{
|
||||||
|
capturedAction(capturedAdmin, capturedPlayer, duration);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
AdminMenu.OpenMenu(admin, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
|
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
|
||||||
|
|||||||
@@ -49,25 +49,27 @@ public static class ManagePlayersMenu
|
|||||||
if (AdminManager.CommandIsOverriden("css_warn")
|
if (AdminManager.CommandIsOverriden("css_warn")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, WarnMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_warn"] ?? "Warn"}: {p.PlayerName}", p, WarnMenu))));
|
||||||
|
|
||||||
if (hasBan)
|
if (hasBan)
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player, BanMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () =>
|
||||||
|
PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (a, p) =>
|
||||||
|
DurationMenu.OpenMenu(a, $"{localizer?["sa_ban"] ?? "Ban"}: {p.PlayerName}", p, BanMenu))));
|
||||||
|
|
||||||
if (hasChat)
|
if (hasChat)
|
||||||
{
|
{
|
||||||
if (AdminManager.CommandIsOverriden("css_gag")
|
if (AdminManager.CommandIsOverriden("css_gag")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, GagMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_gag"] ?? "Gag"}: {p.PlayerName}", p, GagMenu))));
|
||||||
if (AdminManager.CommandIsOverriden("css_mute")
|
if (AdminManager.CommandIsOverriden("css_mute")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player, MuteMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_mute"] ?? "Mute"}: {p.PlayerName}", p, MuteMenu))));
|
||||||
if (AdminManager.CommandIsOverriden("css_silence")
|
if (AdminManager.CommandIsOverriden("css_silence")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, SilenceMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_silence"] ?? "Silence"}: {p.PlayerName}", p, SilenceMenu))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AdminManager.CommandIsOverriden("css_team")
|
if (AdminManager.CommandIsOverriden("css_team")
|
||||||
@@ -162,19 +164,6 @@ public static class ManagePlayersMenu
|
|||||||
|
|
||||||
CS2_SimpleAdmin.MenuApi?.CloseMenu(admin);
|
CS2_SimpleAdmin.MenuApi?.CloseMenu(admin);
|
||||||
});
|
});
|
||||||
|
|
||||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player?.PlayerName}");
|
|
||||||
//
|
|
||||||
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons)
|
|
||||||
// {
|
|
||||||
// menu?.AddMenuOption(option, (_, _) =>
|
|
||||||
// {
|
|
||||||
// if (player is { IsValid: true })
|
|
||||||
// Ban(admin, player, duration, option);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Ban(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
|
private static void Ban(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
|
||||||
|
|||||||
@@ -1,28 +1,89 @@
|
|||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Core.Translations;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin.Menus;
|
namespace CS2_SimpleAdmin.Menus;
|
||||||
public class MenuBuilder(string title)
|
public class MenuBuilder
|
||||||
{
|
{
|
||||||
|
private readonly string _title;
|
||||||
|
private readonly CCSPlayerController? _player;
|
||||||
|
private readonly IStringLocalizer? _localizer;
|
||||||
private readonly List<MenuOption> _options = [];
|
private readonly List<MenuOption> _options = [];
|
||||||
private MenuBuilder? _parentMenu;
|
private MenuBuilder? _parentMenu;
|
||||||
private Action<CCSPlayerController>? _backAction;
|
private Action<CCSPlayerController>? _backAction;
|
||||||
private Action<CCSPlayerController>? _resetAction;
|
private Action<CCSPlayerController>? _resetAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for player-localized menu with translation key
|
||||||
|
/// </summary>
|
||||||
|
public MenuBuilder(string titleKey, CCSPlayerController player, IStringLocalizer? localizer = null)
|
||||||
|
{
|
||||||
|
_title = titleKey;
|
||||||
|
_player = player;
|
||||||
|
_localizer = localizer ?? CS2_SimpleAdmin._localizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for static title (backward compatibility)
|
||||||
|
/// </summary>
|
||||||
|
public MenuBuilder(string title)
|
||||||
|
{
|
||||||
|
_title = title;
|
||||||
|
_player = null;
|
||||||
|
_localizer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized title for the player
|
||||||
|
/// </summary>
|
||||||
|
private string GetLocalizedTitle()
|
||||||
|
{
|
||||||
|
if (_player != null && _localizer != null)
|
||||||
|
{
|
||||||
|
using (new WithTemporaryCulture(_player.GetLanguage()))
|
||||||
|
{
|
||||||
|
return _localizer[_title];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _title;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a menu option with an action.
|
/// Adds a menu option with an action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null)
|
/// <param name="name">Display name or translation key</param>
|
||||||
|
/// <param name="action">Action to perform when selected</param>
|
||||||
|
/// <param name="disabled">Whether the option is disabled</param>
|
||||||
|
/// <param name="permission">Required permission</param>
|
||||||
|
/// <param name="isTranslationKey">If true, name is a translation key to be localized</param>
|
||||||
|
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, bool disabled = false, string? permission = null, bool isTranslationKey = false)
|
||||||
{
|
{
|
||||||
_options.Add(new MenuOption
|
_options.Add(new MenuOption
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Action = action,
|
Action = action,
|
||||||
Disabled = disabled,
|
Disabled = disabled,
|
||||||
Permission = permission
|
Permission = permission,
|
||||||
|
IsTranslationKey = isTranslationKey
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name for a menu option
|
||||||
|
/// </summary>
|
||||||
|
private string GetLocalizedOptionName(MenuOption option)
|
||||||
|
{
|
||||||
|
if (option.IsTranslationKey && _player != null && _localizer != null)
|
||||||
|
{
|
||||||
|
using (new WithTemporaryCulture(_player.GetLanguage()))
|
||||||
|
{
|
||||||
|
return _localizer[option.Name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return option.Name;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a menu option that opens a submenu.
|
/// Adds a menu option that opens a submenu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -99,8 +160,11 @@ public class MenuBuilder(string title)
|
|||||||
{
|
{
|
||||||
if (!player.IsValid) return;
|
if (!player.IsValid) return;
|
||||||
|
|
||||||
|
// Get localized title
|
||||||
|
var localizedTitle = GetLocalizedTitle();
|
||||||
|
|
||||||
// Use MenuManager dependency
|
// Use MenuManager dependency
|
||||||
var menu = Helper.CreateMenu(title, _backAction);
|
var menu = Helper.CreateMenu(localizedTitle, _backAction);
|
||||||
if (menu == null) return;
|
if (menu == null) return;
|
||||||
|
|
||||||
foreach (var option in _options)
|
foreach (var option in _options)
|
||||||
@@ -115,7 +179,10 @@ public class MenuBuilder(string title)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.AddMenuOption(option.Name, (menuPlayer, menuOption) =>
|
// Get localized option name
|
||||||
|
var localizedName = GetLocalizedOptionName(option);
|
||||||
|
|
||||||
|
menu.AddMenuOption(localizedName, (menuPlayer, menuOption) =>
|
||||||
{
|
{
|
||||||
option.Action?.Invoke(menuPlayer);
|
option.Action?.Invoke(menuPlayer);
|
||||||
}, option.Disabled);
|
}, option.Disabled);
|
||||||
@@ -166,5 +233,6 @@ public class MenuOption
|
|||||||
public Action<CCSPlayerController>? Action { get; set; }
|
public Action<CCSPlayerController>? Action { get; set; }
|
||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; set; }
|
||||||
public string? Permission { get; set; }
|
public string? Permission { get; set; }
|
||||||
|
public bool IsTranslationKey { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Core.Translations;
|
||||||
using CounterStrikeSharp.API.Modules.Admin;
|
using CounterStrikeSharp.API.Modules.Admin;
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
using CounterStrikeSharp.API.Modules.Entities;
|
||||||
|
|
||||||
@@ -38,6 +39,26 @@ public class MenuManager
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new menu category with per-player localization support for modules.
|
||||||
|
/// 🆕 NEW: Enables modules to provide localized category names based on each player's css_lang!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="categoryId">Unique identifier for the category.</param>
|
||||||
|
/// <param name="categoryNameKey">Translation key from module's lang files.</param>
|
||||||
|
/// <param name="permission">Required permission to access this category.</param>
|
||||||
|
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||||
|
public void RegisterCategory(string categoryId, string categoryNameKey, string permission, Microsoft.Extensions.Localization.IStringLocalizer moduleLocalizer)
|
||||||
|
{
|
||||||
|
_menuCategories[categoryId] = new MenuCategory
|
||||||
|
{
|
||||||
|
Id = categoryId,
|
||||||
|
Name = categoryNameKey, // Store the key, not translated text
|
||||||
|
Permission = permission,
|
||||||
|
MenuFactories = new Dictionary<string, Func<CCSPlayerController, MenuBuilder>>(),
|
||||||
|
ModuleLocalizer = moduleLocalizer // Store module's localizer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a menu within a category (API for other plugins).
|
/// Registers a menu within a category (API for other plugins).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -66,6 +87,37 @@ public class MenuManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a menu with per-player localization support for modules.
|
||||||
|
/// 🆕 NEW: Enables modules to provide localized menu names based on each player's css_lang!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="categoryId">The category to add this menu to.</param>
|
||||||
|
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||||
|
/// <param name="menuNameKey">Translation key from module's lang files.</param>
|
||||||
|
/// <param name="menuFactory">Factory function that creates the menu for a player.</param>
|
||||||
|
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||||
|
/// <param name="commandName">Command name for permission override checking (optional).</param>
|
||||||
|
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||||
|
public void RegisterMenu(string categoryId, string menuId, string menuNameKey, Func<CCSPlayerController, MenuBuilder> menuFactory, string? permission, string? commandName, Microsoft.Extensions.Localization.IStringLocalizer moduleLocalizer)
|
||||||
|
{
|
||||||
|
if (!_menuCategories.ContainsKey(categoryId))
|
||||||
|
{
|
||||||
|
RegisterCategory(categoryId, categoryId); // Auto-create category if it doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
|
_menuCategories[categoryId].MenuFactories[menuId] = menuFactory;
|
||||||
|
_menuCategories[categoryId].MenuNames[menuId] = menuNameKey; // Store the key
|
||||||
|
_menuCategories[categoryId].MenuLocalizers[menuId] = moduleLocalizer; // Store localizer
|
||||||
|
if (permission != null)
|
||||||
|
{
|
||||||
|
_menuCategories[categoryId].MenuPermissions[menuId] = permission;
|
||||||
|
}
|
||||||
|
if (commandName != null)
|
||||||
|
{
|
||||||
|
_menuCategories[categoryId].MenuCommandNames[menuId] = commandName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unregisters a menu from a category.
|
/// Unregisters a menu from a category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -88,7 +140,7 @@ public class MenuManager
|
|||||||
public MenuBuilder CreateMainMenu(CCSPlayerController player)
|
public MenuBuilder CreateMainMenu(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var mainMenu = new MenuBuilder(localizer?["sa_title"] ?? "SimpleAdmin");
|
var mainMenu = new MenuBuilder("sa_title", player, localizer);
|
||||||
|
|
||||||
foreach (var category in _menuCategories.Values)
|
foreach (var category in _menuCategories.Values)
|
||||||
{
|
{
|
||||||
@@ -98,8 +150,23 @@ public class MenuManager
|
|||||||
if (!AdminManager.PlayerHasPermissions(steamId, category.Permission))
|
if (!AdminManager.PlayerHasPermissions(steamId, category.Permission))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Get localized category name for this player
|
||||||
|
// If category has a module localizer, use it; otherwise use main plugin localizer
|
||||||
|
string localizedCategoryName;
|
||||||
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
|
{
|
||||||
|
if (category.ModuleLocalizer != null)
|
||||||
|
{
|
||||||
|
localizedCategoryName = category.ModuleLocalizer[category.Name] ?? category.Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
localizedCategoryName = localizer?[category.Name] ?? category.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pass player to CreateCategoryMenu
|
// Pass player to CreateCategoryMenu
|
||||||
mainMenu.AddSubMenu(category.Name, () => CreateCategoryMenu(category, player),
|
mainMenu.AddSubMenu(localizedCategoryName, () => CreateCategoryMenu(category, player),
|
||||||
permission: category.Permission);
|
permission: category.Permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +181,24 @@ public class MenuManager
|
|||||||
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
/// <returns>A MenuBuilder instance for the category menu.</returns>
|
||||||
private MenuBuilder CreateCategoryMenu(MenuCategory category, CCSPlayerController player)
|
private MenuBuilder CreateCategoryMenu(MenuCategory category, CCSPlayerController player)
|
||||||
{
|
{
|
||||||
var categoryMenu = new MenuBuilder(category.Name);
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
|
|
||||||
|
// Get localized category name for this player
|
||||||
|
// If category has a module localizer, use it; otherwise use main plugin localizer
|
||||||
|
string localizedCategoryName;
|
||||||
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
|
{
|
||||||
|
if (category.ModuleLocalizer != null)
|
||||||
|
{
|
||||||
|
localizedCategoryName = category.ModuleLocalizer[category.Name] ?? category.Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
localizedCategoryName = localizer?[category.Name] ?? category.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var categoryMenu = new MenuBuilder(localizedCategoryName);
|
||||||
|
|
||||||
foreach (var kvp in category.MenuFactories)
|
foreach (var kvp in category.MenuFactories)
|
||||||
{
|
{
|
||||||
@@ -159,8 +243,30 @@ public class MenuManager
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get localized menu name for this player
|
||||||
|
// If menu has its own localizer, use it; otherwise use category or main plugin localizer
|
||||||
|
string localizedMenuName;
|
||||||
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
|
{
|
||||||
|
if (category.MenuLocalizers.TryGetValue(menuId, out var menuLocalizer))
|
||||||
|
{
|
||||||
|
// Menu has its own module localizer
|
||||||
|
localizedMenuName = menuLocalizer[menuName] ?? menuName;
|
||||||
|
}
|
||||||
|
else if (category.ModuleLocalizer != null)
|
||||||
|
{
|
||||||
|
// Use category's module localizer
|
||||||
|
localizedMenuName = category.ModuleLocalizer[menuName] ?? menuName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use main plugin localizer
|
||||||
|
localizedMenuName = localizer?[menuName] ?? menuName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Call the actual factory with player parameter
|
// Call the actual factory with player parameter
|
||||||
categoryMenu.AddSubMenu(menuName, () => menuFactory(player), permission: permission);
|
categoryMenu.AddSubMenu(localizedMenuName, () => menuFactory(player), permission: permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
return categoryMenu.WithBackButton();
|
return categoryMenu.WithBackButton();
|
||||||
@@ -190,12 +296,12 @@ public class MenuManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void InitializeDefaultCategories()
|
public void InitializeDefaultCategories()
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
// Register categories with translation keys instead of translated names
|
||||||
|
// The actual translation will happen per-player in CreateMainMenu/CreateCategoryMenu
|
||||||
RegisterCategory("players", localizer?["sa_menu_players_manage"] ?? "Manage Players", "@css/generic");
|
RegisterCategory("players", "sa_menu_players_manage", "@css/generic");
|
||||||
RegisterCategory("server", localizer?["sa_menu_server_manage"] ?? "Server Management", "@css/generic");
|
RegisterCategory("server", "sa_menu_server_manage", "@css/generic");
|
||||||
// RegisterCategory("fun", localizer?["sa_menu_fun_commands"] ?? "Fun Commands", "@css/generic");
|
// RegisterCategory("fun", "sa_menu_fun_commands", "@css/generic");
|
||||||
RegisterCategory("admin", localizer?["sa_menu_admins_manage"] ?? "Admin Management", "@css/root");
|
RegisterCategory("admin", "sa_menu_admins_manage", "@css/root");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -222,4 +328,17 @@ public class MenuCategory
|
|||||||
public Dictionary<string, string> MenuNames { get; set; } = [];
|
public Dictionary<string, string> MenuNames { get; set; } = [];
|
||||||
public Dictionary<string, string> MenuPermissions { get; set; } = [];
|
public Dictionary<string, string> MenuPermissions { get; set; } = [];
|
||||||
public Dictionary<string, string> MenuCommandNames { get; set; } = [];
|
public Dictionary<string, string> MenuCommandNames { get; set; } = [];
|
||||||
|
|
||||||
|
// 🆕 NEW: Support for per-player localization in modules
|
||||||
|
/// <summary>
|
||||||
|
/// Optional IStringLocalizer from external module for per-player translation of category name.
|
||||||
|
/// If null, Name is used as-is (for CS2-SimpleAdmin's built-in categories with translation keys).
|
||||||
|
/// </summary>
|
||||||
|
public Microsoft.Extensions.Localization.IStringLocalizer? ModuleLocalizer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores IStringLocalizer for each menu that uses module localization.
|
||||||
|
/// Key: menuId, Value: module's localizer
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, Microsoft.Extensions.Localization.IStringLocalizer> MenuLocalizers { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ public static class PlayersMenu
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var enabled = admin.CanTarget(player);
|
var enabled = admin.CanTarget(player);
|
||||||
|
var capturedPlayer = player; // Capture in local variable to avoid closure issues
|
||||||
|
|
||||||
if (optionName != null)
|
if (optionName != null)
|
||||||
menu?.AddMenuOption(optionName, (_, _) =>
|
menu?.AddMenuOption(optionName, (controller, option) =>
|
||||||
{
|
{
|
||||||
if (player != null) onSelectAction.Invoke(admin, player);
|
if (capturedPlayer != null) onSelectAction.Invoke(admin, capturedPlayer);
|
||||||
},
|
},
|
||||||
!enabled);
|
!enabled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public record BanRecord
|
|||||||
[Column("player_ip")]
|
[Column("player_ip")]
|
||||||
public string? PlayerIp { get; set; }
|
public string? PlayerIp { get; set; }
|
||||||
|
|
||||||
|
[Column("created")]
|
||||||
|
public DateTime Created { get; init; }
|
||||||
|
|
||||||
[Column("status")]
|
[Column("status")]
|
||||||
public required string Status { get; init; }
|
public required string Status { get; init; }
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.7.8-beta-3
|
1.7.8-beta-8
|
||||||
@@ -40,7 +40,6 @@ public partial class CS2_SimpleAdmin
|
|||||||
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
|
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
|
||||||
|
|
||||||
// Player Management
|
// Player Management
|
||||||
private static readonly HashSet<int> GodPlayers = [];
|
|
||||||
internal static readonly HashSet<int> SilentPlayers = [];
|
internal static readonly HashSet<int> SilentPlayers = [];
|
||||||
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
|
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
|
||||||
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
|
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"sa_team_spec": "Spec",
|
"sa_team_spec": "Spec",
|
||||||
|
|
||||||
"sa_slap": "Slap",
|
"sa_slap": "Slap",
|
||||||
"sa_slay": "slay",
|
"sa_slay": "Slay",
|
||||||
"sa_kick": "Kick",
|
"sa_kick": "Kick",
|
||||||
"sa_ban": "Ban",
|
"sa_ban": "Ban",
|
||||||
"sa_gag": "Gag",
|
"sa_gag": "Gag",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -140,6 +140,16 @@ public interface ICS2_SimpleAdminApi
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
|
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a menu category with per-player localization support for modules.
|
||||||
|
/// 🆕 NEW: Supports per-player localization using module's IStringLocalizer!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="categoryId">The category ID (unique identifier).</param>
|
||||||
|
/// <param name="categoryNameKey">Translation key from module's lang files.</param>
|
||||||
|
/// <param name="permission">Required permission to access this category.</param>
|
||||||
|
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||||
|
void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a menu in a category.
|
/// Registers a menu in a category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -163,6 +173,19 @@ public interface ICS2_SimpleAdminApi
|
|||||||
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
/// <param name="commandName">Command name for permission override checking (optional, e.g., "css_god").</param>
|
||||||
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null);
|
void RegisterMenu(string categoryId, string menuId, string menuName, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a menu with per-player localization support for modules.
|
||||||
|
/// 🆕 NEW: Supports per-player localization using module's IStringLocalizer!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="categoryId">The category to add this menu to.</param>
|
||||||
|
/// <param name="menuId">Unique identifier for the menu.</param>
|
||||||
|
/// <param name="menuNameKey">Translation key from module's lang files.</param>
|
||||||
|
/// <param name="menuFactory">Factory function that receives player and menu context.</param>
|
||||||
|
/// <param name="permission">Required permission to access this menu (optional).</param>
|
||||||
|
/// <param name="commandName">Command name for permission override checking (optional).</param>
|
||||||
|
/// <param name="moduleLocalizer">Module's IStringLocalizer for per-player translation.</param>
|
||||||
|
void RegisterMenu(string categoryId, string menuId, string menuNameKey, Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unregisters a menu from a category.
|
/// Unregisters a menu from a category.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Binary file not shown.
@@ -145,20 +145,38 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
|||||||
// STEP 1: Register a menu category
|
// STEP 1: Register a menu category
|
||||||
// This creates a new section in the main admin menu
|
// This creates a new section in the main admin menu
|
||||||
// Permission: @css/generic means all admins can see it
|
// Permission: @css/generic means all admins can see it
|
||||||
|
//
|
||||||
|
// ⚠️ LOCALIZATION OPTIONS:
|
||||||
|
//
|
||||||
|
// OPTION A - No translations (hard-coded text):
|
||||||
_sharedApi.RegisterMenuCategory(
|
_sharedApi.RegisterMenuCategory(
|
||||||
"example", // Category ID (unique identifier)
|
"example", // Category ID (unique identifier)
|
||||||
"Example Features", // Display name in admin menu
|
"Example Features", // Display name (hard-coded, same for all players)
|
||||||
"@css/generic" // Required permission
|
"@css/generic" // Required permission
|
||||||
);
|
);
|
||||||
|
//
|
||||||
|
// OPTION B - With per-player translations (🆕 NEW!):
|
||||||
|
// If your module has lang/ folder with translations, use this pattern:
|
||||||
|
// _sharedApi.RegisterMenuCategory(
|
||||||
|
// "example", // Category ID
|
||||||
|
// "example_category_name", // Translation key
|
||||||
|
// "@css/generic", // Permission
|
||||||
|
// Localizer! // Module's localizer
|
||||||
|
// );
|
||||||
|
// This will translate the category name per-player based on their css_lang setting!
|
||||||
|
|
||||||
// STEP 2: Register individual menu items in the category
|
// STEP 2: Register individual menu items in the category
|
||||||
// 🆕 NEW: These use MenuContext API - factory receives (admin, context) parameters
|
// 🆕 NEW: These use MenuContext API - factory receives (admin, context) parameters
|
||||||
|
//
|
||||||
|
// ⚠️ LOCALIZATION OPTIONS:
|
||||||
|
//
|
||||||
|
// OPTION A - No translations (hard-coded text):
|
||||||
|
|
||||||
// Example 1: Simple menu with options
|
// Example 1: Simple menu with options
|
||||||
_sharedApi.RegisterMenu(
|
_sharedApi.RegisterMenu(
|
||||||
"example", // Category ID
|
"example", // Category ID
|
||||||
"simple_action", // Menu ID (unique within category)
|
"simple_action", // Menu ID (unique within category)
|
||||||
"Simple Actions", // Display name
|
"Simple Actions", // Display name (hard-coded)
|
||||||
CreateSimpleActionMenu, // Factory method
|
CreateSimpleActionMenu, // Factory method
|
||||||
"@css/generic" // Required permission
|
"@css/generic" // Required permission
|
||||||
);
|
);
|
||||||
@@ -167,7 +185,7 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
|||||||
_sharedApi.RegisterMenu(
|
_sharedApi.RegisterMenu(
|
||||||
"example",
|
"example",
|
||||||
"player_selection",
|
"player_selection",
|
||||||
"Select Player",
|
"Select Player", // Display name
|
||||||
CreatePlayerSelectionMenu,
|
CreatePlayerSelectionMenu,
|
||||||
"@css/kick" // Requires kick permission
|
"@css/kick" // Requires kick permission
|
||||||
);
|
);
|
||||||
@@ -176,7 +194,7 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
|||||||
_sharedApi.RegisterMenu(
|
_sharedApi.RegisterMenu(
|
||||||
"example",
|
"example",
|
||||||
"nested_menu",
|
"nested_menu",
|
||||||
"Give Credits",
|
"Give Credits", // Display name
|
||||||
CreateGiveCreditsMenu,
|
CreateGiveCreditsMenu,
|
||||||
"@css/generic"
|
"@css/generic"
|
||||||
);
|
);
|
||||||
@@ -185,12 +203,26 @@ public class CS2_SimpleAdmin_ExampleModule: BasePlugin
|
|||||||
_sharedApi.RegisterMenu(
|
_sharedApi.RegisterMenu(
|
||||||
"example",
|
"example",
|
||||||
"test_command",
|
"test_command",
|
||||||
"Test Command",
|
"Test Command", // Display name
|
||||||
CreateTestCommandMenu,
|
CreateTestCommandMenu,
|
||||||
"@css/root", // Default permission
|
"@css/root", // Default permission
|
||||||
"css_test" // Command name for override checking
|
"css_test" // Command name for override checking
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// OPTION B - With per-player translations (🆕 NEW!):
|
||||||
|
// If your module has lang/ folder, use this pattern:
|
||||||
|
// _sharedApi.RegisterMenu(
|
||||||
|
// "example", // Category ID
|
||||||
|
// "menu_id", // Menu ID
|
||||||
|
// "menu_translation_key", // Translation key (NOT translated text!)
|
||||||
|
// CreateYourMenu, // Factory method
|
||||||
|
// "@css/generic", // Permission
|
||||||
|
// "css_command", // Command name (optional)
|
||||||
|
// Localizer! // Module's localizer
|
||||||
|
// );
|
||||||
|
// This will translate the menu name per-player based on their css_lang!
|
||||||
|
// See FunCommands module for real example.
|
||||||
|
|
||||||
_menusRegistered = true;
|
_menusRegistered = true;
|
||||||
Logger.LogInformation("Example menus registered successfully!");
|
Logger.LogInformation("Example menus registered successfully!");
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -366,66 +366,73 @@ public partial class CS2_SimpleAdmin_FunCommands : BasePlugin, IPluginConfig<Con
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_sharedApi.RegisterMenuCategory("fun", Localizer?["fun_category_name"] ?? "Fun Commands", "@css/generic");
|
// 🆕 NEW: Per-player localization support for modules!
|
||||||
|
// - This module has its own lang/ folder with translations
|
||||||
|
// - We pass translation KEYS and the module's Localizer to the API
|
||||||
|
// - SimpleAdmin will translate menu names per-player based on their css_lang
|
||||||
|
// - Each player sees menus in their own language!
|
||||||
|
_sharedApi.RegisterMenuCategory("fun", "fun_category_name", "@css/generic", Localizer!);
|
||||||
|
|
||||||
// Register menus with command names for permission override support
|
// Register menus with command names for permission override support
|
||||||
// Server admins can override default permissions via CounterStrikeSharp admin system
|
// Server admins can override default permissions via CounterStrikeSharp admin system
|
||||||
// Example: If "css_god" is overdden to "@css/vip", only VIPs will see the God Mode menu
|
// Example: If "css_god" is overridden to "@css/vip", only VIPs will see the God Mode menu
|
||||||
|
//
|
||||||
|
// 🆕 NEW: All menus use translation keys and module's Localizer for per-player localization!
|
||||||
|
|
||||||
if (Config.GodCommands.Count > 0)
|
if (Config.GodCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "god",
|
_sharedApi.RegisterMenu("fun", "god",
|
||||||
Localizer?["fun_menu_god"] ?? "God Mode",
|
"fun_menu_god",
|
||||||
CreateGodModeMenu, "@css/cheats", "css_god");
|
CreateGodModeMenu, "@css/cheats", "css_god", Localizer!);
|
||||||
|
|
||||||
if (Config.NoclipCommands.Count > 0)
|
if (Config.NoclipCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "noclip",
|
_sharedApi.RegisterMenu("fun", "noclip",
|
||||||
Localizer?["fun_menu_noclip"] ?? "No Clip",
|
"fun_menu_noclip",
|
||||||
CreateNoClipMenu, "@css/cheats", "css_noclip");
|
CreateNoClipMenu, "@css/cheats", "css_noclip", Localizer!);
|
||||||
|
|
||||||
if (Config.RespawnCommands.Count > 0)
|
if (Config.RespawnCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "respawn",
|
_sharedApi.RegisterMenu("fun", "respawn",
|
||||||
Localizer?["fun_menu_respawn"] ?? "Respawn",
|
"fun_menu_respawn",
|
||||||
CreateRespawnMenu, "@css/cheats", "css_respawn");
|
CreateRespawnMenu, "@css/cheats", "css_respawn", Localizer!);
|
||||||
|
|
||||||
if (Config.GiveCommands.Count > 0)
|
if (Config.GiveCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "give",
|
_sharedApi.RegisterMenu("fun", "give",
|
||||||
Localizer?["fun_menu_give"] ?? "Give Weapon",
|
"fun_menu_give",
|
||||||
CreateGiveWeaponMenu, "@css/cheats", "css_give");
|
CreateGiveWeaponMenu, "@css/cheats", "css_give", Localizer!);
|
||||||
|
|
||||||
if (Config.StripCommands.Count > 0)
|
if (Config.StripCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "strip",
|
_sharedApi.RegisterMenu("fun", "strip",
|
||||||
Localizer?["fun_menu_strip"] ?? "Strip Weapons",
|
"fun_menu_strip",
|
||||||
CreateStripWeaponsMenu, "@css/slay", "css_strip");
|
CreateStripWeaponsMenu, "@css/slay", "css_strip", Localizer!);
|
||||||
|
|
||||||
if (Config.FreezeCommands.Count > 0)
|
if (Config.FreezeCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "freeze",
|
_sharedApi.RegisterMenu("fun", "freeze",
|
||||||
Localizer?["fun_menu_freeze"] ?? "Freeze",
|
"fun_menu_freeze",
|
||||||
CreateFreezeMenu, "@css/slay", "css_freeze");
|
CreateFreezeMenu, "@css/slay", "css_freeze", Localizer!);
|
||||||
|
|
||||||
if (Config.HpCommands.Count > 0)
|
if (Config.HpCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "hp",
|
_sharedApi.RegisterMenu("fun", "hp",
|
||||||
Localizer?["fun_menu_hp"] ?? "Set HP",
|
"fun_menu_hp",
|
||||||
CreateSetHpMenu, "@css/slay", "css_hp");
|
CreateSetHpMenu, "@css/slay", "css_hp", Localizer!);
|
||||||
|
|
||||||
if (Config.SpeedCommands.Count > 0)
|
if (Config.SpeedCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "speed",
|
_sharedApi.RegisterMenu("fun", "speed",
|
||||||
Localizer?["fun_menu_speed"] ?? "Set Speed",
|
"fun_menu_speed",
|
||||||
CreateSetSpeedMenu, "@css/slay", "css_speed");
|
CreateSetSpeedMenu, "@css/slay", "css_speed", Localizer!);
|
||||||
|
|
||||||
if (Config.GravityCommands.Count > 0)
|
if (Config.GravityCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "gravity",
|
_sharedApi.RegisterMenu("fun", "gravity",
|
||||||
Localizer?["fun_menu_gravity"] ?? "Set Gravity",
|
"fun_menu_gravity",
|
||||||
CreateSetGravityMenu, "@css/slay", "css_gravity");
|
CreateSetGravityMenu, "@css/slay", "css_gravity", Localizer!);
|
||||||
|
|
||||||
if (Config.MoneyCommands.Count > 0)
|
if (Config.MoneyCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "money",
|
_sharedApi.RegisterMenu("fun", "money",
|
||||||
Localizer?["fun_menu_money"] ?? "Set Money",
|
"fun_menu_money",
|
||||||
CreateSetMoneyMenu, "@css/slay", "css_money");
|
CreateSetMoneyMenu, "@css/slay", "css_money", Localizer!);
|
||||||
|
|
||||||
if (Config.ResizeCommands.Count > 0)
|
if (Config.ResizeCommands.Count > 0)
|
||||||
_sharedApi.RegisterMenu("fun", "resize",
|
_sharedApi.RegisterMenu("fun", "resize",
|
||||||
Localizer?["fun_menu_resize"] ?? "Resize Player",
|
"fun_menu_resize",
|
||||||
CreateSetResizeMenu, "@css/slay", "css_resize");
|
CreateSetResizeMenu, "@css/slay", "css_resize", Localizer!);
|
||||||
|
|
||||||
_menusRegistered = true;
|
_menusRegistered = true;
|
||||||
Logger.LogInformation("Fun menus registered successfully!");
|
Logger.LogInformation("Fun menus registered successfully!");
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using CounterStrikeSharp.API;
|
using CounterStrikeSharp.API;
|
||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Core.Translations;
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin_FunCommands;
|
namespace CS2_SimpleAdmin_FunCommands;
|
||||||
|
|
||||||
@@ -139,8 +140,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
private object CreateWeaponSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
|
// Translate title per-player based on admin's css_lang
|
||||||
|
string translatedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
translatedTitle = Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
var weaponMenu = _sharedApi!.CreateMenuWithBack(
|
var weaponMenu = _sharedApi!.CreateMenuWithBack(
|
||||||
Localizer?["fun_menu_give_player", target.PlayerName] ?? $"Give Weapon: {target.PlayerName}",
|
translatedTitle,
|
||||||
"fun",
|
"fun",
|
||||||
admin);
|
admin);
|
||||||
|
|
||||||
@@ -212,8 +220,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
private object CreateHpSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
|
// Translate title per-player based on admin's css_lang
|
||||||
|
string translatedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
translatedTitle = Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
var hpSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
var hpSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||||
Localizer?["fun_menu_hp_player", target.PlayerName] ?? $"Set HP: {target.PlayerName}",
|
translatedTitle,
|
||||||
"fun",
|
"fun",
|
||||||
admin);
|
admin);
|
||||||
|
|
||||||
@@ -222,8 +237,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
|
|
||||||
foreach (var hp in hpValues)
|
foreach (var hp in hpValues)
|
||||||
{
|
{
|
||||||
|
// Translate option label per-player
|
||||||
|
string optionLabel;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
optionLabel = Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP";
|
||||||
|
}
|
||||||
|
|
||||||
_sharedApi.AddMenuOption(hpSelectionMenu,
|
_sharedApi.AddMenuOption(hpSelectionMenu,
|
||||||
Localizer?["fun_menu_hp_value", hp] ?? $"{hp} HP",
|
optionLabel,
|
||||||
_ =>
|
_ =>
|
||||||
{
|
{
|
||||||
if (target.IsValid)
|
if (target.IsValid)
|
||||||
@@ -261,8 +283,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private object CreateSpeedSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
private object CreateSpeedSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
|
// Translate title per-player based on admin's css_lang
|
||||||
|
string translatedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
translatedTitle = Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
var speedSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
var speedSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||||
Localizer?["fun_menu_speed_player", target.PlayerName] ?? $"Set Speed: {target.PlayerName}",
|
translatedTitle,
|
||||||
"fun",
|
"fun",
|
||||||
admin);
|
admin);
|
||||||
|
|
||||||
@@ -275,8 +304,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
|
|
||||||
foreach (var (speed, display) in speedValues)
|
foreach (var (speed, display) in speedValues)
|
||||||
{
|
{
|
||||||
|
// Translate option label per-player
|
||||||
|
string optionLabel;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
optionLabel = Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}";
|
||||||
|
}
|
||||||
|
|
||||||
_sharedApi.AddMenuOption(speedSelectionMenu,
|
_sharedApi.AddMenuOption(speedSelectionMenu,
|
||||||
Localizer?["fun_menu_speed_value", display] ?? $"Speed {display}",
|
optionLabel,
|
||||||
_ =>
|
_ =>
|
||||||
{
|
{
|
||||||
if (target.IsValid)
|
if (target.IsValid)
|
||||||
@@ -316,8 +352,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
|
|
||||||
private object CreateGravitySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
private object CreateGravitySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
|
// Translate title per-player based on admin's css_lang
|
||||||
|
string translatedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
translatedTitle = Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
var gravitySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
var gravitySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||||
Localizer?["fun_menu_gravity_player", target.PlayerName] ?? $"Set Gravity: {target.PlayerName}",
|
translatedTitle,
|
||||||
"fun",
|
"fun",
|
||||||
admin);
|
admin);
|
||||||
var gravityValues = new[]
|
var gravityValues = new[]
|
||||||
@@ -365,8 +408,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
|
|
||||||
private object CreateMoneySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
private object CreateMoneySelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
|
// Translate title per-player based on admin's css_lang
|
||||||
|
string translatedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
translatedTitle = Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
var moneySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
var moneySelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||||
Localizer?["fun_menu_money_player", target.PlayerName] ?? $"Set Money: {target.PlayerName}",
|
translatedTitle,
|
||||||
"fun",
|
"fun",
|
||||||
admin);
|
admin);
|
||||||
var moneyValues = new[] { 0, 1000, 2500, 5000, 10000, 16000 };
|
var moneyValues = new[] { 0, 1000, 2500, 5000, 10000, 16000 };
|
||||||
@@ -407,8 +457,15 @@ public partial class CS2_SimpleAdmin_FunCommands
|
|||||||
|
|
||||||
private object CreateResizeSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
private object CreateResizeSelectionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||||
{
|
{
|
||||||
|
// Translate title per-player based on admin's css_lang
|
||||||
|
string translatedTitle;
|
||||||
|
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||||
|
{
|
||||||
|
translatedTitle = Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}";
|
||||||
|
}
|
||||||
|
|
||||||
var resizeSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
var resizeSelectionMenu = _sharedApi!.CreateMenuWithBack(
|
||||||
Localizer?["fun_menu_resize_player", target.PlayerName] ?? $"Resize: {target.PlayerName}",
|
translatedTitle,
|
||||||
"fun",
|
"fun",
|
||||||
admin);
|
admin);
|
||||||
var resizeValues = new[]
|
var resizeValues = new[]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -75,10 +75,17 @@ public class MyModule : BasePlugin
|
|||||||
if (_api == null) return;
|
if (_api == null) return;
|
||||||
|
|
||||||
// 1. Register a new category
|
// 1. Register a new category
|
||||||
|
// IMPORTANT: If your module has lang/ folder with translations, use YOUR module's Localizer!
|
||||||
|
// Example: _api.RegisterMenuCategory("mymodule", Localizer?["mymodule_category_name"] ?? "My Module", "@css/generic");
|
||||||
|
//
|
||||||
|
// This will use YOUR module's translations in server language.
|
||||||
|
// For modules without translations, you can use hard-coded text:
|
||||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||||
|
|
||||||
// 2. Register menu items in the category
|
// 2. Register menu items in the category
|
||||||
// 🆕 NEW: Use MenuContext-aware overload (no duplication!)
|
// 🆕 NEW: Use MenuContext-aware overload (no duplication!)
|
||||||
|
// NOTE: Use YOUR module's Localizer if you have lang/ folder with translations:
|
||||||
|
// _api.RegisterMenu("mymodule", "action1", Localizer?["mymodule_menu_action1"] ?? "Action 1", CreateAction1Menu, "@css/generic");
|
||||||
_api.RegisterMenu("mymodule", "action1", "Action 1", CreateAction1Menu, "@css/generic");
|
_api.RegisterMenu("mymodule", "action1", "Action 1", CreateAction1Menu, "@css/generic");
|
||||||
_api.RegisterMenu("mymodule", "action2", "Action 2", CreateAction2Menu, "@css/kick");
|
_api.RegisterMenu("mymodule", "action2", "Action 2", CreateAction2Menu, "@css/kick");
|
||||||
}
|
}
|
||||||
@@ -356,12 +363,27 @@ Registers a new menu category that appears in the main admin menu.
|
|||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `categoryId` - Unique identifier for the category (e.g., "fun", "vip", "economy")
|
- `categoryId` - Unique identifier for the category (e.g., "fun", "vip", "economy")
|
||||||
- `categoryName` - Display name shown in menu (e.g., "Fun Commands")
|
- `categoryName` - **TRANSLATION KEY** for the display name (e.g., "vip_category_name", NOT "VIP Features")
|
||||||
- `permission` - Required permission to see the category (default: "@css/generic")
|
- `permission` - Required permission to see the category (default: "@css/generic")
|
||||||
|
|
||||||
|
**IMPORTANT FOR MODULES WITH TRANSLATIONS:**
|
||||||
|
- If your module has a `lang/` folder with translation files, use **YOUR module's Localizer**
|
||||||
|
- This will display menu names in the **server's language** (not per-player)
|
||||||
|
- For per-player localization, only **CS2-SimpleAdmin's built-in menus** support this currently
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```csharp
|
```csharp
|
||||||
|
// ✅ CORRECT: Module with translations uses its own Localizer
|
||||||
|
_api.RegisterMenuCategory("vip", Localizer?["vip_category_name"] ?? "VIP Features", "@css/vip");
|
||||||
|
// In YOUR module's lang/en.json: "vip_category_name": "VIP Features"
|
||||||
|
// In YOUR module's lang/pl.json: "vip_category_name": "Funkcje VIP"
|
||||||
|
|
||||||
|
// ✅ ALSO CORRECT: Module without translations uses hard-coded text
|
||||||
_api.RegisterMenuCategory("vip", "VIP Features", "@css/vip");
|
_api.RegisterMenuCategory("vip", "VIP Features", "@css/vip");
|
||||||
|
|
||||||
|
// ❌ INCORRECT: Using translation key without Localizer
|
||||||
|
_api.RegisterMenuCategory("vip", "vip_category_name", "@css/vip");
|
||||||
|
// This would display "vip_category_name" literally!
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Menu Registration
|
### 2. Menu Registration
|
||||||
@@ -373,13 +395,28 @@ Registers a menu item within a category.
|
|||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `categoryId` - The category to add this menu to
|
- `categoryId` - The category to add this menu to
|
||||||
- `menuId` - Unique identifier for the menu
|
- `menuId` - Unique identifier for the menu
|
||||||
- `menuName` - Display name in the category menu
|
- `menuName` - Display name for the menu (use **your module's Localizer** if you have translations)
|
||||||
- `menuFactory` - Function that creates the menu when selected (receives admin player)
|
- `menuFactory` - Function that creates the menu when selected (receives admin player and MenuContext)
|
||||||
- `permission` - Optional permission required to see this menu item
|
- `permission` - Optional permission required to see this menu item
|
||||||
|
|
||||||
|
**IMPORTANT FOR MODULES WITH TRANSLATIONS:**
|
||||||
|
- Use **your module's Localizer** if you have a `lang/` folder: `Localizer?["key"] ?? "Fallback"`
|
||||||
|
- This shows menu in **server language**, not per-player
|
||||||
|
- Per-player localization is only available for CS2-SimpleAdmin's built-in menus
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```csharp
|
```csharp
|
||||||
|
// ✅ CORRECT: Module with translations uses its own Localizer
|
||||||
|
_api.RegisterMenu("fun", "godmode", Localizer?["fun_menu_god"] ?? "God Mode", CreateGodModeMenu, "@css/cheats");
|
||||||
|
// In YOUR module's lang/en.json: "fun_menu_god": "God Mode"
|
||||||
|
// In YOUR module's lang/pl.json: "fun_menu_god": "Tryb Boga"
|
||||||
|
|
||||||
|
// ✅ ALSO CORRECT: Module without translations uses hard-coded text
|
||||||
_api.RegisterMenu("fun", "godmode", "God Mode", CreateGodModeMenu, "@css/cheats");
|
_api.RegisterMenu("fun", "godmode", "God Mode", CreateGodModeMenu, "@css/cheats");
|
||||||
|
|
||||||
|
// ❌ INCORRECT: Using translation key without Localizer
|
||||||
|
_api.RegisterMenu("fun", "godmode", "fun_menu_god", CreateGodModeMenu, "@css/cheats");
|
||||||
|
// This would display "fun_menu_god" literally!
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `UnregisterMenu(string categoryId, string menuId)`
|
#### `UnregisterMenu(string categoryId, string menuId)`
|
||||||
@@ -625,12 +662,32 @@ private object CreateAdminToolsMenu(CCSPlayerController admin)
|
|||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Always check for API availability**
|
1. **Use your module's Localizer for translations** 🌍
|
||||||
|
```csharp
|
||||||
|
// ✅ CORRECT: Module with translations uses its own Localizer
|
||||||
|
_api.RegisterMenuCategory("mymodule", Localizer?["mymodule_category"] ?? "My Module", "@css/generic");
|
||||||
|
_api.RegisterMenu("mymodule", "action", Localizer?["mymodule_menu_action"] ?? "My Action", CreateMenu, "@css/generic");
|
||||||
|
|
||||||
|
// ✅ ALSO CORRECT: Module without translations uses hard-coded text
|
||||||
|
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||||
|
_api.RegisterMenu("mymodule", "action", "My Action", CreateMenu, "@css/generic");
|
||||||
|
|
||||||
|
// ❌ WRONG: Using translation key without Localizer
|
||||||
|
_api.RegisterMenuCategory("mymodule", "mymodule_category", "@css/generic");
|
||||||
|
// This would display "mymodule_category" literally!
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Per-player localization now available!**
|
||||||
|
- Both CS2-SimpleAdmin built-in menus AND module menus support per-player localization
|
||||||
|
- Each player sees menus in their own language based on their `css_lang` setting
|
||||||
|
- See "Advanced: Per-Player Localization for Modules" section below for implementation details
|
||||||
|
|
||||||
|
2. **Always check for API availability**
|
||||||
```csharp
|
```csharp
|
||||||
if (_api == null) return;
|
if (_api == null) return;
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Validate player state before actions**
|
3. **Validate player state before actions**
|
||||||
```csharp
|
```csharp
|
||||||
if (target.IsValid && target.PlayerPawn?.Value != null)
|
if (target.IsValid && target.PlayerPawn?.Value != null)
|
||||||
{
|
{
|
||||||
@@ -638,11 +695,11 @@ private object CreateAdminToolsMenu(CCSPlayerController admin)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Use descriptive category and menu IDs**
|
4. **Use descriptive category and menu IDs**
|
||||||
- Good: `"economy"`, `"vip_features"`, `"fun_commands"`
|
- Good: `"economy"`, `"vip_features"`, `"fun_commands"`
|
||||||
- Bad: `"cat1"`, `"menu"`, `"test"`
|
- Bad: `"cat1"`, `"menu"`, `"test"`
|
||||||
|
|
||||||
4. **Clean up on unload**
|
5. **Clean up on unload**
|
||||||
```csharp
|
```csharp
|
||||||
public override void Unload(bool hotReload)
|
public override void Unload(bool hotReload)
|
||||||
{
|
{
|
||||||
@@ -651,14 +708,14 @@ private object CreateAdminToolsMenu(CCSPlayerController admin)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Use appropriate permissions**
|
6. **Use appropriate permissions**
|
||||||
- `@css/generic` - All admins
|
- `@css/generic` - All admins
|
||||||
- `@css/ban` - Admins who can ban
|
- `@css/ban` - Admins who can ban
|
||||||
- `@css/kick` - Admins who can kick
|
- `@css/kick` - Admins who can kick
|
||||||
- `@css/root` - Root admins only
|
- `@css/root` - Root admins only
|
||||||
- Custom permissions from your module
|
- Custom permissions from your module
|
||||||
|
|
||||||
6. **Handle hot reload**
|
7. **Handle hot reload**
|
||||||
```csharp
|
```csharp
|
||||||
_api.OnSimpleAdminReady += RegisterMenus;
|
_api.OnSimpleAdminReady += RegisterMenus;
|
||||||
RegisterMenus(); // Fallback for hot reload case
|
RegisterMenus(); // Fallback for hot reload case
|
||||||
@@ -717,3 +774,72 @@ See the `CS2-SimpleAdmin_FunCommands` module in the `Modules/` directory for a c
|
|||||||
**Q: API is null in OnAllPluginsLoaded**
|
**Q: API is null in OnAllPluginsLoaded**
|
||||||
- Wait for the `OnSimpleAdminReady` event instead of immediate registration
|
- Wait for the `OnSimpleAdminReady` event instead of immediate registration
|
||||||
- Make sure CS2-SimpleAdmin is loaded before your module
|
- Make sure CS2-SimpleAdmin is loaded before your module
|
||||||
|
|
||||||
|
## Advanced: Per-Player Localization for Modules (✅ NOW AVAILABLE!)
|
||||||
|
|
||||||
|
**🆕 NEW:** Module menus now support **per-player localization** based on `css_lang`!
|
||||||
|
|
||||||
|
Both CS2-SimpleAdmin's built-in menus AND module menus can show in each player's configured language.
|
||||||
|
|
||||||
|
### How to Use Per-Player Localization in Your Module
|
||||||
|
|
||||||
|
**1. Register Category with Localizer:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Pass translation KEY (not translated text) and module's Localizer
|
||||||
|
_api.RegisterMenuCategory(
|
||||||
|
"mymodule",
|
||||||
|
"mymodule_category_name", // Translation key from your lang/ folder
|
||||||
|
"@css/generic",
|
||||||
|
Localizer! // Your module's localizer
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Register Menus with Localizer:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
_api.RegisterMenu(
|
||||||
|
"mymodule",
|
||||||
|
"action",
|
||||||
|
"mymodule_menu_action", // Translation key from your lang/ folder
|
||||||
|
CreateMenu,
|
||||||
|
"@css/generic", // Permission
|
||||||
|
"css_mycommand", // Command name for override (optional)
|
||||||
|
Localizer! // Your module's localizer
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. Your module passes its `IStringLocalizer` and translation **key** (not translated text)
|
||||||
|
2. SimpleAdmin's `MenuManager` stores both the key and the localizer
|
||||||
|
3. When displaying menu to a player, MenuManager translates using:
|
||||||
|
```csharp
|
||||||
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
|
{
|
||||||
|
localizedName = moduleLocalizer[translationKey];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
4. Each player sees menus in their own language based on their `css_lang` setting!
|
||||||
|
|
||||||
|
**Complete Example:**
|
||||||
|
|
||||||
|
See `Modules/CS2-SimpleAdmin_FunCommands/` for a real implementation:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Register category with per-player localization
|
||||||
|
_api.RegisterMenuCategory("fun", "fun_category_name", "@css/generic", Localizer!);
|
||||||
|
|
||||||
|
// Register menu with per-player localization
|
||||||
|
_api.RegisterMenu("fun", "god", "fun_menu_god",
|
||||||
|
CreateGodModeMenu, "@css/cheats", "css_god", Localizer!);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Without Per-Player Localization (backwards compatible):**
|
||||||
|
|
||||||
|
If you don't need per-player localization, the old API still works:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Hard-coded text (same for all players)
|
||||||
|
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||||
|
_api.RegisterMenu("mymodule", "action", "Do Action", CreateMenu, "@css/generic");
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user