├── CODEOWNERS ├── external-dlls └── K4-ArenaSharedApi.dll ├── src ├── Core │ ├── Manifest.cs │ ├── ExternalSupports.cs │ ├── Commands.cs │ ├── Events.cs │ ├── Settings.cs │ └── Stocks.cs ├── Models │ ├── Database │ │ ├── Migrations │ │ │ ├── Bans │ │ │ │ ├── 1.7_Zenith_Bans_IncreaseNameFieldSize.cs │ │ │ │ ├── 1.2_Zenith_Bans_CurrentServer.cs │ │ │ │ ├── 1.5_Zenith_Bans_CustomOverrides.cs │ │ │ │ ├── 1.3_Zenith_Bans_WarnBanRemoveReason.cs │ │ │ │ ├── 1.1_Zenith_Bans_StatusTypeRanks.cs │ │ │ │ ├── 1.6_Zenith_Bans_TableUTF8MB4.cs │ │ │ │ └── 1.0_Zenith_Bans_Create.cs │ │ │ ├── Stats │ │ │ │ ├── 1.1_Zenith_Stats_TableUTF8MB4.cs │ │ │ │ └── 1.0_Zenith_Stats_CreateTables.cs │ │ │ ├── 1.2_Zenith_IncreaseNameFieldSize.cs │ │ │ ├── 1.1_Zenith_TableUTF8MB4.cs │ │ │ └── 1.0_Zenith_CreateTables.cs │ │ └── Model.cs │ ├── Api │ │ ├── Core.cs │ │ └── Command.cs │ ├── Player │ │ └── Static.cs │ └── Config.cs ├── lang │ ├── pl.json │ ├── lv.json │ └── en.json └── K4-Zenith.csproj ├── modules ├── custom-tags │ ├── lang │ │ ├── en.json │ │ └── pl.json │ └── K4-Zenith-CustomTags.csproj ├── time-stats │ ├── lang │ │ ├── pl.json │ │ ├── en.json │ │ └── lv.json │ └── K4-Zenith-TimeStats.csproj ├── extended-commands │ ├── Events.cs │ ├── K4-Zenith-ExtendedCommands.csproj │ ├── K4-Zenith-ExtendedCommands.cs │ ├── lang │ │ ├── pl.json │ │ └── en.json │ ├── Weapon.cs │ └── Stocks.cs ├── ranks │ ├── K4-Zenith-Ranks.csproj │ ├── lang │ │ ├── pl.json │ │ ├── lv.json │ │ └── en.json │ ├── Stocks.cs │ ├── Config.cs │ └── RankConfig.cs ├── zenith-bans │ ├── K4-Zenith-Bans.csproj │ ├── Models.cs │ ├── Events.cs │ ├── lang │ │ ├── pl.json │ │ └── en.json │ └── Menu.cs ├── statistics │ ├── K4-Zenith-Stats.csproj │ └── lang │ │ ├── en.json │ │ ├── lv.json │ │ └── pl.json └── toplists │ ├── K4-Zenith-Toplists.csproj │ ├── lang │ ├── en.json │ ├── lv.json │ └── pl.json │ ├── RankTopHandler.cs │ ├── TimeTopHandler.cs │ └── StatsTopHandler.cs ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── FUNDING.yml ├── src-api └── K4-ZenithAPI.csproj └── deploy.sh /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # The owner of everything in this repository 2 | 3 | - @K4ryuu 4 | -------------------------------------------------------------------------------- /external-dlls/K4-ArenaSharedApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K4ryuu/K4-Zenith/HEAD/external-dlls/K4-ArenaSharedApi.dll -------------------------------------------------------------------------------- /src/Core/Manifest.cs: -------------------------------------------------------------------------------- 1 | namespace Zenith 2 | { 3 | using CounterStrikeSharp.API.Core; 4 | 5 | public sealed partial class Plugin : BasePlugin 6 | { 7 | public override string ModuleName => "K4-Zenith | Core"; 8 | 9 | public override string ModuleDescription => "A modular core that includes developer tools and utilities."; 10 | 11 | public override string ModuleAuthor => "K4ryuu @ KitsuneLab"; 12 | 13 | public override string ModuleVersion => "1.0.41"; 14 | } 15 | } -------------------------------------------------------------------------------- /modules/custom-tags/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "customtags.menu.title": "Select Tag Configuration", 3 | "customtags.menu.default": "Default", 4 | "customtags.menu.none": "None", 5 | "customtags.applied.default": "{silver}Applied default tag configuration.", 6 | "customtags.applied.none": "{silver}Removed all tag configurations.", 7 | "customtags.applied.config": "{silver}Applied {gold}{0} {silver}tag configuration.", 8 | "customtags.applied.default_predefined": "{silver}Applied default predefined tag configuration: {gold}{0}", 9 | "customtags.menu.no-tags": "No tags available" 10 | } 11 | -------------------------------------------------------------------------------- /modules/custom-tags/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "customtags.menu.title": "Wybierz konfigurację tagu", 3 | "customtags.menu.default": "Domyślny", 4 | "customtags.menu.none": "Brak", 5 | "customtags.applied.default": "{silver}Zastosowano domyślną konfigurację tagu.", 6 | "customtags.applied.none": "{silver}Usunięto wszystkie konfiguracje tagów.", 7 | "customtags.applied.config": "{silver}Zastosowano konfigurację tagu {gold}{0}{silver}.", 8 | "customtags.applied.default_predefined": "{silver}Zastosowano domyślną, predefiniowaną konfigurację tagu: {gold}{0}", 9 | "customtags.menu.no-tags": "Brak dostępnych tagów" 10 | } -------------------------------------------------------------------------------- /modules/time-stats/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestats.notification": "{silver}Twój całkowity czas gry na serwerze wynosi {gold}{0}{silver}. Dziękujemy za spędzony z nami czas!", 3 | "timestats.center.title": "+ Statystyki Czasu Gry +", 4 | "timestats.center.total.label": "Łącznie:", 5 | "timestats.center.teams.label": "Drużyny:", 6 | "timestats.center.teams.value": "T: {0} | CT: {1}", 7 | "timestats.center.spectator.label": "Obserwator:", 8 | "timestats.center.status.label": "Status:", 9 | "timestats.center.status.value": "Żywy: {0} | Martwy: {1}", 10 | "timestats.time.format": "{0}d {1}h {2}m", 11 | 12 | "settings.ShowPlaytime": "Powiadomienia o Czasie Gry" 13 | } 14 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Bans/1.7_Zenith_Bans_IncreaseNameFieldSize.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations.Bans 4 | { 5 | [Migration(202501052)] 6 | public class Bans_IncreaseNameFieldSize : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_bans_players").Exists()) 11 | { 12 | Alter.Table("zenith_bans_players") 13 | .AlterColumn("name").AsString(255).Nullable(); 14 | } 15 | } 16 | 17 | public override void Down() 18 | { 19 | if (Schema.Table("zenith_bans_players").Exists()) 20 | { 21 | Alter.Table("zenith_bans_players") 22 | .AlterColumn("name").AsString(64).Nullable(); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[REQ]" 5 | labels: enhancement 6 | assignees: K4ryuu 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Bans/1.2_Zenith_Bans_CurrentServer.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202410195)] 6 | public class Bans_CreateZenithBansTablesUpgradeV2 : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_bans_players").Exists()) 11 | { 12 | if (!Schema.Table("zenith_bans_players").Column("current_server").Exists()) 13 | Alter.Table("zenith_bans_players").AddColumn("current_server").AsString(50).Nullable(); 14 | } 15 | } 16 | 17 | public override void Down() 18 | { 19 | if (Schema.Table("zenith_bans_players").Column("current_server").Exists()) 20 | Delete.Column("current_server").FromTable("zenith_bans_players"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: K4ryuu 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Media (Optional)** 23 | If applicable, add screenshots or videos to help explain your problem. 24 | 25 | **Logs (Optional)** 26 | If applicable, add logs to help explain your problem. 27 | 28 | **Additional context (Optional)** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://www.buymeacoffee.com/k4ryuu'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Bans/1.5_Zenith_Bans_CustomOverrides.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202411101)] 6 | public class Bans_AddCustomOverrides : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (!Schema.Table("zenith_bans_player_overrides").Exists()) 11 | { 12 | Create.Table("zenith_bans_player_overrides") 13 | .WithColumn("id").AsInt32().PrimaryKey().Identity() 14 | .WithColumn("player_rank_id").AsInt32().ForeignKey("FK_player_overrides_rank_id", "zenith_bans_player_ranks", "id") 15 | .WithColumn("command").AsString(100).NotNullable() 16 | .WithColumn("value").AsBoolean(); 17 | } 18 | } 19 | 20 | public override void Down() 21 | { 22 | if (Schema.Table("zenith_bans_player_overrides").Exists()) 23 | { 24 | Delete.Table("zenith_bans_player_overrides"); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/extended-commands/Events.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Modules.Utils; 3 | 4 | namespace Zenith_ExtendedCommands 5 | { 6 | public sealed partial class Plugin : BasePlugin 7 | { 8 | private readonly Dictionary _deathLocations = []; 9 | 10 | private void Initialize_Events() 11 | { 12 | RegisterEventHandler((EventPlayerDeath @event, GameEventInfo info) => 13 | { 14 | CCSPlayerController? player = @event.Userid; 15 | if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) 16 | return HookResult.Continue; 17 | 18 | var location = player.PlayerPawn.Value?.AbsOrigin; 19 | if (location == null) 20 | return HookResult.Continue; 21 | 22 | _deathLocations[player] = new Vector(location.X, location.Y, location.Z); 23 | return HookResult.Continue; 24 | }, HookMode.Pre); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src-api/K4-ZenithAPI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-ZenithAPI/ 7 | 8 | 9 | net8.0 10 | ZenithAPI 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Stats/1.1_Zenith_Stats_TableUTF8MB4.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202411183)] 6 | public class Stats_SetTablesUtf8mb4 : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_weapon_stats").Exists()) 11 | Execute.Sql("ALTER TABLE zenith_weapon_stats CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 12 | 13 | if (Schema.Table("zenith_map_stats").Exists()) 14 | Execute.Sql("ALTER TABLE zenith_map_stats CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 15 | } 16 | 17 | public override void Down() 18 | { 19 | if (Schema.Table("zenith_weapon_stats").Exists()) 20 | Execute.Sql("ALTER TABLE zenith_weapon_stats CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 21 | 22 | if (Schema.Table("zenith_map_stats").Exists()) 23 | Execute.Sql("ALTER TABLE zenith_map_stats CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Models/Database/Migrations/1.2_Zenith_IncreaseNameFieldSize.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202508051)] 6 | public class Default_IncreaseNameFieldSize : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_player_settings").Exists()) 11 | { 12 | Alter.Table("zenith_player_settings") 13 | .AlterColumn("name").AsString(255).Nullable(); 14 | } 15 | 16 | if (Schema.Table("zenith_player_storage").Exists()) 17 | { 18 | Alter.Table("zenith_player_storage") 19 | .AlterColumn("name").AsString(255).Nullable(); 20 | } 21 | } 22 | 23 | public override void Down() 24 | { 25 | if (Schema.Table("zenith_player_settings").Exists()) 26 | { 27 | Alter.Table("zenith_player_settings") 28 | .AlterColumn("name").AsString(64).Nullable(); 29 | } 30 | 31 | if (Schema.Table("zenith_player_storage").Exists()) 32 | { 33 | Alter.Table("zenith_player_storage") 34 | .AlterColumn("name").AsString(64).Nullable(); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Models/Api/Core.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | public static class CallerIdentifier 4 | { 5 | private static readonly string CurrentPluginName = Assembly.GetExecutingAssembly().GetName().Name!; 6 | private static readonly string[] BlockAssemblies = ["System.", "K4-ZenithAPI", "KitsuneMenu"]; 7 | public static readonly List ModuleList = []; 8 | 9 | public static string GetCallingPluginName() 10 | { 11 | var stackTrace = new System.Diagnostics.StackTrace(true); 12 | 13 | for (int i = 1; i < stackTrace.FrameCount; i++) 14 | { 15 | var assembly = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType?.Assembly; 16 | var assemblyName = assembly?.GetName().Name; 17 | 18 | if (assemblyName == "CounterStrikeSharp.API") 19 | break; 20 | 21 | if (assemblyName != CurrentPluginName && assemblyName != null && !BlockAssemblies.Any(assemblyName.StartsWith)) 22 | { 23 | if (!ModuleList.Contains(assemblyName)) 24 | ModuleList.Add(assemblyName); 25 | 26 | return assemblyName; 27 | } 28 | } 29 | 30 | return CurrentPluginName; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.general.prefix": "{silver}- {gold}+ Zenith + {silver}- ", 3 | 4 | "k4.tag.all": "{white}[WSZYSCY] ", 5 | "k4.tag.dead": "{grey}[MARTWY] ", 6 | "k4.tag.team.ct": "{lightblue}[CT] ", 7 | "k4.tag.team.t": "{yellow}[T] ", 8 | "k4.tag.team.spectator": "{white}[OBS] ", 9 | "k4.tag.team.unassigned": "{white}[NIEPRZYDZIELONY] ", 10 | "k4.tag.separator": "{white}: ", 11 | 12 | "k4.command.help": "{silver}Oczekiwane użycie: {lime}!{0} {1}", 13 | "k4.command.no-permission": "{lightred}Nie masz uprawnień do użycia tej komendy.", 14 | "k4.command.server-only": "{lightred}Ta komenda może być użyta tylko przez serwer.", 15 | "k4.command.client-only": "{lightred}Ta komenda może być użyta tylko przez klientów.", 16 | 17 | "k4.settings.title": " + Ustawienia +", 18 | "k4.settings.disabled": "Wyłączone", 19 | "k4.settings.enabled": "Włączone", 20 | "k4.settings.invalid-input": "Nieprawidłowe dane. Proszę wprowadzić poprawną liczbę całkowitą.", 21 | 22 | "settings.ShowClanTags": "Wyświetlaj tag klanu", 23 | "settings.ShowChatTags": "Wyświetlaj tag czatu" 24 | } 25 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/1.1_Zenith_TableUTF8MB4.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202411181)] 6 | public class Default_SetTablesUtf8mb4 : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_player_settings").Exists()) 11 | { 12 | Execute.Sql("ALTER TABLE zenith_player_settings CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 13 | } 14 | 15 | if (Schema.Table("zenith_player_storage").Exists()) 16 | { 17 | Execute.Sql("ALTER TABLE zenith_player_storage CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 18 | } 19 | } 20 | 21 | public override void Down() 22 | { 23 | if (Schema.Table("zenith_player_settings").Exists()) 24 | { 25 | Execute.Sql("ALTER TABLE zenith_player_settings CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 26 | } 27 | 28 | if (Schema.Table("zenith_player_storage").Exists()) 29 | { 30 | Execute.Sql("ALTER TABLE zenith_player_storage CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Core/ExternalSupports.cs: -------------------------------------------------------------------------------- 1 | namespace Zenith 2 | { 3 | using CounterStrikeSharp.API.Core; 4 | using CounterStrikeSharp.API.Core.Capabilities; 5 | using K4ArenaSharedApi; 6 | using Microsoft.Extensions.Logging; 7 | 8 | public sealed partial class Plugin : BasePlugin 9 | { 10 | public static IK4ArenaSharedApi? SharedAPI_Arena { get; private set; } 11 | public (bool ArenaFound, bool Checked) ArenaSupport = (false, false); 12 | public string GetPlayerArenaName(CCSPlayerController player) 13 | { 14 | if (!ArenaSupport.Checked) 15 | { 16 | string arenaPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "K4-Arenas")); 17 | ArenaSupport.ArenaFound = Directory.Exists(arenaPath); 18 | ArenaSupport.Checked = true; 19 | } 20 | 21 | if (!ArenaSupport.ArenaFound) 22 | return string.Empty; 23 | 24 | if (SharedAPI_Arena is null) 25 | { 26 | PluginCapability Capability_SharedAPI = new("k4-arenas:sharedapi"); 27 | SharedAPI_Arena = Capability_SharedAPI.Get(); 28 | } 29 | 30 | return SharedAPI_Arena?.GetArenaName(player) ?? string.Empty; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Models/Player/Static.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using CounterStrikeSharp.API.Core; 3 | 4 | namespace Zenith.Models; 5 | 6 | public sealed partial class Player 7 | { 8 | public static ConcurrentDictionary List { get; } = new ConcurrentDictionary(); 9 | 10 | public static Player? Find(ulong steamID) 11 | { 12 | if (List.TryGetValue(steamID, out var player)) 13 | { 14 | if (!player.IsValid) 15 | { 16 | player.Dispose(); 17 | return null; 18 | } 19 | return player; 20 | } 21 | return null; 22 | } 23 | 24 | public static Player? Find(CCSPlayerController? controller) 25 | { 26 | if (controller == null) return null; 27 | 28 | var player = List.Values.FirstOrDefault(p => p.Controller == controller); 29 | 30 | if (player != null && !player.IsValid) 31 | { 32 | player.Dispose(); 33 | return null; 34 | } 35 | 36 | return player; 37 | } 38 | 39 | public static void AddToList(Player player) 40 | { 41 | List[player.SteamID] = player; 42 | } 43 | 44 | public static void RemoveFromList(ulong playerToRemove) 45 | { 46 | List.TryRemove(playerToRemove, out _); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/lang/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.general.prefix": "{silver}- {gold}+ Zenith + {silver}- ", 3 | 4 | "k4.tag.all": "{white} ", 5 | "k4.tag.dead": "{grey}[DEAD] ", 6 | "k4.tag.team.ct": "{lightblue}[CT] ", 7 | "k4.tag.team.t": "{yellow}[T] ", 8 | "k4.tag.team.spectator": "{white}[SPEC] ", 9 | "k4.tag.team.unassigned": "{white}[UNASSIGNED] ", 10 | "k4.tag.separator": "{white}: ", 11 | 12 | "k4.command.help": "{silver}Lietojums: {lime}!{0} {1}", 13 | "k4.command.no-permission": "{lightred}Tev nav pieejas izmantot šo komandu.", 14 | "k4.command.server-only": "{lightred}Šo komandu var izmantot tikai serveris.", 15 | "k4.command.client-only": "{lightred}Šo komandu var izmantot tikai spēlētāji.", 16 | 17 | "k4.settings.title": " + Iestatījumi +", 18 | "k4.settings.disabled": "Izslēgts", 19 | "k4.settings.enabled": "Ieslēgts", 20 | "k4.settings.invalid-input": "Nederīgs inputs. Lūdzu, ievadi derīgu veselu skaitli.", 21 | 22 | "settings.ShowClanTags": "Rādīt klana tagu", 23 | "settings.ShowChatTags": "Rādīt čata tagu", 24 | "settings.FreezeInMenu": "Iesaldēt izvēlnē", 25 | 26 | "reset.storage.success": "Veiksmīgi resetota {0} krātuve visiem spēlētājiem.", 27 | "reset.storage.all.modules": "visi moduļi" 28 | } 29 | -------------------------------------------------------------------------------- /src/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.general.prefix": "{silver}- {gold}+ Zenith + {silver}- ", 3 | 4 | "k4.tag.all": "{white}[ALL] ", 5 | "k4.tag.dead": "{grey}[DEAD] ", 6 | "k4.tag.team.ct": "{lightblue}[CT] ", 7 | "k4.tag.team.t": "{yellow}[T] ", 8 | "k4.tag.team.spectator": "{white}[SPEC] ", 9 | "k4.tag.team.unassigned": "{white}[UNASSIGNED] ", 10 | "k4.tag.separator": "{white}: ", 11 | 12 | "k4.command.help": "{silver}Expected Usage: {lime}!{0} {1}", 13 | "k4.command.no-permission": "{lightred}You do not have permission to use this command.", 14 | "k4.command.server-only": "{lightred}This command can only be used by the server.", 15 | "k4.command.client-only": "{lightred}This command can only be used by clients.", 16 | 17 | "k4.settings.title": " + Settings +", 18 | "k4.settings.disabled": "Disabled", 19 | "k4.settings.enabled": "Enabled", 20 | "k4.settings.invalid-input": "Invalid input. Please enter a valid integer.", 21 | 22 | "settings.ShowClanTags": "Display Clan Tag", 23 | "settings.ShowChatTags": "Display Chat Tag", 24 | "settings.FreezeInMenu": "Freeze in Menu", 25 | 26 | "reset.storage.success": "Successfully reset {0} storage for all players.", 27 | "reset.storage.all.modules": "all modules" 28 | } 29 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/1.0_Zenith_CreateTables.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202410191)] 6 | public class Default_CreatePlayerDataTables : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (!Schema.Table("zenith_player_settings").Exists()) 11 | { 12 | Create.Table("zenith_player_settings") 13 | .WithColumn("steam_id").AsString(32).PrimaryKey() 14 | .WithColumn("name").AsString(64).Nullable() 15 | .WithColumn("last_online").AsCustom("TIMESTAMP").WithDefault(SystemMethods.CurrentDateTime).Nullable(); 16 | } 17 | 18 | if (!Schema.Table("zenith_player_storage").Exists()) 19 | { 20 | Create.Table("zenith_player_storage") 21 | .WithColumn("steam_id").AsString(32).PrimaryKey() 22 | .WithColumn("name").AsString(64).Nullable() 23 | .WithColumn("last_online").AsCustom("TIMESTAMP").WithDefault(SystemMethods.CurrentDateTime).Nullable(); 24 | } 25 | } 26 | 27 | public override void Down() 28 | { 29 | if (Schema.Table("zenith_player_settings").Exists()) 30 | Delete.Table("zenith_player_settings"); 31 | 32 | if (Schema.Table("zenith_player_storage").Exists()) 33 | Delete.Table("zenith_player_storage"); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /modules/time-stats/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestats.notification": "{silver}Your total playtime on the server is {gold}{0}{silver}. We appreciate your time spent with us!", 3 | "timestats.center.title": "+ Playtime Statistics +", 4 | "timestats.center.total.label": "Total:", 5 | "timestats.center.teams.label": "Teams:", 6 | "timestats.center.teams.value": "T: {0} | CT: {1}", 7 | "timestats.center.spectator.label": "Spectator:", 8 | "timestats.center.status.label": "Status:", 9 | "timestats.center.status.value": "Alive: {0} | Dead: {1}", 10 | "timestats.time.format": "{0}d {1}h {2}m", 11 | 12 | "settings.ShowPlaytime": "PlayTime Notifications", 13 | 14 | "timestats.chat.title": "{lime}{0}{silver}'s Playtime Statistics", 15 | "timestats.chat.total": "--- {silver}Total Playtime: {lime}{0}", 16 | "timestats.chat.teams": "--- {silver}Team Playtime: {red}T: {0}{silver}, {blue}CT: {1}", 17 | "timestats.chat.spectator": "--- {silver}Spectator Time: {grey}{0}", 18 | "timestats.chat.status": "--- {silver}Status: {green}Alive: {0}{silver}, {darkred}Dead: {1}", 19 | 20 | "timestats.today.title": "+ Today's Playtime +", 21 | "timestats.today.chat.title": "{lime}{0}{silver}'s Today's Playtime", 22 | "timestats.today.chat.playtime": "--- {silver}Today's Playtime: {lime}{0}" 23 | } 24 | -------------------------------------------------------------------------------- /modules/time-stats/K4-Zenith-TimeStats.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith-TimeStats/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | $(ProjectDir)\..\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /modules/extended-commands/K4-Zenith-ExtendedCommands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith-ExtendedCommands/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | $(ProjectDir)\..\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /modules/time-stats/lang/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestats.notification": "{silver}Tavs kopējais pavadītais laiks serverī ir {gold}{0}{silver}. Paldies, ka spēlē kopā ar mums!", 3 | "timestats.center.title": "+ Laika Statistika +", 4 | "timestats.center.total.label": "Kopā:", 5 | "timestats.center.teams.label": "Komandas:", 6 | "timestats.center.teams.value": "T: {0} | CT: {1}", 7 | "timestats.center.spectator.label": "SPEC:", 8 | "timestats.center.status.label": "Statuss:", 9 | "timestats.center.status.value": "Dzīvs: {0} | Miris: {1}", 10 | "timestats.time.format": "{0}d {1}h {2}m", 11 | 12 | "settings.ShowPlaytime": "Paziņojumi par pavadīto laiku", 13 | 14 | "timestats.chat.title": "{lime}{0}{silver} pavadītā laika statistika", 15 | "timestats.chat.total": "--- {silver}Kopējais pavadītais laiks: {lime}{0}", 16 | "timestats.chat.teams": "--- {silver}Pavadītais laiks komandā: {red}T: {0}{silver}, {blue}CT: {1}", 17 | "timestats.chat.spectator": "--- {silver}SPEC laiks: {grey}{0}", 18 | "timestats.chat.status": "--- {silver}Statuss: {green}Dzīvs: {0}{silver}, {darkred}Miris: {1}", 19 | 20 | "timestats.today.title": "+ Šodien pavadītais Laiks +", 21 | "timestats.today.chat.title": "{lime}{0}{silver} šodien pavadītais laiks", 22 | "timestats.today.chat.playtime": "--- {silver}Šodien pavadīts laiks: {lime}{0}" 23 | } 24 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Bans/1.3_Zenith_Bans_WarnBanRemoveReason.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202410196)] 6 | public class Bans_CreateZenithBansTablesUpgradeV3 : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_bans_punishments").Exists()) 11 | { 12 | if (Schema.Table("zenith_bans_punishments").Column("status").Exists()) 13 | { 14 | Alter.Table("zenith_bans_punishments").AlterColumn("status").AsCustom("ENUM('active', 'warn_ban', 'expired', 'removed', 'removed_console')").NotNullable().WithDefaultValue("active"); 15 | } 16 | if (!Schema.Table("zenith_bans_punishments").Column("remove_reason").Exists()) 17 | Alter.Table("zenith_bans_punishments").AddColumn("remove_reason").AsCustom("TEXT").Nullable(); 18 | } 19 | } 20 | 21 | public override void Down() 22 | { 23 | if (Schema.Table("zenith_bans_punishments").Column("remove_reason").Exists()) 24 | Delete.Column("remove_reason").FromTable("zenith_bans_punishments"); 25 | 26 | if (Schema.Table("zenith_bans_punishments").Column("status").Exists()) 27 | Alter.Table("zenith_bans_punishments").AlterColumn("status").AsCustom("ENUM('active', 'expired', 'removed', 'removed_console')").NotNullable().WithDefaultValue("active"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/custom-tags/K4-Zenith-CustomTags.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith-CustomTags/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | $(ProjectDir)\..\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 24 | false 25 | 26 | 27 | 28 | 29 | /Users/sples/Projects/CS2_Random/Menu-main/src/bin/KitsuneMenu/shared/KitsuneMenu/KitsuneMenu.dll 30 | false 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /modules/ranks/K4-Zenith-Ranks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith-Ranks/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | $(ProjectDir)\..\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 25 | false 26 | 27 | 28 | 29 | 30 | /Users/sples/Projects/CS2_Random/Menu-main/src/bin/KitsuneMenu/shared/KitsuneMenu/KitsuneMenu.dll 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /modules/zenith-bans/K4-Zenith-Bans.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith-Bans/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | $(ProjectDir)\..\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 26 | false 27 | 28 | 29 | 30 | 31 | /Users/sples/Projects/CS2_Random/Menu-main/src/bin/KitsuneMenu/shared/KitsuneMenu/KitsuneMenu.dll 32 | false 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /modules/statistics/K4-Zenith-Stats.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith-Stats/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | $(ProjectDir)\..\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 26 | false 27 | 28 | 29 | 30 | 31 | /Users/sples/Projects/CS2_Random/Menu-main/src/bin/KitsuneMenu/shared/KitsuneMenu/KitsuneMenu.dll 32 | false 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /modules/toplists/K4-Zenith-Toplists.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith-Toplists/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | $(ProjectDir)\..\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 26 | false 27 | 28 | 29 | 30 | 31 | /Users/sples/Projects/CS2_Random/Menu-main/src/bin/KitsuneMenu/shared/KitsuneMenu/KitsuneMenu.dll 32 | false 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/Core/Commands.cs: -------------------------------------------------------------------------------- 1 | namespace Zenith 2 | { 3 | using CounterStrikeSharp.API.Core; 4 | using CounterStrikeSharp.API.Modules.Commands; 5 | using Microsoft.Extensions.Logging; 6 | using Zenith.Models; 7 | 8 | public sealed partial class Plugin : BasePlugin 9 | { 10 | public void Initialize_Commands() // ? Decide whether or not its needed 11 | { 12 | RegisterZenithCommand("css_placeholderlist", "List all active placeholders in Zenith", (CCSPlayerController? player, CommandInfo command) => 13 | { 14 | ListAllPlaceholders(player: player); 15 | }, CommandUsage.CLIENT_AND_SERVER, permission: "@zenith/placeholders"); 16 | 17 | RegisterZenithCommand("css_commandlist", "List all active commands in Zenith", (CCSPlayerController? player, CommandInfo command) => 18 | { 19 | ListAllCommands(player: player); 20 | }, CommandUsage.CLIENT_AND_SERVER, permission: "@zenith/commands"); 21 | 22 | RegisterZenithCommand("css_zreload", "Reload Zenith configurations manually", (CCSPlayerController? player, CommandInfo command) => 23 | { 24 | ConfigManager.ReloadAllConfigs(); 25 | Player.Find(player)?.Print("Zenith configurations reloaded."); 26 | }, CommandUsage.CLIENT_AND_SERVER, permission: "@zenith/reload"); 27 | 28 | RegisterZenithCommand("css_zresetall", "Reset Zenith storages for all players", (CCSPlayerController? player, CommandInfo command) => 29 | { 30 | Player? caller = Player.Find(player); 31 | string argument = command.GetArg(1); 32 | 33 | Task.Run(async () => await Player.ResetModuleStorageAll(this, caller, argument)); 34 | }, CommandUsage.CLIENT_AND_SERVER, 1, "[all|rank|stat|time]", permission: "@zenith/resetall"); 35 | 36 | RegisterZenithCommand("css_zmigrate", "Migrate other supported plugins' sql data to Zenith", (CCSPlayerController? player, CommandInfo command) => 37 | { 38 | Task.Run(async () => await MigrateOldData()); 39 | }, CommandUsage.SERVER_ONLY, permission: "@zenith/root"); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /modules/statistics/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.stats.title": "+ Player Statistics +", 3 | "k4.weaponstats.title": "+ Weapon Statistics +", 4 | "k4.stats.weapon-disabled": "Weapon stats are disabled.", 5 | "k4.stats.map-disabled": "Map stats are disabled.", 6 | "k4.stats.kills": "Kills", 7 | "k4.stats.firstblood": "First Blood", 8 | "k4.stats.deaths": "Deaths", 9 | "k4.stats.assists": "Assists", 10 | "k4.stats.shoots": "Shots Fired", 11 | "k4.stats.hitstaken": "Hits Taken", 12 | "k4.stats.hitsgiven": "Hits Given", 13 | "k4.stats.headshots": "Headshots", 14 | "k4.stats.headhits": "Head Hits", 15 | "k4.stats.chesthits": "Chest Hits", 16 | "k4.stats.stomachhits": "Stomach Hits", 17 | "k4.stats.leftarmhits": "Left Arm Hits", 18 | "k4.stats.rightarmhits": "Right Arm Hits", 19 | "k4.stats.leftleghits": "Left Leg Hits", 20 | "k4.stats.rightleghits": "Right Leg Hits", 21 | "k4.stats.neckhits": "Neck Hits", 22 | "k4.stats.gearhits": "Gear Hits", 23 | "k4.stats.grenades": "Grenades Thrown", 24 | "k4.stats.mvp": "MVP Awards", 25 | "k4.stats.roundwin": "Rounds Won", 26 | "k4.stats.roundlose": "Rounds Lost", 27 | "k4.stats.gamewin": "Games Won", 28 | "k4.stats.gamelose": "Games Lost", 29 | "k4.stats.roundsoverall": "Total Rounds Played", 30 | "k4.stats.roundsct": "CT Rounds Played", 31 | "k4.stats.roundst": "T Rounds Played", 32 | "k4.stats.bombplanted": "Bombs Planted", 33 | "k4.stats.bombdefused": "Bombs Defused", 34 | "k4.stats.hostagerescued": "Hostages Rescued", 35 | "k4.stats.hostagekilled": "Hostages Killed", 36 | "k4.stats.noscopekill": "No Scope Kills", 37 | "k4.stats.penetratedkill": "Penetration Kills", 38 | "k4.stats.thrusmokekill": "Through Smoke Kills", 39 | "k4.stats.flashedkill": "Flashed Kills", 40 | "k4.stats.dominatedkill": "Domination Kills", 41 | "k4.stats.revengekill": "Revenge Kills", 42 | "k4.stats.assistflash": "Flash Assists", 43 | "k4.stats.accuracy": "Accuracy", 44 | "k4.stats.kpr": "KPR", 45 | "k4.stats.kda": "KDA", 46 | "k4.stats.kd": "K/D", 47 | "k4.stats.no_stats": "No stats available yet.", 48 | "k4.stats.stats_disabled": "{silver}Stats are disabled. Minimum {gold}{0} {silver}players required." 49 | } 50 | -------------------------------------------------------------------------------- /modules/statistics/lang/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.stats.title": "+ Spēlētāja Statistika +", 3 | "k4.weaponstats.title": "+ Ieroču Statistika +", 4 | "k4.stats.weapon-disabled": "Ieroču statistika ir atspējota.", 5 | "k4.stats.map-disabled": "Kartes statistika ir atspējota.", 6 | "k4.stats.kills": "Killi", 7 | "k4.stats.firstblood": "First Blood", 8 | "k4.stats.deaths": "Nāves", 9 | "k4.stats.assists": "Asisti", 10 | "k4.stats.shoots": "Izšautie šāvieni", 11 | "k4.stats.hitstaken": "Saņemtie hiti", 12 | "k4.stats.hitsgiven": "Dotie hiti", 13 | "k4.stats.headshots": "Šāvieni galvā", 14 | "k4.stats.headhits": "Hiti galvā", 15 | "k4.stats.chesthits": "Hiti krūtīs", 16 | "k4.stats.stomachhits": "Hiti vēderā", 17 | "k4.stats.leftarmhits": "Hiti kreisajā rokā", 18 | "k4.stats.rightarmhits": "Hiti labajā rokā", 19 | "k4.stats.leftleghits": "Hiti kreisajā kājā", 20 | "k4.stats.rightleghits": "Hiti labajā kājā", 21 | "k4.stats.neckhits": "Hiti kaklā", 22 | "k4.stats.gearhits": "Hiti ekipējumā", 23 | "k4.stats.grenades": "Granātu stati", 24 | "k4.stats.mvp": "MVP stati", 25 | "k4.stats.roundwin": "Uzvarēti raundi", 26 | "k4.stats.roundlose": "Zaudēti raundi", 27 | "k4.stats.gamewin": "Uzvarētas spēles", 28 | "k4.stats.gamelose": "Zaudētas spēles", 29 | "k4.stats.roundsoverall": "Kopā nospēlēti raundi", 30 | "k4.stats.roundsct": "CT raundi", 31 | "k4.stats.roundst": "T raundi", 32 | "k4.stats.bombplanted": "plantotās bumbas", 33 | "k4.stats.bombdefused": "defusotās bumbas", 34 | "k4.stats.hostagerescued": "Izglābti hosti", 35 | "k4.stats.hostagekilled": "Nogalināti hosti", 36 | "k4.stats.noscopekill": "No Scope killi", 37 | "k4.stats.penetratedkill": "Šāvieni caur objektiem", 38 | "k4.stats.thrusmokekill": "Killi caur smoke", 39 | "k4.stats.flashedkill": "Killi ar flash", 40 | "k4.stats.dominatedkill": "Domination killi", 41 | "k4.stats.revengekill": "Revenge killi", 42 | "k4.stats.assistflash": "Flash asisti", 43 | "k4.stats.accuracy": "Precizitāte", 44 | "k4.stats.kpr": "KPR (Nogalin./raundā)", 45 | "k4.stats.kda": "KDA (Killi/Nāves/Assisti)", 46 | "k4.stats.kd": "K/D (Nogalināšanas/Nāves)", 47 | "k4.stats.no_stats": "Vēl nav pieejama statistika.", 48 | "k4.stats.stats_disabled": "{silver}Statistika ir atspējota. Nepieciešami vismaz {gold}{0} {silver}spēlētāji." 49 | } 50 | 51 | -------------------------------------------------------------------------------- /modules/statistics/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.stats.title": "+ Statystyki Gracza +", 3 | "k4.weaponstats.title": "+ Statystyki Broni +", 4 | "k4.stats.weapon-disabled": "Statystyki broni są wyłączone.", 5 | "k4.stats.map-disabled": "Statystyki mapy są wyłączone.", 6 | "k4.stats.kills": "Zabicia", 7 | "k4.stats.firstblood": "Pierwsza Krew", 8 | "k4.stats.deaths": "Śmierci", 9 | "k4.stats.assists": "Asysty", 10 | "k4.stats.shoots": "Oddane Strzały", 11 | "k4.stats.hitstaken": "Otrzymane Trafienia", 12 | "k4.stats.hitsgiven": "Zadane Trafienia", 13 | "k4.stats.headshots": "Headshoty", 14 | "k4.stats.headhits": "Trafienia w Głowę", 15 | "k4.stats.chesthits": "Trafienia w Klatkę Piersiową", 16 | "k4.stats.stomachhits": "Trafienia w Brzuch", 17 | "k4.stats.leftarmhits": "Trafienia w Lewą Rękę", 18 | "k4.stats.rightarmhits": "Trafienia w Prawą Rękę", 19 | "k4.stats.leftleghits": "Trafienia w Lewą Nogę", 20 | "k4.stats.rightleghits": "Trafienia w Prawą Nogę", 21 | "k4.stats.neckhits": "Trafienia w Szyję", 22 | "k4.stats.gearhits": "Trafienia w Wyposażenie", 23 | "k4.stats.grenades": "Rzucone Granaty", 24 | "k4.stats.mvp": "Nagrody MVP", 25 | "k4.stats.roundwin": "Wygrane Rundy", 26 | "k4.stats.roundlose": "Przegrane Rundy", 27 | "k4.stats.gamewin": "Wygrane Mecze", 28 | "k4.stats.gamelose": "Przegrane Mecze", 29 | "k4.stats.roundsoverall": "Łączna Liczba Rozegranych Rund", 30 | "k4.stats.roundsct": "Rozegrane Rundy jako CT", 31 | "k4.stats.roundst": "Rozegrane Rundy jako T", 32 | "k4.stats.bombplanted": "Podłożone Bomby", 33 | "k4.stats.bombdefused": "Rozbrojone Bomby", 34 | "k4.stats.hostagerescued": "Uratowani Zakładnicy", 35 | "k4.stats.hostagekilled": "Zabici Zakładnicy", 36 | "k4.stats.noscopekill": "Zabójstwa bez Celownika", 37 | "k4.stats.penetratedkill": "Zabójstwa przez Przeszkody", 38 | "k4.stats.thrusmokekill": "Zabójstwa przez Dym", 39 | "k4.stats.flashedkill": "Zabójstwa oślepionych wrogów", 40 | "k4.stats.dominatedkill": "Zabójstwa Dominacji", 41 | "k4.stats.revengekill": "Zabójstwa za Zemstę", 42 | "k4.stats.assistflash": "Asysty poprzez Oślepienie", 43 | "k4.stats.accuracy": "Celność", 44 | "k4.stats.kpr": "Zabicia na Rundę (KPR)", 45 | "k4.stats.kda": "KDA", 46 | "k4.stats.kd": "Zabicia/Śmierci (K/D)", 47 | "k4.stats.no_stats": "Brak dostępnych statystyk.", 48 | "k4.stats.stats_disabled": "{silver}Statystyki są wyłączone. Wymagana minimalna liczba graczy: {gold}{0} {silver}." 49 | } 50 | -------------------------------------------------------------------------------- /modules/zenith-bans/Models.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Entities; 4 | using MySqlConnector; 5 | 6 | namespace Zenith_Bans 7 | { 8 | public sealed partial class Plugin : BasePlugin 9 | { 10 | public class PlayerDataRaw 11 | { 12 | public int Id { get; set; } 13 | public ulong SteamId { get; set; } 14 | public string Name { get; set; } = ""; 15 | public string IpAddresses { get; set; } = ""; 16 | public DateTime LastOnline { get; set; } 17 | public string GroupsString { get; set; } = ""; 18 | public string PermissionsString { get; set; } = ""; 19 | public int? Immunity { get; set; } 20 | public MySqlDateTime? RankExpiry { get; set; } 21 | public string? GroupPermissionsString { get; set; } 22 | public string? OverridesString { get; set; } 23 | } 24 | 25 | public class PlayerData 26 | { 27 | public ulong SteamId { get; set; } 28 | public string Name { get; set; } = ""; 29 | public string IpAddress { get; set; } = ""; 30 | public List Groups { get; set; } = []; 31 | public List Permissions { get; set; } = []; 32 | public int? Immunity { get; set; } 33 | public MySqlDateTime? RankExpiry { get; set; } 34 | public List Punishments { get; set; } = []; 35 | public Dictionary Overrides { get; set; } = []; 36 | } 37 | 38 | public class Punishment 39 | { 40 | public int Id { get; set; } 41 | public PunishmentType Type { get; set; } 42 | public int? Duration { get; set; } 43 | public MySqlDateTime? ExpiresAt { get; set; } = null; 44 | public string PunisherName { get; set; } = "Console"; 45 | public ulong? AdminSteamId { get; set; } 46 | public string Reason { get; set; } = ""; 47 | } 48 | 49 | public enum PunishmentType 50 | { 51 | Mute, 52 | Gag, 53 | Silence, 54 | Ban, 55 | Warn, 56 | Kick, 57 | SilentKick 58 | } 59 | 60 | public enum TargetFailureReason 61 | { 62 | TargetNotFound, 63 | TargetImmunity, 64 | TargetSelf 65 | } 66 | 67 | public class DisconnectedPlayer 68 | { 69 | public ulong SteamId { get; set; } 70 | public required string PlayerName { get; set; } 71 | public DateTime DisconnectedAt { get; set; } 72 | } 73 | 74 | private class PlayerInfo 75 | { 76 | public required ulong SteamID { get; set; } 77 | public required string PlayerName { get; set; } 78 | } 79 | 80 | private class AdminGroupInfo 81 | { 82 | [JsonPropertyName("flags")] 83 | public List Flags { get; set; } = []; 84 | 85 | [JsonPropertyName("immunity")] 86 | public int Immunity { get; set; } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/K4-Zenith.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | None 5 | false 6 | ./bin/K4-Zenith/plugins/K4-Zenith/ 7 | 8 | 9 | true 10 | net8.0 11 | enable 12 | enable 13 | 14 | 15 | 16 | none 17 | runtime 18 | compile; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | $(ProjectDir)\..\src-api\bin\K4-ZenithAPI\K4-ZenithAPI.dll 31 | false 32 | 33 | 34 | 35 | 36 | /Users/sples/Projects/CS2_Random/Menu-main/src/bin/KitsuneMenu/shared/KitsuneMenu/KitsuneMenu.dll 37 | false 38 | 39 | 40 | 41 | 42 | $(ProjectDir)/../external-dlls/K4-ArenaSharedApi.dll 43 | true 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/Models/Database/Model.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using Microsoft.Extensions.Logging; 3 | using MySqlConnector; 4 | 5 | namespace Zenith.Models; 6 | 7 | public partial class Database(Plugin plugin) 8 | { 9 | public MySqlConnection CreateConnection() 10 | { 11 | MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder 12 | { 13 | Server = plugin.GetCoreConfig("Database", "Hostname"), 14 | UserID = plugin.GetCoreConfig("Database", "Username"), 15 | Password = plugin.GetCoreConfig("Database", "Password"), 16 | Database = plugin.GetCoreConfig("Database", "Database"), 17 | Port = plugin.GetCoreConfig("Database", "Port"), 18 | SslMode = Enum.TryParse(plugin.GetCoreConfig("Database", "Sslmode"), true, out MySqlSslMode sslMode) ? sslMode : MySqlSslMode.Preferred, 19 | AllowZeroDateTime = true, 20 | ConvertZeroDateTime = true, 21 | TreatTinyAsBoolean = true, 22 | OldGuids = true, 23 | CharacterSet = "utf8mb4" 24 | }; 25 | 26 | return new MySqlConnection(builder.ToString()); 27 | } 28 | 29 | public bool TestConnection() 30 | { 31 | try 32 | { 33 | using var connection = CreateConnection(); 34 | connection.Open(); 35 | return true; 36 | } 37 | catch 38 | { 39 | return false; 40 | } 41 | } 42 | 43 | public async Task PurgeOldData() 44 | { 45 | if (TablePurgeDays <= 0) return; 46 | 47 | using var connection = CreateConnection(); 48 | await connection.OpenAsync(); 49 | 50 | var tables = new[] { Player.TABLE_PLAYER_SETTINGS, Player.TABLE_PLAYER_STORAGE }; 51 | 52 | foreach (var table in tables) 53 | { 54 | var query = $@" 55 | DELETE FROM `{table}` 56 | WHERE `last_online` < DATE_SUB(NOW(), INTERVAL @PurgeDays DAY);"; 57 | 58 | var affectedRows = await connection.ExecuteAsync(query, new { PurgeDays = TablePurgeDays }); 59 | 60 | if (affectedRows > 0) 61 | plugin.Logger.LogInformation($"Purged {affectedRows} rows older than {TablePurgeDays} days from {table} table."); 62 | } 63 | } 64 | 65 | public void ScheduleMidnightPurge() 66 | { 67 | DateTime now = DateTime.Now; 68 | TimeSpan timeUntilMidnight = now.Date.AddDays(1) - now; 69 | 70 | plugin.AddTimer((float)timeUntilMidnight.TotalSeconds, () => 71 | { 72 | Task.Run(async () => 73 | { 74 | try 75 | { 76 | await PurgeOldData(); 77 | } 78 | catch (Exception ex) 79 | { 80 | plugin.Logger.LogError($"Midnight data purge failed: {ex.Message}"); 81 | } 82 | finally 83 | { 84 | ScheduleMidnightPurge(); 85 | } 86 | }); 87 | }); 88 | } 89 | 90 | public int TablePurgeDays => plugin.GetCoreConfig("Database", "TablePurgeDays"); 91 | public string GetConnectionString() => CreateConnection().ConnectionString; 92 | } -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Bans/1.1_Zenith_Bans_StatusTypeRanks.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202410194)] 6 | public class Bans_CreateZenithBansTablesUpdate : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_bans_players").Exists()) 11 | { 12 | if (!Schema.Table("zenith_bans_players").Column("ip_addresses").Exists()) 13 | Alter.Table("zenith_bans_players").AddColumn("ip_addresses").AsCustom("JSON").Nullable(); 14 | } 15 | 16 | if (Schema.Table("zenith_bans_player_ranks").Exists()) 17 | { 18 | if (!Schema.Table("zenith_bans_player_ranks").Column("groups").Exists()) 19 | Alter.Table("zenith_bans_player_ranks").AddColumn("groups").AsCustom("JSON").Nullable(); 20 | 21 | if (!Schema.Table("zenith_bans_player_ranks").Column("permissions").Exists()) 22 | Alter.Table("zenith_bans_player_ranks").AddColumn("permissions").AsCustom("JSON").Nullable(); 23 | } 24 | 25 | if (Schema.Table("zenith_bans_admin_groups").Exists()) 26 | { 27 | if (!Schema.Table("zenith_bans_admin_groups").Column("permissions").Exists()) 28 | Alter.Table("zenith_bans_admin_groups").AddColumn("permissions").AsCustom("JSON").Nullable(); 29 | } 30 | 31 | if (Schema.Table("zenith_bans_punishments").Exists()) 32 | { 33 | if (!Schema.Table("zenith_bans_punishments").Column("status").Exists()) 34 | Alter.Table("zenith_bans_punishments").AddColumn("status").AsCustom("ENUM('active', 'expired', 'removed', 'removed_console')").NotNullable().WithDefaultValue("active"); 35 | 36 | if (!Schema.Table("zenith_bans_punishments").Column("type").Exists()) 37 | Alter.Table("zenith_bans_punishments").AddColumn("type").AsCustom("ENUM('mute', 'gag', 'silence', 'ban', 'warn', 'kick')").Nullable(); 38 | } 39 | } 40 | 41 | public override void Down() 42 | { 43 | if (Schema.Table("zenith_bans_players").Column("ip_addresses").Exists()) 44 | Delete.Column("ip_addresses").FromTable("zenith_bans_players"); 45 | 46 | if (Schema.Table("zenith_bans_player_ranks").Column("groups").Exists()) 47 | Delete.Column("groups").FromTable("zenith_bans_player_ranks"); 48 | 49 | if (Schema.Table("zenith_bans_player_ranks").Column("permissions").Exists()) 50 | Delete.Column("permissions").FromTable("zenith_bans_player_ranks"); 51 | 52 | if (Schema.Table("zenith_bans_admin_groups").Column("permissions").Exists()) 53 | Delete.Column("permissions").FromTable("zenith_bans_admin_groups"); 54 | 55 | if (Schema.Table("zenith_bans_punishments").Column("status").Exists()) 56 | Delete.Column("status").FromTable("zenith_bans_punishments"); 57 | 58 | if (Schema.Table("zenith_bans_punishments").Column("type").Exists()) 59 | Delete.Column("type").FromTable("zenith_bans_punishments"); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /modules/extended-commands/K4-Zenith-ExtendedCommands.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Core.Attributes; 4 | using CounterStrikeSharp.API.Core.Capabilities; 5 | using Microsoft.Extensions.Logging; 6 | using ZenithAPI; 7 | 8 | namespace Zenith_ExtendedCommands; 9 | 10 | [MinimumApiVersion(250)] 11 | public sealed partial class Plugin : BasePlugin 12 | { 13 | private const string MODULE_ID = "ExtendedCommands"; 14 | 15 | public override string ModuleName => $"K4-Zenith | {MODULE_ID}"; 16 | public override string ModuleAuthor => "K4ryuu @ KitsuneLab"; 17 | public override string ModuleVersion => "1.0.8"; 18 | 19 | private IModuleConfigAccessor _coreAccessor = null!; 20 | 21 | private PluginCapability? _moduleServicesCapability; 22 | 23 | private IZenithEvents? _zenithEvents; 24 | private IModuleServices? _moduleServices; 25 | 26 | public override void OnAllPluginsLoaded(bool hotReload) 27 | { 28 | string pluginDirectory = Path.GetDirectoryName(ModuleDirectory)!; 29 | List blockPlugins = ["CS2-SimpleAdmin"]; 30 | foreach (var p in blockPlugins) 31 | { 32 | if (Directory.GetDirectories(pluginDirectory, p).Any()) 33 | { 34 | Logger.LogCritical($"This module is not compatible with {p}. You can use only one of them. Unloading..."); 35 | Server.ExecuteCommand($"css_plugins unload {Path.GetFileNameWithoutExtension(ModulePath)}"); 36 | return; 37 | } 38 | } 39 | 40 | try 41 | { 42 | _moduleServicesCapability = new("zenith:module-services"); 43 | } 44 | catch (Exception ex) 45 | { 46 | Logger.LogError($"Failed to initialize Zenith API: {ex.Message}"); 47 | Logger.LogInformation("Please check if Zenith is installed, configured and loaded correctly."); 48 | 49 | Server.ExecuteCommand($"css_plugins unload {Path.GetFileNameWithoutExtension(ModulePath)}"); 50 | return; 51 | } 52 | 53 | _moduleServices = _moduleServicesCapability.Get(); 54 | if (_moduleServices == null) 55 | { 56 | Logger.LogError("Failed to get Module-Services API for Zenith."); 57 | Server.ExecuteCommand($"css_plugins unload {Path.GetFileNameWithoutExtension(ModulePath)}"); 58 | return; 59 | } 60 | 61 | _coreAccessor = _moduleServices.GetModuleConfigAccessor(); 62 | 63 | _zenithEvents = _moduleServices.GetEventHandler(); 64 | if (_zenithEvents != null) 65 | { 66 | _zenithEvents.OnZenithCoreUnload += OnZenithCoreUnload; 67 | } 68 | else 69 | { 70 | Logger.LogError("Failed to get Zenith event handler."); 71 | } 72 | 73 | Initialize_Commands(); 74 | Initialize_Events(); 75 | 76 | Logger.LogInformation("Zenith {0} module successfully registered.", MODULE_ID); 77 | } 78 | 79 | private void OnZenithCoreUnload(bool hotReload) 80 | { 81 | if (hotReload) 82 | { 83 | AddTimer(3.0f, () => 84 | { 85 | try { File.SetLastWriteTime(Path.Combine(ModulePath), DateTime.Now); } 86 | catch (Exception ex) { Logger.LogError($"Failed to update file: {ex.Message}"); } 87 | }); 88 | } 89 | } 90 | 91 | public override void Unload(bool hotReload) 92 | { 93 | _moduleServicesCapability?.Get()?.DisposeModule(this.GetType().Assembly); 94 | } 95 | } -------------------------------------------------------------------------------- /modules/extended-commands/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.general.console": "KONSOLA", 3 | "k4.general.admin": "ADMIN", 4 | 5 | "commands.error.invalid_health": "{lightred}Nieprawidłowa wartość zdrowia.", 6 | "commands.error.invalid_armor": "{lightred}Nieprawidłowa wartość pancerza.", 7 | "commands.error.invalid_coordinates": "{lightred}Nieprawidłowe współrzędne.", 8 | "commands.error.invalid_weapon": "{lightred}Nieprawidłowa broń.", 9 | "commands.error.invalid_speed": "{lightred}Nieprawidłowa wartość prędkości.", 10 | "commands.error.invalid_damage": "{lightred}Nieprawidłowa wartość obrażeń.", 11 | "commands.error.invalid_time": "{lightred}Nieprawidłowa wartość czasu.", 12 | "commands.error.invalid_immunity": "{lightred}Nie możesz wybrać tego gracza jako cel.", 13 | "commands.error.invalid_target": "{lightred}Cel nie został znaleziony.", 14 | "commands.error.invalid_dead": "{lightred}Cel musi być żywy.", 15 | "commands.error.invalid_team": "{lightred}Nieprawidłowa drużyna.", 16 | "commands.error.invalid_convar": "{lightred}Podany ConVar jest nieprawidłowy.", 17 | 18 | "commands.hp.success": "{gold}{0} {silver}ustawił zdrowie {gold}{1}{silver} na {gold}{2}{silver}.", 19 | "commands.armor.success": "{gold}{0} {silver}ustawił pancerz {gold}{1}{silver} na {gold}{2}{silver}.", 20 | "commands.freeze.success": "{gold}{0} {silver}zamroził {gold}{1}{silver}.", 21 | "commands.unfreeze.success": "{gold}{0} {silver}odmroził {gold}{1}{silver}.", 22 | "commands.noclip.enable": "{gold}{0} {silver}włączył noclip dla siebie.", 23 | "commands.noclip.disable": "{gold}{0} {silver}wyłączył noclip dla siebie.", 24 | "commands.slay.success": "{gold}{0} {silver}zabił {gold}{1}{silver}.", 25 | "commands.rename.success": "{gold}{0} {silver}zmienił nazwę {gold}{1} {silver}na {gold}{2}{silver}.", 26 | "commands.respawn.success": "{gold}{0} {silver}odrodził {gold}{1}{silver}.", 27 | "commands.strip.success": "{gold}{0} {silver}odebrał wszystkie bronie {gold}{1}{silver}.", 28 | "commands.tppos.success": "{gold}{0} {silver}teleportował {gold}{1} {silver}na współrzędne ({gold}{2}, {3}, {4}{silver}).", 29 | "commands.tp.success": "{gold}{0} {silver}teleportował {gold}{1} {silver}do {gold}{2}{silver}.", 30 | "commands.rcon.success": "{gold}{0} {silver}wykonał polecenie RCON: {gold}{1}", 31 | "commands.give.success": "{gold}{0} {silver}dał {gold}{1} {silver}broń: {gold}{2}", 32 | "commands.cvar.success": "{gold}{0} {silver}ustawił ConVar {gold}{1} {silver}na {gold}{2}", 33 | "commands.speed.success": "{gold}{0} {silver}ustawił prędkość {gold}{1}{silver} na {gold}{2}", 34 | "commands.revive.success": "{gold}{0} {silver}wskrzesił {gold}{1} {silver}w miejscu ich śmierci.", 35 | "commands.bury.success": "{gold}{0} {silver}pochował {gold}{1}{silver}.", 36 | "commands.unbury.success": "{gold}{0} {silver}odkopał {gold}{1}{silver}.", 37 | "commands.slap.success": "{gold}{0} {silver}uderzył {gold}{1} {silver}z siłą {gold}{2} {silver}obrażeń.", 38 | "commands.blind.success": "{gold}{0} {silver}oślepił {gold}{1} {silver}na {gold}{2} {silver}sekund.", 39 | "commands.unblind.success": "{gold}{0} {silver}przywrócił wzrok {gold}{1}{silver}.", 40 | "commands.god.enable": "{gold}{0} {silver}włączył tryb boga dla {gold}{1}{silver}.", 41 | "commands.god.disable": "{gold}{0} {silver}wyłączył tryb boga dla {gold}{1}{silver}.", 42 | "commands.team.success": "{gold}{0} {silver}ustawił drużynę {gold}{1}{silver} na {gold}{2}{silver}.", 43 | "commands.swap.success": "{gold}{0} {silver}zamienił drużyny {gold}{1}{silver}.", 44 | "commands.hide.success": "{gold}Jesteś teraz ukryty." 45 | } -------------------------------------------------------------------------------- /modules/ranks/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.phrases.gain": "{silver}Punkty: {green}{0} [+{1} za {2}]", 3 | "k4.phrases.loss": "{silver}Punkty: {red}{0} [-{1} za {2}]", 4 | "k4.phrases.kill-extended": "zabicie {0} ({1})", 5 | "k4.phrases.death-extended": "zginął od {0} ({1})", 6 | "k4.phrases.assist-extended": "pomoc w zabiciu {0} ({1})", 7 | "k4.phrases.round-summary-earn": "{silver}Zarobiłeś {green}{0} punktów{silver} w tej rundzie.", 8 | "k4.phrases.round-summary-lose": "{silver}Straciłeś {red}{0} punktów{silver} w tej rundzie.", 9 | "k4.phrases.points_disabled": "{silver}Punkty są wyłączone. Wymagana minimalna liczba graczy: {gold}{0} {silver}.", 10 | "k4.phrases.rankup": "Gratulacje!", 11 | "k4.phrases.rankdown": "Ranga obniżona", 12 | "k4.phrases.newrank": "Twoja nowa ranga to: {0}", 13 | "k4.phrases.rank.none": "Brak rangi", 14 | 15 | "k4.events.roundmvp": "za bycie MVP", 16 | "k4.events.hostagerescued": "za uratowanie zakładnika", 17 | "k4.events.bombdefused": "za rozbrojenie bomby", 18 | "k4.events.bombplanted": "za podłożenie bomby", 19 | "k4.events.playerdeath": "za śmierć", 20 | "k4.events.hostagekilled": "za zabicie zakładnika", 21 | "k4.events.hostagehurt": "za zranienie zakładnika", 22 | "k4.events.bombpickup": "za podniesienie bomby", 23 | "k4.events.bombdropped": "za upuszczenie bomby", 24 | "k4.events.bombexploded": "za wybuch bomby", 25 | "k4.events.hostagerescuedall": "za uratowanie wszystkich zakładników", 26 | "k4.events.roundend": "za zakończenie rundy", 27 | 28 | "k4.events.suicide": "za popełnienie samobójstwa", 29 | "k4.events.kill": "za zabicie wroga", 30 | "k4.events.teamkill": "za zabicie sojusznika", 31 | "k4.events.headshot": "za zabicie headshotem", 32 | "k4.events.penetrated": "za zabicie przez przeszkodę", 33 | "k4.events.noscope": "za zabicie bez celownika", 34 | "k4.events.thrusmoke": "za zabicie przez dym", 35 | "k4.events.blindkill": "za zabicie na ślepo", 36 | "k4.events.longdistance": "za zabicie na długi dystans", 37 | "k4.events.grenadekill": "za zabicie granatem", 38 | "k4.events.infernokill": "za zabicie ogniem", 39 | "k4.events.impactkill": "za zabicie obrażeniami od upadku", 40 | "k4.events.knifekill": "za zabicie nożem", 41 | "k4.events.taserkill": "za zabicie taserem", 42 | "k4.events.assist": "za pomoc w zabiciu", 43 | "k4.events.assistflash": "za pomoc poprzez oślepienie", 44 | "k4.events.teamkillassist": "za pomoc w zabiciu sojusznika", 45 | "k4.events.teamkillassistflash": "za pomoc w zabiciu sojusznika poprzez oślepienie", 46 | "k4.events.playtime": "za czas gry", 47 | 48 | "k4.events.killstreak2": "za podwójne zabicie", 49 | "k4.events.killstreak3": "za potrójne zabicie", 50 | "k4.events.killstreak4": "za dominację", 51 | "k4.events.killstreak5": "za szaleństwo", 52 | "k4.events.killstreak6": "za mega zabicie", 53 | "k4.events.killstreak7": "za własność", 54 | "k4.events.killstreak8": "za ultra zabicie", 55 | "k4.events.killstreak9": "za szał zabijania", 56 | "k4.events.killstreak10": "za zabicie potwora", 57 | "k4.events.killstreak11": "za bycie niepowstrzymanym", 58 | "k4.events.killstreak12": "za bycie boskim", 59 | 60 | "k4.events.roundwin": "za wygranie rundy", 61 | "k4.events.roundlose": "za przegranie rundy", 62 | 63 | "k4.ranks.info.title": "+ Informacje o Rangach +", 64 | "k4.ranks.info.current": "Obecna Ranga:", 65 | "k4.ranks.info.points": "Suma Punktów:", 66 | "k4.ranks.info.next": "Następna Ranga:", 67 | "k4.ranks.info.pointstonext": "Punkty do następnej rangi:", 68 | 69 | "settings.ShowRankChanges": "Pokazuj zmiany rang" 70 | } 71 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Color definitions 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[1;33m' 7 | BLUE='\033[0;34m' 8 | NC='\033[0m' # No Color 9 | 10 | # Function to print custom messages 11 | print_message() { 12 | local color=$1 13 | local message=$2 14 | echo -e "${color}[ INFO ] ${message}${NC}" 15 | } 16 | 17 | rm -rf ./Zenith 18 | 19 | # Normal deploy (copy and prepare DLLs) 20 | print_message "${BLUE}" "Creating directory structure..." 21 | mkdir -p ./Zenith/plugins/K4-Zenith \ 22 | ./Zenith/shared/K4-ZenithAPI \ 23 | ./Zenith/plugins/K4-Zenith-TimeStats \ 24 | ./Zenith/plugins/K4-Zenith-Ranks \ 25 | ./Zenith/plugins/K4-Zenith-Stats \ 26 | ./Zenith/plugins/K4-Zenith-Bans \ 27 | ./Zenith/plugins/K4-Zenith-ExtendedCommands \ 28 | ./Zenith/plugins/K4-Zenith-CustomTags \ 29 | ./Zenith/plugins/K4-Zenith-Toplists 30 | 31 | print_message "${BLUE}" "Running dotnet publish..." 32 | dotnet publish -f net8.0 -c Release 33 | exit_code=$? 34 | 35 | if [ $exit_code -ne 0 ]; then 36 | print_message "${RED}" "dotnet publish failed with exit code $exit_code" 37 | exit 1 38 | else 39 | print_message "${GREEN}" "dotnet publish completed successfully" 40 | fi 41 | 42 | # Copy main plugin folder to plugins folder, ignoring specific file names 43 | print_message "${YELLOW}" "Copying main plugin files..." 44 | rsync -a --quiet ./src/bin/K4-Zenith/plugins/K4-Zenith/ ./Zenith/plugins/K4-Zenith/ 45 | 46 | # Copy shared folder's dirs to shared folder 47 | print_message "${YELLOW}" "Copying shared files..." 48 | rsync -a --quiet ./src/bin/K4-Zenith/shared/ ./Zenith/shared/ 49 | rsync -a --quiet ./src-api/bin/K4-ZenithAPI/ ./Zenith/shared/K4-ZenithAPI/ 50 | 51 | # Copy modules to plugins folder 52 | print_message "${YELLOW}" "Copying TimeStats module..." 53 | rsync -a --quiet ./modules/time-stats/bin/K4-Zenith-TimeStats/ ./Zenith/plugins/K4-Zenith-TimeStats/ 54 | 55 | print_message "${YELLOW}" "Copying Ranks module..." 56 | rsync -a --quiet ./modules/ranks/bin/K4-Zenith-Ranks/ ./Zenith/plugins/K4-Zenith-Ranks/ 57 | 58 | print_message "${YELLOW}" "Copying Statistics module..." 59 | rsync -a --quiet ./modules/statistics/bin/K4-Zenith-Stats/ ./Zenith/plugins/K4-Zenith-Stats/ 60 | 61 | print_message "${YELLOW}" "Copying Admin module..." 62 | rsync -a --quiet ./modules/zenith-bans/bin/K4-Zenith-Bans/ ./Zenith/plugins/K4-Zenith-Bans/ 63 | 64 | print_message "${YELLOW}" "Copying Extended Commands module..." 65 | rsync -a --quiet ./modules/extended-commands/bin/K4-Zenith-ExtendedCommands/ ./Zenith/plugins/K4-Zenith-ExtendedCommands/ 66 | 67 | print_message "${YELLOW}" "Copying Custom Tags module..." 68 | rsync -a --quiet ./modules/custom-tags/bin/K4-Zenith-CustomTags/ ./Zenith/plugins/K4-Zenith-CustomTags/ 69 | 70 | print_message "${YELLOW}" "Copying Toplists module..." 71 | rsync -a --quiet ./modules/toplists/bin/K4-Zenith-Toplists/ ./Zenith/plugins/K4-Zenith-Toplists/ 72 | 73 | # Download latest GeoLite2-Country.mmdb 74 | print_message "${YELLOW}" "Downloading GeoLite2-Country.mmdb..." 75 | curl -sL https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb -o ./Zenith/plugins/K4-Zenith/GeoLite2-Country.mmdb 76 | 77 | # Delete unnecessary files 78 | print_message "${BLUE}" "Cleaning up unnecessary files..." 79 | find ./Zenith -type f \( -name "*.pdb" -o -name "*.yaml" -o -name ".DS_Store" \) -delete 2>/dev/null 80 | 81 | # Delete build directories 82 | print_message "${BLUE}" "Cleaning up build directories..." 83 | find ./src ./modules -type d -name "bin" -exec rm -rf {} + 84 | 85 | print_message "${GREEN}" "Deployment completed successfully!" 86 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Bans/1.6_Zenith_Bans_TableUTF8MB4.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202411182)] 6 | public class Bans_SetTablesUtf8mb4 : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (Schema.Table("zenith_bans_players").Exists()) 11 | Execute.Sql("ALTER TABLE zenith_bans_players CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 12 | 13 | if (Schema.Table("zenith_bans_player_ranks").Exists()) 14 | Execute.Sql("ALTER TABLE zenith_bans_player_ranks CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 15 | 16 | if (Schema.Table("zenith_bans_admin_groups").Exists()) 17 | Execute.Sql("ALTER TABLE zenith_bans_admin_groups CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 18 | 19 | if (Schema.Table("zenith_bans_punishments").Exists()) 20 | Execute.Sql("ALTER TABLE zenith_bans_punishments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 21 | 22 | if (Schema.Table("zenith_bans_ip_addresses").Exists()) 23 | Execute.Sql("ALTER TABLE zenith_bans_ip_addresses CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 24 | 25 | if (Schema.Table("zenith_bans_player_groups").Exists()) 26 | Execute.Sql("ALTER TABLE zenith_bans_player_groups CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 27 | 28 | if (Schema.Table("zenith_bans_player_permissions").Exists()) 29 | Execute.Sql("ALTER TABLE zenith_bans_player_permissions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 30 | 31 | if (Schema.Table("zenith_bans_admin_group_permissions").Exists()) 32 | Execute.Sql("ALTER TABLE zenith_bans_admin_group_permissions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 33 | 34 | if (Schema.Table("zenith_bans_player_overrides").Exists()) 35 | Execute.Sql("ALTER TABLE zenith_bans_player_overrides CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); 36 | } 37 | 38 | public override void Down() 39 | { 40 | if (Schema.Table("zenith_bans_players").Exists()) 41 | Execute.Sql("ALTER TABLE zenith_bans_players CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 42 | 43 | if (Schema.Table("zenith_bans_player_ranks").Exists()) 44 | Execute.Sql("ALTER TABLE zenith_bans_player_ranks CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 45 | 46 | if (Schema.Table("zenith_bans_admin_groups").Exists()) 47 | Execute.Sql("ALTER TABLE zenith_bans_admin_groups CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 48 | 49 | if (Schema.Table("zenith_bans_punishments").Exists()) 50 | Execute.Sql("ALTER TABLE zenith_bans_punishments CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 51 | 52 | if (Schema.Table("zenith_bans_ip_addresses").Exists()) 53 | Execute.Sql("ALTER TABLE zenith_bans_ip_addresses CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 54 | 55 | if (Schema.Table("zenith_bans_player_groups").Exists()) 56 | Execute.Sql("ALTER TABLE zenith_bans_player_groups CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 57 | 58 | if (Schema.Table("zenith_bans_player_permissions").Exists()) 59 | Execute.Sql("ALTER TABLE zenith_bans_player_permissions CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 60 | 61 | if (Schema.Table("zenith_bans_admin_group_permissions").Exists()) 62 | Execute.Sql("ALTER TABLE zenith_bans_admin_group_permissions CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 63 | 64 | if (Schema.Table("zenith_bans_player_overrides").Exists()) 65 | Execute.Sql("ALTER TABLE zenith_bans_player_overrides CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;"); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /modules/toplists/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "top.clantag": "TOP{0} |", 3 | 4 | "top.menu.main.title": "Toplists", 5 | 6 | "ranktop.invalid.count": "Invalid player count. Using default ({0}).", 7 | "ranktop.no.data": "No ranking data available.", 8 | "ranktop.player.entry.center": "#{0} {1}: {2} points", 9 | "ranktop.player.entry.chat": "#{0} {1}: {2} points", 10 | 11 | "timetop.invalid.count": "Invalid player count. Using default ({0}).", 12 | "timetop.category.menu.title": "Select Playtime Category", 13 | "timetop.category.TotalPlaytime": "Total Playtime", 14 | "timetop.category.TerroristPlaytime": "T Playtime", 15 | "timetop.category.CounterTerroristPlaytime": "CT Playtime", 16 | "timetop.category.SpectatorPlaytime": "Spectator Playtime", 17 | "timetop.category.AlivePlaytime": "Alive Playtime", 18 | "timetop.category.DeadPlaytime": "Dead Playtime", 19 | "timetop.no.data": "No playtime data available.", 20 | 21 | "top.menu.title": "Top {0} Players", 22 | 23 | "timetop.player.entry.center": "#{0} {1}: {2}", 24 | "timetop.player.entry.chat": "#{0} {1}: {2}", 25 | "timetop.time.format": "{0}d {1}h {2}m", 26 | 27 | "statstop.invalid.count": "Invalid player count. Using default ({0}).", 28 | "statstop.category.menu.title": "Select Statistics Category", 29 | "statstop.no.data": "No statistics data available.", 30 | "statstop.player.entry.center": "#{0} {1}: {2}", 31 | "statstop.player.entry.chat": "#{0} {1}: {2}", 32 | "statstop.category.ChestHits": "Chest Hits", 33 | "statstop.category.MVP": "MVPs", 34 | "statstop.category.HitsGiven": "Hits Given", 35 | "statstop.category.HostageKilled": "Hostages Killed", 36 | "statstop.category.FirstBlood": "First Bloods", 37 | "statstop.category.Assists": "Assists", 38 | "statstop.category.FlashedKill": "Flashed Kills", 39 | "statstop.category.RoundWin": "Rounds Won", 40 | "statstop.category.Shoots": "Shots Fired", 41 | "statstop.category.GameLose": "Games Lost", 42 | "statstop.category.RightLegHits": "Right Leg Hits", 43 | "statstop.category.NoScopeKill": "No Scope Kills", 44 | "statstop.category.RoundsT": "T Rounds Played", 45 | "statstop.category.BombDefused": "Bombs Defused", 46 | "statstop.category.UnusedHits": "Unused Hits", 47 | "statstop.category.Headshots": "Headshots", 48 | "statstop.category.HeadHits": "Head Hits", 49 | "statstop.category.HitsTaken": "Hits Taken", 50 | "statstop.category.HostageRescued": "Hostages Rescued", 51 | "statstop.category.RoundsOverall": "Total Rounds Played", 52 | "statstop.category.GameWin": "Games Won", 53 | "statstop.category.LeftArmHits": "Left Arm Hits", 54 | "statstop.category.RevengeKill": "Revenge Kills", 55 | "statstop.category.AssistFlash": "Flash Assists", 56 | "statstop.category.GearHits": "Gear Hits", 57 | "statstop.category.StomachHits": "Stomach Hits", 58 | "statstop.category.RoundsCT": "CT Rounds Played", 59 | "statstop.category.RoundLose": "Rounds Lost", 60 | "statstop.category.RightArmHits": "Right Arm Hits", 61 | "statstop.category.BombPlanted": "Bombs Planted", 62 | "statstop.category.Kills": "Kills", 63 | "statstop.category.NeckHits": "Neck Hits", 64 | "statstop.category.ThruSmokeKill": "Through Smoke Kills", 65 | "statstop.category.DominatedKill": "Dominated Kills", 66 | "statstop.category.LeftLegHits": "Left Leg Hits", 67 | "statstop.category.Grenades": "Grenades Used", 68 | "statstop.category.PenetratedKill": "Penetration Kills", 69 | "statstop.category.Deaths": "Deaths", 70 | "statstop.category.SpecialHits": "Special Hits", 71 | 72 | "top.menu.rank": "Rank Toplist", 73 | "top.menu.time": "Time Toplists", 74 | "top.menu.stats": "Statistic Toplists" 75 | } 76 | -------------------------------------------------------------------------------- /modules/toplists/lang/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "top.clantag": "TOP{0} |", 3 | 4 | "top.menu.main.title": "Top saraksti", 5 | 6 | "ranktop.invalid.count": "Nederīgs spēlētāju skaits. Tiek izmantots noklusējums ({0}).", 7 | "ranktop.no.data": "Nav pieejamu ranku dati.", 8 | "ranktop.player.entry.center": "#{0} {1}: {2} punkti", 9 | "ranktop.player.entry.chat": "#{0} {1}: {2} punkti", 10 | 11 | "timetop.invalid.count": "Nederīgs spēlētāju skaits. Tiek izmantots noklusējums ({0}).", 12 | "timetop.category.menu.title": "Izvēlies pavādīta laika kategoriju", 13 | "timetop.category.TotalPlaytime": "Kopējais pavadītais laiks", 14 | "timetop.category.TerroristPlaytime": "T pavadītāis laiks", 15 | "timetop.category.CounterTerroristPlaytime": "CT pavadītais laiks laiks", 16 | "timetop.category.SpectatorPlaytime": "SPEC pavadītais laiks", 17 | "timetop.category.AlivePlaytime": "Laiks esot dzīvam", 18 | "timetop.category.DeadPlaytime": "Laiks esot mirušam", 19 | "timetop.no.data": "Nav pieejami pavadītā laika dati.", 20 | 21 | "top.menu.title": "Top {0} spēlētāji", 22 | 23 | "timetop.player.entry.center": "#{0} {1}: {2}", 24 | "timetop.player.entry.chat": "#{0} {1}: {2}", 25 | "timetop.time.format": "{0}d {1}h {2}m", 26 | 27 | "statstop.invalid.count": "Nederīgs spēlētāju skaits. Tiek izmantots noklusējums ({0}).", 28 | "statstop.category.menu.title": "Izvēlies statistikas kategoriju", 29 | "statstop.no.data": "Nav pieejamu statistikas datu.", 30 | "statstop.player.entry.center": "#{0} {1}: {2}", 31 | "statstop.player.entry.chat": "#{0} {1}: {2}", 32 | "statstop.category.HitsGiven": "Dotie hiti", 33 | "statstop.category.HostageKilled": "Hostu killi", 34 | "statstop.category.FirstBlood": "First Blood", 35 | "statstop.category.Assists": "Asisti", 36 | "statstop.category.FlashedKill": "killi ar flash", 37 | "statstop.category.RoundWin": "Uzvarētie raundi", 38 | "statstop.category.Shoots": "Izšautie šāvieni", 39 | "statstop.category.GameLose": "Zaudētās spēles", 40 | "statstop.category.RightLegHits": "Hiti labajā kājā", 41 | "statstop.category.NoScopeKill": "No Scope killi", 42 | "statstop.category.RoundsT": "T raundi", 43 | "statstop.category.BombDefused": "defusotas bumbas", 44 | "statstop.category.UnusedHits": "Neizmantotie hiti", 45 | "statstop.category.Headshots": "Headshoti", 46 | "statstop.category.HeadHits": "Head hiti", 47 | "statstop.category.HitsTaken": "Saņemtie hiti", 48 | "statstop.category.HostageRescued": "Izglābtie hosti", 49 | "statstop.category.RoundsOverall": "Kopējais raundu skaits", 50 | "statstop.category.GameWin": "Uzvarētās spēles", 51 | "statstop.category.LeftArmHits": "Hiti kreisajā rokā", 52 | "statstop.category.RevengeKill": "Revenge killi", 53 | "statstop.category.AssistFlash": "Flash asisti", 54 | "statstop.category.GearHits": "Hiti ekipējumā", 55 | "statstop.category.StomachHits": "Hiti vēderā", 56 | "statstop.category.RoundsCT": "CT raundi", 57 | "statstop.category.RoundLose": "Zaudētie raundi", 58 | "statstop.category.RightArmHits": "Hiti labajā rokā", 59 | "statstop.category.BombPlanted": "Uzstādītas bumbas", 60 | "statstop.category.Kills": "Killi", 61 | "statstop.category.NeckHits": "Hiti kaklā", 62 | "statstop.category.ThruSmokeKill": "Killi caur smoke", 63 | "statstop.category.DominatedKill": "Domination killi", 64 | "statstop.category.LeftLegHits": "Hiti kreisajā kājā", 65 | "statstop.category.Grenades": "Granātu stati", 66 | "statstop.category.PenetratedKill": "Killi cauršaujot", 67 | "statstop.category.Deaths": "Nāves", 68 | "statstop.category.SpecialHits": "Speciālie Hiti", 69 | 70 | "top.menu.rank": "Ranka Top", 71 | "top.menu.time": "Laika Topi", 72 | "top.menu.stats": "Statistikas Topi" 73 | } 74 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Bans/1.0_Zenith_Bans_Create.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202410193)] 6 | public class Bans_CreateZenithBansTables : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (!Schema.Table("zenith_bans_players").Exists()) 11 | { 12 | Create.Table("zenith_bans_players") 13 | .WithColumn("id").AsInt32().PrimaryKey().Identity() 14 | .WithColumn("steam_id").AsInt64().Unique() 15 | .WithColumn("name").AsString(64).Nullable() 16 | .WithColumn("ip_addresses").AsCustom("JSON").Nullable() 17 | .WithColumn("last_online").AsDateTime().Nullable(); 18 | 19 | Execute.Sql("ALTER TABLE zenith_bans_players CHARACTER SET = utf8mb4, COLLATE = utf8mb4_unicode_ci;"); 20 | } 21 | 22 | if (!Schema.Table("zenith_bans_player_ranks").Exists()) 23 | { 24 | Create.Table("zenith_bans_player_ranks") 25 | .WithColumn("id").AsInt32().PrimaryKey().Identity() 26 | .WithColumn("steam_id").AsInt64().ForeignKey("FK_player_ranks_steam_id", "zenith_bans_players", "steam_id") 27 | .WithColumn("server_ip").AsString(50).NotNullable() 28 | .WithColumn("groups").AsCustom("JSON").Nullable() 29 | .WithColumn("permissions").AsCustom("JSON").Nullable() 30 | .WithColumn("immunity").AsInt32().Nullable() 31 | .WithColumn("rank_expiry").AsDateTime().Nullable(); 32 | 33 | Create.UniqueConstraint("unique_player_server").OnTable("zenith_bans_player_ranks").Columns("steam_id", "server_ip"); 34 | Execute.Sql("ALTER TABLE zenith_bans_player_ranks CHARACTER SET = utf8mb4, COLLATE = utf8mb4_unicode_ci;"); 35 | } 36 | 37 | if (!Schema.Table("zenith_bans_admin_groups").Exists()) 38 | { 39 | Create.Table("zenith_bans_admin_groups") 40 | .WithColumn("id").AsInt32().PrimaryKey().Identity() 41 | .WithColumn("name").AsString(50).Unique() 42 | .WithColumn("permissions").AsCustom("JSON").Nullable() 43 | .WithColumn("immunity").AsInt32().Nullable(); 44 | 45 | Execute.Sql("ALTER TABLE zenith_bans_admin_groups CHARACTER SET = utf8mb4, COLLATE = utf8mb4_unicode_ci;"); 46 | } 47 | 48 | if (!Schema.Table("zenith_bans_punishments").Exists()) 49 | { 50 | Create.Table("zenith_bans_punishments") 51 | .WithColumn("id").AsInt32().PrimaryKey().Identity() 52 | .WithColumn("steam_id").AsInt64().ForeignKey("FK_punishments_steam_id", "zenith_bans_players", "steam_id") 53 | .WithColumn("type").AsCustom("ENUM('mute', 'gag', 'silence', 'ban', 'warn', 'kick')").Nullable() 54 | .WithColumn("duration").AsInt32().Nullable() 55 | .WithColumn("created_at").AsDateTime().Nullable() 56 | .WithColumn("expires_at").AsDateTime().Nullable() 57 | .WithColumn("admin_steam_id").AsInt64().Nullable().ForeignKey("FK_punishments_admin_steam_id", "zenith_bans_players", "steam_id") 58 | .WithColumn("removed_at").AsDateTime().Nullable() 59 | .WithColumn("remove_admin_steam_id").AsInt64().Nullable().ForeignKey("FK_punishments_remove_admin_steam_id", "zenith_bans_players", "steam_id") 60 | .WithColumn("server_ip").AsString(50).NotNullable().WithDefaultValue("all") 61 | .WithColumn("reason").AsCustom("TEXT").Nullable(); 62 | 63 | Execute.Sql("ALTER TABLE zenith_bans_punishments CHARACTER SET = utf8mb4, COLLATE = utf8mb4_unicode_ci;"); 64 | } 65 | } 66 | 67 | public override void Down() 68 | { 69 | if (Schema.Table("zenith_bans_punishments").Exists()) 70 | Delete.Table("zenith_bans_punishments"); 71 | 72 | if (Schema.Table("zenith_bans_admin_groups").Exists()) 73 | Delete.Table("zenith_bans_admin_groups"); 74 | 75 | if (Schema.Table("zenith_bans_player_ranks").Exists()) 76 | Delete.Table("zenith_bans_player_ranks"); 77 | 78 | if (Schema.Table("zenith_bans_players").Exists()) 79 | Delete.Table("zenith_bans_players"); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /modules/toplists/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "top.clantag": "TOP{0} |", 3 | 4 | "top.menu.main.title": "Toplisty", 5 | 6 | "ranktop.invalid.count": "Nieprawidłowa liczba graczy. Używanie domyślnej ({0}).", 7 | "ranktop.no.data": "Brak danych o rankingu.", 8 | "ranktop.player.entry.center": "#{0} {1}: {2} punktów", 9 | "ranktop.player.entry.chat": "#{0} {1}: {2} punktów", 10 | 11 | "timetop.invalid.count": "Nieprawidłowa liczba graczy. Używanie domyślnej ({0}).", 12 | "timetop.category.menu.title": "Wybierz kategorię czasu gry", 13 | "timetop.category.TotalPlaytime": "Całkowity czas gry", 14 | "timetop.category.TerroristPlaytime": "Czas gry jako T", 15 | "timetop.category.CounterTerroristPlaytime": "Czas gry jako CT", 16 | "timetop.category.SpectatorPlaytime": "Czas gry jako obserwator", 17 | "timetop.category.AlivePlaytime": "Czas gry jako żywy", 18 | "timetop.category.DeadPlaytime": "Czas gry jako nieżywy", 19 | "timetop.no.data": "Brak danych o czasie gry.", 20 | 21 | "top.menu.title": "Top {0} graczy", 22 | 23 | "timetop.player.entry.center": "#{0} {1}: {2}", 24 | "timetop.player.entry.chat": "#{0} {1}: {2}", 25 | "timetop.time.format": "{0}d {1}h {2}m", 26 | 27 | "statstop.invalid.count": "Nieprawidłowa liczba graczy. Używanie domyślnej ({0}).", 28 | "statstop.category.menu.title": "Wybierz kategorię statystyk", 29 | "statstop.no.data": "Brak danych o statystykach.", 30 | "statstop.player.entry.center": "#{0} {1}: {2}", 31 | "statstop.player.entry.chat": "#{0} {1}: {2}", 32 | "statstop.category.ChestHits": "Strzały w klatkę piersiową", 33 | "statstop.category.MVP": "MVP", 34 | "statstop.category.HitsGiven": "Oddane strzały", 35 | "statstop.category.HostageKilled": "Zabici zakładnicy", 36 | "statstop.category.FirstBlood": "Pierwsza krew", 37 | "statstop.category.Assists": "Asysty", 38 | "statstop.category.FlashedKill": "Zabójstwa po oślepieniu", 39 | "statstop.category.RoundWin": "Wygrane rundy", 40 | "statstop.category.Shoots": "Oddane strzały", 41 | "statstop.category.GameLose": "Przegrane gry", 42 | "statstop.category.RightLegHits": "Strzały w prawą nogę", 43 | "statstop.category.NoScopeKill": "Zabójstwa bez celownika", 44 | "statstop.category.RoundsT": "Rundy T", 45 | "statstop.category.BombDefused": "Rozbrojone bomby", 46 | "statstop.category.UnusedHits": "Niezużyte strzały", 47 | "statstop.category.Headshots": "Strzały w głowę", 48 | "statstop.category.HeadHits": "Oddane strzały w głowę", 49 | "statstop.category.HitsTaken": "Zatrzymane strzały", 50 | "statstop.category.HostageRescued": "Uratowani zakładnicy", 51 | "statstop.category.RoundsOverall": "Całkowita liczba rund", 52 | "statstop.category.GameWin": "Wygrane gry", 53 | "statstop.category.LeftArmHits": "Strzały w lewą rękę", 54 | "statstop.category.RevengeKill": "Zabójstwa zemsty", 55 | "statstop.category.AssistFlash": "Asysty oślepiające", 56 | "statstop.category.GearHits": "Strzały w wyposażenie", 57 | "statstop.category.StomachHits": "Strzały w brzuch", 58 | "statstop.category.RoundsCT": "Rundy CT", 59 | "statstop.category.RoundLose": "Przegrane rundy", 60 | "statstop.category.RightArmHits": "Strzały w prawą rękę", 61 | "statstop.category.BombPlanted": "Podłożone bomby", 62 | "statstop.category.Kills": "Zabójstwa", 63 | "statstop.category.NeckHits": "Strzały w szyję", 64 | "statstop.category.ThruSmokeKill": "Zabójstwa przez dym", 65 | "statstop.category.DominatedKill": "Zabójstwa dominacyjne", 66 | "statstop.category.LeftLegHits": "Strzały w lewą nogę", 67 | "statstop.category.Grenades": "Rzuty granatami", 68 | "statstop.category.PenetratedKill": "Zabójstwa przez penetrację", 69 | "statstop.category.Deaths": "Śmierci", 70 | "statstop.category.SpecialHits": "Specjalne trafienia", 71 | 72 | "top.menu.rank": "Ranking Toplista", 73 | "top.menu.time": "Toplista Czasu", 74 | "top.menu.stats": "Toplista Statystyk" 75 | } 76 | -------------------------------------------------------------------------------- /src/Core/Events.cs: -------------------------------------------------------------------------------- 1 | namespace Zenith 2 | { 3 | using CounterStrikeSharp.API; 4 | using CounterStrikeSharp.API.Core; 5 | using CounterStrikeSharp.API.Core.Attributes.Registration; 6 | using CounterStrikeSharp.API.Core.Translations; 7 | using CounterStrikeSharp.API.Modules.UserMessages; 8 | using CounterStrikeSharp.API.Modules.Utils; 9 | using Zenith.Models; 10 | 11 | public sealed partial class Plugin : BasePlugin 12 | { 13 | public void Initialize_Events() 14 | { 15 | HookUserMessage(118, OnMessage, HookMode.Pre); 16 | } 17 | 18 | public HookResult OnMessage(UserMessage um) 19 | { 20 | int entity = um.ReadInt("entityindex"); 21 | Player? player = Player.Find(Utilities.GetPlayerFromIndex(entity)); 22 | if (player == null || !player.IsValid || player.Controller is null) 23 | return HookResult.Continue; 24 | 25 | if (player.IsGagged) 26 | return HookResult.Stop; 27 | 28 | if (!GetCoreConfig("Core", "HookChatMessages")) 29 | return HookResult.Continue; 30 | 31 | bool enabledChatModifier = player.GetSetting("ShowChatTags"); 32 | 33 | string dead = player.IsAlive ? string.Empty : Localizer.ForPlayer(player.Controller, "k4.tag.dead"); 34 | string team = um.ReadString("messagename").Contains("All") ? Localizer.ForPlayer(player.Controller, "k4.tag.all") : TeamLocalizer(player.Controller); 35 | string tag = enabledChatModifier ? player.GetNameTag() : string.Empty; 36 | 37 | char namecolor = enabledChatModifier ? player.GetNameColor() : ChatColors.ForTeam(player.Controller!.Team); 38 | char chatcolor = enabledChatModifier ? player.GetChatColor() : ChatColors.Default; 39 | 40 | string message = um.ReadString("param2"); 41 | 42 | string formattedMessage = FormatMessage(player.Controller!, $" {dead}{team}{tag}{namecolor}{um.ReadString("param1")}{RemoveLeadingSpaceBeforeColorCode(Localizer.ForPlayer(player.Controller, "k4.tag.separator"))}{chatcolor}{message}"); 43 | 44 | um.SetString("messagename", formattedMessage); 45 | 46 | _moduleServices?.InvokteZenithChatMessage(player.Controller!, message, formattedMessage); 47 | 48 | return HookResult.Changed; 49 | } 50 | 51 | private static string FormatMessage(CCSPlayerController player, string message) 52 | { 53 | return StringExtensions.ReplaceColorTags(message) 54 | .Replace("{team}", ChatColors.ForPlayer(player).ToString()); 55 | } 56 | 57 | private string TeamLocalizer(CCSPlayerController player) 58 | { 59 | return player.Team switch 60 | { 61 | CsTeam.Spectator => Localizer.ForPlayer(player, "k4.tag.team.spectator"), 62 | CsTeam.Terrorist => Localizer.ForPlayer(player, "k4.tag.team.t"), 63 | CsTeam.CounterTerrorist => Localizer.ForPlayer(player, "k4.tag.team.ct"), 64 | _ => Localizer.ForPlayer(player, "k4.tag.team.unassigned"), 65 | }; 66 | } 67 | 68 | [GameEventHandler] 69 | public HookResult OnPlayerActivate(EventPlayerActivate @event, GameEventInfo info) 70 | { 71 | CCSPlayerController? player = @event.Userid; 72 | if (player is null || !player.IsValid || player.IsHLTV || player.IsBot) 73 | return HookResult.Continue; 74 | 75 | _ = new Player(this, player); 76 | return HookResult.Continue; 77 | } 78 | 79 | [GameEventHandler(HookMode.Post)] 80 | public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info) 81 | { 82 | var player = Player.Find(@event.Userid); 83 | if (player == null) 84 | return HookResult.Continue; 85 | 86 | string joinFormat = GetCoreConfig("Modular", "LeaveMessage"); 87 | if (!string.IsNullOrEmpty(joinFormat)) 88 | _moduleServices?.PrintForAll(StringExtensions.ReplaceColorTags(ReplacePlaceholders(player.Controller, joinFormat)), false); 89 | 90 | player.Dispose(); 91 | 92 | return HookResult.Continue; 93 | } 94 | 95 | [GameEventHandler] 96 | public HookResult OnRoundEnd(EventRoundEnd @event, GameEventInfo info) 97 | { 98 | if (GetCoreConfig("Database", "SaveOnRoundEnd")) 99 | Task.Run(async () => await Player.SaveAllOnlinePlayerDataWithTransaction(this)); 100 | return HookResult.Continue; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /modules/ranks/lang/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.phrases.gain": "{silver}Punkti: {green}{0} [+{1} par {2}]", 3 | "k4.phrases.loss": "{silver}Punkti: {red}{0} [-{1} par {2}]", 4 | "k4.phrases.kill-extended": "nogalinot {0} ({1})", 5 | "k4.phrases.death-extended": "mirstot no {0} ({1})", 6 | "k4.phrases.assist-extended": "palīdzot nogalināt {0} ({1})", 7 | "k4.phrases.round-summary-earn": "{silver}Šajā raundā nopelnīji {green}{0} punktus{silver}.", 8 | "k4.phrases.round-summary-lose": "{silver}Šajā raundā zaudēji {red}{0} punktus{silver}.", 9 | "k4.phrases.points_disabled": "{silver}Punkti ir atspējoti. Nepieciešami vismaz {gold}{0} {silver}spēlētāji.", 10 | "k4.phrases.rankup": "Apsveicam!", 11 | "k4.phrases.rankdown": "Ranks samazināts", 12 | "k4.phrases.newrank": "Tavs jaunais ranks ir: {0}", 13 | "k4.phrases.rank.none": "Bez ranka", 14 | 15 | "k4.ranks.list.title": "Pieejamie ranki", 16 | "k4.ranks.points": "punkti", 17 | 18 | "k4.phrases.no-target": "{lightred}Target nav atrasts.", 19 | "k4.phrases.invalid-amount": "{lightred}Nederīgs daudzums.", 20 | "k4.phrases.cant-target": "{lightred}{0} šobrīd nav iespējams targetot.", 21 | 22 | "k4.phrases.points-given": "{gold}{0} {green}tev piešķīra {red}{1} {green}ranku punktus.", 23 | "k4.phrases.points-taken": "{gold}{0} {green}tev atņēma {red}{1} {green}ranka punktus.", 24 | "k4.phrases.points-reset": "{gold}{0} {lightred}resetoja tavus ranku punktus.", 25 | "k4.phrases.points-set": "{gold}{0} {green}iestatīja tavu ranku punktu skaitu uz {red}{1}{green}.", 26 | "k4.phrases.all-points-reset": "{lightred}Visi ranka punkti ir resetoti.", 27 | 28 | "k4.phrases.rank.title": "{lime}{0}{silver} ranks", 29 | "k4.phrases.rank.line1": "--- {silver}Tavs pašreizējais ranks: {0}{1} ({2} punkti)", 30 | "k4.phrases.rank.line2": "--- {silver}Nākamais ranks: {0}{1} (vajag vēl {2} punktus)", 31 | 32 | "k4.events.roundmvp": "MVP raundu", 33 | "k4.events.hostagerescued": "izglābtu hostu", 34 | "k4.events.bombdefused": "defusotu bumbu", 35 | "k4.events.bombplanted": "plantotu bumbu", 36 | "k4.events.playerdeath": "nāvi", 37 | "k4.events.hostagekilled": "nogalinātu hostu", 38 | "k4.events.hostagehurt": "ievainotu hostu", 39 | "k4.events.bombpickup": "paņemtu bumbu", 40 | "k4.events.bombdropped": "nomestu bumbu", 41 | "k4.events.bombexploded": "bumbas sprādzienu", 42 | "k4.events.hostagerescuedall": "visu hostu izglābšanu", 43 | "k4.events.roundend": "raunda beigām", 44 | 45 | "k4.events.suicide": "pašnāvību", 46 | "k4.events.kill": "enemy killu", 47 | "k4.events.teamkill": "teamkillu", 48 | "k4.events.headshot": "headshot killu", 49 | "k4.events.penetrated": "killu caur objektu", 50 | "k4.events.noscope": "no scope killu", 51 | "k4.events.thrusmoke": "killu caur smoke", 52 | "k4.events.blindkill": "full flash killu", 53 | "k4.events.longdistance": "killu lielā attālumā", 54 | "k4.events.grenadekill": "nade killu", 55 | "k4.events.infernokill": "killu ugunī", 56 | "k4.events.impactkill": "impact killu", 57 | "k4.events.knifekill": "knife killu", 58 | "k4.events.taserkill": "taser killu", 59 | "k4.events.assist": "asista killu", 60 | "k4.events.assistflash": "asistu ar flash", 61 | "k4.events.teamkillassist": "team asista killu", 62 | "k4.events.teamkillassistflash": "flash killa asistu", 63 | "k4.events.playtime": "nospēlēto laiku", 64 | 65 | "k4.events.killstreak2": "double kill", 66 | "k4.events.killstreak3": "triple kill", 67 | "k4.events.killstreak4": "domination", 68 | "k4.events.killstreak5": "rampage", 69 | "k4.events.killstreak6": "mega kill", 70 | "k4.events.killstreak7": "ownage", 71 | "k4.events.killstreak8": "ultra kill", 72 | "k4.events.killstreak9": "killing spree", 73 | "k4.events.killstreak10": "monster kill", 74 | "k4.events.killstreak11": "unstoppable", 75 | "k4.events.killstreak12": "godlike", 76 | 77 | "k4.events.roundwin": "par uzvarēto raundu", 78 | "k4.events.roundlose": "par zaudēto raundu", 79 | 80 | "k4.ranks.info.title": "+ Ranka Informācija +", 81 | "k4.ranks.info.current": "Pašreizējais ranks:", 82 | "k4.ranks.info.points": "Kopējie punkti:", 83 | "k4.ranks.info.next": "Nākamais Ranks:", 84 | "k4.ranks.info.pointstonext": "Punkti līdz nakošajam rankam:", 85 | 86 | "settings.ShowRankChanges": "Rādit ranku izmaiņas" 87 | } 88 | -------------------------------------------------------------------------------- /modules/ranks/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.phrases.gain": "{silver}Points: {green}{0} [+{1} for {2}]", 3 | "k4.phrases.loss": "{silver}Points: {red}{0} [-{1} for {2}]", 4 | "k4.phrases.kill-extended": "killing {0} ({1})", 5 | "k4.phrases.death-extended": "dying to {0} ({1})", 6 | "k4.phrases.assist-extended": "assisting to kill {0} ({1})", 7 | "k4.phrases.round-summary-earn": "{silver}You earned {green}{0} points{silver} this round.", 8 | "k4.phrases.round-summary-lose": "{silver}You lost {red}{0} points{silver} this round.", 9 | "k4.phrases.points_disabled": "{silver}Points are disabled. Minimum {gold}{0} {silver}players required.", 10 | "k4.phrases.rankup": "Congratulations!", 11 | "k4.phrases.rankdown": "Rank Decreased", 12 | "k4.phrases.newrank": "Your new rank is: {0}", 13 | "k4.phrases.rank.none": "Unranked", 14 | 15 | "k4.ranks.list.title": "Available Ranks", 16 | "k4.ranks.points": "points", 17 | 18 | "k4.phrases.no-target": "{lightred}Target not found.", 19 | "k4.phrases.invalid-amount": "{lightred}Invalid amount.", 20 | "k4.phrases.cant-target": "{lightred}{0} cannot be targeted currently.", 21 | 22 | "k4.phrases.points-given": "{gold}{0} {green}has given you {red}{1} {green}rank points.", 23 | "k4.phrases.points-taken": "{gold}{0} {green}has taken {red}{1} {green}rank points from you.", 24 | "k4.phrases.points-reset": "{gold}{0} {lightred}has reset your rank points.", 25 | "k4.phrases.points-set": "{gold}{0} {green}has set your rank points to {red}{1}{green}.", 26 | "k4.phrases.all-points-reset": "{lightred}All rank points have been reset.", 27 | 28 | "k4.phrases.rank.title": "{lime}{0}{silver}'s Rank", 29 | "k4.phrases.rank.line1": "--- {silver}You are currently ranked: {0}{1} ({2} points)", 30 | "k4.phrases.rank.line2": "--- {silver}Next rank: {0}{1} ({2} more points needed)", 31 | 32 | "k4.events.roundmvp": "being the MVP", 33 | "k4.events.hostagerescued": "rescuing a hostage", 34 | "k4.events.bombdefused": "defusing the bomb", 35 | "k4.events.bombplanted": "planting the bomb", 36 | "k4.events.playerdeath": "dying", 37 | "k4.events.hostagekilled": "killing a hostage", 38 | "k4.events.hostagehurt": "hurting a hostage", 39 | "k4.events.bombpickup": "picking up the bomb", 40 | "k4.events.bombdropped": "dropping the bomb", 41 | "k4.events.bombexploded": "bomb explosion", 42 | "k4.events.hostagerescuedall": "all hostages rescued", 43 | "k4.events.roundend": "round end", 44 | 45 | "k4.events.suicide": "committing suicide", 46 | "k4.events.kill": "killing an enemy", 47 | "k4.events.teamkill": "killing a teammate", 48 | "k4.events.headshot": "headshot kill", 49 | "k4.events.penetrated": "penetration kill", 50 | "k4.events.noscope": "no scope kill", 51 | "k4.events.thrusmoke": "kill through smoke", 52 | "k4.events.blindkill": "blind kill", 53 | "k4.events.longdistance": "long distance kill", 54 | "k4.events.grenadekill": "grenade kill", 55 | "k4.events.infernokill": "fire kill", 56 | "k4.events.impactkill": "impact kill", 57 | "k4.events.knifekill": "knife kill", 58 | "k4.events.taserkill": "taser kill", 59 | "k4.events.assist": "assisting a kill", 60 | "k4.events.assistflash": "flash assist", 61 | "k4.events.teamkillassist": "assisting a teamkill", 62 | "k4.events.teamkillassistflash": "flash assist a teamkill", 63 | "k4.events.playtime": "playtime", 64 | 65 | "k4.events.killstreak2": "double kill", 66 | "k4.events.killstreak3": "triple kill", 67 | "k4.events.killstreak4": "domination", 68 | "k4.events.killstreak5": "rampage", 69 | "k4.events.killstreak6": "mega kill", 70 | "k4.events.killstreak7": "ownage", 71 | "k4.events.killstreak8": "ultra kill", 72 | "k4.events.killstreak9": "killing spree", 73 | "k4.events.killstreak10": "monster kill", 74 | "k4.events.killstreak11": "being unstoppable", 75 | "k4.events.killstreak12": "being godlike", 76 | 77 | "k4.events.roundwin": "winning the round", 78 | "k4.events.roundlose": "losing the round", 79 | 80 | "k4.ranks.info.title": "+ Rank Information +", 81 | "k4.ranks.info.current": "Current Rank:", 82 | "k4.ranks.info.points": "Total Points:", 83 | "k4.ranks.info.next": "Next Rank:", 84 | "k4.ranks.info.pointstonext": "Points to Next Rank:", 85 | 86 | "settings.ShowRankChanges": "Show Rank Changes" 87 | } 88 | -------------------------------------------------------------------------------- /modules/extended-commands/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.general.console": "CONSOLE", 3 | "k4.general.admin": "ADMIN", 4 | 5 | "commands.error.invalid_health": "{lightred}Invalid health value.", 6 | "commands.error.invalid_armor": "{lightred}Invalid armor value.", 7 | "commands.error.invalid_coordinates": "{lightred}Invalid coordinates.", 8 | "commands.error.invalid_weapon": "{lightred}Invalid weapon.", 9 | "commands.error.invalid_speed": "{lightred}Invalid speed value.", 10 | "commands.error.invalid_damage": "{lightred}Invalid damage value.", 11 | "commands.error.invalid_time": "{lightred}Invalid time value.", 12 | "commands.error.invalid_immunity": "{lightred}You cannot target this player.", 13 | "commands.error.invalid_target": "{lightred}Target not found.", 14 | "commands.error.invalid_dead": "{lightred}Target must be alive.", 15 | "commands.error.invalid_alive": "{lightred}Target must be dead.", 16 | "commands.error.invalid_team": "{lightred}Invalid team.", 17 | "commands.error.invalid_convar": "{lightred}The given ConVar is not valid.", 18 | "commands.error.grenade_limit": "{lightred}The target has reached the grenade limit of {gold}{0}{lightred}.", 19 | "commands.error.flashbang_limit": "{lightred}The target has reached the flashbang limit of {gold}{0}{lightred}.", 20 | "commands.error.default_limit": "{lightred}The target has reached the default grenade limit of {gold}{0}{lightred}.", 21 | "commands.error.healthshot_limit": "{lightred}The target has reached the healthshot limit of {gold}{0}{lightred}.", 22 | 23 | "commands.hp.success": "{gold}{0} {silver}set {gold}{1}{silver}'s health to {gold}{2}{silver}.", 24 | "commands.armor.success": "{gold}{0} {silver}set {gold}{1}{silver}'s armor to {gold}{2}{silver}.", 25 | "commands.freeze.success": "{gold}{0} {silver}froze {gold}{1}{silver}.", 26 | "commands.unfreeze.success": "{gold}{0} {silver}unfroze {gold}{1}{silver}.", 27 | "commands.noclip.enable": "{gold}{0} {silver}enabled noclip for themselves.", 28 | "commands.noclip.disable": "{gold}{0} {silver}disabled noclip for themselves.", 29 | "commands.slay.success": "{gold}{0} {silver}slayed {gold}{1}{silver}.", 30 | "commands.rename.success": "{gold}{0} {silver}renamed {gold}{1} {silver}to {gold}{2}{silver}.", 31 | "commands.respawn.success": "{gold}{0} {silver}respawned {gold}{1}{silver}.", 32 | "commands.strip.success": "{gold}{0} {silver}stripped {gold}{1} {silver}of all weapons.", 33 | "commands.tppos.success": "{gold}{0} {silver}teleported {gold}{1} {silver}to coordinates ({gold}{2}, {3}, {4}{silver}).", 34 | "commands.tp.success": "{gold}{0} {silver}teleported {gold}{1} {silver}to {gold}{2}{silver}.", 35 | "commands.rcon.success": "{gold}{0} {silver}executed RCON command: {gold}{1}", 36 | "commands.give.success": "{gold}{0} {silver}gave {gold}{1} {silver}the weapon: {gold}{2}", 37 | "commands.cvar.success": "{gold}{0} {silver}set ConVar {gold}{1} {silver}to {gold}{2}", 38 | "commands.speed.success": "{gold}{0} {silver}set {gold}{1}{silver}'s speed to {gold}{2}", 39 | "commands.revive.success": "{gold}{0} {silver}revived {gold}{1} {silver}at their death location.", 40 | "commands.bury.success": "{gold}{0} {silver}buried {gold}{1}{silver}.", 41 | "commands.unbury.success": "{gold}{0} {silver}unburied {gold}{1}{silver}.", 42 | "commands.slap.success": "{gold}{0} {silver}slapped {gold}{1} {silver}for {gold}{2} {silver}damage.", 43 | "commands.blind.success": "{gold}{0} {silver}blinded {gold}{1} {silver}for {gold}{2} {silver}seconds.", 44 | "commands.unblind.success": "{gold}{0} {silver}unblinded {gold}{1}{silver}.", 45 | "commands.god.enable": "{gold}{0} {silver}enabled god mode for {gold}{1}{silver}.", 46 | "commands.god.disable": "{gold}{0} {silver}disabled god mode for {gold}{1}{silver}.", 47 | "commands.team.success": "{gold}{0} {silver}set {gold}{1}{silver}'s team to {gold}{2}{silver}.", 48 | "commands.swap.success": "{gold}{0} {silver}swapped {gold}{1}{silver}'s team.", 49 | "commands.hide.success": "{gold}You are now hidden now.", 50 | "commands.ghosting.header": "{gold}Players on the same IP addresses:", 51 | "commands.ghosting.none": "{lightred}No players found on the same IP.", 52 | "commands.ghosting.ip": "{gold}IP: {0}", 53 | "commands.ghosting.player": "{silver} - {gold}{0} {silver}({gold}{1}{silver})" 54 | } 55 | -------------------------------------------------------------------------------- /modules/toplists/RankTopHandler.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Menu; 4 | using CounterStrikeSharp.API.Modules.Commands; 5 | using Dapper; 6 | using MySqlConnector; 7 | using Menu; 8 | using Menu.Enums; 9 | using Microsoft.Extensions.Logging; 10 | using CounterStrikeSharp.API.Core.Translations; 11 | 12 | namespace Zenith_TopLists; 13 | 14 | public class RankTopHandler 15 | { 16 | private readonly TopListsPlugin _plugin; 17 | 18 | public RankTopHandler(TopListsPlugin plugin) 19 | { 20 | _plugin = plugin; 21 | } 22 | 23 | public void HandleRankTopCommand(CCSPlayerController player, CommandInfo? command = null) 24 | { 25 | int playerCount = TopListsPlugin.DEFAULT_PLAYER_COUNT; 26 | if (command?.ArgCount > 1) 27 | { 28 | if (int.TryParse(command.ArgByIndex(1), out int customCount)) 29 | { 30 | playerCount = Math.Max(1, Math.Min(customCount, 100)); // Limit between 1 and 100 31 | } 32 | else 33 | { 34 | _plugin.ModuleServices?.PrintForPlayer(player, _plugin.Localizer.ForPlayer(player, "ranktop.invalid.count", TopListsPlugin.DEFAULT_PLAYER_COUNT)); 35 | } 36 | } 37 | 38 | ShowRankTopMenu(player, playerCount, command is null); 39 | } 40 | 41 | private void ShowRankTopMenu(CCSPlayerController player, int playerCount, bool subMenu) 42 | { 43 | Task.Run(async () => 44 | { 45 | var topPlayers = await GetTopPlayersAsync(playerCount); 46 | 47 | Server.NextWorldUpdate(() => 48 | { 49 | if (topPlayers.Count == 0) 50 | { 51 | _plugin.ModuleServices?.PrintForPlayer(player, _plugin.Localizer.ForPlayer(player, "ranktop.no.data")); 52 | return; 53 | } 54 | 55 | try 56 | { 57 | if (_plugin.CoreAccessor?.GetValue("Core", "CenterMenuMode") == true) 58 | { 59 | ShowCenterRankTopMenu(player, topPlayers, subMenu); 60 | } 61 | else 62 | { 63 | ShowChatRankTopMenu(player, topPlayers); 64 | } 65 | } 66 | catch (Exception ex) 67 | { 68 | _plugin.Logger.LogError($"Error showing rank top menu: {ex.Message}"); 69 | } 70 | }); 71 | }); 72 | } 73 | 74 | private void ShowCenterRankTopMenu(CCSPlayerController player, List<(string Name, int Points)> topPlayers, bool subMenu) 75 | { 76 | var items = topPlayers.Select((p, index) => new MenuItem(MenuItemType.Button, [new MenuValue(_plugin.Localizer.ForPlayer(player, "ranktop.player.entry.center", index + 1, p.Name, $"{p.Points:N0}"))])).ToList(); 77 | 78 | _plugin.Menu?.ShowScrollableMenu(player, _plugin.Localizer.ForPlayer(player, "top.menu.title", topPlayers.Count), items, (_, _, _) => { }, subMenu, _plugin.CoreAccessor!.GetValue("Core", "FreezeInMenu") && (_plugin.GetZenithPlayer(player)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), 5, disableDeveloper: !_plugin.CoreAccessor!.GetValue("Core", "ShowDevelopers")); 79 | } 80 | 81 | private void ShowChatRankTopMenu(CCSPlayerController player, List<(string Name, int Points)> topPlayers) 82 | { 83 | var chatMenu = new ChatMenu(_plugin.Localizer.ForPlayer(player, "top.menu.title", topPlayers.Count)); 84 | 85 | for (int i = 0; i < topPlayers.Count; i++) 86 | { 87 | var (Name, Points) = topPlayers[i]; 88 | chatMenu.AddMenuOption(_plugin.Localizer.ForPlayer(player, "ranktop.player.entry.chat", i + 1, Name, $"{Points:N0}"), (_, _) => { }); 89 | } 90 | 91 | MenuManager.OpenChatMenu(player, chatMenu); 92 | } 93 | 94 | private async Task> GetTopPlayersAsync(int limit) 95 | { 96 | var topPlayers = new List<(string Name, int Points)>(); 97 | 98 | try 99 | { 100 | string? connectionString = _plugin.ModuleServices?.GetConnectionString(); 101 | if (string.IsNullOrEmpty(connectionString)) 102 | { 103 | throw new Exception("Database connection string is null or empty."); 104 | } 105 | 106 | using var connection = new MySqlConnection(connectionString); 107 | await connection.OpenAsync(); 108 | 109 | var columnName = "K4-Zenith-Ranks.storage"; 110 | var query = $@" 111 | SELECT p.name, 112 | CAST(JSON_EXTRACT(p.`{columnName}`, '$.Points') AS UNSIGNED) as Points 113 | FROM zenith_player_storage p 114 | WHERE JSON_VALID(p.`{columnName}`) = 1 115 | AND JSON_EXTRACT(p.`{columnName}`, '$.Points') IS NOT NULL 116 | ORDER BY Points DESC 117 | LIMIT @Limit"; 118 | 119 | var results = await connection.QueryAsync<(string Name, int Points)>(query, new { ColumnName = columnName, Limit = limit }); 120 | 121 | topPlayers = results.Select(player => (TopListsPlugin.TruncateString(player.Name), player.Points)).ToList(); 122 | } 123 | catch (Exception ex) 124 | { 125 | _plugin.Logger.LogError($"Error fetching top players: {ex.Message}"); 126 | } 127 | 128 | return topPlayers; 129 | } 130 | } -------------------------------------------------------------------------------- /modules/extended-commands/Weapon.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | 3 | namespace Zenith_ExtendedCommands 4 | { 5 | public class Weapon 6 | { 7 | public ushort WeaponID { get; } 8 | public string ClassName { get; } 9 | public gear_slot_t? Slot { get; } 10 | public List Aliases { get; } 11 | public string Name { get; } 12 | 13 | public Weapon(ushort weaponID, string className, string name, gear_slot_t? slot, List aliases) 14 | { 15 | WeaponID = weaponID; 16 | ClassName = className; 17 | Name = name; 18 | Slot = slot; 19 | Aliases = aliases; 20 | } 21 | 22 | public bool IsPrimary => Slot == gear_slot_t.GEAR_SLOT_RIFLE; 23 | public bool IsSecondary => Slot == gear_slot_t.GEAR_SLOT_PISTOL; 24 | public bool IsUtility => Slot == gear_slot_t.GEAR_SLOT_UTILITY; 25 | public bool IsGrenade => Slot == gear_slot_t.GEAR_SLOT_GRENADES; 26 | public bool IsWeapon => IsPrimary || IsSecondary; 27 | 28 | public static List List { get; } = [ 29 | new Weapon(60, "weapon_m4a1_silencer", "M4A1-S", gear_slot_t.GEAR_SLOT_RIFLE, ["m4a1s", "m4s"]), 30 | new Weapon(40, "weapon_ssg08", "SSG 08", gear_slot_t.GEAR_SLOT_RIFLE, ["ssg", "scout", "ssg08"]), 31 | new Weapon(39, "weapon_sg556", "SG 556", gear_slot_t.GEAR_SLOT_RIFLE, ["sg556", "sg"]), 32 | new Weapon(38, "weapon_scar20", "SCAR-20", gear_slot_t.GEAR_SLOT_RIFLE, ["scar20", "scar"]), 33 | new Weapon(35, "weapon_nova", "Nova", gear_slot_t.GEAR_SLOT_RIFLE, ["nova"]), 34 | new Weapon(34, "weapon_mp9", "MP9", gear_slot_t.GEAR_SLOT_RIFLE, ["mp9"]), 35 | new Weapon(33, "weapon_mp7", "MP7", gear_slot_t.GEAR_SLOT_RIFLE, ["mp7"]), 36 | new Weapon(29, "weapon_sawedoff", "Sawed-Off", gear_slot_t.GEAR_SLOT_RIFLE, ["sawedoff"]), 37 | new Weapon(28, "weapon_negev", "Negev", gear_slot_t.GEAR_SLOT_RIFLE, ["negev"]), 38 | new Weapon(27, "weapon_mag7", "MAG-7", gear_slot_t.GEAR_SLOT_RIFLE, ["mag7", "mag"]), 39 | new Weapon(26, "weapon_bizon", "PP-Bizon", gear_slot_t.GEAR_SLOT_RIFLE, ["bizon"]), 40 | new Weapon(25, "weapon_xm1014", "XM1014", gear_slot_t.GEAR_SLOT_RIFLE, ["xm1014", "xm"]), 41 | new Weapon(24, "weapon_ump45", "UMP-45", gear_slot_t.GEAR_SLOT_RIFLE, ["ump45", "ump"]), 42 | new Weapon(23, "weapon_mp5sd", "MP5-SD", gear_slot_t.GEAR_SLOT_RIFLE, ["mp5sd", "mp5"]), 43 | new Weapon(19, "weapon_p90", "P90", gear_slot_t.GEAR_SLOT_RIFLE, ["p90"]), 44 | new Weapon(17, "weapon_mac10", "MAC-10", gear_slot_t.GEAR_SLOT_RIFLE, ["mac10", "mac"]), 45 | new Weapon(16, "weapon_m4a1", "M4A1", gear_slot_t.GEAR_SLOT_RIFLE, ["m4a1", "m4"]), 46 | new Weapon(14, "weapon_m249", "M249", gear_slot_t.GEAR_SLOT_RIFLE, ["m249"]), 47 | new Weapon(13, "weapon_galilar", "Galil AR", gear_slot_t.GEAR_SLOT_RIFLE, ["galilar", "galil"]), 48 | new Weapon(11, "weapon_g3sg1", "G3SG1", gear_slot_t.GEAR_SLOT_RIFLE, ["g3sg1"]), 49 | new Weapon(10, "weapon_famas", "FAMAS", gear_slot_t.GEAR_SLOT_RIFLE, ["famas"]), 50 | new Weapon(9, "weapon_awp", "AWP", gear_slot_t.GEAR_SLOT_RIFLE, ["awp"]), 51 | new Weapon(8, "weapon_aug", "AUG", gear_slot_t.GEAR_SLOT_RIFLE, ["aug"]), 52 | new Weapon(7, "weapon_ak47", "AK-47", gear_slot_t.GEAR_SLOT_RIFLE, ["ak47", "ak"]), 53 | 54 | new Weapon(64, "weapon_revolver", "Revolver", gear_slot_t.GEAR_SLOT_PISTOL, ["revolver"]), 55 | new Weapon(63, "weapon_cz75a", "CZ75-Auto", gear_slot_t.GEAR_SLOT_PISTOL, ["cz75a", "cz"]), 56 | new Weapon(61, "weapon_usp_silencer", "USP-S", gear_slot_t.GEAR_SLOT_PISTOL, ["usp_silencer", "usp"]), 57 | new Weapon(36, "weapon_p250", "P250", gear_slot_t.GEAR_SLOT_PISTOL, ["p250"]), 58 | new Weapon(32, "weapon_hkp2000", "P2000", gear_slot_t.GEAR_SLOT_PISTOL, ["hkp2000", "hkp"]), 59 | new Weapon(30, "weapon_tec9", "Tec-9", gear_slot_t.GEAR_SLOT_PISTOL, ["tec9", "tec"]), 60 | new Weapon(4, "weapon_glock", "Glock-18", gear_slot_t.GEAR_SLOT_PISTOL, ["glock"]), 61 | new Weapon(3, "weapon_fiveseven", "Five-SeveN", gear_slot_t.GEAR_SLOT_PISTOL, ["fiveseven"]), 62 | new Weapon(2, "weapon_elite", "Dual Berettas", gear_slot_t.GEAR_SLOT_PISTOL, ["elite"]), 63 | new Weapon(1, "weapon_deagle", "Desert Eagle", gear_slot_t.GEAR_SLOT_PISTOL, ["deagle"]), 64 | 65 | new Weapon(62, "weapon_taser", "Taser", null, ["taser"]), 66 | new Weapon(58, "weapon_shield", "Shield", null, ["shield"]), 67 | new Weapon(57, "weapon_healthshot", "Healthshot", null, ["healthshot"]), 68 | 69 | new Weapon(31, "weapon_flashbang", "Flashbang", gear_slot_t.GEAR_SLOT_GRENADES, ["flashbang", "flash"]), 70 | new Weapon(20, "weapon_smokegrenade", "Smoke Grenade", gear_slot_t.GEAR_SLOT_GRENADES, ["smokegrenade", "smoke"]), 71 | new Weapon(18, "weapon_molotov", "Molotov", gear_slot_t.GEAR_SLOT_GRENADES, ["molotov"]), 72 | new Weapon(15, "weapon_hegrenade", "HE Grenade", gear_slot_t.GEAR_SLOT_GRENADES, ["hegrenade", "grenade"]), 73 | new Weapon(37, "weapon_incgrenade", "Incendiary Grenade", gear_slot_t.GEAR_SLOT_GRENADES, ["incgrenade"]), 74 | new Weapon(41, "weapon_decoy", "Decoy Grenade", gear_slot_t.GEAR_SLOT_GRENADES, ["decoy"]), 75 | new Weapon(42, "weapon_tagrenade", "Tactical Grenade", gear_slot_t.GEAR_SLOT_GRENADES, ["tagrenade"]), 76 | ]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Models/Database/Migrations/Stats/1.0_Zenith_Stats_CreateTables.cs: -------------------------------------------------------------------------------- 1 | using FluentMigrator; 2 | 3 | namespace Zenith.Migrations 4 | { 5 | [Migration(202410192)] 6 | public class Stats_CreateZenithStatsTables : Migration 7 | { 8 | public override void Up() 9 | { 10 | if (!Schema.Table("zenith_weapon_stats").Exists()) 11 | { 12 | Create.Table("zenith_weapon_stats") 13 | .WithColumn("steam_id").AsString(32).NotNullable() 14 | .WithColumn("weapon").AsString(64).NotNullable() 15 | .WithColumn("kills").AsInt32().NotNullable().WithDefaultValue(0) 16 | .WithColumn("shots").AsInt32().NotNullable().WithDefaultValue(0) 17 | .WithColumn("hits").AsInt32().NotNullable().WithDefaultValue(0) 18 | .WithColumn("headshots").AsInt32().NotNullable().WithDefaultValue(0) 19 | .WithColumn("head_hits").AsInt32().NotNullable().WithDefaultValue(0) 20 | .WithColumn("chest_hits").AsInt32().NotNullable().WithDefaultValue(0) 21 | .WithColumn("stomach_hits").AsInt32().NotNullable().WithDefaultValue(0) 22 | .WithColumn("left_arm_hits").AsInt32().NotNullable().WithDefaultValue(0) 23 | .WithColumn("right_arm_hits").AsInt32().NotNullable().WithDefaultValue(0) 24 | .WithColumn("left_leg_hits").AsInt32().NotNullable().WithDefaultValue(0) 25 | .WithColumn("right_leg_hits").AsInt32().NotNullable().WithDefaultValue(0) 26 | .WithColumn("neck_hits").AsInt32().NotNullable().WithDefaultValue(0) 27 | .WithColumn("gear_hits").AsInt32().NotNullable().WithDefaultValue(0); 28 | Create.PrimaryKey("PK_zenith_weapon_stats").OnTable("zenith_weapon_stats").Columns("steam_id", "weapon"); 29 | } 30 | 31 | if (!Schema.Table("zenith_map_stats").Exists()) 32 | { 33 | Create.Table("zenith_map_stats") 34 | .WithColumn("steam_id").AsString(32).NotNullable() 35 | .WithColumn("map_name").AsString(64).NotNullable() 36 | .WithColumn("kills").AsInt32().NotNullable().WithDefaultValue(0) 37 | .WithColumn("first_blood").AsInt32().NotNullable().WithDefaultValue(0) 38 | .WithColumn("deaths").AsInt32().NotNullable().WithDefaultValue(0) 39 | .WithColumn("assists").AsInt32().NotNullable().WithDefaultValue(0) 40 | .WithColumn("shoots").AsInt32().NotNullable().WithDefaultValue(0) 41 | .WithColumn("hits_taken").AsInt32().NotNullable().WithDefaultValue(0) 42 | .WithColumn("hits_given").AsInt32().NotNullable().WithDefaultValue(0) 43 | .WithColumn("headshots").AsInt32().NotNullable().WithDefaultValue(0) 44 | .WithColumn("head_hits").AsInt32().NotNullable().WithDefaultValue(0) 45 | .WithColumn("chest_hits").AsInt32().NotNullable().WithDefaultValue(0) 46 | .WithColumn("stomach_hits").AsInt32().NotNullable().WithDefaultValue(0) 47 | .WithColumn("left_arm_hits").AsInt32().NotNullable().WithDefaultValue(0) 48 | .WithColumn("right_arm_hits").AsInt32().NotNullable().WithDefaultValue(0) 49 | .WithColumn("left_leg_hits").AsInt32().NotNullable().WithDefaultValue(0) 50 | .WithColumn("right_leg_hits").AsInt32().NotNullable().WithDefaultValue(0) 51 | .WithColumn("neck_hits").AsInt32().NotNullable().WithDefaultValue(0) 52 | .WithColumn("unused_hits").AsInt32().NotNullable().WithDefaultValue(0) 53 | .WithColumn("gear_hits").AsInt32().NotNullable().WithDefaultValue(0) 54 | .WithColumn("special_hits").AsInt32().NotNullable().WithDefaultValue(0) 55 | .WithColumn("grenades").AsInt32().NotNullable().WithDefaultValue(0) 56 | .WithColumn("mvp").AsInt32().NotNullable().WithDefaultValue(0) 57 | .WithColumn("round_win").AsInt32().NotNullable().WithDefaultValue(0) 58 | .WithColumn("round_lose").AsInt32().NotNullable().WithDefaultValue(0) 59 | .WithColumn("game_win").AsInt32().NotNullable().WithDefaultValue(0) 60 | .WithColumn("game_lose").AsInt32().NotNullable().WithDefaultValue(0) 61 | .WithColumn("rounds_overall").AsInt32().NotNullable().WithDefaultValue(0) 62 | .WithColumn("rounds_ct").AsInt32().NotNullable().WithDefaultValue(0) 63 | .WithColumn("rounds_t").AsInt32().NotNullable().WithDefaultValue(0) 64 | .WithColumn("bomb_planted").AsInt32().NotNullable().WithDefaultValue(0) 65 | .WithColumn("bomb_defused").AsInt32().NotNullable().WithDefaultValue(0) 66 | .WithColumn("hostage_rescued").AsInt32().NotNullable().WithDefaultValue(0) 67 | .WithColumn("hostage_killed").AsInt32().NotNullable().WithDefaultValue(0) 68 | .WithColumn("no_scope_kill").AsInt32().NotNullable().WithDefaultValue(0) 69 | .WithColumn("penetrated_kill").AsInt32().NotNullable().WithDefaultValue(0) 70 | .WithColumn("thru_smoke_kill").AsInt32().NotNullable().WithDefaultValue(0) 71 | .WithColumn("flashed_kill").AsInt32().NotNullable().WithDefaultValue(0) 72 | .WithColumn("dominated_kill").AsInt32().NotNullable().WithDefaultValue(0) 73 | .WithColumn("revenge_kill").AsInt32().NotNullable().WithDefaultValue(0) 74 | .WithColumn("assist_flash").AsInt32().NotNullable().WithDefaultValue(0); 75 | Create.PrimaryKey("PK_zenith_map_stats").OnTable("zenith_map_stats").Columns("steam_id", "map_name"); 76 | } 77 | } 78 | 79 | public override void Down() 80 | { 81 | if (Schema.Table("zenith_weapon_stats").Exists()) 82 | { 83 | Delete.Table("zenith_weapon_stats"); 84 | } 85 | 86 | if (Schema.Table("zenith_map_stats").Exists()) 87 | { 88 | Delete.Table("zenith_map_stats"); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Models/Config.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using Microsoft.Extensions.Logging; 3 | using ZenithAPI; 4 | 5 | namespace Zenith 6 | { 7 | public sealed partial class Plugin : BasePlugin 8 | { 9 | private ModuleConfigAccessor _coreAccessor = null!; 10 | 11 | private void RegisterCoreConfigs() 12 | { 13 | _coreAccessor = GetModuleConfigAccessor(); 14 | 15 | // Database settings 16 | RegisterModuleConfig("Database", "Hostname", "The IP address or hostname of the database", "localhost", ConfigFlag.Protected | ConfigFlag.Locked); 17 | RegisterModuleConfig("Database", "Port", "The port number of the database", 3306, ConfigFlag.Protected | ConfigFlag.Locked); 18 | RegisterModuleConfig("Database", "Username", "The username for accessing the database", "root", ConfigFlag.Protected | ConfigFlag.Locked); 19 | RegisterModuleConfig("Database", "Password", "The password for accessing the database", "password", ConfigFlag.Protected | ConfigFlag.Locked); 20 | RegisterModuleConfig("Database", "Database", "The name of the database", "database", ConfigFlag.Protected | ConfigFlag.Locked); 21 | RegisterModuleConfig("Database", "Sslmode", "The SSL mode for the database connection (none, preferred, required, verifyca, verifyfull)", "preferred", ConfigFlag.Locked); 22 | RegisterModuleConfig("Database", "TablePurgeDays", "The number of days of inactivity after which unused data is automatically purged", 30, ConfigFlag.Locked); 23 | RegisterModuleConfig("Database", "SaveOnRoundEnd", "Whether to save every player setting and storage change on round ends", true, ConfigFlag.Locked | ConfigFlag.Global); 24 | RegisterModuleConfig("Database", "AutoSaveInterval", "The interval in minutes for automatic saving of player settings and storage (0 - disable)", 0, ConfigFlag.Locked | ConfigFlag.Global); 25 | 26 | // Commands settings 27 | RegisterModuleConfig("Commands", "SettingsCommands", "Open the settings menu for players", new List { "settings", "preferences", "prefs" }); 28 | 29 | // Modular settings 30 | RegisterModuleConfig("Modular", "PlayerClantagFormat", "The format for displaying the player clantag. css_placeholderlist for list", "{country_short} | {rank} |"); 31 | RegisterModuleConfig("Modular", "PlayerChatRankFormat", "The format for displaying the player rank in chat. css_placeholderlist for list", "{rank_color}[{rank}] "); 32 | RegisterModuleConfig("Modular", "JoinMessage", "The message to display when a player joins the server. You can use placeholders here from css_placeholderlist. Leave empty to disable.", "{gold}{name} ({steamid}) {green}has joined to the server from {gold}{country_long}{green}!"); 33 | RegisterModuleConfig("Modular", "LeaveMessage", "The message to display when a player leaves the server. You can use placeholders here from css_placeholderlist. Leave empty to disable.", "{gold}{name} ({steamid}) {lightred}has left the server."); 34 | RegisterModuleConfig("Modular", "OverridePlugins", "If you have any plugin that hides you Zenith menu, handle events agressively and blocks Zenith add to this list the folder name of that plugin. Zenith is going to force them to be lower priority than Zenith on some actions.", new List { "SharpTimer" }); 35 | 36 | // Core settings 37 | RegisterModuleConfig("Core", "GlobalChangeTracking", "Whether to enable global change tracking. When you change config values through commands, they are saved to files.", true); 38 | RegisterModuleConfig("Core", "AutoReload", "Whether to enable auto-reload of configurations. When config values are changed in a file, they are automatically changed on the server.", true); 39 | RegisterModuleConfig("Core", "FreezeInMenu", "Whether to freeze the player when opening the menu.", true, ConfigFlag.Global); 40 | RegisterModuleConfig("Core", "ShowDevelopers", "Support the developers by showing their names in the menu.", true, ConfigFlag.Global); 41 | RegisterModuleConfig("Core", "CenterMessageTime", "The time in seconds for how long the center message is displayed by default.", 10, ConfigFlag.Global); 42 | RegisterModuleConfig("Core", "CenterAlertTime", "The time in seconds for how long the center alert is displayed by default.", 5, ConfigFlag.Global); 43 | RegisterModuleConfig("Core", "HookChatMessages", "Whether to hook chat messages to modify them.", true, ConfigFlag.Global); 44 | RegisterModuleConfig("Core", "CenterMenuMode", "Enable to use CenterMenu, disable to use ChatMenu.", true, ConfigFlag.Global); 45 | RegisterModuleConfig("Core", "ShowActivity", "Specifies how admin activity should be relayed to users (1: Show to non-admins, 2: Show admin names to non-admins, 4: Show to admins, 8: Show admin names to admins, 16: Always show admin names to root users (admins are @zenith/admin)). Default is 13 due to 1+4+8", 13, ConfigFlag.Global); 46 | 47 | // Apply global settings 48 | ConfigManager.GlobalChangeTracking = GetModuleConfigValue("Core", "GlobalChangeTracking"); 49 | ConfigManager.GlobalAutoReloadEnabled = GetModuleConfigValue("Core", "AutoReload"); 50 | } 51 | 52 | public T GetCoreConfig(string groupName, string configName) where T : notnull 53 | { 54 | return _coreAccessor.GetValue(groupName, configName); 55 | } 56 | 57 | public void SetCoreConfig(string groupName, string configName, T value) where T : notnull 58 | { 59 | _coreAccessor.SetValue(groupName, configName, value); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/Core/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace Zenith 2 | { 3 | using System.Text.Json; 4 | using CounterStrikeSharp.API; 5 | using CounterStrikeSharp.API.Core; 6 | using CounterStrikeSharp.API.Core.Translations; 7 | using CounterStrikeSharp.API.Modules.Commands; 8 | using CounterStrikeSharp.API.Modules.Menu; 9 | using CounterStrikeSharp.API.Modules.Utils; 10 | using Menu; 11 | using Menu.Enums; 12 | using Microsoft.Extensions.Logging; 13 | using Zenith.Models; 14 | 15 | public sealed partial class Plugin : BasePlugin 16 | { 17 | public void Initialize_Settings() 18 | { 19 | var commands = _coreAccessor.GetValue>("Commands", "SettingsCommands"); 20 | 21 | foreach (var command in commands) 22 | { 23 | RegisterZenithCommand($"css_{command}", "Change player Zenith settings and storage", (CCSPlayerController? player, CommandInfo commandInfo) => 24 | { 25 | if (player == null) return; 26 | 27 | var zenithPlayer = Player.Find(player); 28 | if (zenithPlayer == null) return; 29 | 30 | if (GetCoreConfig("Core", "CenterMenuMode")) 31 | { 32 | CreateCenterMenu(zenithPlayer); 33 | } 34 | else 35 | { 36 | CreateChatMenu(zenithPlayer); 37 | } 38 | }); 39 | } 40 | } 41 | 42 | public void CreateChatMenu(Player player) 43 | { 44 | ChatMenu settingsMenu = new ChatMenu(Localizer.ForPlayer(player.Controller, "k4.settings.title")); 45 | 46 | foreach (var moduleItem in player.Settings) 47 | { 48 | string moduleID = moduleItem.Key; 49 | var moduleLocalizer = Player.GetModuleLocalizer(moduleID); 50 | 51 | foreach (var setting in moduleItem.Value) 52 | { 53 | string key = setting.Key; 54 | string displayName = moduleLocalizer != null ? $"{moduleLocalizer[$"settings.{key}"]}: " : $"{moduleID}.{key}: "; 55 | 56 | bool currentValue = player.GetSetting(key, moduleID); 57 | settingsMenu.AddMenuOption($"{ChatColors.Gold}{displayName}{ChatColors.Default}{(currentValue ? $"{ChatColors.Green}✔" : $"{ChatColors.Red}✘")}", 58 | (p, o) => 59 | { 60 | bool newValue = !currentValue; 61 | player.SetSetting(key, newValue, false, moduleID); 62 | 63 | string localizedValue = Localizer.ForPlayer(player.Controller, newValue ? "k4.settings.enabled" : "k4.settings.disabled"); 64 | localizedValue = newValue ? $"{ChatColors.Lime}{localizedValue}" : $"{ChatColors.LightRed}{localizedValue}"; 65 | player.Print($"{moduleLocalizer?[$"settings.{key}"] ?? key}: {localizedValue}"); 66 | } 67 | ); 68 | } 69 | } 70 | 71 | MenuManager.OpenChatMenu(player.Controller!, settingsMenu); 72 | } 73 | 74 | public void CreateCenterMenu(Player player) 75 | { 76 | var items = new List(); 77 | var defaultValues = new Dictionary(); 78 | var dataMap = new Dictionary(); 79 | 80 | int index = 0; 81 | 82 | foreach (var moduleItem in player.Settings) 83 | { 84 | string moduleID = moduleItem.Key; 85 | var moduleLocalizer = Player.GetModuleLocalizer(moduleID); 86 | 87 | foreach (var setting in moduleItem.Value) 88 | { 89 | string key = setting.Key; 90 | var defaultValue = setting.Value; 91 | var currentValue = player.GetSetting(key, moduleID); 92 | currentValue ??= defaultValue; 93 | 94 | var displayName = moduleLocalizer != null ? $"{moduleLocalizer[$"settings.{key}"]}: " : $"{moduleID}.{key}: "; 95 | 96 | switch (currentValue) 97 | { 98 | case bool boolValue: 99 | items.Add(new MenuItem(MenuItemType.Bool, new MenuValue(displayName))); 100 | defaultValues[index] = boolValue; 101 | break; 102 | case JsonElement jsonElement: 103 | switch (jsonElement.ValueKind) 104 | { 105 | case JsonValueKind.True: 106 | case JsonValueKind.False: 107 | items.Add(new MenuItem(MenuItemType.Bool, new MenuValue(displayName))); 108 | defaultValues[index] = jsonElement.GetBoolean(); 109 | break; 110 | default: 111 | Logger.LogWarning($"Unsupported JsonElement type for {moduleID}.{key}: {jsonElement.ValueKind} ({jsonElement.GetRawText()})"); 112 | continue; 113 | } 114 | break; 115 | default: 116 | Logger.LogWarning($"Unknown setting type for {moduleID}.{key} ({currentValue?.GetType().Name ?? "null"})"); 117 | continue; 118 | } 119 | 120 | dataMap[index] = (moduleID, key); 121 | index++; 122 | } 123 | } 124 | 125 | Menu?.ShowScrollableMenu(player.Controller!, Localizer.ForPlayer(player.Controller, "k4.settings.title"), items, (buttons, menu, selected) => 126 | { 127 | if (selected == null) return; 128 | 129 | if (dataMap.TryGetValue(menu.Option, out var dataInfo)) 130 | { 131 | string moduleID = dataInfo.ModuleID; 132 | string key = dataInfo.Key; 133 | var moduleLocalizer = Player.GetModuleLocalizer(moduleID); 134 | 135 | switch (buttons) 136 | { 137 | case MenuButtons.Select: 138 | if (selected.Type == MenuItemType.Bool) 139 | { 140 | bool newBoolValue = selected.Data[0] == 1; 141 | player.SetSetting(key, newBoolValue, false, moduleID); 142 | string localizedValue = Localizer.ForPlayer(player.Controller, newBoolValue ? "k4.settings.enabled" : "k4.settings.disabled"); 143 | localizedValue = newBoolValue ? $"{ChatColors.Lime}{localizedValue}" : $"{ChatColors.LightRed}{localizedValue}"; 144 | player.Print($"{moduleLocalizer?[$"settings.{key}"] ?? key}: {localizedValue}"); 145 | } 146 | break; 147 | } 148 | } 149 | }, false, GetCoreConfig("Core", "FreezeInMenu") && player.GetSetting("FreezeInMenu", "K4-Zenith"), 5, defaultValues, !GetCoreConfig("Core", "ShowDevelopers")); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /modules/zenith-bans/Events.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Core.Translations; 4 | using CounterStrikeSharp.API.Modules.Admin; 5 | using CounterStrikeSharp.API.Modules.Commands; 6 | 7 | namespace Zenith_Bans 8 | { 9 | public sealed partial class Plugin : BasePlugin 10 | { 11 | public Dictionary _disconnectTImers = []; 12 | 13 | private void Initialize_Events() 14 | { 15 | RegisterEventHandler((EventPlayerConnectFull @event, GameEventInfo info) => 16 | { 17 | CCSPlayerController? player = @event.Userid; 18 | if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) 19 | return HookResult.Continue; 20 | 21 | ProcessPlayerData(player, true); 22 | return HookResult.Continue; 23 | }); 24 | 25 | RegisterEventHandler((EventPlayerDisconnect @event, GameEventInfo info) => 26 | { 27 | CCSPlayerController? player = @event.Userid; 28 | if (player?.IsValid == true && !player.IsBot && !player.IsHLTV) 29 | { 30 | ulong steamID = player.SteamID; 31 | Task.Run(async () => 32 | { 33 | await HandlePlayerDisconnectAsync(steamID); 34 | }); 35 | 36 | AddDisconnectedPlayer(new DisconnectedPlayer 37 | { 38 | SteamId = steamID, 39 | PlayerName = player.PlayerName, 40 | DisconnectedAt = DateTime.Now 41 | }); 42 | _playerCache.Remove(steamID); 43 | } 44 | return HookResult.Continue; 45 | }); 46 | 47 | RegisterEventHandler((EventPlayerHurt @event, GameEventInfo info) => 48 | { 49 | CCSPlayerController? attacker = @event.Attacker; 50 | if (attacker == null || !attacker.IsValid || attacker.IsBot || attacker.IsHLTV || !_disconnectTImers.ContainsKey(attacker)) 51 | return HookResult.Continue; 52 | 53 | CCSPlayerController? victim = @event.Userid; 54 | if (victim == null || !victim.IsValid || victim.PlayerPawn.Value == null) 55 | return HookResult.Continue; 56 | 57 | CCSPlayerPawn playerPawn = victim.PlayerPawn.Value; 58 | 59 | victim.Health += @event.DmgHealth; 60 | Utilities.SetStateChanged(victim, "CBaseEntity", "m_iHealth"); 61 | 62 | playerPawn.ArmorValue += @event.DmgArmor; 63 | Utilities.SetStateChanged(playerPawn, "CCSPlayerPawn", "m_ArmorValue"); 64 | return HookResult.Continue; 65 | }, HookMode.Pre); 66 | 67 | AddCommandListener("say", OnAdminChatAll); 68 | AddCommandListener("say_team", OnAdminChat); 69 | } 70 | 71 | private HookResult OnAdminChatAll(CCSPlayerController? player, CommandInfo info) 72 | { 73 | if (player == null || !player.IsValid || player.AuthorizedSteamID == null) 74 | return HookResult.Continue; 75 | 76 | if (info.GetArg(1).Length == 0) 77 | return HookResult.Continue; 78 | 79 | if (!AdminManager.PlayerHasPermissions(player, "@zenith/admin") && !AdminManager.PlayerHasPermissions(player, "@zenith/root")) 80 | return HookResult.Continue; 81 | 82 | string message = info.GetArg(1); 83 | if (message[0] == '@') 84 | { 85 | var players = Utilities.GetPlayers(); 86 | string adminName = Localizer.ForPlayer(player, "k4.general.admin"); 87 | message = message.Replace("@", string.Empty); 88 | 89 | foreach (var target in players) 90 | { 91 | if (target != null && target.IsValid && !target.IsBot && !target.IsHLTV) 92 | { 93 | if (ShouldShowActivity(player.SteamID, target, true)) 94 | { 95 | _moduleServices?.PrintForPlayer(target, Localizer.ForPlayer(target, "k4.chat.announce", player.PlayerName, message), false); 96 | } 97 | else if (ShouldShowActivity(player.SteamID, target, false)) 98 | { 99 | _moduleServices?.PrintForPlayer(target, Localizer.ForPlayer(target, "k4.chat.announce", adminName, message), false); 100 | } 101 | } 102 | } 103 | return HookResult.Handled; 104 | } 105 | 106 | return HookResult.Continue; 107 | } 108 | 109 | private HookResult OnAdminChat(CCSPlayerController? player, CommandInfo info) 110 | { 111 | if (player == null || !player.IsValid || player.AuthorizedSteamID == null) 112 | return HookResult.Continue; 113 | 114 | if (info.GetArg(1).Length == 0) 115 | return HookResult.Continue; 116 | 117 | if (!AdminManager.PlayerHasPermissions(player, "@zenith/admin") && !AdminManager.PlayerHasPermissions(player, "@zenith/root")) 118 | return HookResult.Continue; 119 | 120 | string message = info.GetArg(1); 121 | if (message[0] == '@') 122 | { 123 | var players = Utilities.GetPlayers(); 124 | message = message.Replace("@", string.Empty); 125 | 126 | foreach (var target in players) 127 | { 128 | if (target != null && target.IsValid && !target.IsBot && !target.IsHLTV && (AdminManager.PlayerHasPermissions(target, "@zenith/admin") || AdminManager.PlayerHasPermissions(target, "@zenith/root"))) 129 | { 130 | _moduleServices?.PrintForPlayer(target, Localizer.ForPlayer(target, "k4.chat.adminsonly", player.PlayerName, message), false); 131 | } 132 | } 133 | return HookResult.Handled; 134 | } 135 | 136 | return HookResult.Continue; 137 | } 138 | 139 | private void OnZenithChatMessage(CCSPlayerController player, string message, string formattedMessage) 140 | { 141 | if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) 142 | return; 143 | 144 | if (string.IsNullOrEmpty(message)) 145 | return; 146 | 147 | var targetsCopy = new List(ChatSpyPlayers); 148 | foreach (var admin in targetsCopy) 149 | { 150 | if (admin.IsValid && !admin.IsBot && !admin.IsHLTV && admin.Connected == PlayerConnectedState.PlayerConnected) 151 | { 152 | if (admin.Team != player.Team) 153 | _moduleServices?.PrintForPlayer(admin, formattedMessage, false); 154 | } 155 | else 156 | ChatSpyPlayers.Remove(admin); 157 | } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /modules/zenith-bans/lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.general.console": "KONSOLA", 3 | "k4.general.admin": "ADMIN", 4 | "k4.general.invalid-usage": "{lightred}Nieprawidłowe użycie. Prawidłowe użycie: {0}", 5 | "k4.general.targetimmunity": "{lightred}Gracz {0} ma immunitet i nie może być celem.", 6 | "k4.general.targetnotfound": "{lightred}Nie znaleziono pasującego celu.", 7 | "k4.general.punishment-already-active": "{lightred}Gracz już ma aktywną karę {0}.", 8 | "k4.general.no-active-punishment": "{lightred}Gracz nie ma aktywnej kary {0}.", 9 | "k4.general.no-active-warns": "{lightred}Gracz nie ma żadnych aktywnych ostrzeżeń.", 10 | "k4.general.invalid-punish-length": "{lightred}Podano nieprawidłową długość kary.", 11 | "k4.general.permanent": "stała", 12 | "k4.general.minutes": "minuty", 13 | "k4.general.console-usage": "{lightred}Użycie: {0} {1}", 14 | "k4.general.invalid-target": "{lightred}Nieprawidłowy cel. Proszę podać poprawną nazwę gracza, Steam ID lub selektor celu.", 15 | "k4.general.max-warnings-reached": "{lightred}Osiągnięto maksymalną liczbę ostrzeżeń.", 16 | "k4.general.time-ago": "{0} min temu", 17 | "k4.general.no-reason": "Nie podano powodu", 18 | "k4.general.invalid-steamid": "{lightred}Podano nieprawidłowe SteamID64.", 19 | "k4.general.noplayersfound": "Nie znaleziono graczy", 20 | 21 | "k4.chat.kick": "{lightred}{0} wyrzucił {1} z serwera. Powód: {2}", 22 | "k4.chat.ban": "{lightred}{0} zbanował {1} na {2}. Powód: {3}", 23 | "k4.chat.ban.permanent": "{lightred}{0} na stałe zbanował {1}. Powód: {2}", 24 | "k4.chat.unban": "{gold}{0} zdjął bana z {1}.", 25 | "k4.chat.mute": "{lightred}{0} wyciszył {1} na {2}. Powód: {3}", 26 | "k4.chat.mute.permanent": "{lightred}{0} na stałe wyciszył {1}. Powód: {2}", 27 | "k4.chat.unmute": "{gold}{0} przywrócił głos {1}.", 28 | "k4.chat.gag": "{lightred}{0} zakneblował {1} na {2}. Powód: {3}", 29 | "k4.chat.gag.permanent": "{lightred}{0} na stałe zakneblował {1}. Powód: {2}", 30 | "k4.chat.ungag": "{gold}{0} zdjął knebel z {1}.", 31 | "k4.chat.silence": "{lightred}{0} wyciszył {1} na {2}. Powód: {3}", 32 | "k4.chat.silence.permanent": "{lightred}{0} na stałe wyciszył {1}. Powód: {2}", 33 | "k4.chat.unsilence": "{gold}{0} przywrócił głos {1}.", 34 | "k4.chat.warn": "{lightred}{0} ostrzegł {1}. Powód: {2}", 35 | "k4.chat.clearwarns": "{gold}{0} usunął wszystkie ostrzeżenia dla {1}.", 36 | 37 | "k4.alert.ban": "Zostaniesz zbanowany za {0} sekund.\n \n\"{1}\"", 38 | "k4.alert.kick": "Zostaniesz wyrzucony za {0} sekund.\n \n\"{1}\"", 39 | 40 | "k4.commscheck.header": "{lightred}Status komunikacji dla {0}:", 41 | "k4.commscheck.mute": "{lightred}⚠ Wyciszenie:{default} Wydane przez {lightblue}{0}{default}, Pozostało: {yellow}{1}", 42 | "k4.commscheck.no-mute": "{green}✓ Nie wyciszony {default}- Rozmowa głosowa dozwolona", 43 | "k4.commscheck.gag": "{lightred}⚠ Knebel:{default} Wydany przez {lightblue}{0}{default}, Pozostało: {yellow}{1}", 44 | "k4.commscheck.no-gag": "{green}✓ Nie zakneblowany {default}- Czat tekstowy dozwolony", 45 | 46 | "k4.warncheck.header": "{lightred}Status ostrzeżeń dla {0} (Łącznie: {1}):", 47 | "k4.warncheck.warn": "{lightred}⚠ Ostrzeżenie {0}:{default} Wydane przez {lightblue}{1}{default}, Powód: {yellow}{2}", 48 | "k4.warncheck.no-warns": "{green}✓ Brak aktywnych ostrzeżeń {default}- Czysta karta", 49 | 50 | "k4.banoffline.no-disconnected-players": "{lightred}Nie ma ostatnio rozłączonych graczy do zbanowania.", 51 | "k4.banoffline.menu-title": "{gold}Wybierz gracza do zbanowania:", 52 | "k4.banoffline.player-info": "{0} ({1}) - {2}", 53 | 54 | "k4.punishment.expired.mute": "{gold}Twoje wyciszenie wygasło. Możesz teraz używać komunikacji głosowej.", 55 | "k4.punishment.expired.gag": "{gold}Twój knebel wygasł. Możesz teraz używać czatu tekstowego.", 56 | "k4.punishment.expired.silence": "{gold}Twoje wyciszenie wygasło. Możesz teraz używać zarówno komunikacji głosowej, jak i tekstowej.", 57 | 58 | "k4.addadmin.invalid-group": "{lightred}Nieprawidłowa grupa admina: {0}", 59 | "k4.addadmin.success": "{gold}{0} dodał {1} do grupy adminów {2}.", 60 | "k4.removeadmin.success": "{lightred}{0} usunął status admina dla {1}.", 61 | "k4.addadmin.select-group": "Wybierz grupę admina", 62 | 63 | "k4.punishment_summary.header": "{gold}=== Status Admina {0} ==={default}", 64 | "k4.punishment_summary.bans": "{lightblue}🚫 Bany: {0}", 65 | "k4.punishment_summary.comms_blocks": "{lightblue}🔇 Blokady komunikacji: {0}", 66 | "k4.punishment_summary.warns": "{lightblue}⚠ Aktywne ostrzeżenia: {0}", 67 | "k4.punishment_summary.clean": "{lightblue}✓ Gracz ma czystą kartę.{default}", 68 | 69 | "k4.menu.selectplayer": "Wybierz gracza", 70 | "k4.menu.selectlength": "Wybierz długość", 71 | 72 | "k4.discord.punishment": "{\"embeds\":[{\"title\":\"🚫 Zastosowano karę\",\"color\":16711680,\"fields\":[{\"name\":\"Gracz\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Typ\",\"value\":\"{type}\",\"inline\":true},{\"name\":\"Czas trwania\",\"value\":\"{duration}\",\"inline\":true},{\"name\":\"Powód\",\"value\":\"{reason}\",\"inline\":false},{\"name\":\"Admin\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}", 73 | "k4.discord.unpunishment": "{\"embeds\":[{\"title\":\"✅ Usunięto karę\",\"color\":65280,\"fields\":[{\"name\":\"Gracz\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Typ\",\"value\":\"{type}\",\"inline\":true},{\"name\":\"Admin\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}", 74 | "k4.discord.addadmin": "{\"embeds\":[{\"title\":\"👑 Dodano admina\",\"color\":16776960,\"fields\":[{\"name\":\"Gracz\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Grupa\",\"value\":\"{group}\",\"inline\":true},{\"name\":\"Dodano przez\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}", 75 | "k4.discord.removeadmin": "{\"embeds\":[{\"title\":\"👤 Usunięto admina\",\"color\":16777215,\"fields\":[{\"name\":\"Gracz\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Usunięto przez\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}" 76 | } 77 | -------------------------------------------------------------------------------- /modules/ranks/Stocks.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Core.Translations; 4 | using CounterStrikeSharp.API.Modules.Admin; 5 | using ZenithAPI; 6 | 7 | namespace Zenith_Ranks; 8 | 9 | public sealed partial class Plugin : BasePlugin 10 | { 11 | private readonly Dictionary _playerRankCache = []; 12 | private readonly Dictionary _roundPoints = []; 13 | 14 | public IEnumerable GetValidPlayers() 15 | { 16 | foreach (var player in Utilities.GetPlayers()) 17 | { 18 | if (player != null) 19 | { 20 | if (_playerCache.TryGetValue(player, out var zenithPlayer)) 21 | { 22 | yield return zenithPlayer; 23 | } 24 | } 25 | } 26 | } 27 | 28 | public void ModifyPlayerPoints(IPlayerServices player, int points, string eventKey, string? extraInfo = null) 29 | { 30 | if (points == 0) return; 31 | 32 | var vipFlags = GetCachedConfigValue>("Settings", "VIPFlags"); 33 | var vipMultiplier = (decimal)GetCachedConfigValue("Settings", "VipMultiplier"); 34 | bool scoreboardSync = GetCachedConfigValue("Settings", "ScoreboardScoreSync"); 35 | bool showSummaries = GetCachedConfigValue("Settings", "PointSummaries"); 36 | 37 | var playerData = GetOrUpdatePlayerRankInfo(player); 38 | 39 | if (points > 0 && vipFlags.Any(f => AdminManager.PlayerHasPermissions(player.Controller, f))) 40 | { 41 | points = (int)(points * vipMultiplier); 42 | } 43 | 44 | long currentPoints = player.GetStorage("Points"); 45 | long newPoints = Math.Max(0, currentPoints + points); 46 | 47 | if (currentPoints == newPoints) 48 | return; 49 | 50 | player.SetStorage("Points", newPoints); 51 | playerData.LastUpdate = DateTime.Now; 52 | 53 | if (scoreboardSync && player.Controller.Score != (int)newPoints) 54 | { 55 | player.Controller.Score = (int)newPoints; 56 | } 57 | 58 | UpdatePlayerRank(player, playerData, newPoints); 59 | 60 | if (showSummaries || !player.GetSetting("ShowRankChanges")) 61 | { 62 | _roundPoints[player.Controller] = _roundPoints.TryGetValue(player.Controller, out int existingPoints) 63 | ? existingPoints + points 64 | : points; 65 | } 66 | else 67 | { 68 | string message = Localizer.ForPlayer(player.Controller, points >= 0 ? "k4.phrases.gain" : "k4.phrases.loss", 69 | $"{newPoints:N0}", Math.Abs(points), extraInfo ?? Localizer.ForPlayer(player.Controller, eventKey)); 70 | Server.NextFrame(() => player.Print(message)); 71 | } 72 | } 73 | 74 | private void UpdatePlayerRank(IPlayerServices player, PlayerRankInfo playerData, long points) 75 | { 76 | var (determinedRank, nextRank) = DetermineRanks(points); 77 | 78 | if (determinedRank?.Id != playerData.Rank?.Id) 79 | { 80 | string newRankName = determinedRank?.Name ?? Localizer.ForPlayer(player.Controller, "k4.phrases.rank.none"); 81 | player.SetStorage("Rank", newRankName); 82 | 83 | bool isRankUp = playerData.Rank is null || CompareRanks(determinedRank, playerData.Rank) > 0; 84 | 85 | playerData.Rank = determinedRank; 86 | playerData.NextRank = nextRank; 87 | playerData.LastUpdate = DateTime.Now; 88 | 89 | if (!GetCachedConfigValue("Settings", "ShowRankChanges")) 90 | return; 91 | 92 | string htmlMessage = $@" 93 | {Localizer.ForPlayer(player.Controller, isRankUp ? "k4.phrases.rankup" : "k4.phrases.rankdown")}
94 | {Localizer.ForPlayer(player.Controller, "k4.phrases.newrank", newRankName)}"; 95 | 96 | player.PrintToCenter(htmlMessage, _configAccessor.GetValue("Core", "CenterAlertTime"), ActionPriority.Normal); 97 | } 98 | } 99 | 100 | private static int CompareRanks(Rank? rank1, Rank? rank2) 101 | { 102 | if (rank1 == rank2) return 0; 103 | 104 | if (rank1 == null) return rank2 == null ? 0 : -1; 105 | if (rank2 == null) return 1; 106 | 107 | return rank1.Point.CompareTo(rank2.Point); 108 | } 109 | 110 | private PlayerRankInfo GetOrUpdatePlayerRankInfo(IPlayerServices player) 111 | { 112 | if (_playerRankCache.TryGetValue(player.SteamID, out var rankInfo) && (DateTime.Now - rankInfo.LastUpdate) < _playerCacheExpiration) 113 | { 114 | return rankInfo; 115 | } 116 | 117 | var currentPoints = player.GetStorage("Points"); 118 | 119 | var (determinedRank, nextRank) = DetermineRanks(currentPoints); 120 | 121 | rankInfo = new PlayerRankInfo 122 | { 123 | Rank = determinedRank, 124 | NextRank = nextRank, 125 | LastUpdate = DateTime.Now 126 | }; 127 | 128 | _playerRankCache[player.SteamID] = rankInfo; 129 | return rankInfo; 130 | } 131 | 132 | private (Rank? CurrentRank, Rank? NextRank) DetermineRanks(long points) 133 | { 134 | Rank? currentRank = null; 135 | Rank? nextRank = null; 136 | 137 | foreach (var rank in Ranks) 138 | { 139 | if (points >= rank.Point) 140 | { 141 | currentRank = rank; 142 | } 143 | else 144 | { 145 | nextRank = rank; 146 | break; 147 | } 148 | } 149 | 150 | return (currentRank, nextRank); 151 | } 152 | 153 | public int CalculateDynamicPoints(IPlayerServices attacker, IPlayerServices victim, int basePoints) 154 | { 155 | if (!_configAccessor.GetValue("Settings", "DynamicDeathPoints")) 156 | return basePoints; 157 | 158 | long attackerPoints = attacker.GetStorage("Points"); 159 | long victimPoints = victim.GetStorage("Points"); 160 | 161 | if (attackerPoints <= 0 || victimPoints <= 0) 162 | return basePoints; 163 | 164 | double minMultiplier = _configAccessor.GetValue("Settings", "DynamicDeathPointsMinMultiplier"); 165 | double maxMultiplier = _configAccessor.GetValue("Settings", "DynamicDeathPointsMaxMultiplier"); 166 | 167 | double pointsRatio = Math.Clamp(victimPoints / (double)attackerPoints, minMultiplier, maxMultiplier); 168 | return (int)Math.Round(pointsRatio * basePoints); 169 | } 170 | 171 | private void UpdateScoreboards() 172 | { 173 | if (!GetCachedConfigValue("Settings", "UseScoreboardRanks")) 174 | return; 175 | 176 | int mode = GetCachedConfigValue("Settings", "ScoreboardMode"); 177 | int rankMax = GetCachedConfigValue("Settings", "RankMax"); 178 | int rankBase = GetCachedConfigValue("Settings", "RankBase"); 179 | int rankMargin = GetCachedConfigValue("Settings", "RankMargin"); 180 | 181 | foreach (var player in GetValidPlayers()) 182 | { 183 | long currentPoints = Math.Max(1, player.GetStorage("Points")); 184 | 185 | var playerData = GetOrUpdatePlayerRankInfo(player); 186 | SetCompetitiveRank(player, mode, playerData.Rank?.Id ?? 0, currentPoints, rankMax, rankBase, rankMargin); 187 | } 188 | } 189 | 190 | private static string FormatPoints(int points) 191 | { 192 | if (points >= 1000000) 193 | { 194 | double millions = points / 1000000.0; 195 | return $"{millions:F1}M"; 196 | } 197 | else if (points >= 1000) 198 | { 199 | double thousands = points / 1000.0; 200 | return $"{thousands:F1}k"; 201 | } 202 | else 203 | { 204 | return points.ToString(); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /modules/toplists/TimeTopHandler.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Menu; 4 | using Dapper; 5 | using MySqlConnector; 6 | using Menu; 7 | using Menu.Enums; 8 | using Microsoft.Extensions.Logging; 9 | using CounterStrikeSharp.API.Modules.Commands; 10 | using CounterStrikeSharp.API.Core.Translations; 11 | 12 | namespace Zenith_TopLists; 13 | 14 | public class TimeTopHandler 15 | { 16 | private readonly TopListsPlugin _plugin; 17 | 18 | private enum TimeCategory 19 | { 20 | TotalPlaytime, 21 | TerroristPlaytime, 22 | CounterTerroristPlaytime, 23 | SpectatorPlaytime, 24 | AlivePlaytime, 25 | DeadPlaytime 26 | } 27 | 28 | public TimeTopHandler(TopListsPlugin plugin) 29 | { 30 | _plugin = plugin; 31 | } 32 | 33 | public void HandleTimeTopCommand(CCSPlayerController player, CommandInfo? command = null) 34 | { 35 | int playerCount = TopListsPlugin.DEFAULT_PLAYER_COUNT; 36 | if (command?.ArgCount > 1) 37 | { 38 | if (int.TryParse(command.ArgByIndex(1), out int customCount)) 39 | { 40 | playerCount = Math.Max(1, Math.Min(customCount, 100)); // Limit between 1 and 100 41 | } 42 | else 43 | { 44 | _plugin.ModuleServices?.PrintForPlayer(player, _plugin.Localizer.ForPlayer(player, "timetop.invalid.count", TopListsPlugin.DEFAULT_PLAYER_COUNT)); 45 | } 46 | } 47 | 48 | ShowTimeTopCategoryMenu(player, playerCount, command is null); 49 | } 50 | 51 | private void ShowTimeTopCategoryMenu(CCSPlayerController player, int playerCount, bool subMenu) 52 | { 53 | if (_plugin.CoreAccessor?.GetValue("Core", "CenterMenuMode") == true) 54 | { 55 | ShowCenterTimeTopCategoryMenu(player, playerCount, subMenu); 56 | } 57 | else 58 | { 59 | ShowChatTimeTopCategoryMenu(player, playerCount); 60 | } 61 | } 62 | 63 | private void ShowCenterTimeTopCategoryMenu(CCSPlayerController player, int playerCount, bool subMenu) 64 | { 65 | var items = Enum.GetValues(typeof(TimeCategory)) 66 | .Cast() 67 | .Select(category => new MenuItem(MenuItemType.Button, [new MenuValue(_plugin.Localizer.ForPlayer(player, $"timetop.category.{category}"))])) 68 | .ToList(); 69 | 70 | _plugin.Menu?.ShowScrollableMenu(player, _plugin.Localizer.ForPlayer(player, "timetop.category.menu.title"), items, (buttons, menu, selected) => 71 | { 72 | if (selected == null) return; 73 | 74 | if (menu.Option >= 0 && menu.Option < Enum.GetValues(typeof(TimeCategory)).Length) 75 | { 76 | TimeCategory selectedCategory = (TimeCategory)menu.Option; 77 | 78 | if (buttons == MenuButtons.Select) 79 | { 80 | ShowTimeTopMenu(player, selectedCategory, playerCount); 81 | } 82 | } 83 | }, subMenu, _plugin.CoreAccessor!.GetValue("Core", "FreezeInMenu") && (_plugin.GetZenithPlayer(player)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), 5, disableDeveloper: !_plugin.CoreAccessor!.GetValue("Core", "ShowDevelopers")); 84 | } 85 | 86 | private void ShowChatTimeTopCategoryMenu(CCSPlayerController player, int playerCount) 87 | { 88 | var chatMenu = new ChatMenu(_plugin.Localizer.ForPlayer(player, "timetop.category.menu.title")); 89 | 90 | foreach (TimeCategory category in Enum.GetValues(typeof(TimeCategory))) 91 | { 92 | chatMenu.AddMenuOption(_plugin.Localizer.ForPlayer(player, $"timetop.category.{category}"), (p, _) => 93 | { 94 | ShowTimeTopMenu(p, category, playerCount); 95 | }); 96 | } 97 | 98 | MenuManager.OpenChatMenu(player, chatMenu); 99 | } 100 | 101 | private void ShowTimeTopMenu(CCSPlayerController player, TimeCategory category, int playerCount) 102 | { 103 | Task.Run(async () => 104 | { 105 | var topPlayers = await GetTopPlayersTimeAsync(category, playerCount); 106 | 107 | Server.NextWorldUpdate(() => 108 | { 109 | if (topPlayers.Count == 0) 110 | { 111 | _plugin.ModuleServices?.PrintForPlayer(player, _plugin.Localizer.ForPlayer(player, "timetop.no.data")); 112 | return; 113 | } 114 | 115 | try 116 | { 117 | if (_plugin.CoreAccessor?.GetValue("Core", "CenterMenuMode") == true) 118 | { 119 | ShowCenterTimeTopMenu(player, topPlayers); 120 | } 121 | else 122 | { 123 | ShowChatTimeTopMenu(player, topPlayers); 124 | } 125 | } 126 | catch (Exception ex) 127 | { 128 | _plugin.Logger.LogError($"Error showing time top menu: {ex.Message}"); 129 | } 130 | }); 131 | }); 132 | } 133 | 134 | private void ShowCenterTimeTopMenu(CCSPlayerController player, List<(string Name, double Time)> topPlayers) 135 | { 136 | var items = topPlayers.Select((p, index) => new MenuItem(MenuItemType.Button, [new MenuValue(_plugin.Localizer.ForPlayer(player, "timetop.player.entry.center", index + 1, p.Name, FormatTime(player, p.Time)))])).ToList(); 137 | 138 | _plugin.Menu?.ShowScrollableMenu(player, _plugin.Localizer.ForPlayer(player, "top.menu.title", topPlayers.Count), items, (_, _, _) => { }, true, _plugin.CoreAccessor!.GetValue("Core", "FreezeInMenu") && (_plugin.GetZenithPlayer(player)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), 5, disableDeveloper: !_plugin.CoreAccessor!.GetValue("Core", "ShowDevelopers")); 139 | } 140 | 141 | private void ShowChatTimeTopMenu(CCSPlayerController player, List<(string Name, double Time)> topPlayers) 142 | { 143 | var chatMenu = new ChatMenu(_plugin.Localizer.ForPlayer(player, "top.menu.title", topPlayers.Count)); 144 | 145 | for (int i = 0; i < topPlayers.Count; i++) 146 | { 147 | var (Name, Time) = topPlayers[i]; 148 | chatMenu.AddMenuOption(_plugin.Localizer.ForPlayer(player, "timetop.player.entry.chat", i + 1, Name, FormatTime(player, Time)), (_, _) => { }); 149 | } 150 | 151 | MenuManager.OpenChatMenu(player, chatMenu); 152 | } 153 | 154 | private string FormatTime(CCSPlayerController player, double minutes) 155 | { 156 | TimeSpan time = TimeSpan.FromMinutes(minutes); 157 | return _plugin.Localizer.ForPlayer(player, "timetop.time.format", time.Days, time.Hours, time.Minutes); 158 | } 159 | 160 | private async Task> GetTopPlayersTimeAsync(TimeCategory category, int limit) 161 | { 162 | var topPlayers = new List<(string Name, double Time)>(); 163 | 164 | try 165 | { 166 | string? connectionString = _plugin.ModuleServices?.GetConnectionString(); 167 | if (string.IsNullOrEmpty(connectionString)) 168 | { 169 | throw new Exception("Database connection string is null or empty."); 170 | } 171 | 172 | using var connection = new MySqlConnection(connectionString); 173 | await connection.OpenAsync(); 174 | 175 | var columnName = "K4-Zenith-TimeStats.storage"; 176 | var query = $@" 177 | SELECT p.name, 178 | CAST(JSON_EXTRACT(p.`{columnName}`, '$.{category}') AS DECIMAL(10,2)) as Time 179 | FROM zenith_player_storage p 180 | WHERE JSON_VALID(p.`{columnName}`) = 1 181 | AND JSON_EXTRACT(p.`{columnName}`, '$.{category}') IS NOT NULL 182 | ORDER BY Time DESC 183 | LIMIT @Limit"; 184 | 185 | var results = await connection.QueryAsync<(string Name, double Time)>(query, new { ColumnName = MySqlHelper.EscapeString(columnName), Limit = limit }); 186 | 187 | topPlayers = [.. results.Select(player => (TopListsPlugin.TruncateString(player.Name), player.Time))]; 188 | } 189 | catch (Exception ex) 190 | { 191 | _plugin.Logger.LogError($"Error fetching top players for time category {category}: {ex.Message}"); 192 | } 193 | 194 | return topPlayers; 195 | } 196 | } -------------------------------------------------------------------------------- /modules/extended-commands/Stocks.cs: -------------------------------------------------------------------------------- 1 | 2 | using CounterStrikeSharp.API; 3 | using CounterStrikeSharp.API.Core; 4 | using CounterStrikeSharp.API.Core.Translations; 5 | using CounterStrikeSharp.API.Modules.Admin; 6 | using CounterStrikeSharp.API.Modules.Commands.Targeting; 7 | using CounterStrikeSharp.API.Modules.Cvars; 8 | using CounterStrikeSharp.API.Modules.Utils; 9 | 10 | namespace Zenith_ExtendedCommands; 11 | 12 | public sealed partial class Plugin : BasePlugin 13 | { 14 | private void ProcessTargetAction(CCSPlayerController? caller, CCSPlayerController target, Action action, bool? aliveState = null) 15 | { 16 | if (!AdminManager.CanPlayerTarget(caller, target)) 17 | { 18 | _moduleServices?.PrintForPlayer(caller, Localizer.ForPlayer(caller, "commands.error.invalid_immunity", target.PlayerName)); 19 | return; 20 | } 21 | 22 | if (aliveState == true && (target.PlayerPawn.Value == null || target.PlayerPawn.Value.LifeState != (byte)LifeState_t.LIFE_ALIVE)) 23 | { 24 | _moduleServices?.PrintForPlayer(caller, Localizer.ForPlayer(caller, "commands.error.invalid_dead", target.PlayerName)); 25 | return; 26 | } 27 | 28 | if (aliveState == false && (target.PlayerPawn.Value == null || target.PlayerPawn.Value.LifeState == (byte)LifeState_t.LIFE_ALIVE)) 29 | { 30 | _moduleServices?.PrintForPlayer(caller, Localizer.ForPlayer(caller, "commands.error.invalid_alive", target.PlayerName)); 31 | return; 32 | } 33 | 34 | action.Invoke(target); 35 | } 36 | 37 | private void ProcessTargetAction(CCSPlayerController? caller, TargetResult targetResult, Action action, bool? aliveState = null) 38 | { 39 | if (!targetResult.Any()) 40 | { 41 | _moduleServices?.PrintForPlayer(caller, Localizer.ForPlayer(caller, "commands.error.invalid_target")); 42 | return; 43 | } 44 | 45 | foreach (CCSPlayerController target in targetResult.Players) 46 | { 47 | ProcessTargetAction(caller, target, action, aliveState); 48 | } 49 | } 50 | 51 | private static void RemoveWeapon(CCSPlayerController player, gear_slot_t? slot = null, string? className = null) 52 | { 53 | if (player.PlayerPawn.Value?.WeaponServices is null) 54 | return; 55 | 56 | List> weaponList = [.. player.PlayerPawn.Value.WeaponServices.MyWeapons]; 57 | foreach (CHandle weapon in weaponList) 58 | { 59 | if (weapon.IsValid && weapon.Value != null) 60 | { 61 | CCSWeaponBase ccsWeaponBase = weapon.Value.As(); 62 | if (ccsWeaponBase?.IsValid == true) 63 | { 64 | CCSWeaponBaseVData? weaponData = ccsWeaponBase.VData; 65 | 66 | if (weaponData == null || (slot != null && weaponData.GearSlot != slot) || (className != null && !ccsWeaponBase.DesignerName.Contains(className))) 67 | continue; 68 | 69 | player.PlayerPawn.Value.WeaponServices.ActiveWeapon.Raw = weapon.Raw; 70 | player.DropActiveWeapon(); 71 | 72 | Server.NextFrame(() => 73 | { 74 | if (ccsWeaponBase != null && ccsWeaponBase.IsValid) 75 | { 76 | ccsWeaponBase.AcceptInput("Kill"); 77 | } 78 | }); 79 | } 80 | } 81 | } 82 | } 83 | 84 | private List GetItems(CCSPlayerController player, gear_slot_t? slot = null, string? className = null) 85 | { 86 | List items = new(); 87 | if (player.PlayerPawn.Value?.WeaponServices is null) 88 | return items; 89 | 90 | List> weaponList = [.. player.PlayerPawn.Value.WeaponServices.MyWeapons]; 91 | foreach (CHandle weapon in weaponList) 92 | { 93 | if (weapon.IsValid && weapon.Value != null) 94 | { 95 | CCSWeaponBase ccsWeaponBase = weapon.Value.As(); 96 | if (ccsWeaponBase?.IsValid == true) 97 | { 98 | CCSWeaponBaseVData? weaponData = ccsWeaponBase.VData; 99 | 100 | if (weaponData == null || (slot != null && weaponData.GearSlot != slot) || (className != null && !ccsWeaponBase.DesignerName.Contains(className))) 101 | continue; 102 | 103 | if (ccsWeaponBase.DesignerName == "weapon_healthshot") 104 | { 105 | for (int i = 0; i < player.PlayerPawn.Value.WeaponServices.Ammo[20]; i++) 106 | items.Add(ccsWeaponBase.DesignerName); 107 | } 108 | else 109 | items.Add(ccsWeaponBase.DesignerName); 110 | } 111 | } 112 | } 113 | 114 | return items; 115 | } 116 | 117 | static float RandomVelocityComponent() 118 | { 119 | return (Random.Shared.Next(180) + 50) * (Random.Shared.Next(2) == 1 ? -1 : 1); 120 | } 121 | 122 | private void ShowActivityToPlayers(ulong? callerSteamId, string localizerKey, params object[] args) 123 | { 124 | var players = Utilities.GetPlayers(); 125 | for (int i = 0; i < players.Count; i++) 126 | { 127 | var player = players[i]; 128 | if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) 129 | continue; 130 | 131 | if (ShouldShowActivity(callerSteamId, player, true)) 132 | { 133 | _moduleServices?.PrintForPlayer(player, Localizer.ForPlayer(player, localizerKey, args)); 134 | } 135 | else if (ShouldShowActivity(callerSteamId, player, false)) 136 | { 137 | var anonymousArgs = new object[args.Length]; 138 | Array.Copy(args, anonymousArgs, args.Length); 139 | anonymousArgs[0] = Localizer.ForPlayer(player, "k4.general.admin"); 140 | _moduleServices?.PrintForPlayer(player, Localizer.ForPlayer(player, localizerKey, anonymousArgs)); 141 | } 142 | } 143 | } 144 | 145 | public void SetConvarValue(ConVar? cvar, string? value) 146 | { 147 | if (cvar == null) return; 148 | if (string.IsNullOrEmpty(value)) return; 149 | 150 | var flag = cvar.Flags; 151 | 152 | if ((flag & ConVarFlags.FCVAR_CHEAT) > 0) 153 | cvar.Flags &= ~ConVarFlags.FCVAR_CHEAT; 154 | 155 | Server.ExecuteCommand($"{cvar.Name} {value}"); 156 | 157 | if (flag != cvar.Flags) 158 | { 159 | AddTimer(0.1f, () => 160 | { 161 | cvar.Flags = flag; 162 | }); 163 | } 164 | } 165 | 166 | private bool ShouldShowActivity(ulong? adminSteamId, CCSPlayerController player, bool showName) 167 | { 168 | if (!adminSteamId.HasValue) return true; // Always show console activity 169 | if (!_coreAccessor.HasValue("Core", "ShowActivity")) return true; // Show activity if no ZenithBans installed 170 | 171 | int showActivity = _coreAccessor.GetValue("Core", "ShowActivity"); 172 | if (showActivity == 0) return false; // If the setting is 0, never show 173 | 174 | bool isRoot = AdminManager.PlayerHasPermissions(player, "@zenith/root"); 175 | bool isPlayerAdmin = AdminManager.PlayerHasPermissions(player, "@zenith/admin"); 176 | 177 | if (isRoot && (showActivity & 16) != 0) return true; // Always show to root 178 | 179 | if (isPlayerAdmin) 180 | { 181 | if ((showActivity & 4) == 0) return false; // Don't show to admins 182 | if (showName && (showActivity & 8) == 0) return false; // Don't show names to admins 183 | } 184 | else 185 | { 186 | if ((showActivity & 1) == 0) return false; // Don't show to non-admins 187 | if (showName && (showActivity & 2) == 0) return false; // Don't show names to non-admins 188 | } 189 | 190 | return true; 191 | } 192 | 193 | private static CsTeam GetTeamFromName(string teamName) 194 | { 195 | return teamName.ToLower() switch 196 | { 197 | "t" => CsTeam.Terrorist, 198 | "terrorist" => CsTeam.Terrorist, 199 | "tt" => CsTeam.Terrorist, 200 | "ct" => CsTeam.CounterTerrorist, 201 | "counterterrorist" => CsTeam.CounterTerrorist, 202 | "spec" => CsTeam.Spectator, 203 | "spectator" => CsTeam.Spectator, 204 | _ => CsTeam.None 205 | }; 206 | } 207 | } -------------------------------------------------------------------------------- /modules/zenith-bans/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "k4.general.console": "CONSOLE", 3 | "k4.general.admin": "ADMIN", 4 | "k4.general.invalid-usage": "{lightred}Invalid usage. Correct usage: {0}", 5 | "k4.general.targetimmunity": "{lightred}Player {0} has immunity and cannot be targeted.", 6 | "k4.general.targetnotfound": "{lightred}No matching target found.", 7 | "k4.general.selftarget": "{lightred}You cannot target yourself", 8 | "k4.general.punishment-already-active": "{lightred}The player already has an active {0}.", 9 | "k4.general.no-active-punishment": "{lightred}The player does not have an active {0}.", 10 | "k4.general.no-active-warns": "{lightred}The player does not have any active warnings.", 11 | "k4.general.invalid-punish-length": "{lightred}Invalid punishment length specified.", 12 | "k4.general.permanent": "permanent", 13 | "k4.general.minutes": "minutes", 14 | "k4.general.console-usage": "{lightred}Usage: {0} {1}", 15 | "k4.general.invalid-target": "{lightred}Invalid target. Please provide a valid player name, Steam ID, or target selector.", 16 | "k4.general.max-warnings-reached": "{lightred}Maximum number of warnings reached", 17 | "k4.general.time-ago": "{0} min ago", 18 | "k4.general.no-reason": "No reason specified", 19 | "k4.general.invalid-steamid": "{lightred}Invalid SteamID64 provided.", 20 | "k4.general.noplayersfound": "{lightred}No players found", 21 | "k4.general.multiple-players-found": "{lightred}Multiple players found", 22 | "k4.general.no-permission-due-to-immunity": "{lightred}You don't have permission to remove this punishment due to immunity levels.", 23 | "k4.general.reason-required": "{lightred}You must provide a reason for this action.", 24 | 25 | "k4.chat.psay": "{grey}{0} -> {1}{white}: {2}", 26 | "k4.chat.announce": "{lightred}[ANNOUNCEMENT] {lightblue}{0}{default}: {1}", 27 | "k4.chat.adminsonly": "{green}[ADMINS] {lightblue}{0}{default}: {1}", 28 | 29 | "k4.chatspy.enabled": "{gold}Admin chat spy {green}ENABLED. You can now see all messages.", 30 | "k4.chatspy.disabled": "{gold}Admin chat spy {lightred}DISABLED. You can no longer see all messages.", 31 | 32 | "k4.console.playerinfo.liner": "+-----+----+-------------------------+-------------------+-----------------+", 33 | "k4.console.playerinfo.line": "| #{0}| {1}| {2}| {3}| {4}|", 34 | 35 | "k4.chat.kick": "{lightred}{0} kicked {1} from the server. Reason: {2}", 36 | "k4.chat.ban": "{lightred}{0} banned {1} for {2}. Reason: {3}", 37 | "k4.chat.ban.permanent": "{lightred}{0} permanently banned {1}. Reason: {2}", 38 | "k4.chat.unban": "{gold}{0} unbanned {1}.", 39 | "k4.chat.unban.withreason": "{gold}{0} unbanned {1}. Reason: {2}", 40 | "k4.chat.mute": "{lightred}{0} muted {1} for {2}. Reason: {3}", 41 | "k4.chat.mute.permanent": "{lightred}{0} permanently muted {1}. Reason: {2}", 42 | "k4.chat.unmute": "{gold}{0} unmuted {1}.", 43 | "k4.chat.unmute.withreason": "{gold}{0} unmuted {1}. Reason: {2}", 44 | "k4.chat.gag": "{lightred}{0} gagged {1} for {2}. Reason: {3}", 45 | "k4.chat.gag.permanent": "{lightred}{0} permanently gagged {1}. Reason: {2}", 46 | "k4.chat.ungag": "{gold}{0} ungagged {1}.", 47 | "k4.chat.ungag.withreason": "{gold}{0} ungagged {1}. Reason: {2}", 48 | "k4.chat.silence": "{lightred}{0} silenced {1} for {2}. Reason: {3}", 49 | "k4.chat.silence.permanent": "{lightred}{0} permanently silenced {1}. Reason: {2}", 50 | "k4.chat.unsilence": "{gold}{0} unsilenced {1}.", 51 | "k4.chat.unsilence.withreason": "{gold}{0} unsilenced {1}. Reason: {2}", 52 | "k4.chat.warn": "{lightred}{0} warned {1}. Reason: {2}", 53 | "k4.chat.clearwarns": "{gold}{0} cleared all warnings for {1}.", 54 | 55 | "k4.alert.ban": "You being banned in {0} seconds.\n \n\"{1}\"", 56 | "k4.alert.kick": "You being kicked in {0} seconds.\n \n\"{1}\"", 57 | 58 | "k4.commscheck.header": "{lightred}Communication status for {0}:", 59 | "k4.commscheck.mute": "{lightred}⚠ Mute:{default} Issued by {lightblue}{0}{default}, Remaining: {yellow}{1}", 60 | "k4.commscheck.no-mute": "{green}✓ Not muted {default}- Voice chat allowed", 61 | "k4.commscheck.gag": "{lightred}⚠ Gag:{default} Issued by {lightblue}{0}{default}, Remaining: {yellow}{1}", 62 | "k4.commscheck.no-gag": "{green}✓ Not gagged {default}- Text chat allowed", 63 | 64 | "k4.admin.ban_expired": "{gold}The ban for player {0} ({1}) has expired.", 65 | 66 | "k4.warncheck.header": "{lightred}Warning status for {0} (Total: {1}):", 67 | "k4.warncheck.warn": "{lightred}⚠ Warn {0}:{default} Issued by {lightblue}{1}{default}, Reason: {yellow}{2}", 68 | "k4.warncheck.no-warns": "{green}✓ No active warnings {default}- Clean record", 69 | 70 | "k4.banoffline.no-disconnected-players": "{lightred}There are no recently disconnected players to ban.", 71 | "k4.banoffline.menu-title": "{gold}Select a player to ban:", 72 | "k4.banoffline.player-info": "{0} ({1}) - {2}", 73 | 74 | "k4.punishment.expired.mute": "{gold}Your mute has expired. You can now use voice communication.", 75 | "k4.punishment.expired.gag": "{gold}Your gag has expired. You can now use text chat.", 76 | "k4.punishment.expired.silence": "{gold}Your silence has expired. You can now use both voice and text communication.", 77 | 78 | "k4.addadmin.invalid-group": "{lightred}Invalid admin group: {0}", 79 | "k4.addadmin.success": "{gold}{0} has added {1} to the {2} admin group.", 80 | "k4.removeadmin.success": "{lightred}{0} has removed {1} from admin status.", 81 | "k4.addadmin.select-group": "Select Admin Group", 82 | 83 | "k4.punishment_summary.header": "{gold}=== {0} Admin Status ==={default}", 84 | "k4.punishment_summary.bans": "{lightblue}🚫 Bans: {0}", 85 | "k4.punishment_summary.comms_blocks": "{lightblue}🔇 Communication Blocks: {0}", 86 | "k4.punishment_summary.warns": "{lightblue}⚠ Active Warnings: {0}", 87 | "k4.punishment_summary.clean": "{lightblue}✓ This player's record is clean.{default}", 88 | 89 | "k4.menu.selectplayer": "Select Player", 90 | "k4.menu.selectlength": "Select Length", 91 | 92 | "k4.discord.punishment": "{\"embeds\":[{\"title\":\"🚫 Punishment Applied\",\"color\":16711680,\"fields\":[{\"name\":\"Player\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Type\",\"value\":\"{type}\",\"inline\":true},{\"name\":\"Duration\",\"value\":\"{duration}\",\"inline\":true},{\"name\":\"Reason\",\"value\":\"{reason}\",\"inline\":false},{\"name\":\"Admin\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}", 93 | "k4.discord.unpunishment": "{\"embeds\":[{\"title\":\"✅ Punishment Removed\",\"color\":65280,\"fields\":[{\"name\":\"Player\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Type\",\"value\":\"{type}\",\"inline\":true},{\"name\":\"Reason\",\"value\":\"{reason}\",\"inline\":false},{\"name\":\"Admin\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}", 94 | "k4.discord.addadmin": "{\"embeds\":[{\"title\":\"👑 Admin Added\",\"color\":16776960,\"fields\":[{\"name\":\"Player\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Group\",\"value\":\"{group}\",\"inline\":true},{\"name\":\"Added by\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}", 95 | "k4.discord.removeadmin": "{\"embeds\":[{\"title\":\"👤 Admin Removed\",\"color\":16777215,\"fields\":[{\"name\":\"Player\",\"value\":\"{player}\",\"inline\":true},{\"name\":\"Removed by\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}", 96 | "k4.discord.banip": "{\"embeds\":[{\"title\":\"🚫 IP Banned\",\"color\":16711680,\"fields\":[{\"name\":\"IP Address\",\"value\":\"{ip}\",\"inline\":true},{\"name\":\"Duration\",\"value\":\"{duration}\",\"inline\":true},{\"name\":\"Reason\",\"value\":\"{reason}\",\"inline\":false},{\"name\":\"Admin\",\"value\":\"{admin}\",\"inline\":false}],\"timestamp\":\"{timestamp}\"}]}" 97 | } 98 | -------------------------------------------------------------------------------- /modules/toplists/StatsTopHandler.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Menu; 4 | using CounterStrikeSharp.API.Modules.Commands; 5 | using Dapper; 6 | using MySqlConnector; 7 | using Menu; 8 | using Menu.Enums; 9 | using Microsoft.Extensions.Logging; 10 | using CounterStrikeSharp.API.Core.Translations; 11 | 12 | namespace Zenith_TopLists; 13 | 14 | public class StatsTopHandler 15 | { 16 | private readonly TopListsPlugin _plugin; 17 | 18 | private enum StatsCategory 19 | { 20 | ChestHits, 21 | MVP, 22 | HitsGiven, 23 | HostageKilled, 24 | FirstBlood, 25 | Assists, 26 | FlashedKill, 27 | RoundWin, 28 | Shoots, 29 | GameLose, 30 | RightLegHits, 31 | NoScopeKill, 32 | RoundsT, 33 | BombDefused, 34 | UnusedHits, 35 | Headshots, 36 | HeadHits, 37 | HitsTaken, 38 | HostageRescued, 39 | RoundsOverall, 40 | GameWin, 41 | LeftArmHits, 42 | RevengeKill, 43 | AssistFlash, 44 | GearHits, 45 | StomachHits, 46 | RoundsCT, 47 | RoundLose, 48 | RightArmHits, 49 | BombPlanted, 50 | Kills, 51 | NeckHits, 52 | ThruSmokeKill, 53 | DominatedKill, 54 | LeftLegHits, 55 | Grenades, 56 | PenetratedKill, 57 | Deaths, 58 | SpecialHits 59 | } 60 | 61 | public StatsTopHandler(TopListsPlugin plugin) 62 | { 63 | _plugin = plugin; 64 | } 65 | 66 | public void HandleStatsTopCommand(CCSPlayerController player, CommandInfo? command = null) 67 | { 68 | int playerCount = TopListsPlugin.DEFAULT_PLAYER_COUNT; 69 | if (command?.ArgCount > 1) 70 | { 71 | if (int.TryParse(command.ArgByIndex(1), out int customCount)) 72 | { 73 | playerCount = Math.Max(1, Math.Min(customCount, 100)); // Limit between 1 and 100 74 | } 75 | else 76 | { 77 | _plugin.ModuleServices?.PrintForPlayer(player, _plugin.Localizer.ForPlayer(player, "statstop.invalid.count", TopListsPlugin.DEFAULT_PLAYER_COUNT)); 78 | } 79 | } 80 | 81 | ShowStatsTopCategoryMenu(player, playerCount, command is null); 82 | } 83 | 84 | private void ShowStatsTopCategoryMenu(CCSPlayerController player, int playerCount, bool subMenu) 85 | { 86 | if (_plugin.CoreAccessor?.GetValue("Core", "CenterMenuMode") == true) 87 | { 88 | ShowCenterStatsTopCategoryMenu(player, playerCount, subMenu); 89 | } 90 | else 91 | { 92 | ShowChatStatsTopCategoryMenu(player, playerCount); 93 | } 94 | } 95 | 96 | private void ShowCenterStatsTopCategoryMenu(CCSPlayerController player, int playerCount, bool subMenu) 97 | { 98 | var items = Enum.GetValues(typeof(StatsCategory)) 99 | .Cast() 100 | .Select(category => new MenuItem(MenuItemType.Button, [new MenuValue(_plugin.Localizer.ForPlayer(player, $"statstop.category.{category}"))])) 101 | .ToList(); 102 | 103 | _plugin.Menu?.ShowScrollableMenu(player, _plugin.Localizer.ForPlayer(player, "statstop.category.menu.title"), items, (buttons, menu, selected) => 104 | { 105 | if (selected == null) return; 106 | 107 | if (menu.Option >= 0 && menu.Option < Enum.GetValues(typeof(StatsCategory)).Length) 108 | { 109 | StatsCategory selectedCategory = (StatsCategory)menu.Option; 110 | 111 | if (buttons == MenuButtons.Select) 112 | { 113 | ShowStatsTopMenu(player, selectedCategory, playerCount); 114 | } 115 | } 116 | }, subMenu, _plugin.CoreAccessor!.GetValue("Core", "FreezeInMenu") && (_plugin.GetZenithPlayer(player)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), 5, disableDeveloper: !_plugin.CoreAccessor!.GetValue("Core", "ShowDevelopers")); 117 | } 118 | 119 | private void ShowChatStatsTopCategoryMenu(CCSPlayerController player, int playerCount) 120 | { 121 | var chatMenu = new ChatMenu(_plugin.Localizer.ForPlayer(player, "statstop.category.menu.title")); 122 | 123 | foreach (StatsCategory category in Enum.GetValues(typeof(StatsCategory))) 124 | { 125 | chatMenu.AddMenuOption(_plugin.Localizer.ForPlayer(player, $"statstop.category.{category}"), (p, _) => 126 | { 127 | ShowStatsTopMenu(p, category, playerCount); 128 | }); 129 | } 130 | 131 | MenuManager.OpenChatMenu(player, chatMenu); 132 | } 133 | 134 | private void ShowStatsTopMenu(CCSPlayerController player, StatsCategory category, int playerCount) 135 | { 136 | Task.Run(async () => 137 | { 138 | var topPlayers = await GetTopPlayersStatsAsync(category, playerCount); 139 | 140 | Server.NextWorldUpdate(() => 141 | { 142 | if (topPlayers.Count == 0) 143 | { 144 | _plugin.ModuleServices?.PrintForPlayer(player, _plugin.Localizer.ForPlayer(player, "statstop.no.data")); 145 | return; 146 | } 147 | 148 | try 149 | { 150 | if (_plugin.CoreAccessor?.GetValue("Core", "CenterMenuMode") == true) 151 | { 152 | ShowCenterStatsTopMenu(player, topPlayers); 153 | } 154 | else 155 | { 156 | ShowChatStatsTopMenu(player, topPlayers); 157 | } 158 | } 159 | catch (Exception ex) 160 | { 161 | _plugin.Logger.LogError($"Error showing stats top menu: {ex.Message}"); 162 | } 163 | }); 164 | }); 165 | } 166 | 167 | private void ShowCenterStatsTopMenu(CCSPlayerController player, List<(string Name, int Value)> topPlayers) 168 | { 169 | var items = topPlayers.Select((p, index) => new MenuItem(MenuItemType.Button, [new MenuValue(_plugin.Localizer.ForPlayer(player, "statstop.player.entry.center", index + 1, p.Name, $"{p.Value:N0}"))])).ToList(); 170 | 171 | _plugin.Menu?.ShowScrollableMenu(player, _plugin.Localizer.ForPlayer(player, "top.menu.title", topPlayers.Count), items, (_, _, _) => { }, true, _plugin.CoreAccessor!.GetValue("Core", "FreezeInMenu") && (_plugin.GetZenithPlayer(player)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), 5, disableDeveloper: !_plugin.CoreAccessor!.GetValue("Core", "ShowDevelopers")); 172 | } 173 | 174 | private void ShowChatStatsTopMenu(CCSPlayerController player, List<(string Name, int Value)> topPlayers) 175 | { 176 | var chatMenu = new ChatMenu(_plugin.Localizer.ForPlayer(player, "top.menu.title", topPlayers.Count)); 177 | 178 | for (int i = 0; i < topPlayers.Count; i++) 179 | { 180 | var (Name, Value) = topPlayers[i]; 181 | chatMenu.AddMenuOption(_plugin.Localizer.ForPlayer(player, "statstop.player.entry.chat", i + 1, Name, $"{Value:N0}"), (_, _) => { }); 182 | } 183 | 184 | MenuManager.OpenChatMenu(player, chatMenu); 185 | } 186 | 187 | private async Task> GetTopPlayersStatsAsync(StatsCategory category, int limit) 188 | { 189 | var topPlayers = new List<(string Name, int Value)>(); 190 | 191 | try 192 | { 193 | string? connectionString = _plugin.ModuleServices?.GetConnectionString(); 194 | if (string.IsNullOrEmpty(connectionString)) 195 | { 196 | throw new Exception("Database connection string is null or empty."); 197 | } 198 | 199 | using var connection = new MySqlConnection(connectionString); 200 | await connection.OpenAsync(); 201 | 202 | var columnName = "K4-Zenith-Stats.storage"; 203 | var query = $@" 204 | SELECT p.name, 205 | CAST(JSON_EXTRACT(p.`{columnName}`, '$.{category}') AS UNSIGNED) as Value 206 | FROM zenith_player_storage p 207 | WHERE JSON_VALID(p.`{columnName}`) = 1 208 | AND JSON_EXTRACT(p.`{columnName}`, '$.{category}') IS NOT NULL 209 | ORDER BY Value DESC 210 | LIMIT @Limit"; 211 | 212 | var results = await connection.QueryAsync<(string Name, int Value)>(query, new { ColumnName = MySqlHelper.EscapeString(columnName), Limit = limit }); 213 | 214 | topPlayers = [.. results.Select(player => (TopListsPlugin.TruncateString(player.Name), player.Value))]; 215 | } 216 | catch (Exception ex) 217 | { 218 | _plugin.Logger.LogError($"Error fetching top players for stats category {category}: {ex.Message}"); 219 | } 220 | 221 | return topPlayers; 222 | } 223 | } -------------------------------------------------------------------------------- /modules/ranks/Config.cs: -------------------------------------------------------------------------------- 1 | 2 | using CounterStrikeSharp.API.Core; 3 | using ZenithAPI; 4 | 5 | namespace Zenith_Ranks; 6 | 7 | public sealed partial class Plugin : BasePlugin 8 | { 9 | public IModuleConfigAccessor _configAccessor = null!; 10 | 11 | private void RegisterConfigs() 12 | { 13 | if (_moduleServices == null) return; 14 | 15 | // Register Commands 16 | _moduleServices.RegisterModuleConfig("Commands", "RankCommands", "Commands to show rank", new List { "rank", "level" }); 17 | 18 | // Register Settings 19 | _moduleServices.RegisterModuleConfig("Settings", "StartPoints", "Starting points for new players", 0); 20 | _moduleServices.RegisterModuleConfig("Settings", "WarmupPoints", "Allow points during warmup", false); 21 | _moduleServices.RegisterModuleConfig("Settings", "PointSummaries", "Show point summaries", false); 22 | _moduleServices.RegisterModuleConfig("Settings", "EnableRequirementMessages", "Enable or disable the messages for points being disabled", true); 23 | _moduleServices.RegisterModuleConfig("Settings", "MinPlayers", "Minimum players for points", 4); 24 | _moduleServices.RegisterModuleConfig("Settings", "PointsForBots", "Allow points for bot kills", false); 25 | _moduleServices.RegisterModuleConfig("Settings", "FFAMode", "Free-for-all mode", false); 26 | _moduleServices.RegisterModuleConfig("Settings", "ScoreboardScoreSync", "Sync scoreboard score", false); 27 | _moduleServices.RegisterModuleConfig("Settings", "VipMultiplier", "VIP point multiplier", 1.25); 28 | _moduleServices.RegisterModuleConfig("Settings", "DynamicDeathPoints", "Use dynamic death points", true); 29 | _moduleServices.RegisterModuleConfig("Settings", "DynamicDeathPointsMaxMultiplier", "Max multiplier for dynamic death points", 3.00); 30 | _moduleServices.RegisterModuleConfig("Settings", "DynamicDeathPointsMinMultiplier", "Min multiplier for dynamic death points", 0.5); 31 | _moduleServices.RegisterModuleConfig("Settings", "UseScoreboardRanks", "Use of ranks on scoreboard", true); 32 | _moduleServices.RegisterModuleConfig("Settings", "ShowRankChanges", "Globally enable or disable rank change center messages", true); 33 | _moduleServices.RegisterModuleConfig("Settings", "ScoreboardMode", "Scoreboard mode (1 - premier, 2 - competitive, 3 - wingman, 4 - danger zone, 0 - custom)", 1); 34 | _moduleServices.RegisterModuleConfig("Settings", "RankBase", "Base rank value for custom ranks", 0); 35 | _moduleServices.RegisterModuleConfig("Settings", "RankMax", "Maximum rank value for custom ranks", 0); 36 | _moduleServices.RegisterModuleConfig("Settings", "RankMargin", "Rank margin value for custom ranks", 0); 37 | _moduleServices.RegisterModuleConfig("Settings", "ExtendedDeathMessages", "Use extended death messages including enemy name and points", true); 38 | _moduleServices.RegisterModuleConfig("Settings", "VIPFlags", "VIP flags for multipliers", new List { "@zenith-ranks/vip" }); 39 | 40 | // Register Points 41 | _moduleServices.RegisterModuleConfig("Points", "Death", "Points for death", -5); 42 | _moduleServices.RegisterModuleConfig("Points", "Kill", "Points for kill", 8); 43 | _moduleServices.RegisterModuleConfig("Points", "Headshot", "Extra points for headshot", 5); 44 | _moduleServices.RegisterModuleConfig("Points", "Penetrated", "Extra points for penetration kill", 3); 45 | _moduleServices.RegisterModuleConfig("Points", "NoScope", "Extra points for no-scope kill", 15); 46 | _moduleServices.RegisterModuleConfig("Points", "Thrusmoke", "Extra points for kill through smoke", 15); 47 | _moduleServices.RegisterModuleConfig("Points", "BlindKill", "Extra points for blind kill", 5); 48 | _moduleServices.RegisterModuleConfig("Points", "TeamKill", "Points for team kill", -10); 49 | _moduleServices.RegisterModuleConfig("Points", "Suicide", "Points for suicide", -5); 50 | _moduleServices.RegisterModuleConfig("Points", "Assist", "Points for assist", 5); 51 | _moduleServices.RegisterModuleConfig("Points", "AssistFlash", "Points for flash assist", 7); 52 | _moduleServices.RegisterModuleConfig("Points", "TeamKillAssist", "Points for team kill assist", -4); 53 | _moduleServices.RegisterModuleConfig("Points", "TeamKillAssistFlash", "Points for team kill flash assist", -2); 54 | _moduleServices.RegisterModuleConfig("Points", "RoundWin", "Points for round win", 5); 55 | _moduleServices.RegisterModuleConfig("Points", "RoundLose", "Points for round loss", -2); 56 | _moduleServices.RegisterModuleConfig("Points", "MVP", "Points for MVP", 10); 57 | _moduleServices.RegisterModuleConfig("Points", "BombDrop", "Points for dropping the bomb", -2); 58 | _moduleServices.RegisterModuleConfig("Points", "BombPickup", "Points for picking up the bomb", 2); 59 | _moduleServices.RegisterModuleConfig("Points", "BombDefused", "Points for defusing the bomb", 10); 60 | _moduleServices.RegisterModuleConfig("Points", "BombDefusedOthers", "Points for others when bomb is defused", 3); 61 | _moduleServices.RegisterModuleConfig("Points", "BombPlant", "Points for planting the bomb", 10); 62 | _moduleServices.RegisterModuleConfig("Points", "BombExploded", "Points for bomb explosion", 10); 63 | _moduleServices.RegisterModuleConfig("Points", "HostageHurt", "Points for hurting a hostage", -2); 64 | _moduleServices.RegisterModuleConfig("Points", "HostageKill", "Points for killing a hostage", -20); 65 | _moduleServices.RegisterModuleConfig("Points", "HostageRescue", "Points for rescuing a hostage", 15); 66 | _moduleServices.RegisterModuleConfig("Points", "HostageRescueAll", "Extra points for rescuing all hostages", 10); 67 | _moduleServices.RegisterModuleConfig("Points", "LongDistanceKill", "Extra points for long-distance kill", 8); 68 | _moduleServices.RegisterModuleConfig("Points", "LongDistance", "Distance for long-distance kill (units)", 30); 69 | _moduleServices.RegisterModuleConfig("Points", "SecondsBetweenKills", "Seconds between kills for multi-kill bonuses", 0); 70 | _moduleServices.RegisterModuleConfig("Points", "RoundEndKillStreakReset", "Reset kill streak on round end", true); 71 | _moduleServices.RegisterModuleConfig("Points", "DoubleKill", "Points for double kill", 5); 72 | _moduleServices.RegisterModuleConfig("Points", "TripleKill", "Points for triple kill", 10); 73 | _moduleServices.RegisterModuleConfig("Points", "Domination", "Points for domination (4 kills)", 15); 74 | _moduleServices.RegisterModuleConfig("Points", "Rampage", "Points for rampage (5 kills)", 20); 75 | _moduleServices.RegisterModuleConfig("Points", "MegaKill", "Points for mega kill (6 kills)", 25); 76 | _moduleServices.RegisterModuleConfig("Points", "Ownage", "Points for ownage (7 kills)", 30); 77 | _moduleServices.RegisterModuleConfig("Points", "UltraKill", "Points for ultra kill (8 kills)", 35); 78 | _moduleServices.RegisterModuleConfig("Points", "KillingSpree", "Points for killing spree (9 kills)", 40); 79 | _moduleServices.RegisterModuleConfig("Points", "MonsterKill", "Points for monster kill (10 kills)", 45); 80 | _moduleServices.RegisterModuleConfig("Points", "Unstoppable", "Points for unstoppable (11 kills)", 50); 81 | _moduleServices.RegisterModuleConfig("Points", "GodLike", "Points for godlike (12+ kills)", 60); 82 | _moduleServices.RegisterModuleConfig("Points", "GrenadeKill", "Points for grenade kill", 30); 83 | _moduleServices.RegisterModuleConfig("Points", "InfernoKill", "Points for inferno (molotov/incendiary) kill", 30); 84 | _moduleServices.RegisterModuleConfig("Points", "ImpactKill", "Points for impact kill", 100); 85 | _moduleServices.RegisterModuleConfig("Points", "TaserKill", "Points for taser kill", 20); 86 | _moduleServices.RegisterModuleConfig("Points", "KnifeKill", "Points for knife kill", 15); 87 | _moduleServices.RegisterModuleConfig("Points", "PlaytimeInterval", "Interval for playtime points (in minutes), or 0 to disable", 5); 88 | _moduleServices.RegisterModuleConfig("Points", "PlaytimePoints", "Points for playtime interval", 20); 89 | 90 | // Get the config accessor 91 | _configAccessor = _moduleServices.GetModuleConfigAccessor(); 92 | } 93 | } -------------------------------------------------------------------------------- /modules/ranks/RankConfig.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Text.RegularExpressions; 3 | using Newtonsoft.Json; 4 | using Microsoft.Extensions.Logging; 5 | using CounterStrikeSharp.API.Core; 6 | using System.Text.Json.Serialization; 7 | using ZenithAPI; 8 | 9 | namespace Zenith_Ranks; 10 | 11 | public sealed partial class Plugin : BasePlugin 12 | { 13 | public List Ranks = []; 14 | 15 | public void Initialize_Ranks() 16 | { 17 | string ranksFilePath = Path.Join(ModuleDirectory, "ranks.jsonc"); 18 | 19 | string defaultRanksContent = @"[ 20 | { 21 | ""Name"": ""Silver I"", 22 | ""Image"": """", // Image URL for the rank. This can be used for web integrations such as GameCMS 23 | ""Point"": 0, // From this amount of experience, the player is Silver I, if its 0, this will be the default rank 24 | ""ChatColor"": ""grey"", // Color code for the rank. Find color names here: https://github.com/roflmuffin/CounterStrikeSharp/blob/main/managed/CounterStrikeSharp.API/Modules/Utils/ChatColors.cs 25 | ""HexColor"": ""#C0C0C0"", // Hexadecimal color code for the rank 26 | ""Permissions"": [ // You can add permissions to the rank. If you don't want to add any, remove this array 27 | { 28 | ""DisplayName"": ""Super Permission"", // This is the name of the permission. Will be displayed in the menu of ranks to let people know the benefits of a rank 29 | ""PermissionName"": ""permission1"" // This is the permission name. You can assign 3rd party permissions here 30 | }, 31 | { 32 | ""DisplayName"": ""Legendary Permission"", 33 | ""PermissionName"": ""permission2"" 34 | } 35 | // You can add as many as you want 36 | ] 37 | }, 38 | { 39 | ""Name"": ""Silver II"", 40 | ""Image"": """", 41 | ""Point"": 5000, 42 | ""ChatColor"": ""grey"", 43 | ""HexColor"": ""#C0C0C0"" 44 | }, 45 | { 46 | ""Name"": ""Silver III"", 47 | ""Image"": """", 48 | ""Point"": 10000, 49 | ""ChatColor"": ""grey"", 50 | ""HexColor"": ""#C0C0C0"" 51 | }, 52 | { 53 | ""Name"": ""Silver IV"", 54 | ""Image"": """", 55 | ""Point"": 15000, 56 | ""ChatColor"": ""grey"", 57 | ""HexColor"": ""#C0C0C0"" 58 | }, 59 | { 60 | ""Name"": ""Silver Elite"", 61 | ""Image"": """", 62 | ""Point"": 20000, 63 | ""ChatColor"": ""grey"", 64 | ""HexColor"": ""#C0C0C0"" 65 | }, 66 | { 67 | ""Name"": ""Silver Elite Master"", 68 | ""Image"": """", 69 | ""Point"": 25000, 70 | ""ChatColor"": ""grey"", 71 | ""HexColor"": ""#C0C0C0"" 72 | }, 73 | { 74 | ""Name"": ""Gold Nova I"", 75 | ""Image"": """", 76 | ""Point"": 30000, 77 | ""ChatColor"": ""gold"", 78 | ""HexColor"": ""#FFD700"" 79 | }, 80 | { 81 | ""Name"": ""Gold Nova II"", 82 | ""Image"": """", 83 | ""Point"": 40000, 84 | ""ChatColor"": ""gold"", 85 | ""HexColor"": ""#FFD700"" 86 | }, 87 | { 88 | ""Name"": ""Gold Nova III"", 89 | ""Image"": """", 90 | ""Point"": 50000, 91 | ""ChatColor"": ""gold"", 92 | ""HexColor"": ""#FFD700"" 93 | }, 94 | { 95 | ""Name"": ""Gold Nova Master"", 96 | ""Image"": """", 97 | ""Point"": 60000, 98 | ""ChatColor"": ""gold"", 99 | ""HexColor"": ""#FFD700"" 100 | }, 101 | { 102 | ""Name"": ""Master Guardian I"", 103 | ""Image"": """", 104 | ""Point"": 75000, 105 | ""ChatColor"": ""green"", 106 | ""HexColor"": ""#00FF00"" 107 | }, 108 | { 109 | ""Name"": ""Master Guardian II"", 110 | ""Image"": """", 111 | ""Point"": 90000, 112 | ""ChatColor"": ""green"", 113 | ""HexColor"": ""#00FF00"" 114 | }, 115 | { 116 | ""Name"": ""Master Guardian Elite"", 117 | ""Image"": """", 118 | ""Point"": 110000, 119 | ""ChatColor"": ""green"", 120 | ""HexColor"": ""#00FF00"" 121 | }, 122 | { 123 | ""Name"": ""Distinguished Master Guardian"", 124 | ""Image"": """", 125 | ""Point"": 130000, 126 | ""ChatColor"": ""green"", 127 | ""HexColor"": ""#00FF00"" 128 | }, 129 | { 130 | ""Name"": ""Legendary Eagle"", 131 | ""Image"": """", 132 | ""Point"": 160000, 133 | ""ChatColor"": ""blue"", 134 | ""HexColor"": ""#0000FF"" 135 | }, 136 | { 137 | ""Name"": ""Legendary Eagle Master"", 138 | ""Image"": """", 139 | ""Point"": 190000, 140 | ""ChatColor"": ""blue"", 141 | ""HexColor"": ""#0000FF"" 142 | }, 143 | { 144 | ""Name"": ""Supreme Master First Class"", 145 | ""Image"": """", 146 | ""Point"": 230000, 147 | ""ChatColor"": ""purple"", 148 | ""HexColor"": ""#800080"" 149 | }, 150 | { 151 | ""Name"": ""Global Elite"", 152 | ""Image"": """", 153 | ""Point"": 280000, 154 | ""ChatColor"": ""lightred"", 155 | ""HexColor"": ""#FF4040"" 156 | } 157 | ]"; 158 | 159 | try 160 | { 161 | if (!File.Exists(ranksFilePath)) 162 | { 163 | File.WriteAllText(ranksFilePath, defaultRanksContent); 164 | Logger.LogInformation("Default ranks file created."); 165 | } 166 | 167 | string fileContent = File.ReadAllText(ranksFilePath); 168 | 169 | if (string.IsNullOrWhiteSpace(fileContent)) 170 | { 171 | ResetToDefaultRanksFile(ranksFilePath, defaultRanksContent); 172 | fileContent = File.ReadAllText(ranksFilePath); 173 | } 174 | 175 | string jsonContent = RemoveComments(fileContent); 176 | 177 | if (string.IsNullOrWhiteSpace(jsonContent)) 178 | { 179 | ResetToDefaultRanksFile(ranksFilePath, defaultRanksContent); 180 | jsonContent = RemoveComments(File.ReadAllText(ranksFilePath)); 181 | } 182 | 183 | Ranks = JsonConvert.DeserializeObject>(jsonContent)!; 184 | if (Ranks == null || Ranks.Count == 0) 185 | { 186 | ResetToDefaultRanksFile(ranksFilePath, defaultRanksContent); 187 | Ranks = JsonConvert.DeserializeObject>(RemoveComments(File.ReadAllText(ranksFilePath)))!; 188 | } 189 | 190 | for (int i = 0; i < Ranks.Count; i++) 191 | { 192 | Ranks[i].Id = i + 1; 193 | } 194 | 195 | foreach (Rank rank in Ranks) 196 | { 197 | rank.ChatColor = ChatColorUtility.ApplyPrefixColors(rank.ChatColor); 198 | } 199 | } 200 | catch (Exception ex) 201 | { 202 | Logger.LogError("An error occurred: " + ex.Message); 203 | } 204 | } 205 | 206 | private void ResetToDefaultRanksFile(string filePath, string defaultContent) 207 | { 208 | File.WriteAllText(filePath, defaultContent); 209 | Logger.LogWarning("Invalid content found. Default ranks file regenerated."); 210 | } 211 | 212 | private string RemoveComments(string content) 213 | { 214 | return Regex.Replace(content, @"/\*(.*?)\*/|//(.*)", string.Empty, RegexOptions.Multiline); 215 | } 216 | 217 | public class Rank 218 | { 219 | public int Id { get; set; } 220 | 221 | [JsonPropertyName("Name")] 222 | public required string Name { get; set; } 223 | 224 | [JsonPropertyName("Point")] 225 | public int Point { get; set; } 226 | 227 | [JsonPropertyName("ChatColor")] 228 | public string ChatColor { get; set; } = "default"; 229 | 230 | [JsonPropertyName("HexColor")] 231 | public string HexColor { get; set; } = "#FFFFFF"; 232 | 233 | [JsonPropertyName("Permissions")] 234 | public List? Permissions { get; set; } 235 | } 236 | 237 | public class Permission 238 | { 239 | [JsonPropertyName("DisplayName")] 240 | public string DisplayName { get; set; } = ""; 241 | 242 | [JsonPropertyName("PermissionName")] 243 | public string PermissionName { get; set; } = ""; 244 | } 245 | } -------------------------------------------------------------------------------- /modules/zenith-bans/Menu.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Core.Translations; 4 | using CounterStrikeSharp.API.Modules.Admin; 5 | using CounterStrikeSharp.API.Modules.Menu; 6 | using CounterStrikeSharp.API.Modules.Utils; 7 | using Menu; 8 | using Menu.Enums; 9 | 10 | namespace Zenith_Bans 11 | { 12 | public sealed partial class Plugin : BasePlugin 13 | { 14 | private void ShowPlayerSelectionMenu(CCSPlayerController? caller, Action callback, bool includeBots = false) 15 | { 16 | if (caller == null) return; 17 | 18 | var players = Utilities.GetPlayers().Where(p => p != null && p.IsValid && (includeBots || (!p.IsBot && !p.IsHLTV)) && p != caller && AdminManager.CanPlayerTarget(caller, p)).ToList(); 19 | 20 | if (_coreAccessor.GetValue("Core", "CenterMenuMode")) 21 | { 22 | ShowCenterPlayerSelectionMenu(caller, players, callback); 23 | } 24 | else 25 | { 26 | ShowChatPlayerSelectionMenu(caller, players, callback); 27 | } 28 | } 29 | 30 | private void ShowCenterPlayerSelectionMenu(CCSPlayerController caller, List players, Action callback) 31 | { 32 | List items = []; 33 | var playerMap = new Dictionary(); 34 | 35 | for (int i = 0; i < players.Count; i++) 36 | { 37 | var player = players[i]; 38 | items.Add(new MenuItem(MenuItemType.Button, [new MenuValue($"#{player.UserId} | {player.PlayerName}")])); 39 | playerMap[i] = player; 40 | } 41 | 42 | if (items.Count == 0) 43 | { 44 | items.Add(new MenuItem(MenuItemType.Text, new MenuValue(Localizer.ForPlayer(caller, "k4.general.noplayersfound")) { Prefix = "", Suffix = "" })); 45 | } 46 | 47 | Menu.ShowScrollableMenu(caller, Localizer.ForPlayer(caller, "k4.menu.selectplayer"), items, (buttons, menu, selected) => 48 | { 49 | if (selected == null) return; 50 | 51 | if (buttons == MenuButtons.Select && playerMap.TryGetValue(menu.Option, out var targetPlayer)) 52 | { 53 | callback(targetPlayer); 54 | } 55 | }, false, _coreAccessor.GetValue("Core", "FreezeInMenu") && (GetZenithPlayer(caller)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), disableDeveloper: !_coreAccessor.GetValue("Core", "ShowDevelopers")); 56 | } 57 | 58 | private void ShowChatPlayerSelectionMenu(CCSPlayerController caller, List players, Action callback) 59 | { 60 | ChatMenu playerMenu = new ChatMenu(Localizer.ForPlayer(caller, "k4.menu.selectplayer")); 61 | 62 | foreach (var player in players) 63 | { 64 | playerMenu.AddMenuOption($"{ChatColors.Gold}#{player.UserId} | {player.PlayerName}", (c, o) => callback(player)); 65 | } 66 | 67 | if (playerMenu.MenuOptions.Count == 0) 68 | { 69 | playerMenu.AddMenuOption($"{ChatColors.LightRed}{Localizer.ForPlayer(caller, "k4.general.noplayersfound")}", (p, o) => { }, true); 70 | } 71 | 72 | MenuManager.OpenChatMenu(caller, playerMenu); 73 | } 74 | 75 | private void ShowLengthSelectionMenu(CCSPlayerController? caller, List lengthList, Action callback) 76 | { 77 | if (caller == null) return; 78 | 79 | if (_coreAccessor.GetValue("Core", "CenterMenuMode")) 80 | { 81 | ShowCenterLengthSelectionMenu(caller, lengthList, callback); 82 | } 83 | else 84 | { 85 | ShowChatLengthSelectionMenu(caller, lengthList, callback); 86 | } 87 | } 88 | 89 | private void ShowCenterLengthSelectionMenu(CCSPlayerController caller, List lengthList, Action callback) 90 | { 91 | List items = []; 92 | 93 | foreach (var length in lengthList) 94 | { 95 | if (length == 0) 96 | { 97 | string permanent = Localizer.ForPlayer(caller, "k4.general.permanent"); 98 | items.Add(new MenuItem(MenuItemType.Button, [new MenuValue(permanent.ToUpper())])); 99 | } 100 | else 101 | { 102 | items.Add(new MenuItem(MenuItemType.Button, [new MenuValue(length.ToString())])); 103 | } 104 | } 105 | 106 | Menu.ShowScrollableMenu(caller, Localizer.ForPlayer(caller, "k4.menu.selectlength"), items, (buttons, menu, selected) => 107 | { 108 | if (selected == null) return; 109 | 110 | if (buttons == MenuButtons.Select) 111 | { 112 | Server.PrintToChatAll($"{caller.PlayerName} selected option: {menu.Option} - length size: {lengthList.Count}"); 113 | callback(lengthList[menu.Option]); 114 | } 115 | }, false, _coreAccessor.GetValue("Core", "FreezeInMenu") && (GetZenithPlayer(caller)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), disableDeveloper: !_coreAccessor.GetValue("Core", "ShowDevelopers")); 116 | } 117 | 118 | private void ShowChatLengthSelectionMenu(CCSPlayerController caller, List lengthList, Action callback) 119 | { 120 | ChatMenu lengthMenu = new ChatMenu(Localizer.ForPlayer(caller, "k4.menu.selectlength")); 121 | 122 | for (int i = 0; i < lengthList.Count; i++) 123 | { 124 | int length = lengthList[i]; 125 | string displayText = length == 0 ? Localizer.ForPlayer(caller, "k4.general.permanent") : length.ToString(); 126 | lengthMenu.AddMenuOption($"{ChatColors.Gold}{displayText.ToUpper()}", (c, o) => callback(length)); 127 | } 128 | 129 | MenuManager.OpenChatMenu(caller, lengthMenu); 130 | } 131 | 132 | public void ShowReasonSelectionMenu(CCSPlayerController? caller, List reasonList, Action callback) 133 | { 134 | if (caller == null) return; 135 | 136 | if (_coreAccessor.GetValue("Core", "CenterMenuMode")) 137 | { 138 | ShowCenterReasonSelectionMenu(caller, reasonList, callback); 139 | } 140 | else 141 | { 142 | ShowChatReasonSelectionMenu(caller, reasonList, callback); 143 | } 144 | } 145 | 146 | private void ShowCenterReasonSelectionMenu(CCSPlayerController caller, List reasonList, Action callback) 147 | { 148 | List items = reasonList.Select(reason => new MenuItem(MenuItemType.Button, [new MenuValue(reason)])).ToList(); 149 | 150 | Menu.ShowScrollableMenu(caller, "Select Reason", items, (buttons, menu, selected) => 151 | { 152 | if (selected == null) return; 153 | 154 | if (buttons == MenuButtons.Select) 155 | { 156 | callback(reasonList[menu.Option]); 157 | Menu.ClearMenus(caller); 158 | } 159 | }, false, _coreAccessor.GetValue("Core", "FreezeInMenu") && (GetZenithPlayer(caller)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), disableDeveloper: !_coreAccessor.GetValue("Core", "ShowDevelopers")); 160 | } 161 | 162 | private void ShowChatReasonSelectionMenu(CCSPlayerController caller, List reasonList, Action callback) 163 | { 164 | ChatMenu reasonMenu = new ChatMenu("Select Reason"); 165 | 166 | foreach (var reason in reasonList) 167 | { 168 | reasonMenu.AddMenuOption($"{ChatColors.Gold}{reason}", (c, o) => callback(reason)); 169 | } 170 | 171 | MenuManager.OpenChatMenu(caller, reasonMenu); 172 | } 173 | 174 | private void ShowGroupSelectionMenu(CCSPlayerController controller, List groups, Action callback) 175 | { 176 | if (_coreAccessor.GetValue("Core", "CenterMenuMode")) 177 | { 178 | ShowCenterGroupSelectionMenu(controller, groups, callback); 179 | } 180 | else 181 | { 182 | ShowChatGroupSelectionMenu(controller, groups, callback); 183 | } 184 | } 185 | 186 | private void ShowCenterGroupSelectionMenu(CCSPlayerController controller, List groups, Action callback) 187 | { 188 | List items = groups.Select(group => new MenuItem(MenuItemType.Button, [new MenuValue(group)])).ToList(); 189 | 190 | Menu.ShowScrollableMenu(controller, Localizer.ForPlayer(controller, "k4.addadmin.select-group"), items, (buttons, menu, selected) => 191 | { 192 | if (selected == null) return; 193 | 194 | if (buttons == MenuButtons.Select) 195 | { 196 | callback(groups[menu.Option]); 197 | } 198 | }, false, _coreAccessor.GetValue("Core", "FreezeInMenu") && (GetZenithPlayer(controller)?.GetSetting("FreezeInMenu", "K4-Zenith") ?? true), disableDeveloper: !_coreAccessor.GetValue("Core", "ShowDevelopers")); 199 | } 200 | 201 | private void ShowChatGroupSelectionMenu(CCSPlayerController controller, List groups, Action callback) 202 | { 203 | ChatMenu groupMenu = new ChatMenu(Localizer.ForPlayer(controller, "k4.addadmin.select-group")); 204 | 205 | foreach (var group in groups) 206 | { 207 | groupMenu.AddMenuOption($"{ChatColors.Gold}{group}", (c, o) => callback(group)); 208 | } 209 | 210 | MenuManager.OpenChatMenu(controller, groupMenu); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Models/Api/Command.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Core.Commands; 4 | using CounterStrikeSharp.API.Core.Translations; 5 | using CounterStrikeSharp.API.Modules.Admin; 6 | using CounterStrikeSharp.API.Modules.Commands; 7 | using Microsoft.Extensions.Logging; 8 | using Zenith.Models; 9 | 10 | namespace Zenith 11 | { 12 | public class ZenithCommand 13 | { 14 | public required string Module; 15 | public required string Command; 16 | public required string Description; 17 | public required CommandInfo.CommandCallback Callback; 18 | public CommandUsage Usage = CommandUsage.CLIENT_AND_SERVER; 19 | public int ArgCount = 0; 20 | public string? HelpText = null; 21 | public string? Permission = null; 22 | } 23 | 24 | public sealed partial class Plugin : BasePlugin 25 | { 26 | private readonly ConcurrentDictionary> _pluginCommands = new(); 27 | private readonly ConcurrentBag _zenithCommands = []; 28 | 29 | public void RegisterZenithCommand(string command, string description, CommandInfo.CommandCallback handler, CommandUsage usage = CommandUsage.CLIENT_AND_SERVER, int argCount = 0, string? helpText = null, string? permission = null) 30 | { 31 | command = EnsureCommandPrefix(command); 32 | string callingPlugin = CallerIdentifier.GetCallingPluginName(); 33 | 34 | var existingCommand = FindExistingCommand(command); 35 | 36 | if (existingCommand.HasValue) 37 | { 38 | if (existingCommand.Value.Plugin != callingPlugin) 39 | { 40 | Logger.LogError($"Command '{command}' is already registered by plugin '{existingCommand.Value.Plugin}'. Registration by '{callingPlugin}' is not allowed."); 41 | return; 42 | } 43 | 44 | RemoveExistingCommand(existingCommand); 45 | Logger.LogWarning($"Command '{command}' already exists for plugin '{callingPlugin}', overwriting."); 46 | } 47 | 48 | RegisterNewCommand(command, description, handler, usage, argCount, helpText, permission, callingPlugin); 49 | } 50 | 51 | private static string EnsureCommandPrefix(string command) => command.StartsWith("css_") ? command : "css_" + command; 52 | 53 | private (string Plugin, CommandDefinition Command)? FindExistingCommand(string command) 54 | { 55 | var existingCommand = _pluginCommands 56 | .SelectMany(kvp => kvp.Value.Select(cmd => new { Plugin = kvp.Key, Command = cmd })) 57 | .FirstOrDefault(x => x.Command.Name == command); 58 | 59 | if (existingCommand != null) 60 | { 61 | return (existingCommand.Plugin, existingCommand.Command); 62 | } 63 | 64 | return null; 65 | } 66 | 67 | private void RemoveExistingCommand((string Plugin, CommandDefinition Command)? existingCommand) 68 | { 69 | if (existingCommand.HasValue) 70 | { 71 | CommandManager.RemoveCommand(existingCommand.Value.Command); 72 | _pluginCommands[existingCommand.Value.Plugin].Remove(existingCommand.Value.Command); 73 | } 74 | } 75 | 76 | private void RegisterNewCommand(string command, string description, CommandInfo.CommandCallback handler, CommandUsage usage, int argCount, string? helpText, string? permission, string callingPlugin) 77 | { 78 | var newCommand = new CommandDefinition(command, description, (controller, info) => 79 | { 80 | if (!CommandHelper(controller, info, usage, argCount, helpText, permission)) 81 | return; 82 | 83 | handler(controller, info); 84 | }); 85 | 86 | CommandManager.RegisterCommand(newCommand); 87 | _pluginCommands.GetOrAdd(callingPlugin, _ => []).Add(newCommand); 88 | 89 | _zenithCommands.Add(new ZenithCommand 90 | { 91 | Module = callingPlugin, 92 | Command = command, 93 | Description = description, 94 | Callback = handler, 95 | Usage = usage, 96 | ArgCount = argCount, 97 | HelpText = helpText, 98 | Permission = permission 99 | }); 100 | } 101 | 102 | public void RemoveModuleCommands(string callingPlugin) 103 | { 104 | if (_pluginCommands.TryGetValue(callingPlugin, out var pluginCommands)) 105 | { 106 | foreach (var command in pluginCommands) 107 | { 108 | CommandManager.RemoveCommand(command); 109 | } 110 | _pluginCommands.TryRemove(callingPlugin, out _); 111 | 112 | var commandsToRemove = _zenithCommands.Where(c => c.Module == callingPlugin).ToList(); 113 | foreach (var cmd in commandsToRemove) 114 | { 115 | _zenithCommands.TryTake(out _); 116 | } 117 | } 118 | } 119 | 120 | public void ListAllCommands(string? pluginName = null, CCSPlayerController? player = null) 121 | { 122 | if (pluginName != null) 123 | { 124 | PrintPluginCommands(pluginName, player); 125 | } 126 | else 127 | { 128 | foreach (var pluginEntry in _pluginCommands) 129 | { 130 | PrintPluginCommands(pluginEntry.Key, player); 131 | } 132 | } 133 | 134 | Player.Find(player)?.Print("Command list has been printed to your console."); 135 | } 136 | 137 | private void PrintPluginCommands(string pluginName, CCSPlayerController? player) 138 | { 139 | var pluginCommands = _zenithCommands.Where(c => c.Module == pluginName).ToList(); 140 | if (pluginCommands.Count != 0) 141 | { 142 | PrintToConsole($"Commands for plugin '{pluginName}':", player); 143 | foreach (var command in pluginCommands) 144 | { 145 | string permission = command.Permission ?? string.Empty; 146 | PrintToConsole($" - {command.Command}{(command.HelpText != null ? " " + command.HelpText : "")}: {command.Description} {(string.IsNullOrEmpty(permission) ? "" : $"({permission})")}", player); 147 | } 148 | } 149 | else 150 | { 151 | PrintToConsole($"No commands found for plugin '{pluginName}'.", player); 152 | } 153 | } 154 | 155 | public void RemoveAllCommands() 156 | { 157 | foreach (var pluginEntry in _pluginCommands) 158 | { 159 | foreach (var command in pluginEntry.Value) 160 | { 161 | CommandManager.RemoveCommand(command); 162 | } 163 | } 164 | 165 | _pluginCommands.Clear(); 166 | _zenithCommands.Clear(); 167 | } 168 | 169 | public void RegisterZenithCommand(List commands, string description, CommandInfo.CommandCallback handler, CommandUsage usage = CommandUsage.CLIENT_AND_SERVER, int argCount = 0, string? helpText = null, string? permission = null) 170 | { 171 | foreach (var command in commands) 172 | { 173 | RegisterZenithCommand(command, description, handler, usage, argCount, helpText, permission); 174 | } 175 | } 176 | 177 | public bool CommandHelper(CCSPlayerController? controller, CommandInfo info, CommandUsage usage, int argCount = 0, string? helpText = null, string? permission = null) 178 | { 179 | Player? player = Player.Find(controller); 180 | 181 | if (!IsCommandUsageValid(player, info, usage)) 182 | return false; 183 | 184 | if (!HasPermission(player, controller, info, permission)) 185 | return false; 186 | 187 | if (IsArgumentCountInvalid(controller, info, argCount, helpText)) 188 | return false; 189 | 190 | return true; 191 | } 192 | 193 | private bool IsCommandUsageValid(Player? player, CommandInfo info, CommandUsage usage) 194 | { 195 | switch (usage) 196 | { 197 | case CommandUsage.CLIENT_ONLY when player == null || !player.IsValid: 198 | info.ReplyToCommand($" {Localizer.ForPlayer(player?.Controller, "k4.general.prefix")} {Localizer.ForPlayer(player?.Controller, "k4.command.client-only")}"); 199 | return false; 200 | case CommandUsage.SERVER_ONLY when player != null: 201 | info.ReplyToCommand($" {Localizer.ForPlayer(player?.Controller, "k4.general.prefix")} {Localizer.ForPlayer(player?.Controller, "k4.command.server-only")}"); 202 | return false; 203 | default: 204 | return true; 205 | } 206 | } 207 | 208 | private bool HasPermission(Player? player, CCSPlayerController? controller, CommandInfo info, string? permission) 209 | { 210 | if (string.IsNullOrEmpty(permission)) 211 | return true; 212 | 213 | if (player != null && !AdminManager.PlayerHasPermissions(controller, permission) && 214 | !AdminManager.PlayerHasPermissions(controller, "@zenith/root") && 215 | !AdminManager.PlayerHasPermissions(controller, "@css/root") && 216 | !AdminManager.GetPlayerCommandOverrideState(controller, info.GetArg(0)) == false) 217 | { 218 | info.ReplyToCommand($" {Localizer.ForPlayer(controller, "k4.general.prefix")} {Localizer.ForPlayer(controller, "k4.command.no-permission")}"); 219 | return false; 220 | } 221 | 222 | return true; 223 | } 224 | 225 | private bool IsArgumentCountInvalid(CCSPlayerController? controller, CommandInfo info, int argCount, string? helpText) 226 | { 227 | if (argCount > 0 && info.ArgCount < argCount + 1 && helpText != null) 228 | { 229 | info.ReplyToCommand($" {Localizer.ForPlayer(controller, "k4.general.prefix")} {Localizer.ForPlayer(controller, "k4.command.help", info.ArgByIndex(0), helpText)}"); 230 | return true; 231 | } 232 | return false; 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/Core/Stocks.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Text; 3 | using CounterStrikeSharp.API; 4 | using CounterStrikeSharp.API.Core; 5 | using CounterStrikeSharp.API.Modules.Cvars; 6 | using CounterStrikeSharp.API.Modules.Utils; 7 | using MaxMind.GeoIP2; 8 | using Microsoft.Extensions.Logging; 9 | using Zenith.Models; 10 | using System.Reflection; 11 | 12 | namespace Zenith 13 | { 14 | public sealed partial class Plugin : BasePlugin 15 | { 16 | public readonly ConcurrentDictionary>> _pluginPlayerPlaceholders = new(); 17 | public readonly ConcurrentDictionary>> _pluginServerPlaceholders = new(); 18 | 19 | private static readonly HashSet _chatColorChars = [.. typeof(ChatColors) 20 | .GetFields(BindingFlags.Public | BindingFlags.Static) 21 | .Where(f => f.FieldType == typeof(char)) 22 | .Select(f => (char)f.GetValue(null)!)]; 23 | 24 | private void Initialize_Placeholders() 25 | { 26 | RegisterZenithPlayerPlaceholder("userid", p => p.UserId?.ToString() ?? "Unknown"); 27 | RegisterZenithPlayerPlaceholder("name", p => p.PlayerName); 28 | RegisterZenithPlayerPlaceholder("steamid", p => p.SteamID.ToString()); 29 | RegisterZenithPlayerPlaceholder("ip", p => p.IpAddress ?? "Unknown"); 30 | RegisterZenithPlayerPlaceholder("country_short", p => GetCountryFromIP(p).ShortName); 31 | RegisterZenithPlayerPlaceholder("country_long", p => GetCountryFromIP(p).LongName); 32 | 33 | RegisterZenithServerPlaceholder("server_name", () => ConVar.Find("hostname")?.StringValue ?? "Unknown"); 34 | RegisterZenithServerPlaceholder("map_name", () => Server.MapName); 35 | RegisterZenithServerPlaceholder("max_players", Server.MaxPlayers.ToString); 36 | 37 | // ? Arena Support 38 | RegisterZenithPlayerPlaceholder("arena", GetPlayerArenaName); 39 | } 40 | 41 | public string ReplacePlaceholders(CCSPlayerController? player, string text) 42 | { 43 | if (string.IsNullOrEmpty(text)) 44 | return text; 45 | 46 | return ReplacePlayerPlaceholders(player, ReplaceServerPlaceholders(text)); 47 | } 48 | 49 | public string ReplaceServerPlaceholders(string text) 50 | { 51 | if (string.IsNullOrEmpty(text) || _pluginServerPlaceholders.IsEmpty) 52 | return text; 53 | 54 | var result = text; 55 | foreach (var pluginPlaceholders in _pluginServerPlaceholders.Values) 56 | { 57 | if (pluginPlaceholders.IsEmpty) 58 | continue; 59 | 60 | foreach (var (key, valueFunc) in pluginPlaceholders) 61 | { 62 | var placeholder = $"{{{key}}}"; 63 | if (result.Contains(placeholder, StringComparison.Ordinal)) 64 | { 65 | result = result.Replace(placeholder, valueFunc()); 66 | } 67 | } 68 | } 69 | 70 | return result; 71 | } 72 | 73 | public string ReplacePlayerPlaceholders(CCSPlayerController? player, string text) 74 | { 75 | if (player == null || !player.IsValid || string.IsNullOrEmpty(text)) 76 | return text; 77 | 78 | var result = text; 79 | foreach (var pluginPlaceholders in _pluginPlayerPlaceholders.Values) 80 | { 81 | if (pluginPlaceholders.IsEmpty) 82 | continue; 83 | 84 | foreach (var (key, valueFunc) in pluginPlaceholders) 85 | { 86 | var placeholder = $"{{{key}}}"; 87 | if (result.Contains(placeholder, StringComparison.Ordinal)) 88 | { 89 | result = result.Replace(placeholder, valueFunc(player)); 90 | } 91 | } 92 | } 93 | 94 | return result; 95 | } 96 | 97 | public static string RemoveLeadingSpaceBeforeColorCode(string input) 98 | { 99 | if (string.IsNullOrEmpty(input) || input.Length < 2) 100 | return input; 101 | 102 | return input[0] == ' ' && IsColorCode(input[1]) 103 | ? input[1..] 104 | : input; 105 | } 106 | 107 | private static bool IsColorCode(char c) 108 | { 109 | return _chatColorChars.Contains(c); 110 | } 111 | 112 | public void RegisterZenithPlayerPlaceholder(string key, Func valueFunc) 113 | { 114 | string callingPlugin = CallerIdentifier.GetCallingPluginName(); 115 | 116 | var placeholders = _pluginPlayerPlaceholders.GetOrAdd(callingPlugin, _ => new ConcurrentDictionary>()); 117 | 118 | if (placeholders.ContainsKey(key)) 119 | { 120 | Logger.LogWarning($"Player placeholder '{key}' already exists for plugin '{callingPlugin}', overwriting."); 121 | } 122 | 123 | placeholders[key] = valueFunc; 124 | } 125 | 126 | public void RegisterZenithServerPlaceholder(string key, Func valueFunc) 127 | { 128 | string callingPlugin = CallerIdentifier.GetCallingPluginName(); 129 | 130 | var placeholders = _pluginServerPlaceholders.GetOrAdd(callingPlugin, _ => new ConcurrentDictionary>()); 131 | 132 | if (placeholders.ContainsKey(key)) 133 | { 134 | Logger.LogWarning($"Server placeholder '{key}' already exists for plugin '{callingPlugin}', overwriting."); 135 | } 136 | 137 | placeholders[key] = valueFunc; 138 | } 139 | 140 | public void RemoveModulePlaceholders(string? callingPlugin = null) 141 | { 142 | if (callingPlugin != null) 143 | { 144 | _pluginPlayerPlaceholders.TryRemove(callingPlugin, out _); 145 | _pluginServerPlaceholders.TryRemove(callingPlugin, out _); 146 | } 147 | else 148 | { 149 | _pluginPlayerPlaceholders.Clear(); 150 | _pluginServerPlaceholders.Clear(); 151 | } 152 | } 153 | 154 | public void DisposeModule(string callingPlugin) 155 | { 156 | Logger.LogInformation($"Disposing module '{callingPlugin}' and freeing resources."); 157 | 158 | RemoveModuleCommands(callingPlugin); 159 | RemoveModulePlaceholders(callingPlugin); 160 | } 161 | 162 | public void ListAllPlaceholders(string? pluginName = null, CCSPlayerController? player = null) 163 | { 164 | if (pluginName != null) 165 | { 166 | ListPlaceholdersForPlugin(pluginName, player); 167 | } 168 | else 169 | { 170 | foreach (var plugin in _pluginPlayerPlaceholders.Keys.Union(_pluginServerPlaceholders.Keys).Distinct()) 171 | { 172 | ListPlaceholdersForPlugin(plugin, player); 173 | } 174 | } 175 | 176 | Player.Find(player)?.Print("Placeholder list has been printed to your console."); 177 | } 178 | 179 | private void ListPlaceholdersForPlugin(string pluginName, CCSPlayerController? player = null) 180 | { 181 | PrintToConsole($"Placeholders for plugin '{pluginName}':", player); 182 | 183 | if (_pluginPlayerPlaceholders.TryGetValue(pluginName, out var playerPlaceholders)) 184 | { 185 | PrintToConsole(" Player placeholders:", player); 186 | foreach (var placeholder in playerPlaceholders.Keys) 187 | { 188 | PrintToConsole($" - {placeholder}", player); 189 | } 190 | } 191 | 192 | if (_pluginServerPlaceholders.TryGetValue(pluginName, out var serverPlaceholders)) 193 | { 194 | PrintToConsole(" Server placeholders:", player); 195 | foreach (var placeholder in serverPlaceholders.Keys) 196 | { 197 | PrintToConsole($" - {placeholder}", player); 198 | } 199 | } 200 | } 201 | 202 | public static void PrintToConsole(string text, CCSPlayerController? player) 203 | { 204 | if (player == null) 205 | { 206 | Server.PrintToConsole(text); 207 | } 208 | else 209 | { 210 | player.PrintToConsole(text); 211 | } 212 | } 213 | 214 | private (string ShortName, string LongName) GetCountryFromIP(CCSPlayerController? player) 215 | { 216 | if (player is null || !Player.List.TryGetValue(player.SteamID, out var playerData)) 217 | return ("??", "Unknown"); 218 | 219 | if (playerData._country != ("??", "Unknown")) 220 | return playerData._country; 221 | 222 | playerData._country = player == null 223 | ? ("??", "Unknown") 224 | : GetCountryFromIP(player.IpAddress?.Split(':')[0]); 225 | 226 | return playerData._country; 227 | } 228 | 229 | private (string ShortName, string LongName) GetCountryFromIP(string? ipAddress) 230 | { 231 | if (string.IsNullOrEmpty(ipAddress)) 232 | return ("??", "Unknown"); 233 | 234 | string databasePath = Path.Combine(ModuleDirectory, "GeoLite2-Country.mmdb"); 235 | if (!File.Exists(databasePath)) 236 | return ("??", "Unknown"); 237 | 238 | try 239 | { 240 | using var reader = new DatabaseReader(databasePath); 241 | var response = reader.Country(ipAddress); 242 | 243 | return ( 244 | response.Country.IsoCode ?? "??", 245 | response.Country.Name ?? "Unknown" 246 | ); 247 | } 248 | catch 249 | { 250 | return ("??", "Unknown"); 251 | } 252 | } 253 | 254 | public static string RemoveColorChars(string input) 255 | { 256 | if (string.IsNullOrEmpty(input)) 257 | return input; 258 | 259 | var result = new StringBuilder(input.Length); 260 | 261 | foreach (char c in input) 262 | { 263 | if (!_chatColorChars.Contains(c)) 264 | { 265 | result.Append(c); 266 | } 267 | } 268 | 269 | return result.ToString(); 270 | } 271 | } 272 | } 273 | --------------------------------------------------------------------------------