diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..232867c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: daffyy diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..57a42c2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,95 @@ +name: Build + +on: + push: + branches: [ "main" ] + paths-ignore: + - '**/README.md' + pull_request: + branches: [ "main" ] + paths-ignore: + - '**/README.md' + +env: + BUILD_NUMBER: ${{ github.run_number }} + PROJECT_PATH_CS2_SIMPLEADMIN: "CS2-SimpleAdmin/CS2-SimpleAdmin.csproj" + PROJECT_PATH_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj" + PROJECT_NAME_CS2_SIMPLEADMIN: "CS2-SimpleAdmin" + PROJECT_NAME_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi" + OUTPUT_PATH: "./counterstrikesharp" + TMP_PATH: "./tmp" + +jobs: + build: + permissions: write-all + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore CS2-SimpleAdmin + run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} + - name: Build CS2-SimpleAdmin + run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }} + - name: Restore CS2-SimpleAdminApi + run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} + - name: Build CS2-SimpleAdminApi + run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }} + + publish: + if: github.event_name == 'push' + permissions: write-all + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore CS2-SimpleAdmin + run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} + - name: Build CS2-SimpleAdmin + run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }} + - name: Restore CS2-SimpleAdminApi + run: dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} + - name: Build CS2-SimpleAdminApi + run: dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }} + - name: Clean files + run: | + rm -f \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/CounterStrikeSharp.API.dll \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/McMaster.NETCore.Plugins.dll \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.DotNet.PlatformAbstractions.dll \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.Extensions.DependencyModel.dll \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/CS2-SimpleAdminApi.* \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/Microsoft.* \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/CounterStrikeSharp.API.dll \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/McMaster.NETCore.Plugins.dll \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/Microsoft.DotNet.PlatformAbstractions.dll \ + ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/Microsoft.Extensions.DependencyModel.dll + - name: Combine projects + run: | + mkdir -p ${{ env.OUTPUT_PATH }}/plugins + mkdir -p ${{ env.OUTPUT_PATH }}/shared + cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }} ${{ env.OUTPUT_PATH }}/plugins/ + cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }} ${{ env.OUTPUT_PATH }}/shared/ + - name: Zip combined + uses: thedoctor0/zip-release@0.7.6 + with: + type: 'zip' + filename: '${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}-${{ env.BUILD_NUMBER }}.zip' + path: ${{ env.OUTPUT_PATH }} + - name: Publish combined release + uses: ncipollo/release-action@v1.14.0 + with: + artifacts: "${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}-${{ env.BUILD_NUMBER }}.zip" + name: "CS2-SimpleAdmin - Build ${{ env.BUILD_NUMBER }}" + tag: "build-${{ env.BUILD_NUMBER }}" + body: | + Place files in addons/counterstrikesharp + After first launch, configure the plugins in the respective configs: + - CS2-SimpleAdmin: addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json diff --git a/CS2-SimpleAdmin.sln b/CS2-SimpleAdmin.sln new file mode 100644 index 0000000..e59a911 --- /dev/null +++ b/CS2-SimpleAdmin.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS2-SimpleAdmin", "CS2-SimpleAdmin\CS2-SimpleAdmin.csproj", "{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS2-SimpleAdminApi", "CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj", "{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Release|Any CPU.Build.0 = Release|Any CPU + {8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {86114444-059F-4DB8-9A55-E6D1BB76238D} + EndGlobalSection +EndGlobal diff --git a/CS2-SimpleAdmin/3rd_party/MenuManagerApi.dll b/CS2-SimpleAdmin/3rd_party/MenuManagerApi.dll new file mode 100644 index 0000000..c4f723a Binary files /dev/null and b/CS2-SimpleAdmin/3rd_party/MenuManagerApi.dll differ diff --git a/CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs b/CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs new file mode 100644 index 0000000..89a704f --- /dev/null +++ b/CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs @@ -0,0 +1,75 @@ +using System.Diagnostics; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Entities; +using CS2_SimpleAdmin.Managers; +using CS2_SimpleAdminApi; + +namespace CS2_SimpleAdmin.Api; + +public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi +{ + public PlayerInfo GetPlayerInfo(CCSPlayerController player) + { + if (!player.UserId.HasValue) + throw new KeyNotFoundException("Player with specific UserId not found"); + + return CS2_SimpleAdmin.PlayersInfo[player.UserId.Value]; + } + + public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString; + public string GetServerAddress() => CS2_SimpleAdmin.IpAddress; + public int? GetServerId() => CS2_SimpleAdmin.ServerId; + + public Dictionary> GetPlayerMuteStatus(CCSPlayerController player) + { + return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot); + } + + public event Action? OnPlayerPenaltied; + public event Action? OnPlayerPenaltiedAdded; + + public void OnPlayerPenaltiedEvent(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType, string reason, + int duration = -1) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, CS2_SimpleAdmin.ServerId); + + public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason, + int duration) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration, CS2_SimpleAdmin.ServerId); + + public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1) + { + switch (penaltyType) + { + case PenaltyType.Ban: + { + CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason); + break; + } + case PenaltyType.Kick: + { + CS2_SimpleAdmin.Instance.Kick(admin, player, reason); + break; + } + case PenaltyType.Gag: + { + CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason); + break; + } + case PenaltyType.Mute: + { + CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason); + break; + } + case PenaltyType.Silence: + { + CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason); + break; + } + case PenaltyType.Warn: + { + CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null); + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs new file mode 100644 index 0000000..f52a65a --- /dev/null +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs @@ -0,0 +1,136 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes; +using CounterStrikeSharp.API.Core.Capabilities; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Commands.Targeting; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CS2_SimpleAdmin.Managers; +using CS2_SimpleAdminApi; +using Discord.Webhook; +using Microsoft.Extensions.Logging; +using MySqlConnector; + +namespace CS2_SimpleAdmin; + +[MinimumApiVersion(260)] +public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig +{ + internal static CS2_SimpleAdmin Instance { get; private set; } = new(); + + public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)"); + public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)"; + public override string ModuleAuthor => "daffyy & Dliix66"; + public override string ModuleVersion => "1.6.0a"; + + public CS2_SimpleAdminConfig Config { get; set; } = new(); + + public override void Load(bool hotReload) + { + Instance = this; + + RegisterEvents(); + + if (hotReload) + { + ServerLoaded = false; + OnGameServerSteamAPIActivated(); + OnMapStart(string.Empty); + + AddTimer(2.0f, () => + { + if (Database == null) return; + + Helper.GetValidPlayers().ForEach(player => + { + var playerManager = new PlayerManager(); + playerManager.LoadPlayerData(player); + }); + }); + } + _cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid(GameData.GetSignature("CBasePlayerController_SetPawn")); + + SimpleAdminApi = new Api.CS2_SimpleAdminApi(); + Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi); + } + + public override void OnAllPluginsLoaded(bool hotReload) + { + AddTimer(3.0f, () => ReloadAdmins(null)); + + MenuApi = MenuCapability.Get(); + if (MenuApi == null) + Logger.LogError("MenuManager Core not found..."); + } + + public void OnConfigParsed(CS2_SimpleAdminConfig config) + { + if (config.DatabaseHost.Length < 1 || config.DatabaseName.Length < 1 || config.DatabaseUser.Length < 1) + { + throw new Exception("[CS2-SimpleAdmin] You need to setup Database credentials in config!"); + } + + Instance = this; + _logger = Logger; + + MySqlConnectionStringBuilder builder = new() + { + Server = config.DatabaseHost, + Database = config.DatabaseName, + UserID = config.DatabaseUser, + Password = config.DatabasePassword, + Port = (uint)config.DatabasePort, + Pooling = true, + MinimumPoolSize = 0, + MaximumPoolSize = 640, + }; + + DbConnectionString = builder.ConnectionString; + Database = new Database.Database(DbConnectionString); + + if (!Database.CheckDatabaseConnection()) + { + Logger.LogError("Unable connect to database!"); + Unload(false); + return; + } + + Task.Run(() => Database.DatabaseMigration()); + + Config = config; + Helper.UpdateConfig(config); + + if (!Directory.Exists(ModuleDirectory + "/data")) + { + Directory.CreateDirectory(ModuleDirectory + "/data"); + } + + _localizer = Localizer; + + if (!string.IsNullOrEmpty(Config.Discord.DiscordLogWebhook)) + DiscordWebhookClientLog = new DiscordWebhookClient(Config.Discord.DiscordLogWebhook); + + PluginInfo.ShowAd(ModuleVersion); + if (Config.EnableUpdateCheck) + Task.Run(async () => await PluginInfo.CheckVersion(ModuleVersion, _logger)); + } + + private static TargetResult? GetTarget(CommandInfo command) + { + var matches = command.GetArgTargetResult(1); + + if (!matches.Any()) + { + command.ReplyToCommand($"Target {command.GetArg(1)} not found."); + return null; + } + + if (command.GetArg(1).StartsWith('@')) + return matches; + + if (matches.Count() == 1) + return matches; + + command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(1)}\"."); + return null; + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj new file mode 100644 index 0000000..7d834d8 --- /dev/null +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj @@ -0,0 +1,45 @@ + + + + net8.0 + CS2_SimpleAdmin + enable + enable + true + true + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + 3rd_party\MenuManagerApi.dll + + + + diff --git a/CS2-SimpleAdmin/Commands/basebans.cs b/CS2-SimpleAdmin/Commands/basebans.cs new file mode 100644 index 0000000..e0f3af4 --- /dev/null +++ b/CS2-SimpleAdmin/Commands/basebans.cs @@ -0,0 +1,421 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.ValveConstants.Protobuf; +using CS2_SimpleAdmin.Managers; +using CS2_SimpleAdminApi; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + [ConsoleCommand("css_ban")] + [RequiresPermissions("@css/ban")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnBanCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + if (command.ArgCount < 2) + return; + + var reason = _localizer?["sa_unknown"] ?? "Unknown"; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList(); + + if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) + { + return; + } + + Database.Database database = new(DbConnectionString); + BanManager banManager = new(database, Config); + + int.TryParse(command.GetArg(2), out var time); + + if (command.ArgCount >= 3 && command.GetArg(3).Length > 0) + reason = command.GetArg(3); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + Ban(caller, player, time, reason, callerName, banManager, command); + } + }); + } + + internal void Ban(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, BanManager? banManager = null, CommandInfo? command = null, bool silent = false) + { + if (Database == null || !player.IsValid || !player.UserId.HasValue) return; + if (!caller.CanTarget(player)) return; + if (!CheckValidBan(caller, time)) return; + + // Set default caller name if not provided + callerName ??= "Console"; + + // Freeze player pawn if alive + if (player.PawnIsAlive) + { + player.Pawn.Value?.Freeze(); + } + + // Get player and admin information + var playerInfo = PlayersInfo[player.UserId.Value]; + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Asynchronously handle banning logic + Task.Run(async () => + { + await (banManager ??= new BanManager(Database, Config)).BanPlayer(playerInfo, adminInfo, reason, time); + }); + + // Update banned players list + if (playerInfo.IpAddress != null && !BannedPlayers.Contains(playerInfo.IpAddress)) + BannedPlayers.Add(playerInfo.IpAddress); + if (!BannedPlayers.Contains(player.SteamID.ToString())) + BannedPlayers.Add(player.SteamID.ToString()); + + // Determine message keys and arguments based on ban time + var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0 + ? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm", + [reason, "CALLER"], + ["CALLER", player.PlayerName, reason]) + : ("sa_player_ban_message_time", "sa_admin_ban_message_time", + new object[] { reason, time, "CALLER" }, + new object[] { "CALLER", player.PlayerName, reason, time }); + + // Display center message to the player + Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs); + + // Display admin activity message if necessary + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Schedule a kick timer + if (player.UserId.HasValue) + { + AddTimer(Config.OtherSettings.KickTime, () => + { + if (player is { IsValid: true, UserId: not null }) + { + Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + + // Execute ban command if necessary + if (UnlockedCommands) + { + Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}"); + } + + if (!silent) + { + if (command == null) + Helper.LogCommand(caller, $"css_ban {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}"); + else + Helper.LogCommand(caller, command); + } + + Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer); + SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time); + } + + [ConsoleCommand("css_addban")] + [RequiresPermissions("@css/ban")] + [CommandHelper(minArgs: 1, usage: " [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + var callerName = caller?.PlayerName ?? "Console"; + if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return; + if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null) + { + command.ReplyToCommand("Invalid SteamID64."); + return; + } + + var steamid = steamId.SteamId64.ToString(); + var reason = command.ArgCount >= 3 && !string.IsNullOrEmpty(command.GetArg(3)) + ? command.GetArg(3) + : _localizer?["sa_unknown"] ?? "Unknown"; + + int.TryParse(command.GetArg(2), out var time); + if (!CheckValidBan(caller, time)) return; + + var adminInfo = caller != null && caller.UserId.HasValue + ? PlayersInfo[caller.UserId.Value] + : null; + + var matches = Helper.GetPlayerFromSteamid64(steamid); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + if (!caller.CanTarget(player)) + return; + + Ban(caller, player, time, reason, callerName, silent: true); + //command.ReplyToCommand($"Banned player {player.PlayerName}."); + } + else + { + // Asynchronous ban operation if player is not online or not found + Task.Run(async () => + { + var banManager = new BanManager(Database, Config); + await banManager.AddBanBySteamid(steamid, adminInfo, reason, time); + }); + + command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline."); + } + + Helper.LogCommand(caller, command); + + if (UnlockedCommands) + Server.ExecuteCommand($"banid 1 {steamId.SteamId3}"); + + SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time); + } + + [ConsoleCommand("css_banip")] + [RequiresPermissions("@css/ban")] + [CommandHelper(minArgs: 1, usage: " [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnBanIpCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + var callerName = caller?.PlayerName ?? "Console"; + if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return; + var ipAddress = command.GetArg(1); + + if (!Helper.IsValidIp(ipAddress)) + { + command.ReplyToCommand($"Invalid IP address."); + return; + } + + var reason = command.ArgCount >= 3 && !string.IsNullOrEmpty(command.GetArg(3)) + ? command.GetArg(3) + : _localizer?["sa_unknown"] ?? "Unknown"; + + int.TryParse(command.GetArg(2), out var time); + if (!CheckValidBan(caller, time)) return; + + var adminInfo = caller != null && caller.UserId.HasValue + ? PlayersInfo[caller.UserId.Value] + : null; + + var matches = Helper.GetPlayerFromIp(ipAddress); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + if (!caller.CanTarget(player)) + return; + + Ban(caller, player, time, reason, callerName, silent: true); + } + else + { + // Asynchronous ban operation if player is not online or not found + Task.Run(async () => + { + var banManager = new BanManager(Database, Config); + await banManager.AddBanByIp(ipAddress, adminInfo, reason, time); + }); + + command.ReplyToCommand($"Player with ip {ipAddress} is not online. Ban has been added offline."); + } + + Helper.LogCommand(caller, command); + } + + private bool CheckValidBan(CCSPlayerController? caller, int duration) + { + if (caller == null) return true; + + bool canPermBan = AdminManager.PlayerHasPermissions(caller, "@css/permban"); + + if (duration <= 0 && canPermBan == false) + { + caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}"); + return false; + } + + if (duration <= Config.OtherSettings.MaxBanDuration || canPermBan) return true; + + caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_max_duration_exceeded", Config.OtherSettings.MaxBanDuration]}"); + return false; + } + + [ConsoleCommand("css_unban")] + [RequiresPermissions("@css/unban")] + [CommandHelper(minArgs: 1, usage: " [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnUnbanCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + var callerSteamId = caller?.SteamID.ToString() ?? "Console"; + + if (command.GetArg(1).Length <= 1) + { + command.ReplyToCommand($"Too short pattern to search."); + return; + } + + var pattern = command.GetArg(1); + var reason = command.GetArg(2); + + BanManager banManager = new(Database, Config); + Task.Run(async () => await banManager.UnbanPlayer(pattern, callerSteamId, reason)); + + Helper.LogCommand(caller, command); + + command.ReplyToCommand($"Unbanned player with pattern {pattern}."); + } + + [ConsoleCommand("css_warn")] + [RequiresPermissions("@css/kick")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnWarnCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) + return; + var callerName = caller == null ? "Console" : caller.PlayerName; + if (command.ArgCount < 2) + return; + + var reason = _localizer?["sa_unknown"] ?? "Unknown"; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList(); + + if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) + { + return; + } + + WarnManager warnManager = new(Database); + + int.TryParse(command.GetArg(2), out var time); + + if (command.ArgCount >= 3 && command.GetArg(3).Length > 0) + reason = command.GetArg(3); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + Warn(caller, player, time, reason, callerName, warnManager, command); + } + }); + } + + internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, WarnManager? warnManager = null, CommandInfo? command = null) + { + if (Database == null || !player.IsValid || !player.UserId.HasValue) return; + if (!caller.CanTarget(player)) return; + if (!CheckValidBan(caller, time)) return; + + // Set default caller name if not provided + callerName ??= "Console"; + + // Freeze player pawn if alive + if (player.PawnIsAlive) + { + player.Pawn.Value?.Freeze(); + } + + // Get player and admin information + var playerInfo = PlayersInfo[player.UserId.Value]; + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Asynchronously handle warning logic + Task.Run(async () => + { + warnManager ??= new WarnManager(Database); + await warnManager.WarnPlayer(playerInfo, adminInfo, reason, time); + + // Check for warn thresholds and execute punish command if applicable + var totalWarns = await warnManager.GetPlayerWarnsCount(player.SteamID.ToString()); + if (Config.WarnThreshold.Count > 0) + { + string? punishCommand = null; + var lastKey = Config.WarnThreshold.Keys.Max(); + + if (totalWarns >= lastKey) + punishCommand = Config.WarnThreshold[lastKey]; + else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value)) + punishCommand = value; + + if (!string.IsNullOrEmpty(punishCommand)) + { + await Server.NextFrameAsync(() => + { + Server.ExecuteCommand(punishCommand.Replace("USERID", playerInfo.UserId.ToString()).Replace("STEAMID64", playerInfo.SteamId?.ToString())); + }); + } + } + }); + + // Determine message keys and arguments based on warning time + var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0 + ? ("sa_player_warn_message_perm", "sa_admin_warn_message_perm", + new object[] { reason, "CALLER" }, + new object[] { "CALLER", player.PlayerName, reason }) + : ("sa_player_warn_message_time", "sa_admin_warn_message_time", + [reason, time, "CALLER"], + ["CALLER", player.PlayerName, reason, time]); + + // Display center message to the playser + Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs); + + // Display admin activity message if necessary + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Log the warning command + if (command == null) + Helper.LogCommand(caller, $"css_warn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}"); + else + Helper.LogCommand(caller, command); + + // Send Discord notification for the warning + Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer); + SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time); + } + + [ConsoleCommand("css_unwarn")] + [RequiresPermissions("@css/kick")] + [CommandHelper(minArgs: 1, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnUnwarnCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + var callerSteamId = caller?.SteamID.ToString() ?? "Console"; + + if (command.GetArg(1).Length <= 1) + { + command.ReplyToCommand($"Too short pattern to search."); + return; + } + + var pattern = command.GetArg(1); + + WarnManager warnManager = new(Database); + Task.Run(async () => await warnManager.UnwarnPlayer(pattern)); + + Helper.LogCommand(caller, command); + command.ReplyToCommand($"Unwarned player with pattern {pattern}."); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Commands/basechat.cs b/CS2-SimpleAdmin/Commands/basechat.cs new file mode 100644 index 0000000..74e18e7 --- /dev/null +++ b/CS2-SimpleAdmin/Commands/basechat.cs @@ -0,0 +1,127 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Core.Translations; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Utils; +using System.Text; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + [ConsoleCommand("css_asay", "Say to all admins.")] + [CommandHelper(1, "")] + [RequiresPermissions("@css/chat")] + public void OnAdminToAdminSayCommand(CCSPlayerController? caller, CommandInfo command) + { + Helper.LogCommand(caller, command); + + var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]); + var utf8String = Encoding.UTF8.GetString(utf8BytesString); + + foreach (var player in Helper.GetValidPlayers() + .Where(p => AdminManager.PlayerHasPermissions(p, "@css/chat"))) + { + if (_localizer != null) + player.PrintToChat(_localizer["sa_adminchat_template_admin", + caller == null ? "Console" : caller.PlayerName, + utf8String]); + } + } + + [ConsoleCommand("css_cssay", "Say custom text to all players - u can use color tags.")] + [CommandHelper(1, "")] + [RequiresPermissions("@css/chat")] + public void OnAdminCustomSayCommand(CCSPlayerController? caller, CommandInfo command) + { + if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return; + + var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]); + var utf8String = Encoding.UTF8.GetString(utf8BytesString); + + Helper.LogCommand(caller, command); + + foreach (var player in Helper.GetValidPlayers()) + { + player.PrintToChat(utf8String.ReplaceColorTags()); + } + } + + [ConsoleCommand("css_say", "Say to all players.")] + [CommandHelper(1, "")] + [RequiresPermissions("@css/chat")] + public void OnAdminSayCommand(CCSPlayerController? caller, CommandInfo command) + { + if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return; + + var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]); + var utf8String = Encoding.UTF8.GetString(utf8BytesString); + + Helper.LogCommand(caller, command); + + foreach (var player in Helper.GetValidPlayers()) + { + player.SendLocalizedMessage(_localizer, + "sa_adminsay_prefix", + utf8String.ReplaceColorTags()); + } + } + + [ConsoleCommand("css_psay", "Private message a player.")] + [CommandHelper(2, "<#userid or name> ")] + [RequiresPermissions("@css/chat")] + public void OnAdminPrivateSayCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + //Helper.LogCommand(caller, command); + + var range = command.GetArg(0).Length + command.GetArg(1).Length + 2; + var message = command.GetCommandString[range..]; + + var utf8BytesString = Encoding.UTF8.GetBytes(message); + var utf8String = Encoding.UTF8.GetString(utf8BytesString); + + playersToTarget.ForEach(player => + { + player.PrintToChat($"({callerName}) {utf8String}".ReplaceColorTags()); + }); + + command.ReplyToCommand($" Private message sent!"); + } + + [ConsoleCommand("css_csay", "Say to all players (in center).")] + [CommandHelper(1, "")] + [RequiresPermissions("@css/chat")] + public void OnAdminCenterSayCommand(CCSPlayerController? caller, CommandInfo command) + { + var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]); + var utf8String = Encoding.UTF8.GetString(utf8BytesString); + + Helper.LogCommand(caller, command); + + Helper.PrintToCenterAll(utf8String.ReplaceColorTags()); + } + + [ConsoleCommand("css_hsay", "Say to all players (in hud).")] + [CommandHelper(1, "")] + [RequiresPermissions("@css/chat")] + public void OnAdminHudSayCommand(CCSPlayerController? caller, CommandInfo command) + { + var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]); + var utf8String = Encoding.UTF8.GetString(utf8BytesString); + + Helper.LogCommand(caller, command); + + VirtualFunctions.ClientPrintAll( + HudDestination.Alert, + utf8String.ReplaceColorTags(), + 0, 0, 0, 0); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Commands/basecommands.cs b/CS2-SimpleAdmin/Commands/basecommands.cs new file mode 100644 index 0000000..0db6de1 --- /dev/null +++ b/CS2-SimpleAdmin/Commands/basecommands.cs @@ -0,0 +1,908 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Core.Translations; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.Modules.Menu; +using CounterStrikeSharp.API.Modules.Utils; +using CS2_SimpleAdmin.Managers; +using CS2_SimpleAdmin.Menus; +using CS2_SimpleAdminApi; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System.Globalization; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + [ConsoleCommand("css_penalties")] + [ConsoleCommand("css_mypenalties")] + [ConsoleCommand("css_comms")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void OnPenaltiesCommand(CCSPlayerController? caller, CommandInfo command) + { + if (caller == null || caller.IsValid == false || !caller.UserId.HasValue || Database == null) + return; + + var userId = caller.UserId.Value; + + Task.Run(async () => + { + // Initialize managers + MuteManager muteManager = new(Database); + WarnManager warnManager = new(Database); + + try + { + var warns = await warnManager.GetPlayerWarns(PlayersInfo[userId], false); + + // Check if the player is muted + var activeMutes = await muteManager.IsPlayerMuted(PlayersInfo[userId].SteamId.SteamId64.ToString()); + + Dictionary> mutesList = new() + { + { PenaltyType.Gag, [] }, + { PenaltyType.Mute, [] }, + { PenaltyType.Silence, [] } + }; + + List warnsList = []; + + bool found = false; + foreach (var warn in warns.TakeWhile(warn => (string)warn.status == "ACTIVE")) + { + DateTime ends = warn.ends; + if (_localizer == null) continue; + using (new WithTemporaryCulture(caller.GetLanguage())) + warnsList.Add(_localizer["sa_player_penalty_info_active_warn", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture), (string)warn.reason]); + found = true; + } + + if (!found) + { + if (_localizer != null) + warnsList.Add(_localizer["sa_player_penalty_info_no_active_warn"]); + } + + if (activeMutes.Count > 0) + { + foreach (var mute in activeMutes) + { + string muteType = mute.type; + DateTime ends = mute.ends; + using (new WithTemporaryCulture(caller.GetLanguage())) + { + switch (muteType) + { + // Apply mute penalty based on mute type + case "GAG": + if (_localizer != null) + mutesList[PenaltyType.Gag].Add(_localizer["sa_player_penalty_info_active_gag", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]); + break; + case "MUTE": + if (_localizer != null) + mutesList[PenaltyType.Mute].Add(_localizer["sa_player_penalty_info_active_mute", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]); + break; + default: + if (_localizer != null) + mutesList[PenaltyType.Silence].Add(_localizer["sa_player_penalty_info_active_silence", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]); + break; + } + } + } + } + + if (_localizer != null) + { + if (mutesList[PenaltyType.Gag].Count == 0) + mutesList[PenaltyType.Gag].Add(_localizer["sa_player_penalty_info_no_active_gag"]); + if (mutesList[PenaltyType.Mute].Count == 0) + mutesList[PenaltyType.Mute].Add(_localizer["sa_player_penalty_info_no_active_mute"]); + if (mutesList[PenaltyType.Silence].Count == 0) + mutesList[PenaltyType.Silence].Add(_localizer["sa_player_penalty_info_no_active_silence"]); + } + + await Server.NextFrameAsync(() => + { + caller.SendLocalizedMessage(_localizer, "sa_player_penalty_info", + [ + caller.PlayerName, + PlayersInfo[userId].TotalBans, + PlayersInfo[userId].TotalGags, + PlayersInfo[userId].TotalMutes, + PlayersInfo[userId].TotalSilences, + PlayersInfo[userId].TotalWarns, + string.Join("\n", mutesList.SelectMany(kvp => kvp.Value)), + string.Join("\n", warnsList) + ]); + }); + } + catch (Exception ex) + { + _logger?.LogError($"Error processing player information: {ex}"); + } + }); + } + + [ConsoleCommand("css_admin")] + [RequiresPermissions("@css/generic")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void OnAdminCommand(CCSPlayerController? caller, CommandInfo command) + { + if (caller == null || caller.IsValid == false) + return; + + AdminMenu.OpenMenu(caller); + } + + [ConsoleCommand("css_adminhelp")] + [RequiresPermissions("@css/generic")] + public void OnAdminHelpCommand(CCSPlayerController? caller, CommandInfo command) + { + var lines = File.ReadAllLines(ModuleDirectory + "/admin_help.txt"); + + foreach (var line in lines) + { + command.ReplyToCommand(string.IsNullOrWhiteSpace(line) ? " " : line.ReplaceColorTags()); + } + } + + [ConsoleCommand("css_addadmin")] + [CommandHelper(minArgs: 4, usage: " ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void OnAddAdminCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + + if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null) + { + command.ReplyToCommand($"Invalid SteamID64."); + return; + } + + var steamid = steamId.SteamId64.ToString(); + + if (command.GetArg(2).Length <= 0) + { + command.ReplyToCommand($"Invalid player name."); + return; + } + if (!command.GetArg(3).Contains('@') && !command.GetArg(3).Contains('#')) + { + command.ReplyToCommand($"Invalid flag or group."); + return; + } + + var name = command.GetArg(2); + var flags = command.GetArg(3); + var globalAdmin = command.GetArg(4).ToLower().Equals("-g") || command.GetArg(5).ToLower().Equals("-g") || + command.GetArg(6).ToLower().Equals("-g"); + int.TryParse(command.GetArg(4), out var immunity); + int.TryParse(command.GetArg(5), out var time); + + AddAdmin(caller, steamid, name, flags, immunity, time, globalAdmin, command); + } + + public static void AddAdmin(CCSPlayerController? caller, string steamid, string name, string flags, int immunity, int time = 0, bool globalAdmin = false, CommandInfo? command = null) + { + if (Database == null) return; + PermissionManager adminManager = new(Database); + + var flagsList = flags.Split(',').Select(flag => flag.Trim()).ToList(); + _ = adminManager.AddAdminBySteamId(steamid, name, flagsList, immunity, time, globalAdmin); + + Helper.LogCommand(caller, $"css_addadmin {steamid} {name} {flags} {immunity} {time}"); + + var msg = $"Added '{flags}' flags to '{name}' ({steamid})"; + if (command != null) + command.ReplyToCommand(msg); + else if (caller != null && caller.IsValid) + caller.PrintToChat(msg); + else + Server.PrintToConsole(msg); + } + + [ConsoleCommand("css_deladmin")] + [CommandHelper(minArgs: 1, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void OnDelAdminCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null) + { + command.ReplyToCommand($"Invalid SteamID64."); + return; + } + + var globalDelete = command.GetArg(2).ToLower().Equals("-g"); + + RemoveAdmin(caller, steamId.SteamId64.ToString(), globalDelete, command); + } + + public void RemoveAdmin(CCSPlayerController? caller, string steamid, bool globalDelete = false, CommandInfo? command = null) + { + if (Database == null) return; + PermissionManager adminManager = new(Database); + _ = adminManager.DeleteAdminBySteamId(steamid, globalDelete); + + AddTimer(2, () => + { + if (string.IsNullOrEmpty(steamid) || !SteamID.TryParse(steamid, out var steamId) || + steamId == null) return; + if (PermissionManager.AdminCache.ContainsKey(steamId)) + { + PermissionManager.AdminCache.TryRemove(steamId, out _); + } + + AdminManager.ClearPlayerPermissions(steamId); + AdminManager.RemovePlayerAdminData(steamId); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + Helper.LogCommand(caller, $"css_deladmin {steamid}"); + + var msg = $"Removed flags from '{steamid}'"; + if (command != null) + command.ReplyToCommand(msg); + else if (caller != null && caller.IsValid) + caller.PrintToChat(msg); + else + Server.PrintToConsole(msg); + } + + [ConsoleCommand("css_addgroup")] + [CommandHelper(minArgs: 3, usage: " ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void OnAddGroup(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + if (!command.GetArg(1).StartsWith("#")) + { + command.ReplyToCommand($"Group name must start with #."); + return; + } + + if (!command.GetArg(2).StartsWith($"@") && !command.GetArg(2).StartsWith($"#")) + { + command.ReplyToCommand($"Invalid flag or group."); + return; + } + + var groupName = command.GetArg(1); + var flags = command.GetArg(2); + int.TryParse(command.GetArg(3), out var immunity); + var globalGroup = command.GetArg(4).ToLower().Equals("-g"); + + AddGroup(caller, groupName, flags, immunity, globalGroup, command); + } + + private static void AddGroup(CCSPlayerController? caller, string name, string flags, int immunity, bool globalGroup, CommandInfo? command = null) + { + if (Database == null) return; + PermissionManager adminManager = new(Database); + + var flagsList = flags.Split(',').Select(flag => flag.Trim()).ToList(); + _ = adminManager.AddGroup(name, flagsList, immunity, globalGroup); + + Helper.LogCommand(caller, $"css_addgroup {name} {flags} {immunity}"); + + var msg = $"Created group '{name}' with flags '{flags}'"; + if (command != null) + command.ReplyToCommand(msg); + else if (caller != null && caller.IsValid) + caller.PrintToChat(msg); + else + Server.PrintToConsole(msg); + } + + [ConsoleCommand("css_delgroup")] + [CommandHelper(minArgs: 1, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void OnDelGroupCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + if (!command.GetArg(1).StartsWith($"#")) + { + command.ReplyToCommand($"Group name must start with #."); + return; + } + + var groupName = command.GetArg(1); + + RemoveGroup(caller, groupName, command); + } + + private void RemoveGroup(CCSPlayerController? caller, string name, CommandInfo? command = null) + { + if (Database == null) return; + PermissionManager adminManager = new(Database); + _ = adminManager.DeleteGroup(name); + + AddTimer(2, () => + { + ReloadAdmins(caller); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + Helper.LogCommand(caller, $"css_delgroup {name}"); + + var msg = $"Removed group '{name}'"; + if (command != null) + command.ReplyToCommand(msg); + else if (caller != null && caller.IsValid) + caller.PrintToChat(msg); + else + Server.PrintToConsole(msg); + } + + [ConsoleCommand("css_reloadadmins")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void OnRelAdminCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + ReloadAdmins(caller); + + command.ReplyToCommand("Reloaded sql admins and groups"); + } + + public void ReloadAdmins(CCSPlayerController? caller) + { + if (Database == null) return; + + for (var index = 0; index < PermissionManager.AdminCache.Keys.ToList().Count; index++) + { + var steamId = PermissionManager.AdminCache.Keys.ToList()[index]; + if (!PermissionManager.AdminCache.TryRemove(steamId, out _)) continue; + + AdminManager.ClearPlayerPermissions(steamId); + AdminManager.RemovePlayerAdminData(steamId); + } + + PermissionManager adminManager = new(Database); + + Task.Run(async () => + { + await adminManager.CrateGroupsJsonFile(); + await adminManager.CreateAdminsJsonFile(); + + var adminsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/admins.json"); + var groupsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/groups.json"); + + await Server.NextFrameAsync(() => + { + if (!string.IsNullOrEmpty(adminsFile)) + AddTimer(0.5f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); + if (!string.IsNullOrEmpty(groupsFile)) + AddTimer(1.0f, () => AdminManager.LoadAdminGroups(ModuleDirectory + "/data/groups.json")); + if (!string.IsNullOrEmpty(adminsFile)) + AddTimer(1.5f, () => AdminManager.LoadAdminData(ModuleDirectory + "/data/admins.json")); + }); + }); + + //_ = _adminManager.GiveAllGroupsFlags(); + //_ = _adminManager.GiveAllFlags(); + } + + [ConsoleCommand("css_stealth")] + [ConsoleCommand("css_hide")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + [RequiresPermissions("@css/kick")] + public void OnHideCommand(CCSPlayerController? caller, CommandInfo command) + { + if (caller == null) return; + + Helper.LogCommand(caller, command); + + if (!SilentPlayers.Add(caller.Slot)) + { + SilentPlayers.Remove(caller.Slot); + caller.PrintToChat($"You aren't hidden now!"); + caller.ChangeTeam(CsTeam.Spectator); + } + else + { + Server.ExecuteCommand("sv_disable_teamselect_menu 1"); + + if (caller.PlayerPawn.Value != null && caller.PawnIsAlive) + caller.PlayerPawn.Value.CommitSuicide(true, false); + + AddTimer(1.0f, () => { Server.NextFrame(() => caller.ChangeTeam(CsTeam.Spectator)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + AddTimer(1.4f, () => { Server.NextFrame(() => caller.ChangeTeam(CsTeam.None)); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + caller.PrintToChat($"You are hidden now!"); + AddTimer(2.0f, () => { Server.NextFrame(() => Server.ExecuteCommand("sv_disable_teamselect_menu 0")); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + } + + [ConsoleCommand("css_who")] + [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/generic")] + public void OnWhoCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + var targets = GetTarget(command); + if (targets == null) return; + + Helper.LogCommand(caller, command); + + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (!player.UserId.HasValue) return; + if (!caller!.CanTarget(player)) return; + + var playerInfo = PlayersInfo[player.UserId.Value]; + + Task.Run(async () => + { + await Server.NextFrameAsync(() => + { + Action printMethod = caller == null ? Server.PrintToConsole : caller.PrintToConsole; + + printMethod($"--------- INFO ABOUT \"{playerInfo.Name}\" ---------"); + + printMethod($"• Clan: \"{player.Clan}\" Name: \"{playerInfo.Name}\""); + printMethod($"• UserID: \"{playerInfo.UserId}\""); + printMethod($"• SteamID64: \"{playerInfo.SteamId.SteamId64}\""); + if (player.Connected == PlayerConnectedState.PlayerConnected) + { + printMethod($"• SteamID2: \"{playerInfo.SteamId.SteamId2}\""); + printMethod($"• Community link: \"{playerInfo.SteamId.ToCommunityUrl()}\""); + } + if (playerInfo.IpAddress != null && AdminManager.PlayerHasPermissions(caller, "@css/showip")) + printMethod($"• IP Address: \"{playerInfo.IpAddress}\""); + printMethod($"• Ping: \"{player.Ping}\""); + if (player.Connected == PlayerConnectedState.PlayerConnected) + { + printMethod($"• Total Bans: \"{playerInfo.TotalBans}\""); + printMethod($"• Total Gags: \"{playerInfo.TotalGags}\""); + printMethod($"• Total Mutes: \"{playerInfo.TotalMutes}\""); + printMethod($"• Total Silences: \"{playerInfo.TotalSilences}\""); + printMethod($"• Total Warns: \"{playerInfo.TotalWarns}\""); + } + + printMethod($"--------- END INFO ABOUT \"{player.PlayerName}\" ---------"); + }); + }); + }); + } + + [ConsoleCommand("css_disconnected")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + [RequiresPermissions("@css/kick")] + public void OnDisconnectedCommand(CCSPlayerController? caller, CommandInfo command) + { + if (_localizer == null || caller == null) return; + + var disconnectedMenu = MenuApi?.NewMenu(_localizer["sa_menu_disconnected_title"]); + + DisconnectedPlayers.ForEach(player => + { + disconnectedMenu?.AddMenuOption(player.Name, (_, _) => + { + var disconnectedMenuAction = MenuApi?.NewMenu(_localizer["sa_menu_disconnected_action_title"]); + disconnectedMenuAction?.AddMenuOption(_localizer["sa_ban"], (_, _) => + { + DurationMenu.OpenMenu(caller, _localizer["sa_ban"], player, (_, _, duration) => + ReasonMenu.OpenMenu(caller, PenaltyType.Ban, "Powód", player, (_, _, reason) => + { + caller.ExecuteClientCommandFromServer($"css_addban {player.SteamId.SteamId64} {duration} \"{reason}\""); + })); + }); + disconnectedMenuAction?.AddMenuOption(_localizer["sa_mute"], (_, _) => + { + DurationMenu.OpenMenu(caller, _localizer["sa_mute"], player, (_, _, duration) => + ReasonMenu.OpenMenu(caller, PenaltyType.Mute, "Powód", player, (_, _, reason) => + { + caller.ExecuteClientCommandFromServer($"css_addmute {player.SteamId.SteamId64} {duration} \"{reason}\""); + })); + }); + disconnectedMenuAction?.AddMenuOption(_localizer["sa_gag"], (_, _) => + { + DurationMenu.OpenMenu(caller, _localizer["sa_gag"], player, (_, _, duration) => + ReasonMenu.OpenMenu(caller, PenaltyType.Mute, "Powód", player, (_, _, reason) => + { + caller.ExecuteClientCommandFromServer($"css_addgag {player.SteamId.SteamId64} {duration} \"{reason}\""); + })); + }); + disconnectedMenuAction?.AddMenuOption(_localizer["sa_silence"], (_, _) => + { + DurationMenu.OpenMenu(caller, _localizer["sa_silence"], player, (_, _, duration) => + ReasonMenu.OpenMenu(caller, PenaltyType.Mute, "Powód", player, (_, _, reason) => + { + caller.ExecuteClientCommandFromServer($"css_addsilence {player.SteamId.SteamId64} {duration} \"{reason}\""); + })); + }); + + disconnectedMenuAction?.Open(caller); + }); + }); + + disconnectedMenu?.Open(caller); + } + + [ConsoleCommand("css_warns")] + [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_ONLY)] + [RequiresPermissions("@css/kick")] + public void OnWarnsCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null || _localizer == null || caller == null) return; + + var targets = GetTarget(command); + if (targets == null) return; + + Helper.LogCommand(caller, command); + + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsBot: false }).ToList(); + + if (playersToTarget.Count > 1) + return; + + Database.Database database = new(DbConnectionString); + WarnManager warnManager = new(database); + + playersToTarget.ForEach(player => + { + if (!player.UserId.HasValue) return; + if (!caller!.CanTarget(player)) return; + + var userId = player.UserId.Value; + + IMenu? warnsMenu = MenuApi?.NewMenu(_localizer["sa_admin_warns_menu_title", player.PlayerName]); + + Task.Run(async () => + { + var warnsList = await warnManager.GetPlayerWarns(PlayersInfo[userId], false); + var sortedWarns = warnsList + .OrderBy(warn => (string)warn.status == "ACTIVE" ? 0 : 1) + .ThenByDescending(warn => (int)warn.id) + .ToList(); + + sortedWarns.ForEach(w => + { + warnsMenu?.AddMenuOption($"[{((string)w.status == "ACTIVE" ? $"{ChatColors.LightRed}X" : $"{ChatColors.Lime}✔️")}{ChatColors.Default}] {(string)w.reason}", + (controller, option) => + { + _ = warnManager.UnwarnPlayer(PlayersInfo[userId], (int)w.id); + player.PrintToChat(_localizer["sa_admin_warns_unwarn", player.PlayerName, (string)w.reason]); + }); + }); + + await Server.NextFrameAsync(() => + { + warnsMenu?.Open(caller); + }); + }); + }); + } + + [ConsoleCommand("css_players")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/generic")] + public void OnPlayersCommand(CCSPlayerController? caller, CommandInfo command) + { + var isJson = command.GetArg(1).ToLower().Equals("-json"); + var isDuplicate = command.GetArg(1).ToLower().Equals("-duplicate") || command.GetArg(2).ToLower().Equals("-duplicate"); + + var playersToTarget = isDuplicate + ? Helper.GetValidPlayers().GroupBy(player => player.IpAddress?.Split(":")[0] ?? "Unknown") + .Where(group => group.Count() > 1) + .SelectMany(group => group) + .ToList() + : Helper.GetValidPlayers(); + + if (!isJson) + { + if (caller != null) + { + caller.PrintToConsole($"--------- PLAYER LIST ---------"); + playersToTarget.ForEach(player => + { + caller.PrintToConsole( + $"• [#{player.UserId}] \"{player.PlayerName}\" (IP Address: \"{(AdminManager.PlayerHasPermissions(caller, "@css/showip") ? player.IpAddress?.Split(":")[0] : "Unknown")}\" SteamID64: \"{player.SteamID}\")"); + }); + caller.PrintToConsole($"--------- END PLAYER LIST ---------"); + } + else + { + Server.PrintToConsole($"--------- PLAYER LIST ---------"); + playersToTarget.ForEach(player => + { + Server.PrintToConsole($"• [#{player.UserId}] \"{player.PlayerName}\" (IP Address: \"{player.IpAddress?.Split(":")[0]}\" SteamID64: \"{player.SteamID}\")"); + }); + Server.PrintToConsole($"--------- END PLAYER LIST ---------"); + } + } + else + { + var playersJson = JsonConvert.SerializeObject(playersToTarget.Select((CCSPlayerController player) => + { + var matchStats = player.ActionTrackingServices?.MatchStats; + + return new + { + player.UserId, + Name = player.PlayerName, + SteamId = player.SteamID.ToString(), + IpAddress = AdminManager.PlayerHasPermissions(caller, "@css/showip") ? player.IpAddress?.Split(":")[0] ?? "Unknown" : "Unknown", + player.Ping, + IsAdmin = AdminManager.PlayerHasPermissions(player, "@css/ban") || AdminManager.PlayerHasPermissions(player, "@css/generic"), + Stats = new + { + player.Score, + Kills = matchStats?.Kills ?? 0, + Deaths = matchStats?.Deaths ?? 0, + player.MVPs + } + }; + })); + + if (caller != null) + caller.PrintToConsole(playersJson); + else + Server.PrintToConsole(playersJson); + } + } + + [ConsoleCommand("css_kick")] + [RequiresPermissions("@css/kick")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnKickCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + var reason = _localizer?["sa_unknown"] ?? "Unknown"; + + var targets = GetTarget(command); + + if (targets == null) return; + var playersToTarget = targets.Players + .Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) + { + return; + } + + if (command.ArgCount >= 2 && command.GetArg(2).Length > 0) + reason = command.GetArg(2); + + playersToTarget.ForEach(player => + { + if (!player.IsValid) + return; + + if (caller!.CanTarget(player)) + { + Kick(caller, player, reason, callerName, command); + } + }); + } + + public void Kick(CCSPlayerController? caller, CCSPlayerController player, string? reason = "Unknown", string? callerName = null, CommandInfo? command = null) + { + if (!player.IsValid) return; + if (!caller.CanTarget(player)) return; + if (!player.UserId.HasValue) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + reason ??= _localizer?["sa_unknown"] ?? "Unknown"; + + var playerInfo = PlayersInfo[player.UserId.Value]; + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Freeze player pawn if alive + if (player.PawnIsAlive) + { + player.Pawn.Value?.Freeze(); + } + + // Determine message keys and arguments for the kick notification + var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = + ("sa_player_kick_message", "sa_admin_kick_message", + new object[] { reason, "CALLER" }, + new object[] { "CALLER", player.PlayerName, reason }); + + // Display center message to the kicked player + Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Schedule the kick for the player + if (player.UserId.HasValue) + { + AddTimer(Config.OtherSettings.KickTime, () => + { + if (player.IsValid) + { + Helper.KickPlayer(player.UserId.Value); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + + // Log the command and send Discord notification + if (command == null) + Helper.LogCommand(caller, $"css_kick {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {reason}"); + else + Helper.LogCommand(caller, command); + + SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Kick, reason); + } + + [ConsoleCommand("css_changemap")] + [ConsoleCommand("css_map")] + [RequiresPermissions("@css/changemap")] + [CommandHelper(minArgs: 1, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnMapCommand(CCSPlayerController? caller, CommandInfo command) + { + var map = command.GetCommandString.Split(" ")[1]; + ChangeMap(caller, map, command); + } + + public void ChangeMap(CCSPlayerController? caller, string map, CommandInfo? command = null) + { + var callerName = caller != null ? caller.PlayerName : "Console"; + map = map.ToLower(); + + if (map.StartsWith("ws:")) + { + var issuedCommand = long.TryParse(map.Replace("ws:", ""), out var mapId) + ? $"host_workshop_map {mapId}" + : $"ds_workshop_changelevel {map.Replace("ws:", "")}"; + + AddTimer(3.0f, () => + { + Server.ExecuteCommand(issuedCommand); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + else + { + if (!Server.IsMapValid(map)) + { + var msg = $"Map {map} not found."; + if (command != null) + command.ReplyToCommand(msg); + else if (caller != null && caller.IsValid) + caller.PrintToChat(msg); + else + Server.PrintToConsole(msg); + return; + } + + AddTimer(3.0f, () => + { + Server.ExecuteCommand($"changelevel {map}"); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_changemap_message", + new object[] { "CALLER", map }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + Helper.LogCommand(caller, command?.GetCommandString ?? $"css_map {map}"); + } + + [ConsoleCommand("css_changewsmap", "Change workshop map.")] + [ConsoleCommand("css_wsmap", "Change workshop map.")] + [ConsoleCommand("css_workshop", "Change workshop map.")] + [CommandHelper(1, "")] + [RequiresPermissions("@css/changemap")] + public void OnWorkshopMapCommand(CCSPlayerController? caller, CommandInfo command) + { + var map = command.GetArg(1); + ChangeWorkshopMap(caller, map, command); + } + + public void ChangeWorkshopMap(CCSPlayerController? caller, string map, CommandInfo? command = null) + { + map = map.ToLower(); + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Determine the workshop command + var issuedCommand = long.TryParse(map, out var mapId) + ? $"host_workshop_map {mapId}" + : $"ds_workshop_changelevel {map}"; + + // Define the admin activity message and arguments + var activityMessageKey = "sa_admin_changemap_message"; + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, ["CALLER", map]); + } + + // Add timer to execute the map change command after a delay + AddTimer(3.0f, () => + { + Server.ExecuteCommand(issuedCommand); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + // Log the command for the change + Helper.LogCommand(caller, command?.GetCommandString ?? $"css_wsmap {map}"); + } + + [ConsoleCommand("css_cvar", "Change a cvar.")] + [CommandHelper(2, " ")] + [RequiresPermissions("@css/cvar")] + public void OnCvarCommand(CCSPlayerController? caller, CommandInfo command) + { + var cvar = ConVar.Find(command.GetArg(1)); + var callerName = caller == null ? "Console" : caller.PlayerName; + + if (cvar == null) + { + command.ReplyToCommand($"Cvar \"{command.GetArg(1)}\" not found."); + return; + } + + if (cvar.Name.Equals("sv_cheats") && !AdminManager.PlayerHasPermissions(caller, "@css/cheats")) + { + command.ReplyToCommand($"You don't have permissions to change \"{command.GetArg(1)}\"."); + return; + } + + Helper.LogCommand(caller, command); + + var value = command.GetArg(2); + + Server.ExecuteCommand($"{cvar.Name} {value}"); + + command.ReplyToCommand($"{callerName} changed cvar {cvar.Name} to {value}."); + Logger.LogInformation($"{callerName} changed cvar {cvar.Name} to {value}."); + } + + [ConsoleCommand("css_rcon", "Run a server console command.")] + [CommandHelper(1, "")] + [RequiresPermissions("@css/rcon")] + public void OnRconCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + + Helper.LogCommand(caller, command); + + Server.ExecuteCommand(command.ArgString); + command.ReplyToCommand($"{callerName} executed command {command.ArgString}."); + Logger.LogInformation($"{callerName} executed command ({command.ArgString})."); + } + + [ConsoleCommand("css_rr")] + [ConsoleCommand("css_rg")] + [ConsoleCommand("css_restart")] + [ConsoleCommand("css_restartgame")] + [RequiresPermissions("@css/generic")] + [CommandHelper(minArgs: 0, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnRestartCommand(CCSPlayerController? caller, CommandInfo command) + { + RestartGame(caller); + } + + public static void RestartGame(CCSPlayerController? admin) + { + Helper.LogCommand(admin, "css_restartgame"); + + // TODO: Localize + var name = admin == null ? "Console" : admin.PlayerName; + Server.PrintToChatAll($"[SA] {name}: Restarting game..."); + Server.ExecuteCommand("mp_restartgame 2"); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Commands/basecomms.cs b/CS2-SimpleAdmin/Commands/basecomms.cs new file mode 100644 index 0000000..4e70c63 --- /dev/null +++ b/CS2-SimpleAdmin/Commands/basecomms.cs @@ -0,0 +1,680 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CS2_SimpleAdmin.Managers; +using CS2_SimpleAdminApi; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + [ConsoleCommand("css_gag")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnGagCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + var callerName = caller == null ? "Console" : caller.PlayerName; + + var reason = _localizer?["sa_unknown"] ?? "Unknown"; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) + { + return; + } + + int.TryParse(command.GetArg(2), out var time); + + if (command.ArgCount >= 3 && command.GetArg(3).Length > 0) + reason = command.GetArg(3); + + MuteManager muteManager = new(Database); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + Gag(caller, player, time, reason, callerName, muteManager, command); + } + }); + } + + internal void Gag(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, MuteManager? muteManager = null, CommandInfo? command = null, bool silent = false) + { + if (Database == null || !player.IsValid || !player.UserId.HasValue) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller == null ? "Console" : caller.PlayerName; + muteManager ??= new MuteManager(Database); + + // Get player and admin information + var playerInfo = PlayersInfo[player.UserId.Value]; + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Asynchronously handle gag logic + Task.Run(async () => + { + await muteManager.MutePlayer(playerInfo, adminInfo, reason, time); + }); + + // Add penalty to the player's penalty manager + PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Gag, DateTime.Now.AddMinutes(time), time); + + // Determine message keys and arguments based on gag time (permanent or timed) + var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0 + ? ("sa_player_gag_message_perm", "sa_admin_gag_message_perm", + [reason, "CALLER"], + ["CALLER", player.PlayerName, reason]) + : ("sa_player_gag_message_time", "sa_admin_gag_message_time", + new object[] { reason, time, "CALLER" }, + new object[] { "CALLER", player.PlayerName, reason, time }); + + // Display center message to the gagged player + Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Increment the player's total gags count + PlayersInfo[player.UserId.Value].TotalGags++; + + // Log the gag command and send Discord notification + if (!silent) + { + if (command == null) + Helper.LogCommand(caller, $"css_gag {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}"); + else + Helper.LogCommand(caller, command); + } + + Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Gag, _localizer); + SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time); + } + + [ConsoleCommand("css_addgag")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: " [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnAddGagCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + // Set caller name + var callerName = caller == null ? "Console" : caller.PlayerName; + + // Validate command arguments + if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return; + + // Validate and extract SteamID + if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null) + { + command.ReplyToCommand("Invalid SteamID64."); + return; + } + + var steamid = steamId.SteamId64.ToString(); + var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0 + ? command.GetArg(3) + : (_localizer?["sa_unknown"] ?? "Unknown"); + + MuteManager muteManager = new(Database); + int.TryParse(command.GetArg(2), out var time); + + // Get player and admin info + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Attempt to match player based on SteamID + var matches = Helper.GetPlayerFromSteamid64(steamid); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + // Check if caller can target the player + if (!caller.CanTarget(player)) return; + + // Perform the gag for an online player + Gag(caller, player, time, reason, callerName, muteManager, silent: true); + } + else + { + // Asynchronous gag operation for offline players + Task.Run(async () => + { + await muteManager.AddMuteBySteamid(steamid, adminInfo, reason, time); + }); + + command.ReplyToCommand($"Player with steamid {steamid} is not online. Gag has been added offline."); + } + + // Log the gag command and respond to the command + Helper.LogCommand(caller, command); + SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time); + } + + [ConsoleCommand("css_ungag")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: " [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnUngagCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + var callerSteamId = caller?.SteamID.ToString() ?? "Console"; + var pattern = command.GetArg(1); + var reason = command.GetArg(2); + + if (pattern.Length <= 1) + { + command.ReplyToCommand($"Too short pattern to search."); + return; + } + + Helper.LogCommand(caller, command); + var muteManager = new MuteManager(Database); + + // Check if pattern is a valid SteamID64 + if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null) + { + var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Gag); + + Task.Run(async () => + { + await muteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason); + }); + + command.ReplyToCommand($"Ungaged player {player.PlayerName}."); + return; + } + } + + // If not a valid SteamID64, check by player name + var nameMatches = Helper.GetPlayerFromName(pattern); + var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null; + + if (namePlayer != null && namePlayer.IsValid) + { + PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Gag); + + if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalGags > 0) + PlayersInfo[namePlayer.UserId.Value].TotalGags--; + + Task.Run(async () => + { + await muteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason); + }); + + command.ReplyToCommand($"Ungaged player {namePlayer.PlayerName}."); + } + else + { + Task.Run(async () => + { + await muteManager.UnmutePlayer(pattern, callerSteamId, reason); + }); + + command.ReplyToCommand($"Ungaged offline player with pattern {pattern}."); + } + } + + [ConsoleCommand("css_mute")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnMuteCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + var callerName = caller == null ? "Console" : caller.PlayerName; + + var reason = _localizer?["sa_unknown"] ?? "Unknown"; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) + { + return; + } + + int.TryParse(command.GetArg(2), out var time); + + if (command.ArgCount >= 3 && command.GetArg(3).Length > 0) + reason = command.GetArg(3); + + MuteManager muteManager = new(Database); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + Mute(caller, player, time, reason, callerName, muteManager, command); + } + }); + } + + internal void Mute(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, MuteManager? muteManager = null, CommandInfo? command = null, bool silent = false) + { + if (Database == null || !player.IsValid || !player.UserId.HasValue) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller == null ? "Console" : caller.PlayerName; + muteManager ??= new MuteManager(Database); + + // Get player and admin information + var playerInfo = PlayersInfo[player.UserId.Value]; + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Set player's voice flags to muted + player.VoiceFlags = VoiceFlags.Muted; + + // Asynchronously handle mute logic + Task.Run(async () => + { + await muteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1); + }); + + // Add penalty to the player's penalty manager + PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Mute, DateTime.Now.AddMinutes(time), time); + + // Determine message keys and arguments based on mute time (permanent or timed) + var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0 + ? ("sa_player_mute_message_perm", "sa_admin_mute_message_perm", + [reason, "CALLER"], + ["CALLER", player.PlayerName, reason]) + : ("sa_player_mute_message_time", "sa_admin_mute_message_time", + new object[] { reason, time, "CALLER" }, + new object[] { "CALLER", player.PlayerName, reason, time }); + + // Display center message to the muted player + Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Increment the player's total mutes count + PlayersInfo[player.UserId.Value].TotalMutes++; + + // Log the mute command and send Discord notification + if (!silent) + { + if (command == null) + Helper.LogCommand(caller, $"css_mute {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}"); + else + Helper.LogCommand(caller, command); + } + + Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Mute, _localizer); + SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time); + } + + [ConsoleCommand("css_addmute")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: " [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnAddMuteCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + // Set caller name + var callerName = caller == null ? "Console" : caller.PlayerName; + + // Validate command arguments + if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return; + + // Validate and extract SteamID + if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null) + { + command.ReplyToCommand("Invalid SteamID64."); + return; + } + + var steamid = steamId.SteamId64.ToString(); + var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0 + ? command.GetArg(3) + : (_localizer?["sa_unknown"] ?? "Unknown"); + + MuteManager muteManager = new(Database); + int.TryParse(command.GetArg(2), out var time); + + // Get player and admin info + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Attempt to match player based on SteamID + var matches = Helper.GetPlayerFromSteamid64(steamid); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + // Check if caller can target the player + if (!caller.CanTarget(player)) return; + + // Perform the mute for an online player + Mute(caller, player, time, reason, callerName, muteManager, silent: true); + } + else + { + // Asynchronous mute operation for offline players + Task.Run(async () => + { + await muteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1); + }); + + command.ReplyToCommand($"Player with steamid {steamid} is not online. Mute has been added offline."); + } + + // Log the mute command and respond to the command + Helper.LogCommand(caller, command); + SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time); + } + + [ConsoleCommand("css_unmute")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnUnmuteCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + var callerSteamId = caller?.SteamID.ToString() ?? "Console"; + var pattern = command.GetArg(1); + var reason = command.GetArg(2); + + if (pattern.Length <= 1) + { + command.ReplyToCommand("Too short pattern to search."); + return; + } + + Helper.LogCommand(caller, command); + var muteManager = new MuteManager(Database); + + // Check if pattern is a valid SteamID64 + if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null) + { + var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Mute); + player.VoiceFlags = VoiceFlags.Normal; + + Task.Run(async () => + { + await muteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason, 1); + }); + + command.ReplyToCommand($"Unmuted player {player.PlayerName}."); + return; + } + } + + // If not a valid SteamID64, check by player name + var nameMatches = Helper.GetPlayerFromName(pattern); + var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null; + + if (namePlayer != null && namePlayer.IsValid) + { + PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Mute); + namePlayer.VoiceFlags = VoiceFlags.Normal; + + if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalMutes > 0) + PlayersInfo[namePlayer.UserId.Value].TotalMutes--; + + Task.Run(async () => + { + await muteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason, 1); + }); + + command.ReplyToCommand($"Unmuted player {namePlayer.PlayerName}."); + } + else + { + Task.Run(async () => + { + await muteManager.UnmutePlayer(pattern, callerSteamId, reason, 1); + }); + + command.ReplyToCommand($"Unmuted offline player with pattern {pattern}."); + } + } + + [ConsoleCommand("css_silence")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnSilenceCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + var callerName = caller == null ? "Console" : caller.PlayerName; + + var reason = _localizer?["sa_unknown"] ?? "Unknown"; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) + { + return; + } + + int.TryParse(command.GetArg(2), out var time); + + if (command.ArgCount >= 3 && command.GetArg(3).Length > 0) + reason = command.GetArg(3); + + MuteManager muteManager = new(Database); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + Silence(caller, player, time, reason, callerName, muteManager, command); + } + }); + } + + internal void Silence(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, MuteManager? muteManager = null, CommandInfo? command = null, bool silent = false) + { + if (Database == null || !player.IsValid || !player.UserId.HasValue) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller == null ? "Console" : caller.PlayerName; + muteManager ??= new MuteManager(Database); + + // Get player and admin information + var playerInfo = PlayersInfo[player.UserId.Value]; + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Asynchronously handle silence logic + Task.Run(async () => + { + await muteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2); // Assuming 2 is the type for silence + }); + + // Add penalty to the player's penalty manager + PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Silence, DateTime.Now.AddMinutes(time), time); + + // Determine message keys and arguments based on silence time (permanent or timed) + var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0 + ? ("sa_player_silence_message_perm", "sa_admin_silence_message_perm", + [reason, "CALLER"], + ["CALLER", player.PlayerName, reason]) + : ("sa_player_silence_message_time", "sa_admin_silence_message_time", + new object[] { reason, time, "CALLER" }, + new object[] { "CALLER", player.PlayerName, reason, time }); + + // Display center message to the silenced player + Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Increment the player's total silences count + PlayersInfo[player.UserId.Value].TotalSilences++; + + // Log the silence command and send Discord notification + if (!silent) + { + if (command == null) + Helper.LogCommand(caller, $"css_silence {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}"); + else + Helper.LogCommand(caller, command); + } + + Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Silence, _localizer); + SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time); + } + + [ConsoleCommand("css_addsilence")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnAddSilenceCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + // Set caller name + var callerName = caller == null ? "Console" : caller.PlayerName; + + // Validate command arguments + if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return; + + // Validate and extract SteamID + if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null) + { + command.ReplyToCommand("Invalid SteamID64."); + return; + } + + var steamid = steamId.SteamId64.ToString(); + var reason = command.ArgCount >= 3 && command.GetArg(3).Length > 0 + ? command.GetArg(3) + : (_localizer?["sa_unknown"] ?? "Unknown"); + + int.TryParse(command.GetArg(2), out var time); + MuteManager muteManager = new(Database); + + // Get player and admin info + var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.UserId.Value] : null; + + // Attempt to match player based on SteamID + var matches = Helper.GetPlayerFromSteamid64(steamid); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + // Check if caller can target the player + if (!caller.CanTarget(player)) return; + + // Perform the silence for an online player + Silence(caller, player, time, reason, callerName, muteManager, silent: true); + } + else + { + // Asynchronous silence operation for offline players + Task.Run(async () => + { + await muteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2); + }); + + command.ReplyToCommand($"Player with steamid {steamid} is not online. Silence has been added offline."); + } + + // Log the silence command and respond to the command + Helper.LogCommand(caller, command); + SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason, time); + } + + [ConsoleCommand("css_unsilence")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: " [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnUnsilenceCommand(CCSPlayerController? caller, CommandInfo command) + { + if (Database == null) return; + + var callerSteamId = caller?.SteamID.ToString() ?? "Console"; + var pattern = command.GetArg(1); + var reason = command.GetArg(2); + + if (pattern.Length <= 1) + { + command.ReplyToCommand("Too short pattern to search."); + return; + } + + Helper.LogCommand(caller, command); + var muteManager = new MuteManager(Database); + + // Check if pattern is a valid SteamID64 + if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null) + { + var matches = Helper.GetPlayerFromSteamid64(steamId.SteamId64.ToString()); + var player = matches.Count == 1 ? matches.FirstOrDefault() : null; + + if (player != null && player.IsValid) + { + PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Silence); + + // Reset voice flags to normal + player.VoiceFlags = VoiceFlags.Normal; + + Task.Run(async () => + { + await muteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason, 2); // Unmute by type 2 (silence) + }); + + command.ReplyToCommand($"Unsilenced player {player.PlayerName}."); + return; + } + } + + // If not a valid SteamID64, check by player name + var nameMatches = Helper.GetPlayerFromName(pattern); + var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null; + + if (namePlayer != null && namePlayer.IsValid) + { + PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Silence); + + // Reset voice flags to normal + namePlayer.VoiceFlags = VoiceFlags.Normal; + + if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.UserId.Value].TotalSilences > 0) + PlayersInfo[namePlayer.UserId.Value].TotalSilences--; + + Task.Run(async () => + { + await muteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason, 2); // Unmute by type 2 (silence) + }); + + command.ReplyToCommand($"Unsilenced player {namePlayer.PlayerName}."); + } + else + { + Task.Run(async () => + { + await muteManager.UnmutePlayer(pattern, callerSteamId, reason, 2); // Unmute by type 2 (silence) + }); + + command.ReplyToCommand($"Unsilenced offline player with pattern {pattern}."); + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Commands/basevotes.cs b/CS2-SimpleAdmin/Commands/basevotes.cs new file mode 100644 index 0000000..9298d60 --- /dev/null +++ b/CS2-SimpleAdmin/Commands/basevotes.cs @@ -0,0 +1,94 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Core.Translations; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Menu; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + [ConsoleCommand("css_vote")] + [RequiresPermissions("@css/generic")] + [CommandHelper(minArgs: 2, usage: " [... options ...]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnVoteCommand(CCSPlayerController? caller, CommandInfo command) + { + if (command.ArgCount < 2 || _localizer == null) + return; + + Helper.LogCommand(caller, command); + + VoteAnswers.Clear(); + + var question = command.GetArg(1); + var answersCount = command.ArgCount; + + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + for (var i = 2; i <= answersCount - 1; i++) + { + VoteAnswers.Add(command.GetArg(i), 0); + } + + foreach (var player in Helper.GetValidPlayers()) + { + using (new WithTemporaryCulture(player.GetLanguage())) + { + IMenu? voteMenu = MenuApi?.NewMenu(_localizer["sa_admin_vote_menu_title", question]); + if (voteMenu == null) + return; + //ChatMenu voteMenu = new(_localizer!["sa_admin_vote_menu_title", question]); + + for (var i = 2; i <= answersCount - 1; i++) + { + voteMenu.AddMenuOption(command.GetArg(i), Helper.HandleVotes); + } + + voteMenu.PostSelectAction = PostSelectAction.Close; + + Helper.PrintToCenterAll(_localizer["sa_admin_vote_message", caller == null ? "Console" : caller.PlayerName, question]); + + player.SendLocalizedMessage(_localizer, + "sa_admin_vote_message", + caller == null ? "Console" : caller.PlayerName, + question); + + voteMenu.Open(player); + + //MenuManager.OpenChatMenu(player, voteMenu); + } + } + + VoteInProgress = true; + } + + if (VoteInProgress) + { + AddTimer(30, () => + { + foreach (var player in Helper.GetValidPlayers()) + { + if (_localizer != null) + player.SendLocalizedMessage(_localizer, + "sa_admin_vote_message_results", + question); + } + + foreach (var (key, value) in VoteAnswers) + { + foreach (var player in Helper.GetValidPlayers()) + { + if (_localizer != null) + player.SendLocalizedMessage(_localizer, + "sa_admin_vote_message_results_answer", + key, + value); + } + } + VoteAnswers.Clear(); + VoteInProgress = false; + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Commands/funcommands.cs b/CS2-SimpleAdmin/Commands/funcommands.cs new file mode 100644 index 0000000..557a953 --- /dev/null +++ b/CS2-SimpleAdmin/Commands/funcommands.cs @@ -0,0 +1,220 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + [ConsoleCommand("css_noclip", "Noclip a player.")] + [CommandHelper(1, "<#userid or name>")] + [RequiresPermissions("@css/cheats")] + public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => + player.IsValid && + player is { PawnIsAlive: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected }).ToList(); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + NoClip(caller, player, callerName); + } + }); + } + + internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) + { + if (!player.IsValid) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + + // Toggle no-clip mode for the player + player.Pawn.Value?.ToggleNoclip(); + + // Determine message keys and arguments for the no-clip notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_noclip_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Log the command + if (command == null) + { + Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); + } + else + { + Helper.LogCommand(caller, command); + } + } + + [ConsoleCommand("css_god")] + [RequiresPermissions("@css/cheats")] + [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnGodCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (player.Connected != PlayerConnectedState.PlayerConnected) + return; + + if (caller!.CanTarget(player)) + { + God(caller, player, command); + } + }); + } + + internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Toggle god mode for the player + if (!GodPlayers.Add(player.Slot)) + { + GodPlayers.Remove(player.Slot); + } + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); + else + Helper.LogCommand(caller, command); + + // Determine message key and arguments for the god mode notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_god_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_freeze", "Freeze a player.")] + [CommandHelper(1, "<#userid or name> [duration]")] + [RequiresPermissions("@css/slay")] + public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + int.TryParse(command.GetArg(2), out var time); + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + Freeze(caller, player, time, callerName, command); + } + }); + } + + internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null) + { + if (!player.IsValid) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + + // Freeze player pawn + player.Pawn.Value?.Freeze(); + + // Determine message keys and arguments for the freeze notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_freeze_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Schedule unfreeze for the player if time is specified + if (time > 0) + { + Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + + // Log the command and send Discord notification + if (command == null) + Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}"); + else + Helper.LogCommand(caller, command); + } + + [ConsoleCommand("css_unfreeze", "Unfreeze a player.")] + [CommandHelper(1, "<#userid or name>")] + [RequiresPermissions("@css/slay")] + public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + Unfreeze(caller, player, callerName, command); + }); + } + + internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) + { + if (!player.IsValid) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + + // Unfreeze player pawn + player.Pawn.Value?.Unfreeze(); + + // Determine message keys and arguments for the unfreeze notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_unfreeze_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Log the command and send Discord notification + if (command == null) + Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); + else + Helper.LogCommand(caller, command); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Commands/playercommands.cs b/CS2-SimpleAdmin/Commands/playercommands.cs new file mode 100644 index 0000000..2d5f896 --- /dev/null +++ b/CS2-SimpleAdmin/Commands/playercommands.cs @@ -0,0 +1,821 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Entities.Constants; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Utils; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + [ConsoleCommand("css_slay")] + [RequiresPermissions("@css/slay")] + [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnSlayCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + Slay(caller, player, callerName, command); + }); + } + + internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) + { + if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + + // Make the player commit suicide + player.CommitSuicide(false, true); + + // Determine message keys and arguments for the slay notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_slay_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + // Log the command and send Discord notification + if (command == null) + Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); + else + Helper.LogCommand(caller, command); + } + + [ConsoleCommand("css_give")] + [RequiresPermissions("@css/cheats")] + [CommandHelper(minArgs: 2, usage: "<#userid or name> ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnGiveCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + var weaponName = command.GetArg(2); + + // check if item is typed + if (weaponName.Length < 5) + { + command.ReplyToCommand($"No weapon typed."); + return; + } + + // check if item is valid + if (!weaponName.Contains("weapon_") && !weaponName.Contains("item_")) + { + command.ReplyToCommand($"{weaponName} is not a valid item."); + return; + } + + // check if weapon is knife + if (weaponName.Contains("_knife") || weaponName.Contains("bayonet")) + { + if (CoreConfig.FollowCS2ServerGuidelines) + { + command.ReplyToCommand($"Cannot Give {weaponName} because it's illegal to be given."); + return; + } + } + + playersToTarget.ForEach(player => + { + if (player.Connected != PlayerConnectedState.PlayerConnected) + return; + + GiveWeapon(caller, player, weaponName, callerName, command); + }); + } + + private static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, string weaponName, string? callerName = null, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + + // Give weapon to the player + player.GiveNamedItem(weaponName); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weaponName}"); + else + Helper.LogCommand(caller, command); + + // Determine message keys and arguments for the weapon give notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_give_message", + new object[] { "CALLER", player.PlayerName, weaponName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + internal static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, CsItem weapon, string? callerName = null, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + + // Give weapon to the player + player.GiveNamedItem(weapon); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weapon.ToString()}"); + else + Helper.LogCommand(caller, command); + + // Determine message keys and arguments for the weapon give notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_give_message", + new object[] { "CALLER", player.PlayerName, weapon.ToString() }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_strip")] + [RequiresPermissions("@css/slay")] + [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnStripCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + StripWeapons(caller, player, callerName, command); + } + }); + } + + internal static void StripWeapons(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller != null ? caller.PlayerName : "Console"; + + // Check if player is valid, alive, and connected + if (!player.IsValid || !player.PawnIsAlive || player.Connected != PlayerConnectedState.PlayerConnected) + return; + + // Strip weapons from the player + player.RemoveWeapons(); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_strip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); + else + Helper.LogCommand(caller, command); + + // Determine message keys and arguments for the weapon strip notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_strip_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_hp")] + [RequiresPermissions("@css/slay")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnHpCommand(CCSPlayerController? caller, CommandInfo command) + { + int.TryParse(command.GetArg(2), out var health); + + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (caller!.CanTarget(player)) + { + SetHp(caller, player, health, command); + } + }); + } + + internal static void SetHp(CCSPlayerController? caller, CCSPlayerController player, int health, CommandInfo? command = null) + { + if (!player.IsValid || player.IsHLTV) return; + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Set player's health + player.SetHp(health); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_hp {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {health}"); + else + Helper.LogCommand(caller, command); + + // Determine message keys and arguments for the HP set notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_hp_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_speed")] + [RequiresPermissions("@css/slay")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnSpeedCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + float.TryParse(command.GetArg(2), out var speed); + + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (player.Connected != PlayerConnectedState.PlayerConnected) + return; + + if (caller!.CanTarget(player)) + { + SetSpeed(caller, player, speed, command); + } + }); + } + + internal static void SetSpeed(CCSPlayerController? caller, CCSPlayerController player, float speed, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Set player's speed + player.SetSpeed(speed); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_speed {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {speed}"); + else + Helper.LogCommand(caller, command); + + // Determine message keys and arguments for the speed set notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_speed_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_gravity")] + [RequiresPermissions("@css/slay")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnGravityCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + float.TryParse(command.GetArg(2), out var gravity); + + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (player.Connected != PlayerConnectedState.PlayerConnected) + return; + + if (caller!.CanTarget(player)) + { + SetGravity(caller, player, gravity, command); + } + }); + } + + internal static void SetGravity(CCSPlayerController? caller, CCSPlayerController player, float gravity, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Set player's gravity + player.SetGravity(gravity); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_gravity {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {gravity}"); + else + Helper.LogCommand(caller, command); + + // Determine message keys and arguments for the gravity set notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_gravity_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_money")] + [RequiresPermissions("@css/slay")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnMoneyCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + int.TryParse(command.GetArg(2), out var money); + + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (player.Connected != PlayerConnectedState.PlayerConnected) + return; + + if (caller!.CanTarget(player)) + { + SetMoney(caller, player, money, command); + } + }); + } + + internal static void SetMoney(CCSPlayerController? caller, CCSPlayerController player, int money, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Set player's money + player.SetMoney(money); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_money {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {money}"); + else + Helper.LogCommand(caller, command); + + // Determine message keys and arguments for the money set notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_money_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller == null || !SilentPlayers.Contains(caller.Slot)) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_slap")] + [RequiresPermissions("@css/slay")] + [CommandHelper(minArgs: 1, usage: "<#userid or name> [damage]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnSlapCommand(CCSPlayerController? caller, CommandInfo command) + { + var damage = 0; + + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player.IsValid && player is { PawnIsAlive: true, IsHLTV: false }).ToList(); + + if (command.ArgCount >= 2) + { + int.TryParse(command.GetArg(2), out damage); + } + + playersToTarget.ForEach(player => + { + if (player.Connected != PlayerConnectedState.PlayerConnected) + return; + + if (caller!.CanTarget(player)) + { + Slap(caller, player, damage, command); + } + }); + } + + internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, CommandInfo? command = null) + { + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Apply slap damage to the player + player.Pawn.Value?.Slap(damage); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}"); + else + Helper.LogCommand(caller, command); + + // Determine message key and arguments for the slap notification + var (activityMessageKey, adminActivityArgs) = + ("sa_admin_slap_message", + new object[] { "CALLER", player.PlayerName }); + + // Display admin activity message to other players + if (caller != null && SilentPlayers.Contains(caller.Slot)) return; + + if (_localizer != null) + { + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + } + + [ConsoleCommand("css_team")] + [RequiresPermissions("@css/kick")] + [CommandHelper(minArgs: 2, usage: "<#userid or name> [] [-k]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnTeamCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + var teamName = command.GetArg(2).ToLower(); + var _teamName = "SPEC"; + var teamNum = CsTeam.Spectator; + + var targets = GetTarget(command); + if (targets == null) return; + + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + switch (teamName) + { + case "ct": + case "counterterrorist": + teamNum = CsTeam.CounterTerrorist; + _teamName = "CT"; + break; + + case "t": + case "tt": + case "terrorist": + teamNum = CsTeam.Terrorist; + _teamName = "TT"; + break; + + case "swap": + _teamName = "SWAP"; + break; + + default: + teamNum = CsTeam.Spectator; + _teamName = "SPEC"; + break; + } + + var kill = command.GetArg(3).ToLower().Equals("-k"); + + playersToTarget.ForEach(player => + { + ChangeTeam(caller, player, _teamName, teamNum, kill, command); + }); + } + + internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null) + { + // Check if the player is valid and connected + if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) + return; + + // Ensure the caller can target the player + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + var callerName = caller != null ? caller.PlayerName : "Console"; + + // Change team based on the provided teamName and conditions + if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase)) + { + if (player.PawnIsAlive && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1) + player.SwitchTeam(teamNum); + else + player.ChangeTeam(teamNum); + } + else + { + if (player.TeamNum != (byte)CsTeam.Spectator) + { + var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist; + teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT"; + if (player.PawnIsAlive && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1) + player.SwitchTeam(_teamNum); + else + player.ChangeTeam(_teamNum); + } + } + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}"); + else + Helper.LogCommand(caller, command); + + // Determine message key and arguments for the team change notification + var activityMessageKey = "sa_admin_team_message"; + var adminActivityArgs = new object[] { "CALLER", player.PlayerName, teamName }; + + // Display admin activity message to other players + if (caller != null && SilentPlayers.Contains(caller.Slot)) return; + + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + [ConsoleCommand("css_rename", "Rename a player.")] + [CommandHelper(1, "<#userid or name> ")] + [RequiresPermissions("@css/kick")] + public void OnRenameCommand(CCSPlayerController? caller, CommandInfo command) + { + // Set default caller name if not provided + var callerName = caller == null ? "Console" : caller.PlayerName; + + // Get the new name from the command arguments + var newName = command.GetArg(2); + + // Check if the new name is valid + if (string.IsNullOrEmpty(newName)) + return; + + // Retrieve the targets based on the command + var targets = GetTarget(command); + if (targets == null) return; + + // Filter out valid players from the targets + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + // Log the command + Helper.LogCommand(caller, command); + + // Process each player to rename + playersToTarget.ForEach(player => + { + // Check if the player is connected and can be targeted + if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player)) + return; + + // Rename the player + player.Rename(newName); + + // Determine message key and arguments for the rename notification + var activityMessageKey = "sa_admin_rename_message"; + var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName }; + + // Display admin activity message to other players + if (caller != null && SilentPlayers.Contains(caller.Slot)) return; + + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + }); + } + + [ConsoleCommand("css_prename", "Permanent rename a player.")] + [CommandHelper(1, "<#userid or name> ")] + [RequiresPermissions("@css/ban")] + public void OnPRenameCommand(CCSPlayerController? caller, CommandInfo command) + { + // Set default caller name if not provided + var callerName = caller == null ? "Console" : caller.PlayerName; + + // Get the new name from the command arguments + var newName = command.GetArg(2); + + // Retrieve the targets based on the command + var targets = GetTarget(command); + if (targets == null) return; + + // Filter out valid players from the targets + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + // Log the command + Helper.LogCommand(caller, command); + + // Process each player to rename + playersToTarget.ForEach(player => + { + // Check if the player is connected and can be targeted + if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player)) + return; + + // Determine if the new name is valid and update the renamed players list + if (!string.IsNullOrEmpty(newName)) + { + RenamedPlayers[player.SteamID] = newName; + player.Rename(newName); + } + else + { + RenamedPlayers.Remove(player.SteamID); + } + + // Determine message key and arguments for the rename notification + var activityMessageKey = "sa_admin_rename_message"; + var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName }; + + // Display admin activity message to other players + if (caller != null && SilentPlayers.Contains(caller.Slot)) return; + + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + }); + } + + [ConsoleCommand("css_respawn", "Respawn a dead player.")] + [CommandHelper(1, "<#userid or name>")] + [RequiresPermissions("@css/cheats")] + public void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command) + { + var callerName = caller == null ? "Console" : caller.PlayerName; + + var targets = GetTarget(command); + if (targets == null) return; + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList(); + + playersToTarget.ForEach(player => + { + if (player.Connected != PlayerConnectedState.PlayerConnected) + return; + + if (caller!.CanTarget(player)) + { + Respawn(caller, player, callerName, command); + } + }); + } + + internal static void Respawn(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) + { + // Check if the caller can target the player + if (!caller.CanTarget(player)) return; + + // Set default caller name if not provided + callerName ??= caller == null ? "Console" : caller.PlayerName; + + // Ensure the player's pawn is valid before attempting to respawn + if (_cBasePlayerControllerSetPawnFunc == null || player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) return; + + // Perform the respawn operation + var playerPawn = player.PlayerPawn.Value; + _cBasePlayerControllerSetPawnFunc.Invoke(player, playerPawn, true, false); + VirtualFunction.CreateVoid(player.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(player); + + if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.UserId.Value, out var value) && value.DiePosition != null) + playerPawn.Teleport(value.DiePosition?.Position, value.DiePosition?.Angle); + + // Log the command + if (command == null) + Helper.LogCommand(caller, $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}"); + else + Helper.LogCommand(caller, command); + + // Determine message key and arguments for the respawn notification + var activityMessageKey = "sa_admin_respawn_message"; + var adminActivityArgs = new object[] { "CALLER", player.PlayerName }; + + // Display admin activity message to other players + if (caller != null && SilentPlayers.Contains(caller.Slot)) return; + + Helper.ShowAdminActivity(activityMessageKey, callerName, adminActivityArgs); + } + + [ConsoleCommand("css_tp", "Teleport to a player.")] + [ConsoleCommand("css_tpto", "Teleport to a player.")] + [ConsoleCommand("css_goto", "Teleport to a player.")] + [CommandHelper(1, "<#userid or name>")] + [RequiresPermissions("@css/kick")] + public void OnGotoCommand(CCSPlayerController? caller, CommandInfo command) + { + // Check if the caller is valid and has a live pawn + if (caller == null || !caller.PawnIsAlive) return; + + // Get the target players + var targets = GetTarget(command); + if (targets == null || targets.Count() > 1) return; + + var playersToTarget = targets.Players + .Where(player => player is { IsValid: true, IsHLTV: false }) + .ToList(); + + // Log the command + Helper.LogCommand(caller, command); + + // Process each player to teleport + foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }).Where(caller.CanTarget)) + { + if (caller.PlayerPawn.Value == null) + continue; + + // Teleport the caller to the player and toggle noclip + caller.TeleportPlayer(player); + caller.PlayerPawn.Value.ToggleNoclip(); + + // Set a timer to toggle noclip back after 3 seconds + AddTimer(3, () => caller.PlayerPawn.Value.ToggleNoclip()); + + // Prepare message key and arguments for the teleport notification + var activityMessageKey = "sa_admin_tp_message"; + var adminActivityArgs = new object[] { player.PlayerName }; + + // Show admin activity + if (!SilentPlayers.Contains(caller.Slot) && _localizer != null) + { + Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, adminActivityArgs); + } + } + } + + [ConsoleCommand("css_bring", "Teleport a player to you.")] + [ConsoleCommand("css_tphere", "Teleport a player to you.")] + [CommandHelper(1, "<#userid or name>")] + [RequiresPermissions("@css/kick")] + public void OnBringCommand(CCSPlayerController? caller, CommandInfo command) + { + // Check if the caller is valid and has a live pawn + if (caller == null || !caller.PawnIsAlive) return; + + // Get the target players + var targets = GetTarget(command); + if (targets == null || targets.Count() > 1) return; + + var playersToTarget = targets.Players + .Where(player => player is { IsValid: true, IsHLTV: false }) + .ToList(); + + // Log the command + Helper.LogCommand(caller, command); + + // Process each player to teleport + foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PawnIsAlive: true }).Where(caller.CanTarget)) + { + if (caller.PlayerPawn.Value == null) + continue; + + // Teleport the player to the caller and toggle noclip + player.TeleportPlayer(caller); + caller.PlayerPawn.Value.ToggleNoclip(); + + // Set a timer to toggle noclip back after 3 seconds + AddTimer(3, () => caller.PlayerPawn.Value.ToggleNoclip()); + + // Prepare message key and arguments for the bring notification + var activityMessageKey = "sa_admin_bring_message"; + var adminActivityArgs = new object[] { player.PlayerName }; + + // Show admin activity + if (!SilentPlayers.Contains(caller.Slot) && _localizer != null) + { + Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, adminActivityArgs); + } + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Config.cs b/CS2-SimpleAdmin/Config.cs new file mode 100644 index 0000000..c9d895c --- /dev/null +++ b/CS2-SimpleAdmin/Config.cs @@ -0,0 +1,270 @@ +using CounterStrikeSharp.API.Core; +using System.Text.Json.Serialization; + +namespace CS2_SimpleAdmin; + +public class DurationItem +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("duration")] + public int Duration { get; set; } +} + +public class AdminFlag +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("flag")] + public required string Flag { get; set; } +} + +public class DiscordPenaltySetting +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("value")] + public string Value { get; set; } = ""; +} + +public class Discord +{ + [JsonPropertyName("DiscordLogWebhook")] + public string DiscordLogWebhook { get; set; } = ""; + + [JsonPropertyName("DiscordPenaltyBanSettings")] + public DiscordPenaltySetting[] DiscordPenaltyBanSettings { get; set; } = + [ + new DiscordPenaltySetting { Name = "Color", Value = "" }, + new DiscordPenaltySetting { Name = "Webhook", Value = "" }, + new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" }, + new DiscordPenaltySetting { Name = "ImageUrl", Value = "" }, + new DiscordPenaltySetting { Name = "Footer", Value = "" }, + new DiscordPenaltySetting { Name = "Time", Value = "{relative}" }, + ]; + + [JsonPropertyName("DiscordPenaltyMuteSettings")] + public DiscordPenaltySetting[] DiscordPenaltyMuteSettings { get; set; } = + [ + new DiscordPenaltySetting { Name = "Color", Value = "" }, + new DiscordPenaltySetting { Name = "Webhook", Value = "" }, + new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" }, + new DiscordPenaltySetting { Name = "ImageUrl", Value = "" }, + new DiscordPenaltySetting { Name = "Footer", Value = "" }, + new DiscordPenaltySetting { Name = "Time", Value = "{relative}" }, + ]; + + [JsonPropertyName("DiscordPenaltyGagSettings")] + public DiscordPenaltySetting[] DiscordPenaltyGagSettings { get; set; } = + [ + new DiscordPenaltySetting { Name = "Color", Value = "" }, + new DiscordPenaltySetting { Name = "Webhook", Value = "" }, + new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" }, + new DiscordPenaltySetting { Name = "ImageUrl", Value = "" }, + new DiscordPenaltySetting { Name = "Footer", Value = "" }, + new DiscordPenaltySetting { Name = "Time", Value = "{relative}" }, + ]; + + [JsonPropertyName("DiscordPenaltySilenceSettings")] + public DiscordPenaltySetting[] DiscordPenaltySilenceSettings { get; set; } = + [ + new DiscordPenaltySetting { Name = "Color", Value = "" }, + new DiscordPenaltySetting { Name = "Webhook", Value = "" }, + new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" }, + new DiscordPenaltySetting { Name = "ImageUrl", Value = "" }, + new DiscordPenaltySetting { Name = "Footer", Value = "" }, + new DiscordPenaltySetting { Name = "Time", Value = "{relative}" }, + ]; + + [JsonPropertyName("DiscordPenaltyWarnSettings")] + public DiscordPenaltySetting[] DiscordPenaltyWarnSettings { get; set; } = + [ + new DiscordPenaltySetting { Name = "Color", Value = "" }, + new DiscordPenaltySetting { Name = "Webhook", Value = "" }, + new DiscordPenaltySetting { Name = "ThumbnailUrl", Value = "" }, + new DiscordPenaltySetting { Name = "ImageUrl", Value = "" }, + new DiscordPenaltySetting { Name = "Footer", Value = "" }, + new DiscordPenaltySetting { Name = "Time", Value = "{relative}" }, + ]; +} + +public class CustomServerCommandData +{ + [JsonPropertyName("Flag")] + public string Flag { get; set; } = "@css/generic"; + + [JsonPropertyName("DisplayName")] + public string DisplayName { get; set; } = ""; + + [JsonPropertyName("Command")] + public string Command { get; set; } = ""; + + [JsonPropertyName("ExecuteOnClient")] + public bool ExecuteOnClient { get; set; } = false; +} + +public class MenuConfig +{ + [JsonPropertyName("Durations")] + public DurationItem[] Durations { get; set; } = + [ + new DurationItem { Name = "1 minute", Duration = 1 }, + new DurationItem { Name = "5 minutes", Duration = 5 }, + new DurationItem { Name = "15 minutes", Duration = 15 }, + new DurationItem { Name = "1 hour", Duration = 60 }, + new DurationItem { Name = "1 day", Duration = 60 * 24 }, + new DurationItem { Name = "7 days", Duration = 60 * 24 * 7 }, + new DurationItem { Name = "14 days", Duration = 60 * 24 * 14 }, + new DurationItem { Name = "30 days", Duration = 60 * 24 * 30 }, + new DurationItem { Name = "Permanent", Duration = 0 } + ]; + + [JsonPropertyName("BanReasons")] + public List BanReasons { get; set; } = + [ + "Hacking", + "Voice Abuse", + "Chat Abuse", + "Admin disrespect", + "Other" + ]; + + [JsonPropertyName("KickReasons")] + public List KickReasons { get; set; } = + [ + "Voice Abuse", + "Chat Abuse", + "Admin disrespect", + "Other" + ]; + + [JsonPropertyName("WarnReasons")] + public List WarnReasons { get; set; } = + [ + "Voice Abuse", + "Chat Abuse", + "Admin disrespect", + "Other" + ]; + + [JsonPropertyName("MuteReasons")] + public List MuteReasons { get; set; } = + [ + "Advertising", + "Spamming", + "Spectator camera abuse", + "Hate", + "Admin disrespect", + "Other" + ]; + + [JsonPropertyName("AdminFlags")] + public AdminFlag[] AdminFlags { get; set; } = + [ + new AdminFlag { Name = "Generic", Flag = "@css/generic" }, + new AdminFlag { Name = "Chat", Flag = "@css/chat" }, + new AdminFlag { Name = "Change Map", Flag = "@css/changemap" }, + new AdminFlag { Name = "Slay", Flag = "@css/slay" }, + new AdminFlag { Name = "Kick", Flag = "@css/kick" }, + new AdminFlag { Name = "Ban", Flag = "@css/ban" }, + new AdminFlag { Name = "Perm Ban", Flag = "@css/permban" }, + new AdminFlag { Name = "Unban", Flag = "@css/unban" }, + new AdminFlag { Name = "Show IP", Flag = "@css/showip" }, + new AdminFlag { Name = "Cvar", Flag = "@css/cvar" }, + new AdminFlag { Name = "Rcon", Flag = "@css/rcon" }, + new AdminFlag { Name = "Root (all flags)", Flag = "@css/root" } + ]; +} + +public class OtherSettings +{ + [JsonPropertyName("ShowActivityType")] + public int ShowActivityType { get; set; } = 2; + + [JsonPropertyName("TeamSwitchType")] + public int TeamSwitchType { get; set; } = 1; + + [JsonPropertyName("KickTime")] + public int KickTime { get; set; } = 5; + + [JsonPropertyName("BanType")] + public int BanType { get; set; } = 1; + + [JsonPropertyName("TimeMode")] + public int TimeMode { get; set; } = 1; + + [JsonPropertyName("DisableDangerousCommands")] + public bool DisableDangerousCommands { get; set; } = true; + + [JsonPropertyName("MaxBanDuration")] + public int MaxBanDuration { get; set; } = 60 * 24 * 7; + + [JsonPropertyName("ExpireOldIpBans")] + public int ExpireOldIpBans { get; set; } = 0; + + [JsonPropertyName("ReloadAdminsEveryMapChange")] + public bool ReloadAdminsEveryMapChange { get; set; } = false; + + [JsonPropertyName("DisconnectedPlayersHistoryCount")] + public int DisconnectedPlayersHistoryCount { get; set; } = 10; +} + +public class CS2_SimpleAdminConfig : BasePluginConfig +{ + [JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 20; + + [JsonPropertyName("DatabaseHost")] + public string DatabaseHost { get; set; } = ""; + + [JsonPropertyName("DatabasePort")] + public int DatabasePort { get; set; } = 3306; + + [JsonPropertyName("DatabaseUser")] + public string DatabaseUser { get; set; } = ""; + + [JsonPropertyName("DatabasePassword")] + public string DatabasePassword { get; set; } = ""; + + [JsonPropertyName("DatabaseName")] + public string DatabaseName { get; set; } = ""; + + [JsonPropertyName("OtherSettings")] + public OtherSettings OtherSettings { get; set; } = new(); + + [JsonPropertyName("EnableMetrics")] + public bool EnableMetrics { get; set; } = true; + + [JsonPropertyName("EnableUpdateCheck")] + public bool EnableUpdateCheck { get; set; } = true; + + [JsonPropertyName("Timezone")] + public string Timezone { get; set; } = "UTC"; + + [JsonPropertyName("WarnThreshold")] + public Dictionary WarnThreshold { get; set; } = new() + { + { 998, "css_addban STEAMID64 60 \"3/4 Warn\"" }, + { 999, "css_ban #USERID 120 \"4/4 Warn\"" }, + }; + + [JsonPropertyName("MultiServerMode")] + public bool MultiServerMode { get; set; } = true; + + [JsonPropertyName("Discord")] + public Discord Discord { get; set; } = new(); + + [JsonPropertyName("DefaultMaps")] + public List DefaultMaps { get; set; } = new(); + + [JsonPropertyName("WorkshopMaps")] + public Dictionary WorkshopMaps { get; set; } = new(); + + [JsonPropertyName("CustomServerCommands")] + public List CustomServerCommands { get; set; } = new(); + + [JsonPropertyName("MenuConfig")] + public MenuConfig MenuConfigs { get; set; } = new(); +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Database/Database.cs b/CS2-SimpleAdmin/Database/Database.cs new file mode 100644 index 0000000..4b40545 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Database.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Logging; +using MySqlConnector; + +namespace CS2_SimpleAdmin.Database; + +public class Database(string dbConnectionString) +{ + public MySqlConnection GetConnection() + { + try + { + var connection = new MySqlConnection(dbConnectionString); + connection.Open(); + return connection; + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogCritical($"Unable to connect to database: {ex.Message}"); + throw; + } + } + + public async Task GetConnectionAsync() + { + try + { + var connection = new MySqlConnection(dbConnectionString); + await connection.OpenAsync(); + return connection; + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogCritical($"Unable to connect to database: {ex.Message}"); + throw; + } + } + + public void DatabaseMigration() + { + Migration migrator = new(this); + migrator.ExecuteMigrations(); + } + + public bool CheckDatabaseConnection() + { + using var connection = GetConnection(); + + try + { + return connection.Ping(); + } + catch + { + return false; + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Database/Migration.cs b/CS2-SimpleAdmin/Database/Migration.cs new file mode 100644 index 0000000..81e1097 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migration.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; +using MySqlConnector; + +namespace CS2_SimpleAdmin.Database; + +public class Migration(Database database) +{ + public void ExecuteMigrations() + { + var migrationsDirectory = CS2_SimpleAdmin.Instance.ModuleDirectory + "/Database/Migrations"; + + var files = Directory.GetFiles(migrationsDirectory, "*.sql") + .OrderBy(f => f); + + using var connection = database.GetConnection(); + + // Create sa_migrations table if not exists + using var cmd = new MySqlCommand(""" + CREATE TABLE IF NOT EXISTS `sa_migrations` ( + `id` INT PRIMARY KEY AUTO_INCREMENT, + `version` VARCHAR(255) NOT NULL + ); + """, connection); + + cmd.ExecuteNonQuery(); + + // Get the last applied migration version + var lastAppliedVersion = GetLastAppliedVersion(connection); + + foreach (var file in files) + { + var version = Path.GetFileNameWithoutExtension(file); + + // Check if the migration has already been applied + if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0) continue; + var sqlScript = File.ReadAllText(file); + + using var cmdMigration = new MySqlCommand(sqlScript, connection); + cmdMigration.ExecuteNonQuery(); + + // Update the last applied migration version + UpdateLastAppliedVersion(connection, version); + + CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied."); + } + } + + private static string GetLastAppliedVersion(MySqlConnection connection) + { + using var cmd = new MySqlCommand("SELECT `version` FROM `sa_migrations` ORDER BY `id` DESC LIMIT 1;", connection); + var result = cmd.ExecuteScalar(); + return result?.ToString() ?? string.Empty; + } + + private static void UpdateLastAppliedVersion(MySqlConnection connection, string version) + { + using var cmd = new MySqlCommand("INSERT INTO `sa_migrations` (`version`) VALUES (@Version);", connection); + cmd.Parameters.AddWithValue("@Version", version); + cmd.ExecuteNonQuery(); + } +} diff --git a/CS2-SimpleAdmin/Database/Migrations/001_CreateTables.sql b/CS2-SimpleAdmin/Database/Migrations/001_CreateTables.sql new file mode 100644 index 0000000..fb8292f --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/001_CreateTables.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS `sa_bans` ( + `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `player_name` VARCHAR(128), + `player_steamid` VARCHAR(64), + `player_ip` VARCHAR(128), + `admin_steamid` VARCHAR(64) NOT NULL, + `admin_name` VARCHAR(128) NOT NULL, + `reason` VARCHAR(255) NOT NULL, + `duration` INT NOT NULL, + `ends` TIMESTAMP NULL, + `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `server_id` INT NULL, + `status` ENUM('ACTIVE', 'UNBANNED', 'EXPIRED', '') NOT NULL DEFAULT 'ACTIVE' + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `sa_mutes` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `player_name` varchar(128) NULL, + `player_steamid` varchar(64) NOT NULL, + `admin_steamid` varchar(64) NOT NULL, + `admin_name` varchar(128) NOT NULL, + `reason` varchar(255) NOT NULL, + `duration` int(11) NOT NULL, + `ends` timestamp NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `type` enum('GAG','MUTE','SILENCE','') NOT NULL DEFAULT 'GAG', + `server_id` INT NULL, + `status` enum('ACTIVE','UNMUTED','EXPIRED','') NOT NULL DEFAULT 'ACTIVE', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `sa_admins` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `player_name` varchar(128) NOT NULL, + `player_steamid` varchar(64) NOT NULL, + `flags` TEXT NULL, + `immunity` int(11) NOT NULL DEFAULT 0, + `server_id` INT NULL, + `ends` timestamp NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `sa_servers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `hostname` varchar(128) NOT NULL, + `address` varchar(64) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `address` (`address`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/CS2-SimpleAdmin/Database/Migrations/002_CreateFlagsTable.sql b/CS2-SimpleAdmin/Database/Migrations/002_CreateFlagsTable.sql new file mode 100644 index 0000000..3c9b75b --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/002_CreateFlagsTable.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS `sa_admins_flags` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `admin_id` int(11) NOT NULL, + `flag` varchar(64) NOT NULL, + PRIMARY KEY (`id`), + FOREIGN KEY (`admin_id`) REFERENCES `sa_admins` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +ALTER TABLE `sa_admins` CHANGE `flags` `flags` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL; diff --git a/CS2-SimpleAdmin/Database/Migrations/003_ChangeColumnsPosition.sql b/CS2-SimpleAdmin/Database/Migrations/003_ChangeColumnsPosition.sql new file mode 100644 index 0000000..d25915a --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/003_ChangeColumnsPosition.sql @@ -0,0 +1,4 @@ +ALTER TABLE `sa_bans` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`; +ALTER TABLE `sa_mutes` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`; +ALTER TABLE `sa_admins` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`; +ALTER TABLE `sa_servers` CHANGE `hostname` `hostname` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`; \ No newline at end of file diff --git a/CS2-SimpleAdmin/Database/Migrations/004_MoveOldFlagsToFlagsTable.sql b/CS2-SimpleAdmin/Database/Migrations/004_MoveOldFlagsToFlagsTable.sql new file mode 100644 index 0000000..71ca42b --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/004_MoveOldFlagsToFlagsTable.sql @@ -0,0 +1,36 @@ +INSERT INTO sa_admins_flags (admin_id, flag) +SELECT + min_admins.admin_id, + TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(sa_admins.flags, ',', numbers.n), ',', -1)) AS flag +FROM ( + SELECT MIN(id) AS admin_id, player_steamid, server_id + FROM sa_admins + WHERE player_steamid != 'Console' + GROUP BY player_steamid, server_id +) AS min_admins +JOIN sa_admins ON min_admins.player_steamid = sa_admins.player_steamid +JOIN ( + SELECT 1 AS n UNION ALL + SELECT 2 UNION ALL + SELECT 3 UNION ALL + SELECT 4 UNION ALL + SELECT 5 UNION ALL + SELECT 6 UNION ALL + SELECT 7 UNION ALL + SELECT 8 UNION ALL + SELECT 9 UNION ALL + SELECT 10 UNION ALL + SELECT 11 UNION ALL + SELECT 12 UNION ALL + SELECT 13 UNION ALL + SELECT 14 UNION ALL + SELECT 15 UNION ALL + SELECT 16 UNION ALL + SELECT 17 UNION ALL + SELECT 18 UNION ALL + SELECT 19 UNION ALL + SELECT 20 +) AS numbers +ON CHAR_LENGTH(sa_admins.flags) - CHAR_LENGTH(REPLACE(sa_admins.flags, ',', '')) >= numbers.n - 1 +AND (min_admins.server_id = sa_admins.server_id OR (min_admins.server_id IS NULL AND sa_admins.server_id IS NULL)) +WHERE sa_admins.id IS NOT NULL; diff --git a/CS2-SimpleAdmin/Database/Migrations/005_CreateUnbansTable.sql b/CS2-SimpleAdmin/Database/Migrations/005_CreateUnbansTable.sql new file mode 100644 index 0000000..6666539 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/005_CreateUnbansTable.sql @@ -0,0 +1,29 @@ +CREATE TABLE IF NOT EXISTS `sa_unbans` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ban_id` int(11) NOT NULL, + `admin_id` int(11) NOT NULL DEFAULT 0, + `reason` varchar(255) NOT NULL DEFAULT 'Unknown', + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `sa_unmutes` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `mute_id` int(11) NOT NULL, + `admin_id` int(11) NOT NULL DEFAULT 0, + `reason` varchar(255) NOT NULL DEFAULT 'Unknown', + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +INSERT INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`) +VALUES (-1, 'Console', 'Console', '', '0', NULL, NULL, NOW()); + +UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1; + +ALTER TABLE `sa_bans` ADD `unban_id` INT NULL AFTER `server_id`; +ALTER TABLE `sa_mutes` ADD `unmute_id` INT NULL AFTER `server_id`; +ALTER TABLE `sa_bans` ADD FOREIGN KEY (`unban_id`) REFERENCES `sa_unbans`(`id`) ON DELETE CASCADE; +ALTER TABLE `sa_mutes` ADD FOREIGN KEY (`unmute_id`) REFERENCES `sa_unmutes`(`id`) ON DELETE CASCADE; +ALTER TABLE `sa_unbans` ADD FOREIGN KEY (`admin_id`) REFERENCES `sa_admins`(`id`) ON DELETE CASCADE; +ALTER TABLE `sa_unmutes` ADD FOREIGN KEY (`admin_id`) REFERENCES `sa_admins`(`id`) ON DELETE CASCADE; diff --git a/CS2-SimpleAdmin/Database/Migrations/006_ServerGroupsFeature.sql b/CS2-SimpleAdmin/Database/Migrations/006_ServerGroupsFeature.sql new file mode 100644 index 0000000..4424189 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/006_ServerGroupsFeature.sql @@ -0,0 +1,26 @@ +CREATE TABLE IF NOT EXISTS `sa_groups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `immunity` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `sa_groups_flags` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `flag` varchar(64) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `sa_groups_servers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `server_id` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +ALTER TABLE `sa_admins` ADD `group_id` INT NULL AFTER `created`; + +ALTER TABLE `sa_groups_flags` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE; +ALTER TABLE `sa_groups_servers` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE; +ALTER TABLE `sa_admins` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE SET NULL; \ No newline at end of file diff --git a/CS2-SimpleAdmin/Database/Migrations/007_ServerGroupsGlobal.sql b/CS2-SimpleAdmin/Database/Migrations/007_ServerGroupsGlobal.sql new file mode 100644 index 0000000..34d1945 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/007_ServerGroupsGlobal.sql @@ -0,0 +1 @@ +ALTER TABLE `sa_groups_servers` CHANGE `server_id` `server_id` INT(11) NULL; \ No newline at end of file diff --git a/CS2-SimpleAdmin/Database/Migrations/008_OnlineTimeInPenalties.sql b/CS2-SimpleAdmin/Database/Migrations/008_OnlineTimeInPenalties.sql new file mode 100644 index 0000000..d22934f --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/008_OnlineTimeInPenalties.sql @@ -0,0 +1 @@ +ALTER TABLE `sa_mutes` ADD `passed` INT NULL AFTER `duration`; diff --git a/CS2-SimpleAdmin/Database/Migrations/009_BanAllUsedIpAddress.sql b/CS2-SimpleAdmin/Database/Migrations/009_BanAllUsedIpAddress.sql new file mode 100644 index 0000000..2fa6688 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/009_BanAllUsedIpAddress.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS `sa_players_ips` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `steamid` bigint(20) NOT NULL, + `address` varchar(64) NOT NULL, + `used_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `steamid` (`steamid`,`address`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/CS2-SimpleAdmin/Database/Migrations/010_CreateWarnsTable.sql b/CS2-SimpleAdmin/Database/Migrations/010_CreateWarnsTable.sql new file mode 100644 index 0000000..de1afa5 --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migrations/010_CreateWarnsTable.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS `sa_warns` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `player_name` varchar(128) DEFAULT NULL, + `player_steamid` varchar(64) NOT NULL, + `admin_steamid` varchar(64) NOT NULL, + `admin_name` varchar(128) NOT NULL, + `reason` varchar(255) NOT NULL, + `duration` int(11) NOT NULL, + `ends` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `server_id` int(11) DEFAULT NULL, + `status` enum('ACTIVE','EXPIRED','') NOT NULL DEFAULT 'ACTIVE', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs new file mode 100644 index 0000000..04b8736 --- /dev/null +++ b/CS2-SimpleAdmin/Events.cs @@ -0,0 +1,330 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.Modules.Utils; +using CS2_SimpleAdmin.Managers; +using CS2_SimpleAdmin.Models; +using CS2_SimpleAdminApi; +using Microsoft.Extensions.Logging; +using System.Text; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + private void RegisterEvents() + { + RegisterListener(OnMapStart); + RegisterListener(OnMapStart); + RegisterListener(OnGameServerSteamAPIActivated); + AddCommandListener(null, OnCommandSayNew); + // AddCommandListener("say", OnCommandSay); + // AddCommandListener("say_team", OnCommandTeamSay); + } + + private void OnGameServerSteamAPIActivated() + { + new ServerManager().LoadServerData(); + } + + [GameEventHandler] + public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info) + { + var player = @event.Userid; + +#if DEBUG + Logger.LogCritical("[OnClientDisconnect] Before"); +#endif + + if (player == null || !player.IsValid || player.IsBot) + { + return HookResult.Continue; + } + +#if DEBUG + Logger.LogCritical("[OnClientDisconnect] After Check"); +#endif + try + { + if (DisconnectedPlayers.Count >= Config.OtherSettings.DisconnectedPlayersHistoryCount) + DisconnectedPlayers.RemoveAt(0); + + var steamId = new SteamID(player.SteamID); + var disconnectedPlayer = DisconnectedPlayers.FirstOrDefault(p => p.SteamId == steamId); + + if (disconnectedPlayer != null) + { + disconnectedPlayer.Name = player.PlayerName; + disconnectedPlayer.IpAddress = player.IpAddress?.Split(":")[0]; + disconnectedPlayer.DisconnectTime = DateTime.Now; + } + else + { + DisconnectedPlayers.Add(new DisconnectedPlayer(steamId, player.PlayerName, player.IpAddress?.Split(":")[0], DateTime.Now)); + } + + PlayerPenaltyManager.RemoveAllPenalties(player.Slot); + + SilentPlayers.Remove(player.Slot); + GodPlayers.Remove(player.Slot); + + if (player.UserId.HasValue) + PlayersInfo.Remove(player.UserId.Value); + + var authorizedSteamId = player.AuthorizedSteamID; + if (authorizedSteamId == null || !PermissionManager.AdminCache.TryGetValue(authorizedSteamId, + out var expirationTime) + || !(expirationTime <= Time.ActualDateTime())) return HookResult.Continue; + + AdminManager.ClearPlayerPermissions(authorizedSteamId); + AdminManager.RemovePlayerAdminData(authorizedSteamId); + + return HookResult.Continue; + } + catch (Exception ex) + { + Logger.LogError($"An error occurred in OnClientDisconnect: {ex.Message}"); + return HookResult.Continue; + } + } + + [GameEventHandler] + public HookResult OnPlayerFullConnect(EventPlayerConnectFull @event, GameEventInfo info) + { + var player = @event.Userid; + + if (player == null || !player.IsValid || player.IsBot) + return HookResult.Continue; + + new PlayerManager().LoadPlayerData(player); + + return HookResult.Continue; + } + + [GameEventHandler] + public HookResult OnRoundEnd(EventRoundStart @event, GameEventInfo info) + { +#if DEBUG + Logger.LogCritical("[OnRoundEnd]"); +#endif + + GodPlayers.Clear(); + foreach (var player in PlayersInfo.Values) + { + player.DiePosition = null; + } + + AddTimer(0.41f, () => + { + foreach (var list in RenamedPlayers) + { + var player = Utilities.GetPlayerFromSteamId(list.Key); + + if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) + continue; + + if (player.PlayerName.Equals(list.Value)) + continue; + + player.Rename(list.Value); + } + }); + + return HookResult.Continue; + } + + private HookResult OnCommandSayNew(CCSPlayerController? player, CommandInfo info) + { + if (player == null || !player.IsValid || player.IsBot) + return HookResult.Continue; + + var command = info.GetArg(0); + + if (!command.Contains("say")) + return HookResult.Continue; + + if (info.GetArg(1).StartsWith($"/") + || info.GetArg(1).StartsWith($"!")) + return HookResult.Continue; + + if (info.GetArg(1).Length == 0) + return HookResult.Handled; + + if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence)) + return HookResult.Handled; + + if (command != "say_team" || !info.GetArg(1).StartsWith($"@")) return HookResult.Continue; + + StringBuilder sb = new(); + + if (AdminManager.PlayerHasPermissions(player, "@css/chat")) + { + sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]); + foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat"))) + { + p.PrintToChat(sb.ToString()); + } + } + else + { + sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]); + player.PrintToChat(sb.ToString()); + foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat"))) + { + p.PrintToChat(sb.ToString()); + } + } + + return HookResult.Handled; + } + + /*public HookResult OnCommandSay(CCSPlayerController? player, CommandInfo info) + { + if (player == null || !player.IsValid || player.IsBot) + return HookResult.Continue; + + if (info.GetArg(1).StartsWith($"/") + || info.GetArg(1).StartsWith($"!")) + return HookResult.Continue; + + if (info.GetArg(1).Length == 0) + return HookResult.Handled; + + if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence)) + return HookResult.Handled; + + return HookResult.Continue; + }*/ + + public HookResult OnCommandTeamSay(CCSPlayerController? player, CommandInfo info) + { + if (player == null || !player.IsValid || player.IsBot) + return HookResult.Continue; + + if (info.GetArg(1).StartsWith($"/") + || info.GetArg(1).StartsWith($"!")) + return HookResult.Continue; + + if (info.GetArg(1).Length == 0) + return HookResult.Handled; + + if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence)) + return HookResult.Handled; + + if (!info.GetArg(1).StartsWith($"@")) return HookResult.Continue; + + StringBuilder sb = new(); + + if (AdminManager.PlayerHasPermissions(player, "@css/chat")) + { + sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]); + foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat"))) + { + p.PrintToChat(sb.ToString()); + } + } + else + { + sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]); + player.PrintToChat(sb.ToString()); + foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(p, "@css/chat"))) + { + p.PrintToChat(sb.ToString()); + } + } + + return HookResult.Handled; + } + + private void OnMapStart(string mapName) + { + if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null) + AddTimer(3.0f, () => ReloadAdmins(null)); + + AddTimer(34, () => + { + if (!ServerLoaded) + OnGameServerSteamAPIActivated(); + }); + + GodPlayers.Clear(); + SilentPlayers.Clear(); + + PlayerPenaltyManager.RemoveAllPenalties(); + new PlayerManager().CheckPlayersTimer(); + } + + [GameEventHandler] + public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info) + { + var player = @event.Userid; + + if (player is null || @event.Attacker is null || !player.PawnIsAlive || player.PlayerPawn.Value == null) + return HookResult.Continue; + + if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue; + + player.PlayerPawn.Value.Health = player.PlayerPawn.Value.MaxHealth; + player.PlayerPawn.Value.ArmorValue = 100; + + return HookResult.Continue; + } + + [GameEventHandler] + public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info) + { + var player = @event.Userid; + + if (player?.UserId == null || player.IsBot || player.Connected != PlayerConnectedState.PlayerConnected) + return HookResult.Continue; + + PlayersInfo[player.UserId.Value].DiePosition = + new DiePosition( + new Vector(player.PlayerPawn.Value?.AbsOrigin?.X, player.PlayerPawn.Value?.AbsOrigin?.Y, + player.PlayerPawn.Value?.AbsOrigin?.Z), + new QAngle(player.PlayerPawn.Value?.AbsRotation?.X, player.PlayerPawn.Value?.AbsRotation?.Y, + player.PlayerPawn.Value?.AbsRotation?.Z)); + + return HookResult.Continue; + } + + [GameEventHandler(HookMode.Pre)] + public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info) + { + var player = @event.Userid; + + if (player == null || !player.IsValid || player.IsBot) + return HookResult.Continue; + + if (!SilentPlayers.Contains(player.Slot)) + return HookResult.Continue; + + info.DontBroadcast = true; + + if (@event.Team > 1) + SilentPlayers.Remove(player.Slot); + + return HookResult.Continue; + } + + [GameEventHandler] + public HookResult OnPlayerInfo(EventPlayerInfo @event, GameEventInfo _) + { + var player = @event.Userid; + + if (player is null || !player.IsValid || player.IsBot) + return HookResult.Continue; + + if (!RenamedPlayers.TryGetValue(player.SteamID, out var name)) return HookResult.Continue; + + if (player.PlayerName.Equals(name)) + return HookResult.Continue; + + player.Rename(name); + + return HookResult.Continue; + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs b/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs new file mode 100644 index 0000000..e116739 --- /dev/null +++ b/CS2-SimpleAdmin/Extensions/PlayerExtensions.cs @@ -0,0 +1,234 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Translations; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.Modules.Memory; +using Microsoft.Extensions.Localization; +using System.Text; +using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; + +namespace CS2_SimpleAdmin; + +public static class PlayerExtensions +{ + public static void Slap(this CBasePlayerPawn pawn, int damage = 0) + { + PerformSlap(pawn, damage); + } + + public static void Print(this CCSPlayerController controller, string message = "") + { + StringBuilder _message = new(CS2_SimpleAdmin._localizer!["sa_prefix"]); + _message.Append(message); + controller.PrintToChat(_message.ToString()); + } + + public static bool CanTarget(this CCSPlayerController? controller, CCSPlayerController? target) + { + if (controller is null || target is null) return true; + if (target.IsBot) return true; + + return AdminManager.CanPlayerTarget(controller, target) || + AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), + new SteamID(target.SteamID)); + } + + public static void SetSpeed(this CCSPlayerController? controller, float speed) + { + var playerPawnValue = controller?.PlayerPawn.Value; + if (playerPawnValue == null) return; + + playerPawnValue.VelocityModifier = speed; + } + + public static void SetGravity(this CCSPlayerController? controller, float gravity) + { + var playerPawnValue = controller?.PlayerPawn.Value; + if (playerPawnValue == null) return; + + playerPawnValue.GravityScale = gravity; + } + + public static void SetMoney(this CCSPlayerController? controller, int money) + { + var moneyServices = controller?.InGameMoneyServices; + if (moneyServices == null) return; + + moneyServices.Account = money; + + if (controller != null) Utilities.SetStateChanged(controller, "CCSPlayerController", "m_pInGameMoneyServices"); + } + + public static void SetHp(this CCSPlayerController? controller, int health = 100) + { + if (controller == null) return; + if ((health <= 0 || !controller.PawnIsAlive || controller.PlayerPawn.Value == null)) return; + + controller.PlayerPawn.Value.Health = health; + + if (health > 100) + { + controller.PlayerPawn.Value.MaxHealth = health; + } + + Utilities.SetStateChanged(controller.PlayerPawn.Value, "CBaseEntity", "m_iHealth"); + } + + public static void Bury(this CBasePlayerPawn pawn, float depth = 10f) + { + var newPos = new Vector(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y, + pawn.AbsOrigin!.Z - depth); + + pawn.Teleport(newPos, pawn.AbsRotation!, pawn.AbsVelocity); + } + + public static void Unbury(this CBasePlayerPawn pawn, float depth = 15f) + { + var newPos = new Vector(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y, + pawn.AbsOrigin!.Z + depth); + + pawn.Teleport(newPos, pawn.AbsRotation!, pawn.AbsVelocity); + } + + public static void Freeze(this CBasePlayerPawn pawn) + { + pawn.MoveType = MoveType_t.MOVETYPE_OBSOLETE; + Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 1); // obsolete + Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); + } + + public static void Unfreeze(this CBasePlayerPawn pawn) + { + pawn.MoveType = MoveType_t.MOVETYPE_WALK; + Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk + Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); + } + + public static void ToggleNoclip(this CBasePlayerPawn pawn) + { + if (pawn.MoveType == MoveType_t.MOVETYPE_NOCLIP) + { + pawn.MoveType = MoveType_t.MOVETYPE_WALK; + Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk + Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); + } + else + { + pawn.MoveType = MoveType_t.MOVETYPE_NOCLIP; + Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 8); // noclip + Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); + } + } + + public static void Rename(this CCSPlayerController? controller, string newName = "Unknown") + { + newName ??= CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; + + if (controller != null) + { + var playerName = new SchemaString(controller, "m_iszPlayerName"); + playerName.Set(newName + " "); + + CS2_SimpleAdmin.Instance.AddTimer(0.25f, () => + { + Utilities.SetStateChanged(controller, "CCSPlayerController", "m_szClan"); + Utilities.SetStateChanged(controller, "CBasePlayerController", "m_iszPlayerName"); + }); + + CS2_SimpleAdmin.Instance.AddTimer(0.3f, () => + { + playerName.Set(newName); + }); + } + + CS2_SimpleAdmin.Instance.AddTimer(0.4f, () => + { + if (controller != null) Utilities.SetStateChanged(controller, "CBasePlayerController", "m_iszPlayerName"); + }); + } + + public static void TeleportPlayer(this CCSPlayerController? controller, CCSPlayerController? target) + { + if (controller?.PlayerPawn.Value == null && target?.PlayerPawn.Value == null) + return; + + if ( + controller?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null } && + target?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null } + ) + { + controller.PlayerPawn.Value.Teleport( + target.PlayerPawn.Value.AbsOrigin, + target.PlayerPawn.Value.AbsRotation, + target.PlayerPawn.Value.AbsVelocity + ); + } + } + + private static void PerformSlap(CBasePlayerPawn pawn, int damage = 0) + { + if (pawn.LifeState != (int)LifeState_t.LIFE_ALIVE) + return; + + /* Teleport in a random direction - thank you, Mani!*/ + /* Thank you AM & al!*/ + var random = new Random(); + var vel = new Vector(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z); + + vel.X += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1); + vel.Y += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1); + vel.Z += random.Next(200) + 100; + + pawn.AbsVelocity.X = vel.X; + pawn.AbsVelocity.Y = vel.Y; + pawn.AbsVelocity.Z = vel.Z; + + if (damage <= 0) + return; + + pawn.Health -= damage; + Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth"); + + if (pawn.Health <= 0) + pawn.CommitSuicide(true, true); + } + + public static void SendLocalizedMessage(this CCSPlayerController? controller, IStringLocalizer? localizer, + string messageKey, params object[] messageArgs) + { + if (controller == null || localizer == null) return; + + using (new WithTemporaryCulture(controller.GetLanguage())) + { + StringBuilder sb = new(); + sb.Append(localizer[messageKey, messageArgs]); + + foreach (var part in Helper.SeparateLines(sb.ToString())) + { + var lineWithPrefix = localizer["sa_prefix"] + part.Trim(); + controller.PrintToChat(lineWithPrefix); + } + } + } + + public static void SendLocalizedMessageCenter(this CCSPlayerController? controller, IStringLocalizer? localizer, + string messageKey, params object[] messageArgs) + { + if (controller == null || localizer == null) return; + + using (new WithTemporaryCulture(controller.GetLanguage())) + { + StringBuilder sb = new(); + sb.Append(localizer[messageKey, messageArgs]); + + foreach (var part in Helper.SeparateLines(sb.ToString())) + { + string _part; + _part = Helper.CenterMessage(part); + var lineWithPrefix = localizer["sa_prefix"] + _part; + controller.PrintToChat(lineWithPrefix); + } + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs new file mode 100644 index 0000000..7f16053 --- /dev/null +++ b/CS2-SimpleAdmin/Helper.cs @@ -0,0 +1,624 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Translations; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Menu; +using CounterStrikeSharp.API.ValveConstants.Protobuf; +using CS2_SimpleAdminApi; +using Discord; +using Discord.Webhook; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using System.Drawing; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using Color = Discord.Color; + +namespace CS2_SimpleAdmin; + +internal static class Helper +{ + private static readonly string AssemblyName = Assembly.GetExecutingAssembly().GetName().Name ?? ""; + private static readonly string CfgPath = $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/configs/plugins/{AssemblyName}/{AssemblyName}.json"; + + private delegate nint CNetworkSystemUpdatePublicIp(nint a1); + + private static CNetworkSystemUpdatePublicIp? _networkSystemUpdatePublicIp; + + internal static CS2_SimpleAdminConfig? Config { get; set; } + + public static bool IsDebugBuild + { + get + { +#if DEBUG + return true; +#else + return false; +#endif + } + } + + public static List GetPlayerFromName(string name) + { + return Utilities.GetPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase)); + } + + public static List GetPlayerFromSteamid64(string steamid) + { + return GetValidPlayers().FindAll(x => + x.SteamID.ToString().Equals(steamid, StringComparison.OrdinalIgnoreCase) + ); + } + + public static List GetPlayerFromIp(string ipAddress) + { + return GetValidPlayers().FindAll(x => + x.IpAddress != null && + x.IpAddress.Split(":")[0].Equals(ipAddress) + ); + } + + public static List GetValidPlayers() + { + return Utilities.GetPlayers().FindAll(p => p is + { IsValid: true, IsBot: false, Connected: PlayerConnectedState.PlayerConnected }); + } + + public static IEnumerable GetValidPlayersWithBots() + { + return Utilities.GetPlayers().FindAll(p => + p is { IsValid: true, IsBot: false, IsHLTV: false } or { IsValid: true, IsBot: true, IsHLTV: false } + ); + } + + public static bool IsValidSteamId64(string input) + { + const string pattern = @"^\d{17}$"; + return Regex.IsMatch(input, pattern); + } + + public static bool ValidateSteamId(string input, out SteamID? steamId) + { + steamId = null; + + if (string.IsNullOrEmpty(input)) + { + return false; + } + + if (!SteamID.TryParse(input, out var parsedSteamId)) return false; + + steamId = parsedSteamId; + return true; + } + + public static bool IsValidIp(string input) + { + const string pattern = @"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; + return Regex.IsMatch(input, pattern); + } + + public static void GivePlayerFlags(SteamID? steamid, List? flags = null, uint immunity = 0) + { + try + { + if (steamid == null || (flags == null && immunity == 0)) + { + return; + } + + if (flags == null) return; + foreach (var flag in flags.Where(flag => !string.IsNullOrEmpty(flag))) + { + if (flag.StartsWith($"@")) + { + //Console.WriteLine($"Adding permission {flag} to SteamID {steamid}"); + AdminManager.AddPlayerPermissions(steamid, flag); + } + else if (flag.StartsWith($"#")) + { + //Console.WriteLine($"Adding SteamID {steamid} to group {flag}"); + AdminManager.AddPlayerToGroup(steamid, flag); + } + } + + AdminManager.SetPlayerImmunity(steamid, immunity); + } + catch + { + } + } + + public static void KickPlayer(int userId, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED) + { + var player = Utilities.GetPlayerFromUserid(userId); + + if (player == null || !player.IsValid || player.IsHLTV) + return; + + player.Disconnect(reason); + + // if (!string.IsNullOrEmpty(reason)) + // { + // var escapeChars = reason.IndexOfAny([';', '|']); + // + // if (escapeChars != -1) + // { + // reason = reason[..escapeChars]; + // } + // } + // + // Server.ExecuteCommand($"kickid {userId} {reason}"); + } + + public static void PrintToCenterAll(string message) + { + Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false }).ToList().ForEach(controller => + { + controller.PrintToCenter(message); + }); + } + + internal static void HandleVotes(CCSPlayerController player, ChatMenuOption option) + { + if (!CS2_SimpleAdmin.VoteInProgress) + return; + + option.Disabled = true; + CS2_SimpleAdmin.VoteAnswers[option.Text]++; + } + + internal static void LogCommand(CCSPlayerController? caller, CommandInfo command) + { + if (CS2_SimpleAdmin._localizer == null) + return; + + var playerName = caller?.PlayerName ?? "Console"; + + var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer["sa_unknown"]; + + CS2_SimpleAdmin.Instance.Logger.LogInformation($"{CS2_SimpleAdmin._localizer[ + "sa_discord_log_command", + playerName, command.GetCommandString]}".Replace("HOSTNAME", hostname).Replace("**", "")); + + SendDiscordLogMessage(caller, command, CS2_SimpleAdmin.DiscordWebhookClientLog, CS2_SimpleAdmin._localizer); + } + + internal static void LogCommand(CCSPlayerController? caller, string command) + { + if (CS2_SimpleAdmin._localizer == null) + return; + + var playerName = caller?.PlayerName ?? "Console"; + var hostnameCvar = ConVar.Find("hostname"); + + var hostname = hostnameCvar?.StringValue ?? CS2_SimpleAdmin._localizer["sa_unknown"]; + + CS2_SimpleAdmin.Instance.Logger.LogInformation($"{CS2_SimpleAdmin._localizer["sa_discord_log_command", + playerName, command]}".Replace("HOSTNAME", hostname).Replace("**", "")); + + SendDiscordLogMessage(caller, command, CS2_SimpleAdmin.DiscordWebhookClientLog, CS2_SimpleAdmin._localizer); + } + + /*public static IEnumerable GenerateEmbedsDiscord(string title, string description, string thumbnailUrl, Color color, string[] fieldNames, string[] fieldValues, bool[] inlineFlags) + { + var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; + var address = $"{ConVar.Find("ip")?.StringValue}:{ConVar.Find("hostport")!.GetPrimitiveValue()}"; + + description = description.Replace("{hostname}", hostname); + description = description.Replace("{address}", address); + + var embed = new EmbedBuilder + { + Title = title, + Description = description, + ThumbnailUrl = thumbnailUrl, + Color = color, + }; + + for (var i = 0; i < fieldNames.Length; i++) + { + fieldValues[i] = fieldValues[i].Replace("{hostname}", hostname ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"); + fieldValues[i] = fieldValues[i].Replace("{address}", address ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"); + + embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]); + + if ((i + 1) % 2 == 0 && i < fieldNames.Length - 1) + { + embed.AddField("\u200b", "\u200b"); + } + } + + return new List { embed.Build() }; + }*/ + + private static void SendDiscordLogMessage(CCSPlayerController? caller, CommandInfo command, DiscordWebhookClient? discordWebhookClientLog, IStringLocalizer? localizer) + { + if (discordWebhookClientLog == null || localizer == null) return; + + var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : ""; + var callerName = caller != null ? caller.PlayerName : "Console"; + discordWebhookClientLog.SendMessageAsync(Helper.GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString])); + } + + private static void SendDiscordLogMessage(CCSPlayerController? caller, string command, DiscordWebhookClient? discordWebhookClientLog, IStringLocalizer? localizer) + { + if (discordWebhookClientLog == null || localizer == null) return; + + var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : ""; + var callerName = caller != null ? caller.PlayerName : "Console"; + discordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command])); + } + + public static void ShowAdminActivity(string messageKey, string? callerName = null, params object[] messageArgs) + { + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return; + if (CS2_SimpleAdmin._localizer == null) return; + + // Determine the localized message key + var localizedMessageKey = $"{messageKey}"; + var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray(); + + // // Replace placeholder based on showActivityType + // for (var i = 0; i < formattedMessageArgs.Length; i++) + // { + // var arg = formattedMessageArgs[i]; // Convert argument to string if not null + // // Replace "CALLER" placeholder in the argument string + // formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch + // { + // 1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]), + // 2 => arg.Replace("CALLER", callerName ?? "Console"), + // _ => arg + // }; + // } + + foreach (var controller in GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false })) + { + var currentMessageArgs = (string[])formattedMessageArgs.Clone(); + + // Replace "CALLER" placeholder based on showActivityType and whether the recipient is an admin + for (var i = 0; i < currentMessageArgs.Length; i++) + { + var arg = currentMessageArgs[i]; + currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch + { + 1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(controller, "@css/kick") || AdminManager.PlayerHasPermissions(controller, "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]), + 2 => arg.Replace("CALLER", callerName ?? "Console"), + _ => arg + }; + } + + // Send the localized message to each player + controller.SendLocalizedMessage(CS2_SimpleAdmin._localizer, localizedMessageKey, currentMessageArgs.Cast().ToArray()); + } + } + + public static void DisplayCenterMessage( + CCSPlayerController player, + string messageKey, + string? callerName = null, + params object[] messageArgs) + { + if (CS2_SimpleAdmin._localizer == null) return; + + // Determine the localized message key + var localizedMessageKey = $"{messageKey}"; + + var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray(); + + // Replace placeholder based on showActivityType + for (var i = 0; i < formattedMessageArgs.Length; i++) + { + var arg = formattedMessageArgs[i]; // Convert argument to string if not null + // Replace "CALLER" placeholder in the argument string + formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch + { + 1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]), + 2 => arg.Replace("CALLER", callerName ?? "Console"), + _ => arg + }; + } + + // Print the localized message to the center of the screen for the player + using (new WithTemporaryCulture(player.GetLanguage())) + { + player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast().ToArray()]); + } + } + + private static string ConvertMinutesToTime(int minutes) + { + var time = TimeSpan.FromMinutes(minutes); + + return time.Days > 0 ? $"{time.Days}d {time.Hours}h {time.Minutes}m" : time.Hours > 0 ? $"{time.Hours}h {time.Minutes}m" : $"{time.Minutes}m"; + } + + public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, CCSPlayerController? target, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer) + { + if (localizer == null) return; + + var penaltySetting = penalty switch + { + PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings, + PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyMuteSettings, + PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyGagSettings, + PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltySilenceSettings, + PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyWarnSettings, + _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null) + }; + + var webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value; + + if (string.IsNullOrEmpty(webhookUrl)) return; + + var callerCommunityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : ""; + var targetCommunityUrl = target != null ? "<" + new SteamID(target.SteamID).ToCommunityUrl() + ">" : ""; + var callerName = caller != null ? caller.PlayerName : "Console"; + var targetName = target != null ? target.PlayerName : localizer["sa_unknown"]; + var targetSteamId = target != null ? new SteamID(target.SteamID).SteamId64.ToString() : localizer["sa_unknown"]; + + var futureTime = DateTime.Now.AddMinutes(duration); + var futureUnixTimestamp = new DateTimeOffset(futureTime).ToUnixTimeSeconds(); + + string time; + + if (penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value == "{relative}") + time = duration != 0 ? $"" : localizer["sa_permanent"]; + else + time = duration != 0 ? ConvertMinutesToTime(duration) : localizer["sa_permanent"]; + + string[] fieldNames = [ + localizer["sa_player"], + localizer["sa_steamid"], + localizer["sa_duration"], + localizer["sa_reason"], + localizer["sa_admin"]]; + string[] fieldValues = + [ + $"[{targetName}]({targetCommunityUrl})", $"||{targetSteamId}||", time, reason, + $"[{callerName}]({callerCommunityUrl})" + ]; + bool[] inlineFlags = [true, true, true, false, false]; + + var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"]; + + var colorHex = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value ?? "#FFFFFF"; + var color = ColorTranslator.FromHtml(colorHex); + + var embed = new EmbedBuilder + { + Color = new Color(color.R, color.G, color.B), + Title = penalty switch + { + PenaltyType.Ban => localizer["sa_discord_penalty_ban"], + PenaltyType.Mute => localizer["sa_discord_penalty_mute"], + PenaltyType.Gag => localizer["sa_discord_penalty_gag"], + PenaltyType.Silence => localizer["sa_discord_penalty_silence"], + PenaltyType.Warn => localizer["sa_discord_penalty_warn"], + _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null) + }, + ThumbnailUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ThumbnailUrl"))?.Value, + ImageUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ImageUrl"))?.Value, + Footer = new EmbedFooterBuilder + { + Text = penaltySetting.FirstOrDefault(s => s.Name.Equals("Footer"))?.Value, + }, + Description = $"{hostname}", + Timestamp = DateTimeOffset.Now, + }; + + for (var i = 0; i < fieldNames.Length; i++) + { + embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]); + } + + Task.Run(async () => + { + await new DiscordWebhookClient(webhookUrl).SendMessageAsync(embeds: [embed.Build()]); + }); + } + + private static string GenerateMessageDiscord(string message) + { + var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; + var address = $"{ConVar.Find("ip")?.StringValue}:{ConVar.Find("hostport")!.GetPrimitiveValue()}"; + + message = message.Replace("HOSTNAME", hostname); + message = message.Replace("ADDRESS", address); + + return message; + } + + public static string[] SeparateLines(string message) + { + return message.Split(["\r\n", "\r", "\n"], StringSplitOptions.None); + } + + public static string CenterMessage(string message) => + $"⠀⠀⠀⠀⠀⠀⠀⠀{message}⠀⠀⠀⠀⠀⠀⠀⠀"; + + public static string GetServerIp() + { + var networkSystem = NativeAPI.GetValveInterface(0, "NetworkSystemVersion001"); + + unsafe + { + if (_networkSystemUpdatePublicIp == null) + { + var funcPtr = *(nint*)(*(nint*)(networkSystem) + 256); + _networkSystemUpdatePublicIp = Marshal.GetDelegateForFunctionPointer(funcPtr); + } + /* + struct netadr_t + { + uint32_t type + uint8_t ip[4] + uint16_t port + } + */ + // + 4 to skip type, because the size of uint32_t is 4 bytes + var ipBytes = (byte*)(_networkSystemUpdatePublicIp(networkSystem) + 4); + // port is always 0, use the one from convar "hostport" + return $"{ipBytes[0]}.{ipBytes[1]}.{ipBytes[2]}.{ipBytes[3]}"; + } + } + + + public static void UpdateConfig(T config) where T : BasePluginConfig, new() + { + // get newest config version + var newCfgVersion = new T().Version; + + // loaded config is up to date + if (config.Version == newCfgVersion) + return; + + // update the version + config.Version = newCfgVersion; + + // serialize the updated config back to json + var updatedJsonContent = JsonSerializer.Serialize(config, + new JsonSerializerOptions + { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + File.WriteAllText(CfgPath, updatedJsonContent); + } + + public static void TryLogCommandOnDiscord(CCSPlayerController? caller, string commandString) + { + if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || CS2_SimpleAdmin._localizer == null) + return; + + if (caller != null && caller.IsValid == false) + caller = null; + + var callerName = caller == null ? "Console" : caller.PlayerName; + var communityUrl = caller != null + ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" + : ""; + CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord( + CS2_SimpleAdmin._localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", + commandString])); + } +} + +public static class PluginInfo +{ + internal static async Task CheckVersion(string version, ILogger logger) + { + using HttpClient client = new(); + + try + { + var response = await client.GetAsync("https://raw.githubusercontent.com/daffyyyy/CS2-SimpleAdmin/main/CS2-SimpleAdmin/VERSION"); + + if (response.IsSuccessStatusCode) + { + var remoteVersion = await response.Content.ReadAsStringAsync(); + remoteVersion = remoteVersion.Trim(); + + var comparisonResult = string.CompareOrdinal(version, remoteVersion); + + switch (comparisonResult) + { + case < 0: + logger.LogWarning("Plugin is outdated! Check https://github.com/daffyyyy/CS2-SimpleAdmin"); + break; + case > 0: + logger.LogInformation("Probably dev version detected"); + break; + default: + logger.LogInformation("Plugin is up to date"); + break; + } + } + else + { + logger.LogWarning("Failed to check version"); + } + } + catch (HttpRequestException ex) + { + logger.LogError(ex, "Failed to connect to the version server."); + } + catch (Exception ex) + { + logger.LogError(ex, "An error occurred while checking version."); + } + } + + internal static void ShowAd(string moduleVersion) + { + Console.WriteLine(" "); + Console.WriteLine(" _______ ___ __ __ _______ ___ _______ _______ ______ __ __ ___ __ _ "); + Console.WriteLine("| || | | |_| || || | | || _ || | | |_| || | | | | |"); + Console.WriteLine("| _____|| | | || _ || | | ___|| |_| || _ || || | | |_| |"); + Console.WriteLine("| |_____ | | | || |_| || | | |___ | || | | || || | | |"); + Console.WriteLine("|_____ || | | || ___|| |___ | ___|| || |_| || || | | _ |"); + Console.WriteLine(" _____| || | | ||_|| || | | || |___ | _ || || ||_|| || | | | | |"); + Console.WriteLine("|_______||___| |_| |_||___| |_______||_______||__| |__||______| |_| |_||___| |_| |__|"); + Console.WriteLine(" >> Version: " + moduleVersion); + Console.WriteLine(" >> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin"); + Console.WriteLine(" "); + } +} + +public class SchemaString(TSchemaClass instance, string member) + : NativeObject(Schema.GetSchemaValue(instance.Handle, typeof(TSchemaClass).Name, member)) + where TSchemaClass : NativeObject +{ + public unsafe void Set(string str) + { + var bytes = GetStringBytes(str); + + for (var i = 0; i < bytes.Length; i++) + { + Unsafe.Write((void*)(Handle.ToInt64() + i), bytes[i]); + } + + Unsafe.Write((void*)(Handle.ToInt64() + bytes.Length), 0); + } + + private static byte[] GetStringBytes(string str) + { + return Encoding.UTF8.GetBytes(str); + } +} + +public static class Time +{ + public static DateTime ActualDateTime() + { + string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone; + DateTime utcNow = DateTime.UtcNow; + + try + { + TimeZoneInfo timezone = TimeZoneInfo.FindSystemTimeZoneById(timezoneId); + DateTime userTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, timezone); + + return userTime; + } + catch (TimeZoneNotFoundException) + { + CS2_SimpleAdmin._logger?.LogWarning($"Time zone '{timezoneId}' not found. Returning UTC time."); + return utcNow; + } + catch (InvalidTimeZoneException) + { + CS2_SimpleAdmin._logger?.LogWarning($"Time zone '{timezoneId}' is invalid. Returning UTC time."); + return utcNow; + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/BanManager.cs b/CS2-SimpleAdmin/Managers/BanManager.cs new file mode 100644 index 0000000..0364bbe --- /dev/null +++ b/CS2-SimpleAdmin/Managers/BanManager.cs @@ -0,0 +1,418 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.ValveConstants.Protobuf; +using CS2_SimpleAdminApi; +using Dapper; +using Microsoft.Extensions.Logging; +using MySqlConnector; +using System.Text; + +namespace CS2_SimpleAdmin.Managers; + +internal class BanManager(Database.Database database, CS2_SimpleAdminConfig config) +{ + public async Task BanPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0) + { + DateTime now = Time.ActualDateTime(); + DateTime futureTime = now.AddMinutes(time); + + await using MySqlConnection connection = await database.GetConnectionAsync(); + try + { + const string sql = + "INSERT INTO `sa_bans` (`player_steamid`, `player_name`, `player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " + + "VALUES (@playerSteamid, @playerName, @playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid)"; + + await connection.ExecuteAsync(sql, new + { + playerSteamid = player.SteamId.SteamId64.ToString(), + playerName = player.Name, + playerIp = config.OtherSettings.BanType == 1 ? player.IpAddress : null, + adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? "Console", + adminName = issuer?.Name ?? "Console", + banReason = reason, + duration = time, + ends = futureTime, + created = now, + serverid = CS2_SimpleAdmin.ServerId + }); + } + catch { } + } + + public async Task AddBanBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0) + { + if (string.IsNullOrEmpty(playerSteamId)) return; + + DateTime now = Time.ActualDateTime(); + DateTime futureTime = now.AddMinutes(time); + + try + { + await using MySqlConnection connection = await database.GetConnectionAsync(); + + var sql = "INSERT INTO `sa_bans` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " + + "VALUES (@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid)"; + + await connection.ExecuteAsync(sql, new + { + playerSteamid = playerSteamId, + adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? "Console", + adminName = issuer?.Name ?? "Console", + banReason = reason, + duration = time, + ends = futureTime, + created = now, + serverid = CS2_SimpleAdmin.ServerId + }); + } + catch { } + } + + public async Task AddBanByIp(string playerIp, PlayerInfo? issuer, string reason, int time = 0) + { + if (string.IsNullOrEmpty(playerIp)) return; + + DateTime now = Time.ActualDateTime(); + DateTime futureTime = now.AddMinutes(time); + + try + { + await using MySqlConnection connection = await database.GetConnectionAsync(); + + var sql = "INSERT INTO `sa_bans` (`player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " + + "VALUES (@playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid)"; + + await connection.ExecuteAsync(sql, new + { + playerIp, + adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? "Console", + adminName = issuer?.Name ?? "Console", + banReason = reason, + duration = time, + ends = futureTime, + created = now, + serverid = CS2_SimpleAdmin.ServerId + }); + } + catch { } + } + + public async Task IsPlayerBanned(PlayerInfo player) + { + if (player.IpAddress == null) + { + return false; + } + +#if DEBUG + if (CS2_SimpleAdmin._logger != null) + CS2_SimpleAdmin._logger.LogCritical($"IsPlayerBanned for {player.Name}"); +#endif + + int banCount; + + DateTime currentTime = Time.ActualDateTime(); + + try + { + var sql = config.MultiServerMode ? """ + UPDATE sa_bans + SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END, + player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END + WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) + AND status = 'ACTIVE' + AND (duration = 0 OR ends > @CurrentTime); + + SELECT COUNT(*) FROM sa_bans + WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) + AND status = 'ACTIVE' + AND (duration = 0 OR ends > @CurrentTime); + """ : @" + UPDATE sa_bans + SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END, + player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END + WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) + AND status = 'ACTIVE' + AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid; + + SELECT COUNT(*) FROM sa_bans + WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) + AND status = 'ACTIVE' + AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid;"; + + await using var connection = await database.GetConnectionAsync(); + + var parameters = new + { + PlayerSteamID = player.SteamId.SteamId64.ToString(), + PlayerIP = config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(player.IpAddress) ? null : player.IpAddress, + PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty, + CurrentTime = currentTime, + serverid = CS2_SimpleAdmin.ServerId + }; + + banCount = await connection.ExecuteScalarAsync(sql, parameters); + + if (config.OtherSettings.BanType == 1 && banCount == 0) + { + sql = """ + SELECT + COUNT(*) + FROM + sa_bans + JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid + WHERE + sa_bans.status = 'ACTIVE' + AND sa_players_ips.address = @PlayerIP; + """; + + banCount = await connection.ExecuteScalarAsync(sql, new + { + PlayerIP = player.IpAddress + }); + } + } + catch (Exception) + { + return false; + } + + return banCount > 0; + } + + public async Task GetPlayerBans(PlayerInfo player) + { + try + { + var sql = ""; + + sql = config.MultiServerMode + ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)" + : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid"; + + int banCount; + + await using var connection = await database.GetConnectionAsync(); + + if (config.OtherSettings.BanType > 0 && !string.IsNullOrEmpty(player.IpAddress)) + { + banCount = await connection.ExecuteScalarAsync(sql, + new + { + PlayerSteamID = player.SteamId.SteamId64.ToString(), + PlayerIP = player.IpAddress, + serverid = CS2_SimpleAdmin.ServerId + }); + } + else + { + banCount = await connection.ExecuteScalarAsync(sql, + new + { + PlayerSteamID = player.SteamId.SteamId64.ToString(), + PlayerIP = DBNull.Value, + serverid = CS2_SimpleAdmin.ServerId + }); + } + + return banCount; + } + catch { } + + return 0; + } + + public async Task UnbanPlayer(string playerPattern, string adminSteamId, string reason) + { + if (playerPattern is not { Length: > 1 }) + { + return; + } + try + { + await using var connection = await database.GetConnectionAsync(); + + var sqlRetrieveBans = config.MultiServerMode + ? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'" + : "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid"; + + var bans = await connection.QueryAsync(sqlRetrieveBans, new { pattern = playerPattern, serverid = CS2_SimpleAdmin.ServerId }); + + var bansList = bans as dynamic[] ?? bans.ToArray(); + if (bansList.Length == 0) + return; + + const string sqlAdmin = "SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId"; + var sqlInsertUnban = "INSERT INTO sa_unbans (ban_id, admin_id, reason) VALUES (@banId, @adminId, @reason); SELECT LAST_INSERT_ID();"; + + var sqlAdminId = await connection.ExecuteScalarAsync(sqlAdmin, new { adminSteamId }); + var adminId = sqlAdminId ?? 0; + + foreach (var ban in bansList) + { + int banId = ban.id; + int? unbanId; + + if (reason != null) + { + unbanId = await connection.ExecuteScalarAsync(sqlInsertUnban, new { banId, adminId, reason }); + } + else + { + sqlInsertUnban = "INSERT INTO sa_unbans (ban_id, admin_id) VALUES (@banId, @adminId); SELECT LAST_INSERT_ID();"; + unbanId = await connection.ExecuteScalarAsync(sqlInsertUnban, new { banId, adminId }); + } + + const string sqlUpdateBan = "UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId"; + await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId }); + } + + } + catch { } + } + + public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players) + { + try + { + await using var connection = await database.GetConnectionAsync(); + bool checkIpBans = config.OtherSettings.BanType > 0; + + var filteredPlayers = players.Where(p => p.UserId.HasValue).ToList(); + + var steamIds = filteredPlayers.Select(p => p.SteamID).Distinct().ToList(); + var ipAddresses = filteredPlayers + .Where(p => !string.IsNullOrEmpty(p.IpAddress)) + .Select(p => p.IpAddress) + .Distinct() + .ToList(); + + var sql = new StringBuilder(); + sql.Append("SELECT `player_steamid`, `player_ip` FROM `sa_bans` WHERE `status` = 'ACTIVE' "); + + if (config.MultiServerMode) + { + sql.Append("AND (player_steamid IN @SteamIDs "); + if (checkIpBans && ipAddresses.Count != 0) + { + sql.Append("OR player_ip IN @IpAddresses"); + } + sql.Append(')'); + } + else + { + sql.Append("AND server_id = @ServerId AND (player_steamid IN @SteamIDs "); + if (checkIpBans && ipAddresses.Count != 0) + { + sql.Append("OR player_ip IN @IpAddresses"); + } + sql.Append(')'); + } + + var bannedPlayers = await connection.QueryAsync<(ulong PlayerSteamID, string PlayerIP)>( + sql.ToString(), + new + { + SteamIDs = steamIds, + IpAddresses = checkIpBans ? ipAddresses : [], + ServerId = CS2_SimpleAdmin.ServerId + }); + + var valueTuples = bannedPlayers.ToList(); + var bannedSteamIds = valueTuples.Select(b => b.PlayerSteamID).ToHashSet(); + var bannedIps = valueTuples.Select(b => b.PlayerIP).ToHashSet(); + + foreach (var player in filteredPlayers.Where(player => bannedSteamIds.Contains(player.SteamID) || + (checkIpBans && bannedIps.Contains(player.IpAddress ?? "")))) + { + if (!player.UserId.HasValue) continue; + + await Server.NextFrameAsync(() => + { + Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED); + }); + } + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}"); + } + } + + public async Task ExpireOldBans() + { + var currentTime = Time.ActualDateTime(); + + try + { + await using var connection = await database.GetConnectionAsync(); + /* + string sql = ""; + await using MySqlConnection connection = await _database.GetConnectionAsync(); + + sql = "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"; + await connection.ExecuteAsync(sql, new { CurrentTime = DateTime.UtcNow }); + */ + + var sql = ""; + + sql = config.MultiServerMode ? """ + + UPDATE sa_bans + SET + status = 'EXPIRED' + WHERE + status = 'ACTIVE' + AND + `duration` > 0 + AND + ends <= @currentTime + """ : """ + + UPDATE sa_bans + SET + status = 'EXPIRED' + WHERE + status = 'ACTIVE' + AND + `duration` > 0 + AND + ends <= @currentTime + AND server_id = @serverid + """; + + await connection.ExecuteAsync(sql, new { currentTime, serverid = CS2_SimpleAdmin.ServerId }); + + if (config.OtherSettings.ExpireOldIpBans > 0) + { + var ipBansTime = currentTime.AddDays(-config.OtherSettings.ExpireOldIpBans); + sql = config.MultiServerMode ? """ + + UPDATE sa_bans + SET + player_ip = NULL + WHERE + status = 'ACTIVE' + AND + ends <= @ipBansTime + """ : """ + + UPDATE sa_bans + SET + player_ip = NULL + WHERE + status = 'ACTIVE' + AND + ends <= @ipBansTime + AND server_id = @serverid + """; + + await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId }); + } + } + catch (Exception) + { + CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired bans"); + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/MuteManager.cs b/CS2-SimpleAdmin/Managers/MuteManager.cs new file mode 100644 index 0000000..021b3fb --- /dev/null +++ b/CS2-SimpleAdmin/Managers/MuteManager.cs @@ -0,0 +1,294 @@ +using CS2_SimpleAdminApi; +using Dapper; +using Microsoft.Extensions.Logging; + +namespace CS2_SimpleAdmin.Managers; + +internal class MuteManager(Database.Database database) +{ + public async Task MutePlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0, int type = 0) + { + var now = Time.ActualDateTime(); + var futureTime = now.AddMinutes(time); + + var muteType = type switch + { + 1 => "MUTE", + 2 => "SILENCE", + _ => "GAG" + }; + + try + { + await using var connection = await database.GetConnectionAsync(); + const string sql = + "INSERT INTO `sa_mutes` (`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`) " + + "VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid)"; + + await connection.ExecuteAsync(sql, new + { + playerSteamid = player.SteamId.SteamId64.ToString(), + playerName = player.Name, + adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? "Console", + adminName = issuer?.Name ?? "Console", + muteReason = reason, + duration = time, + ends = futureTime, + created = now, + type = muteType, + serverid = CS2_SimpleAdmin.ServerId + }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex.Message); + }; + } + + public async Task AddMuteBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0, int type = 0) + { + if (string.IsNullOrEmpty(playerSteamId)) return; + + + var now = Time.ActualDateTime(); + var futureTime = now.AddMinutes(time); + + var muteType = type switch + { + 1 => "MUTE", + 2 => "SILENCE", + _ => "GAG" + }; + + try + { + await using var connection = await database.GetConnectionAsync(); + const string sql = "INSERT INTO `sa_mutes` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`) " + + "VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid)"; + + await connection.ExecuteAsync(sql, new + { + playerSteamid = playerSteamId, + adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? "Console", + adminName = issuer?.Name ?? "Console", + muteReason = reason, + duration = time, + ends = futureTime, + created = now, + type = muteType, + serverid = CS2_SimpleAdmin.ServerId + }); + } + catch { }; + } + + public async Task> IsPlayerMuted(string steamId) + { + if (string.IsNullOrEmpty(steamId)) + { + return []; + } + +#if DEBUG + if (CS2_SimpleAdmin._logger != null) + CS2_SimpleAdmin._logger.LogCritical($"IsPlayerMuted for {steamId}"); +#endif + + try + { + await using var connection = await database.GetConnectionAsync(); + var currentTime = Time.ActualDateTime(); + var sql = ""; + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1 + ? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)" + : "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0))"; + } + else + { + sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1 + ? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid" + : "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0)) AND server_id = @serverid"; + + } + + var parameters = new { PlayerSteamID = steamId, CurrentTime = currentTime, serverid = CS2_SimpleAdmin.ServerId }; + var activeMutes = (await connection.QueryAsync(sql, parameters)).ToList(); + return activeMutes; + } + catch (Exception) + { + return []; + } + } + + public async Task GetPlayerMutes(PlayerInfo playerInfo, int type = 0) + { + try + { + var muteType = type switch + { + 1 => "MUTE", + 2 => "SILENCE", + _ => "GAG" + }; + + await using var connection = await database.GetConnectionAsync(); + + var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "SELECT COUNT(*) FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND type = @MuteType" + : "SELECT COUNT(*) FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND type = @MuteType"; + + var muteCount = await connection.ExecuteScalarAsync(sql, new { PlayerSteamID = playerInfo.SteamId.SteamId64.ToString(), serverid = CS2_SimpleAdmin.ServerId, MuteType = muteType }); + return muteCount; + } + catch (Exception) + { + return 0; + } + } + + public async Task CheckOnlineModeMutes(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players) + { + try + { + int batchSize = 10; + await using var connection = await database.GetConnectionAsync(); + + var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE'" + : "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid"; + + for (var i = 0; i < players.Count; i += batchSize) + { + var batch = players.Skip(i).Take(batchSize); + var parametersList = new List(); + + foreach (var (IpAddress, SteamID, UserId, Slot) in batch) + { + parametersList.Add(new { PlayerSteamID = SteamID, serverid = CS2_SimpleAdmin.ServerId }); + } + + await connection.ExecuteAsync(sql, parametersList); + } + + sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE'" + : "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid"; + + + foreach (var (IpAddress, SteamID, UserId, Slot) in players) + { + var muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = SteamID, serverid = CS2_SimpleAdmin.ServerId }); + + foreach (var muteRecord in muteRecords) + { + DateTime endDateTime = muteRecord.ends; + PlayerPenaltyManager.RemovePenaltiesByDateTime(Slot, endDateTime); + } + + } + } + catch { } + } + + public async Task UnmutePlayer(string playerPattern, string adminSteamId, string reason, int type = 0) + { + if (playerPattern.Length <= 1) + { + return; + } + + try + { + await using var connection = await database.GetConnectionAsync(); + + var muteType = type switch + { + 1 => "MUTE", + 2 => "SILENCE", + _ => "GAG" + }; + + string sqlRetrieveMutes; + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + sqlRetrieveMutes = "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND " + + "type = @muteType AND status = 'ACTIVE'"; + } + else + { + sqlRetrieveMutes = "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND " + + "type = @muteType AND status = 'ACTIVE' AND server_id = @serverid"; + } + + var mutes = await connection.QueryAsync(sqlRetrieveMutes, new { pattern = playerPattern, muteType, serverid = CS2_SimpleAdmin.ServerId }); + + var mutesList = mutes as dynamic[] ?? mutes.ToArray(); + if (mutesList.Length == 0) + return; + + const string sqlAdmin = "SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId"; + var sqlInsertUnmute = "INSERT INTO sa_unmutes (mute_id, admin_id, reason) VALUES (@muteId, @adminId, @reason); SELECT LAST_INSERT_ID();"; + + var sqlAdminId = await connection.ExecuteScalarAsync(sqlAdmin, new { adminSteamId }); + var adminId = sqlAdminId ?? 0; + + foreach (var mute in mutesList) + { + int muteId = mute.id; + int? unmuteId; + + // Insert into sa_unmutes + if (reason != null) + { + unmuteId = await connection.ExecuteScalarAsync(sqlInsertUnmute, new { muteId, adminId, reason }); + } + else + { + sqlInsertUnmute = "INSERT INTO sa_unmutes (muteId, admin_id) VALUES (@muteId, @adminId); SELECT LAST_INSERT_ID();"; + unmuteId = await connection.ExecuteScalarAsync(sqlInsertUnmute, new { muteId, adminId }); + } + + // Update sa_mutes to set unmute_id + const string sqlUpdateMute = "UPDATE sa_mutes SET status = 'UNMUTED', unmute_id = @unmuteId WHERE id = @muteId"; + await connection.ExecuteAsync(sqlUpdateMute, new { unmuteId, muteId }); + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + public async Task ExpireOldMutes() + { + try + { + await using var connection = await database.GetConnectionAsync(); + var sql = ""; + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1 + ? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime" + : "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration`"; + } + else + { + sql = CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 1 + ? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid" + : "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration` AND server_id = @serverid"; + } + + await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId }); + } + catch (Exception) + { + CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired mutes"); + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/PermissionManager.cs b/CS2-SimpleAdmin/Managers/PermissionManager.cs new file mode 100644 index 0000000..c094191 --- /dev/null +++ b/CS2-SimpleAdmin/Managers/PermissionManager.cs @@ -0,0 +1,535 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Modules.Entities; +using Dapper; +using Microsoft.Extensions.Logging; +using MySqlConnector; +using Newtonsoft.Json; +using System.Collections.Concurrent; + +namespace CS2_SimpleAdmin.Managers; + +public class PermissionManager(Database.Database database) +{ + // Unused for now + //public static readonly ConcurrentDictionary> _adminCache = new ConcurrentDictionary>(); + public static readonly ConcurrentDictionary AdminCache = new(); + + /* + public async Task, int)>> GetAdminFlags(string steamId) + { + DateTime now = Time.ActualDateTime(); + + await using MySqlConnection connection = await _database.GetConnectionAsync(); + + string sql = "SELECT flags, immunity, ends FROM sa_admins WHERE player_steamid = @PlayerSteamID AND (ends IS NULL OR ends > @CurrentTime) AND (server_id IS NULL OR server_id = @serverid)"; + List? activeFlags = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId }))?.ToList(); + + if (activeFlags == null) + { + return new List<(List, int)>(); + } + + List<(List, int)> filteredFlagsWithImmunity = []; + + foreach (dynamic flags in activeFlags) + { + if (flags is not IDictionary flagsDict) + { + continue; + } + + if (!flagsDict.TryGetValue("flags", out var flagsValueObj) || !flagsDict.TryGetValue("immunity", out var immunityValueObj)) + { + continue; + } + + if (!(flagsValueObj is string flagsValue) || !int.TryParse(immunityValueObj.ToString(), out var immunityValue)) + { + continue; + } + + //Console.WriteLine($"Flags: {flagsValue}, Immunity: {immunityValue}"); + + filteredFlagsWithImmunity.Add((flagsValue.Split(',').ToList(), immunityValue)); + } + + return filteredFlagsWithImmunity; + } + */ + + private async Task, int, DateTime?)>> GetAllPlayersFlags() + { + var now = Time.ActualDateTime(); + + try + { + await using var connection = await database.GetConnectionAsync(); + + const string sql = """ + SELECT sa_admins.player_steamid, sa_admins.player_name, sa_admins_flags.flag, sa_admins.immunity, sa_admins.ends + FROM sa_admins_flags + JOIN sa_admins ON sa_admins_flags.admin_id = sa_admins.id + WHERE (sa_admins.ends IS NULL OR sa_admins.ends > @CurrentTime) + AND (sa_admins.server_id IS NULL OR sa_admins.server_id = @serverid) + ORDER BY sa_admins.player_steamid + """; + + var admins = (await connection.QueryAsync(sql, new { CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId })).ToList(); + + // Group by player_steamid and aggregate the flags + var groupedPlayers = admins + .GroupBy(r => new { r.player_steamid, r.player_name, r.immunity, r.ends }) + .Select(g => ( + PlayerSteamId: (string)g.Key.player_steamid, + PlayerName: (string)g.Key.player_name, + Flags: g.Select(r => (string)r.flag).Distinct().ToList(), + Immunity: g.Key.immunity is int i ? i : int.TryParse((string)g.Key.immunity, out var immunity) ? immunity : 0, + Ends: g.Key.ends is DateTime dateTime ? dateTime : (DateTime?)null + )) + .ToList(); + + /* + foreach (var player in groupedPlayers) + { + Console.WriteLine($"Player SteamID: {player.PlayerSteamId}, Name: {player.PlayerName}, Flags: {string.Join(", ", player.Flags)}, Immunity: {player.Immunity}, Ends: {player.Ends}"); + } + */ + + List<(string, string, List, int, DateTime?)> filteredFlagsWithImmunity = []; + + // Add the grouped players to the list + filteredFlagsWithImmunity.AddRange(groupedPlayers); + + return filteredFlagsWithImmunity; + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex.ToString()); + return []; + } + } + + /* + public async Task, List>, int>>> GetAllGroupsFlags() + { + try + { + await using MySqlConnection connection = await _database.GetConnectionAsync(); + + string sql = "SELECT group_id FROM sa_groups_servers WHERE server_id = @serverid"; + var groupIds = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); + + sql = @" + SELECT g.group_id, f.flag + FROM sa_groups_flags f + JOIN sa_groups_servers g ON f.group_id = g.group_id + WHERE g.server_id = @serverid"; + + var groupFlagData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); + + if (groupIds.Count == 0 || groupFlagData.Count == 0) + { + return []; + } + + var groupInfoDictionary = new Dictionary, List>, int>>(); + + foreach (var groupId in groupIds) + { + groupInfoDictionary[groupId] = new Tuple, List>, int>([], [], 0); + } + + foreach (var row in groupFlagData) + { + var groupId = (int)row.group_id; + var flag = (string)row.flag; + + groupInfoDictionary[groupId].Item1.Add(flag); + } + + sql = @" + SELECT a.group_id, a.player_steamid, a.ends, g.immunity, g.name + FROM sa_admins a + JOIN sa_groups g ON a.group_id = g.id + WHERE a.group_id IN @groupIds"; + + var playerData = (await connection.QueryAsync(sql, new { groupIds })).ToList(); + + foreach (var row in playerData) + { + var groupId = (int)row.group_id; + var playerSteamid = (string)row.player_steamid; + var ends = row.ends as DateTime?; + var immunity = (int)row.immunity; + + groupInfoDictionary[groupId].Item2.Add(new Tuple(playerSteamid, ends)); + groupInfoDictionary[groupId] = new Tuple, List>, int>(groupInfoDictionary[groupId].Item1, groupInfoDictionary[groupId].Item2, immunity); + } + + return groupInfoDictionary; + } + catch { } + + return []; + } + */ + + private async Task, int)>> GetAllGroupsData() + { + await using MySqlConnection connection = await database.GetConnectionAsync(); + try + { + var sql = "SELECT group_id FROM sa_groups_servers WHERE (server_id = @serverid OR server_id IS NULL)"; + var groupDataSql = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); + + sql = """ + SELECT g.group_id, sg.name AS group_name, sg.immunity, f.flag + FROM sa_groups_flags f + JOIN sa_groups_servers g ON f.group_id = g.group_id + JOIN sa_groups sg ON sg.id = g.group_id + WHERE (g.server_id = @serverid OR server_id IS NULL) + """; + + var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList(); + + if (groupDataSql.Count == 0 || groupData.Count == 0) + { + return []; + } + + var groupInfoDictionary = new Dictionary, int)>(); + + foreach (var row in groupData) + { + var groupName = (string)row.group_name; + var flag = (string)row.flag; + var immunity = (int)row.immunity; + + // Check if the group name already exists in the dictionary + if (!groupInfoDictionary.TryGetValue(groupName, out (List, int) value)) + { + value = ([], immunity); + // If it doesn't exist, add a new entry with an empty list of flags and immunity + groupInfoDictionary[groupName] = value; + } + + value.Item1.Add(flag); + } + + return groupInfoDictionary; + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex.ToString()); + } + + return []; + } + + public async Task CrateGroupsJsonFile() + { + var groupsData = await GetAllGroupsData(); + + var jsonData = new Dictionary(); + + foreach (var kvp in groupsData) + { + var groupData = new Dictionary + { + ["flags"] = kvp.Value.Item1, + ["immunity"] = kvp.Value.Item2 + }; + + jsonData[kvp.Key] = groupData; + } + + var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented); + var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "groups.json"); + await File.WriteAllTextAsync(filePath, json); + } + + /* + public async Task GiveAllGroupsFlags() + { + Dictionary, List>, int>> groupFlags = await GetAllGroupsFlags(); + + foreach (var kvp in groupFlags) + { + var flags = kvp.Value.Item1; + var players = kvp.Value.Item2; + int immunity = kvp.Value.Item3; + + foreach (var playerTuple in players) + { + var steamIdStr = playerTuple.Item1; + var ends = playerTuple.Item2; + + if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null) + { + if (!_adminCache.ContainsKey(steamId)) + { + _adminCache.TryAdd(steamId, ends); + } + + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + // Often need to call 2 times + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + } + } + } + } + */ + /* + public async Task GiveAllFlags() + { + List<(string, string, List, int, DateTime?)> allPlayers = await GetAllPlayersFlags(); + + foreach (var record in allPlayers) + { + string steamIdStr = record.Item1; + List flags = record.Item2; + int immunity = record.Item3; + + DateTime? ends = record.Item4; + + if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null) + { + if (!_adminCache.ContainsKey(steamId)) + { + _adminCache.TryAdd(steamId, ends); + //_adminCacheTimestamps.Add(steamId, ends); + } + + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + // Often need to call 2 times + Helper.GivePlayerFlags(steamId, flags, (uint)immunity); + } + } + } + */ + + public async Task CreateAdminsJsonFile() + { + List<(string identity, string name, List flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags(); + var validPlayers = allPlayers + .Where(player => SteamID.TryParse(player.identity, out _)) // Filter invalid SteamID + .ToList(); + + /* + foreach (var player in allPlayers) + { + var (steamId, name, flags, immunity, ends) = player; + + // Print or process each item + Console.WriteLine($"Player SteamID: {steamId}"); + Console.WriteLine($"Player Name: {name}"); + Console.WriteLine($"Flags: {string.Join(", ", flags)}"); + Console.WriteLine($"Immunity: {immunity}"); + Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}"); + Console.WriteLine(); // New line for better readability + } + */ + + var jsonData = validPlayers + .Select(player => + { + SteamID.TryParse(player.identity, out var steamId); + + // Update cache if SteamID is valid and not already cached + if (steamId != null && !AdminCache.ContainsKey(steamId)) + { + AdminCache.TryAdd(steamId, player.ends); + } + + // Create an anonymous object with player data + return new + { + playerName = player.name, + playerData = new + { + player.identity, + player.immunity, + flags = player.flags.Where(flag => flag.StartsWith("@")).ToList(), + groups = player.flags.Where(flag => flag.StartsWith("#")).ToList() + } + }; + }) + .ToDictionary(item => item.playerName, item => (object)item.playerData); + + var json = JsonConvert.SerializeObject(jsonData, Formatting.Indented); + + var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json"); + await File.WriteAllTextAsync(filePath, json); + + //await File.WriteAllTextAsync(CS2_SimpleAdmin.Instance.ModuleDirectory + "/data/admins.json", json); + } + + public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false) + { + if (string.IsNullOrEmpty(playerSteamId)) return; + + //_adminCache.TryRemove(playerSteamId, out _); + + try + { + await using var connection = await database.GetConnectionAsync(); + + var sql = globalDelete + ? "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID" + : "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId"; + + await connection.ExecuteAsync(sql, new { PlayerSteamID = playerSteamId, CS2_SimpleAdmin.ServerId }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex.ToString()); + } + } + + public async Task AddAdminBySteamId(string playerSteamId, string playerName, List flagsList, int immunity = 0, int time = 0, bool globalAdmin = false) + { + if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return; + + var now = Time.ActualDateTime(); + DateTime? futureTime; + + if (time != 0) + futureTime = now.AddMinutes(time); + else + futureTime = null; + + try + { + await using var connection = await database.GetConnectionAsync(); + + // Insert admin into sa_admins table + const string insertAdminSql = "INSERT INTO `sa_admins` (`player_steamid`, `player_name`, `immunity`, `ends`, `created`, `server_id`) " + + "VALUES (@playerSteamid, @playerName, @immunity, @ends, @created, @serverid); SELECT LAST_INSERT_ID();"; + + var adminId = await connection.ExecuteScalarAsync(insertAdminSql, new + { + playerSteamId, + playerName, + immunity, + ends = futureTime, + created = now, + serverid = globalAdmin ? null : CS2_SimpleAdmin.ServerId + }); + + // Insert flags into sa_admins_flags table + foreach (var flag in flagsList) + { + if (flag.StartsWith($"#")) + { + const string sql = "SELECT id FROM `sa_groups` WHERE name = @groupName"; + var groupId = await connection.QuerySingleOrDefaultAsync(sql, new { groupName = flag }); + + if (groupId != null) + { + const string updateAdminGroup = "UPDATE `sa_admins` SET group_id = @groupId WHERE id = @adminId"; + await connection.ExecuteAsync(updateAdminGroup, new + { + groupId, + adminId + }); + } + } + + const string insertFlagsSql = "INSERT INTO `sa_admins_flags` (`admin_id`, `flag`) " + + "VALUES (@adminId, @flag)"; + + await connection.ExecuteAsync(insertFlagsSql, new + { + adminId, + flag + }); + } + + await Server.NextFrameAsync(() => + { + CS2_SimpleAdmin.Instance.ReloadAdmins(null); + }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex.ToString()); + } + } + + public async Task AddGroup(string groupName, List flagsList, int immunity = 0, bool globalGroup = false) + { + if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return; + + await using var connection = await database.GetConnectionAsync(); + try + { + // Insert group into sa_groups table + const string insertGroup = "INSERT INTO `sa_groups` (`name`, `immunity`) " + + "VALUES (@groupName, @immunity); SELECT LAST_INSERT_ID();"; + var groupId = await connection.ExecuteScalarAsync(insertGroup, new + { + groupName, + immunity + }); + + // Insert flags into sa_groups_flags table + foreach (var flag in flagsList) + { + const string insertFlagsSql = "INSERT INTO `sa_groups_flags` (`group_id`, `flag`) " + + "VALUES (@groupId, @flag)"; + + await connection.ExecuteAsync(insertFlagsSql, new + { + groupId, + flag + }); + } + + const string insertGroupServer = "INSERT INTO `sa_groups_servers` (`group_id`, `server_id`) " + + "VALUES (@groupId, @server_id)"; + + await connection.ExecuteAsync(insertGroupServer, new { groupId, server_id = globalGroup ? null : CS2_SimpleAdmin.ServerId }); + + await Server.NextFrameAsync(() => + { + CS2_SimpleAdmin.Instance.ReloadAdmins(null); + }); + + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex.ToString()); + } + } + + public async Task DeleteGroup(string groupName) + { + if (string.IsNullOrEmpty(groupName)) return; + + await using var connection = await database.GetConnectionAsync(); + try + { + const string sql = "DELETE FROM `sa_groups` WHERE name = @groupName"; + await connection.ExecuteAsync(sql, new { groupName }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex.ToString()); + } + } + + public async Task DeleteOldAdmins() + { + try + { + await using var connection = await database.GetConnectionAsync(); + + const string sql = "DELETE FROM sa_admins WHERE ends IS NOT NULL AND ends <= @CurrentTime"; + await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime() }); + } + catch (Exception) + { + CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired admins"); + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs new file mode 100644 index 0000000..af75b68 --- /dev/null +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -0,0 +1,320 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.ValveConstants.Protobuf; +using CS2_SimpleAdminApi; +using Dapper; +using Microsoft.Extensions.Logging; + +namespace CS2_SimpleAdmin.Managers; + +public class PlayerManager +{ + private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config; + + public void LoadPlayerData(CCSPlayerController player) + { + if (player.IsBot || string.IsNullOrEmpty(player.IpAddress) || player.IpAddress.Contains("127.0.0.1") + || !player.UserId.HasValue) + return; + + var ipAddress = player.IpAddress?.Split(":")[0]; + + CS2_SimpleAdmin.PlayersInfo[player.UserId.Value] = + new PlayerInfo(player.UserId.Value, player.Slot, new SteamID(player.SteamID), player.PlayerName, ipAddress); + + var userId = player.UserId.Value; + + // Check if the player's IP or SteamID is in the bannedPlayers list + if (_config.OtherSettings.BanType > 0 && CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress) || CS2_SimpleAdmin.BannedPlayers.Contains(player.SteamID.ToString())) + { + // Kick the player if banned + if (player.UserId.HasValue) + Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + + return; + } + + if (CS2_SimpleAdmin.Database == null) return; + + // Perform asynchronous database operations within a single method + Task.Run(async () => + { + // Initialize managers + BanManager banManager = new(CS2_SimpleAdmin.Database, _config); + MuteManager muteManager = new(CS2_SimpleAdmin.Database); + WarnManager warnManager = new(CS2_SimpleAdmin.Database); + + try + { + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + + const string query = """ + INSERT INTO `sa_players_ips` (steamid, address) + VALUES (@SteamID, @IPAddress) ON DUPLICATE KEY UPDATE `used_at` = CURRENT_TIMESTAMP + """; + + await connection.ExecuteAsync(query, new + { + SteamID = CS2_SimpleAdmin.PlayersInfo[userId].SteamId, + IPAddress = ipAddress, + }); + } + catch { } + + try + { + // Check if the player is banned + bool isBanned = await banManager.IsPlayerBanned(CS2_SimpleAdmin.PlayersInfo[userId]); + + if (isBanned) + { + // Add player's IP and SteamID to bannedPlayers list if not already present + if (_config.OtherSettings.BanType > 0 && ipAddress != null && + !CS2_SimpleAdmin.BannedPlayers.Contains(ipAddress)) + { + CS2_SimpleAdmin.BannedPlayers.Add(ipAddress); + } + + if (!CS2_SimpleAdmin.BannedPlayers.Contains(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString())) + { + CS2_SimpleAdmin.BannedPlayers.Add(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()); + } + + // Kick the player if banned + await Server.NextFrameAsync(() => + { + var victim = Utilities.GetPlayerFromUserid(userId); + + if (victim?.UserId == null) return; + + if (CS2_SimpleAdmin.UnlockedCommands) + Server.ExecuteCommand($"banid 1 {userId}"); + + Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + }); + + return; + } + + var warns = await warnManager.GetPlayerWarns(CS2_SimpleAdmin.PlayersInfo[userId], false); + + CS2_SimpleAdmin.PlayersInfo[userId].TotalBans = + await banManager.GetPlayerBans(CS2_SimpleAdmin.PlayersInfo[userId]); + CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes = + await muteManager.GetPlayerMutes(CS2_SimpleAdmin.PlayersInfo[userId], 1); + CS2_SimpleAdmin.PlayersInfo[userId].TotalGags = + await muteManager.GetPlayerMutes(CS2_SimpleAdmin.PlayersInfo[userId], 0); + CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences = + await muteManager.GetPlayerMutes(CS2_SimpleAdmin.PlayersInfo[userId], 2); + CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns = warns.Count; + + + // Check if the player is muted + var activeMutes = await muteManager.IsPlayerMuted(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString()); + + /* + Dictionary> mutesList = new() + { + { PenaltyType.Gag, [] }, + { PenaltyType.Mute, [] }, + { PenaltyType.Silence, [] } + }; + + List warnsList = []; + + bool found = false; + foreach (var warn in warns.TakeWhile(warn => (string)warn.status == "ACTIVE")) + { + DateTime ends = warn.ends; + if (CS2_SimpleAdmin._localizer == null) continue; + + Console.WriteLine(ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)); + + warnsList.Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_warn", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture), (string)warn.reason]); + found = true; + } + + if (!found) + { + if (CS2_SimpleAdmin._localizer != null) + warnsList.Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_no_active_warn"]); + } + */ + + if (activeMutes.Count > 0) + { + foreach (var mute in activeMutes) + { + string muteType = mute.type; + DateTime ends = mute.ends; + int duration = mute.duration; + switch (muteType) + { + // Apply mute penalty based on mute type + case "GAG": + PlayerPenaltyManager.AddPenalty(CS2_SimpleAdmin.PlayersInfo[userId].Slot, PenaltyType.Gag, ends, duration); + // if (CS2_SimpleAdmin._localizer != null) + // mutesList[PenaltyType.Gag].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_gag", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]); + break; + case "MUTE": + PlayerPenaltyManager.AddPenalty(CS2_SimpleAdmin.PlayersInfo[userId].Slot, PenaltyType.Mute, ends, duration); + await Server.NextFrameAsync(() => + { + player.VoiceFlags = VoiceFlags.Muted; + }); + // if (CS2_SimpleAdmin._localizer != null) + // mutesList[PenaltyType.Mute].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_mute", ends.ToLocalTime().ToString(CultureInfo.InvariantCulture)]); + break; + default: + PlayerPenaltyManager.AddPenalty(CS2_SimpleAdmin.PlayersInfo[userId].Slot, PenaltyType.Silence, ends, duration); + await Server.NextFrameAsync(() => + { + player.VoiceFlags = VoiceFlags.Muted; + }); + // if (CS2_SimpleAdmin._localizer != null) + // mutesList[PenaltyType.Silence].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_silence", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]); + break; + } + } + } + + /* + if (CS2_SimpleAdmin._localizer != null) + { + if (mutesList[PenaltyType.Gag].Count == 0) + mutesList[PenaltyType.Gag].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_no_active_gag"]); + if (mutesList[PenaltyType.Mute].Count == 0) + mutesList[PenaltyType.Mute].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_no_active_mute"]); + if (mutesList[PenaltyType.Silence].Count == 0) + mutesList[PenaltyType.Silence].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_no_active_silence"]); + } + */ + + /* + await Server.NextFrameAsync(() => + { + CS2_SimpleAdmin.Instance.AddTimer(3.0f, () => + { + player.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_player_penalty_info", + [ + player.PlayerName, + CS2_SimpleAdmin.PlayersInfo[userId].TotalBans, + CS2_SimpleAdmin.PlayersInfo[userId].TotalGags, + CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes, + CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences, + CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns, + string.Join("\n", mutesList.SelectMany(kvp => kvp.Value)), + string.Join("\n", warnsList) + ]); + }); + */ + + await Server.NextFrameAsync(() => + { + foreach (var admin in Helper.GetValidPlayers() + .Where(p => (AdminManager.PlayerHasPermissions(p, "@css/kick") || + AdminManager.PlayerHasPermissions(p, "@css/ban")) && + p.Connected == PlayerConnectedState.PlayerConnected)) + { + if (CS2_SimpleAdmin._localizer != null && admin != player) + admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer, "sa_admin_penalty_info", + player.PlayerName, + CS2_SimpleAdmin.PlayersInfo[userId].TotalBans, + CS2_SimpleAdmin.PlayersInfo[userId].TotalGags, + CS2_SimpleAdmin.PlayersInfo[userId].TotalMutes, + CS2_SimpleAdmin.PlayersInfo[userId].TotalSilences, + CS2_SimpleAdmin.PlayersInfo[userId].TotalWarns + ); + } + }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError($"Error processing player connection: {ex}"); + } + }); + + if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name)) + { + player.Rename(name); + } + } + + public void CheckPlayersTimer() + { + CS2_SimpleAdmin.Database = new Database.Database(CS2_SimpleAdmin.Instance.DbConnectionString); + + CS2_SimpleAdmin.Instance.AddTimer(61.0f, () => + { +#if DEBUG + CS2_SimpleAdmin._logger?.LogCritical("[OnMapStart] Expired check"); +#endif + + var players = Helper.GetValidPlayers(); + var onlinePlayers = players + .Where(player => player.IpAddress != null) + .Select(player => (player.IpAddress, player.SteamID, player.UserId, player.Slot)) + .ToList(); + + Task.Run(async () => + { + PermissionManager adminManager = new(CS2_SimpleAdmin.Database); + BanManager banManager = new(CS2_SimpleAdmin.Database, _config); + MuteManager muteManager = new(CS2_SimpleAdmin.Database); + WarnManager warnManager = new(CS2_SimpleAdmin.Database); + + await muteManager.ExpireOldMutes(); + await banManager.ExpireOldBans(); + await warnManager.ExpireOldWarns(); + await adminManager.DeleteOldAdmins(); + + CS2_SimpleAdmin.BannedPlayers.Clear(); + + if (onlinePlayers.Count > 0) + { + try + { + await banManager.CheckOnlinePlayers(onlinePlayers); + + if (_config.OtherSettings.TimeMode == 0) + { + await muteManager.CheckOnlineModeMutes(onlinePlayers); + } + } + catch (Exception) + { + CS2_SimpleAdmin._logger?.LogError("Unable to check bans for online players"); + } + } + + await Server.NextFrameAsync(() => + { + if (onlinePlayers.Count > 0) + { + try + { + foreach (var player in players.Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot))) + { + if (!PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute) && !PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence)) + player.VoiceFlags = VoiceFlags.Normal; + + if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence) || + PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute) || + PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag)) continue; + player.VoiceFlags = VoiceFlags.Normal; + } + + PlayerPenaltyManager.RemoveExpiredPenalties(); + } + catch (Exception) + { + CS2_SimpleAdmin._logger?.LogError("Unable to remove old penalties"); + } + } + }); + }); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT | CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/PlayerPenaltyManager.cs b/CS2-SimpleAdmin/Managers/PlayerPenaltyManager.cs new file mode 100644 index 0000000..1b21441 --- /dev/null +++ b/CS2-SimpleAdmin/Managers/PlayerPenaltyManager.cs @@ -0,0 +1,190 @@ +using CS2_SimpleAdminApi; +using System.Collections.Concurrent; + +namespace CS2_SimpleAdmin.Managers; + +public static class PlayerPenaltyManager +{ + private static readonly ConcurrentDictionary>> Penalties = + new(); + + // Add a penalty for a player + public static void AddPenalty(int slot, PenaltyType penaltyType, DateTime endDateTime, int durationInMinutes) + { + Penalties.AddOrUpdate(slot, + (_) => + { + var dict = new Dictionary> + { + [penaltyType] = [(endDateTime, durationInMinutes, false)] + }; + return dict; + }, + (_, existingDict) => + { + if (!existingDict.TryGetValue(penaltyType, out var value)) + { + value = new List<(DateTime, int, bool)>(); + existingDict[penaltyType] = value; + } + + value.Add((endDateTime, durationInMinutes, false)); + return existingDict; + }); + } + + public static bool IsPenalized(int slot, PenaltyType penaltyType) + { + //Console.WriteLine($"Checking penalties for player with slot {slot} and penalty type {penaltyType}"); + + if (!Penalties.TryGetValue(slot, out var penaltyDict) || + !penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) return false; + //Console.WriteLine($"Found penalties for player with slot {slot} and penalty type {penaltyType}"); + + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0) + return penaltiesList.Count != 0; + + var now = Time.ActualDateTime(); + + // Check if any active penalties exist + foreach (var penalty in penaltiesList.ToList()) + { + // Check if the penalty is still active + if (penalty.Duration > 0 && now >= penalty.EndDateTime) + { + //Console.WriteLine($"Removing expired penalty for player with slot {slot} and penalty type {penaltyType}"); + penaltiesList.Remove(penalty); // Remove expired penalty + if (penaltiesList.Count == 0) + { + //Console.WriteLine($"No more penalties of type {penaltyType} for player with slot {slot}. Removing penalty type."); + penaltyDict.Remove(penaltyType); // Remove penalty type if no more penalties exist + } + } + else if (penalty.Duration == 0 || now < penalty.EndDateTime) + { + //Console.WriteLine($"Player with slot {slot} is penalized for type {penaltyType}"); + // Return true if there's an active penalty + return true; + } + } + + // Return false if no active penalties are found + //Console.WriteLine($"Player with slot {slot} is not penalized for type {penaltyType}"); + return false; + + // Return false if no penalties of the specified type were found for the player + //Console.WriteLine($"No penalties found for player with slot {slot} and penalty type {penaltyType}"); + } + + // Get the end datetime and duration of penalties for a player and penalty type + public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, PenaltyType penaltyType) + { + if (Penalties.TryGetValue(slot, out var penaltyDict) && + penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) + { + return penaltiesList; + } + return []; + } + + public static Dictionary> GetAllPlayerPenalties(int slot) + { + // Check if the player has any penalties in the dictionary + return Penalties.TryGetValue(slot, out var penaltyDict) ? + // Return all penalty types and their respective penalties for the player + penaltyDict : + // If the player has no penalties, return an empty dictionary + new Dictionary>(); + } + + public static bool IsSlotInPenalties(int slot) + { + return Penalties.ContainsKey(slot); + } + + // Remove all penalties for a player slot + public static void RemoveAllPenalties(int slot) + { + if (Penalties.ContainsKey(slot)) + { + Penalties.TryRemove(slot, out _); + } + } + + // Remove all penalties + public static void RemoveAllPenalties() + { + Penalties.Clear(); + } + + // Remove all penalties of a selected type from a specific player + public static void RemovePenaltiesByType(int slot, PenaltyType penaltyType) + { + if (Penalties.TryGetValue(slot, out var penaltyDict) && + penaltyDict.ContainsKey(penaltyType)) + { + penaltyDict.Remove(penaltyType); + } + } + + public static void RemovePenaltiesByDateTime(int slot, DateTime dateTime) + { + if (!Penalties.TryGetValue(slot, out var penaltyDict)) return; + + foreach (var penaltiesList in penaltyDict.Values) + { + for (var i = 0; i < penaltiesList.Count; i++) + { + if (penaltiesList[i].EndDateTime != dateTime) continue; + // Create a copy of the penalty + var penalty = penaltiesList[i]; + + // Update the end datetime of the copied penalty to the current datetime + penalty.Passed = true; + + // Replace the original penalty with the modified one + penaltiesList[i] = penalty; + } + } + } + + // Remove all expired penalties for all players and penalty types + public static void RemoveExpiredPenalties() + { + if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0) + { + foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating + { + // Remove expired penalties for the player + foreach (var penaltiesList in penaltyDict.Values) + { + penaltiesList.RemoveAll(p => p is { Duration: > 0, Passed: true }); + } + + // Remove player slot if no penalties left + if (penaltyDict.Count == 0) + { + Penalties.TryRemove(playerSlot, out _); + } + } + + return; + } + + var now = Time.ActualDateTime(); + foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating + { + // Remove expired penalties for the player + foreach (var penaltiesList in penaltyDict.Values) + { + penaltiesList.RemoveAll(p => p.Duration > 0 && now >= p.EndDateTime); + } + + // Remove player slot if no penalties left + if (penaltyDict.Count == 0) + { + Penalties.TryRemove(playerSlot, out _); + } + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/ServerManager.cs b/CS2-SimpleAdmin/Managers/ServerManager.cs new file mode 100644 index 0000000..4b570c7 --- /dev/null +++ b/CS2-SimpleAdmin/Managers/ServerManager.cs @@ -0,0 +1,96 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Modules.Cvars; +using Dapper; +using Microsoft.Extensions.Logging; + +namespace CS2_SimpleAdmin.Managers; + +public class ServerManager +{ + private int _getIpTryCount = 0; + + public void LoadServerData() + { + CS2_SimpleAdmin.Instance.AddTimer(2.0f, () => + { + if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.ServerId != null || CS2_SimpleAdmin.Database == null) return; + + var ipAddress = ConVar.Find("ip")?.StringValue; + + if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")) + { + ipAddress = Helper.GetServerIp(); + } + + if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")) + { + if (_getIpTryCount < 12) + { + _getIpTryCount++; + LoadServerData(); + return; + } + } + + var address = $"{ipAddress}:{ConVar.Find("hostport")?.GetPrimitiveValue()}"; + var hostname = ConVar.Find("hostname")!.StringValue; + CS2_SimpleAdmin.IpAddress = address; + + Task.Run(async () => + { + try + { + await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync(); + var addressExists = await connection.ExecuteScalarAsync( + "SELECT COUNT(*) FROM sa_servers WHERE address = @address", + new { address }); + + if (!addressExists) + { + await connection.ExecuteAsync( + "INSERT INTO sa_servers (address, hostname) VALUES (@address, @hostname)", + new { address, hostname }); + } + else + { + await connection.ExecuteAsync( + "UPDATE `sa_servers` SET `hostname` = @hostname, `id` = `id` WHERE `address` = @address", + new { address, hostname }); + } + + int? serverId = await connection.ExecuteScalarAsync( + "SELECT `id` FROM `sa_servers` WHERE `address` = @address", + new { address }); + + CS2_SimpleAdmin.ServerId = serverId; + + if (CS2_SimpleAdmin.ServerId != null) + { + await Server.NextFrameAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null)); + } + + CS2_SimpleAdmin.ServerLoaded = true; + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogCritical("Unable to create or get server_id: " + ex.Message); + } + + if (CS2_SimpleAdmin.Instance.Config.EnableMetrics) + { + var queryString = $"?address={address}&hostname={hostname}"; + using HttpClient client = new(); + + try + { + await client.GetAsync($"https://api.daffyy.love/index.php{queryString}"); + } + catch (HttpRequestException ex) + { + CS2_SimpleAdmin._logger?.LogWarning($"Unable to make metrics call: {ex.Message}"); + } + } + }); + }); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Managers/WarnManager.cs b/CS2-SimpleAdmin/Managers/WarnManager.cs new file mode 100644 index 0000000..6e0ba60 --- /dev/null +++ b/CS2-SimpleAdmin/Managers/WarnManager.cs @@ -0,0 +1,174 @@ +using CS2_SimpleAdminApi; +using Dapper; +using Microsoft.Extensions.Logging; + +namespace CS2_SimpleAdmin.Managers; + +internal class WarnManager(Database.Database database) +{ + public async Task WarnPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0) + { + var now = Time.ActualDateTime(); + var futureTime = now.AddMinutes(time); + + try + { + await using var connection = await database.GetConnectionAsync(); + const string sql = + "INSERT INTO `sa_warns` (`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " + + "VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @serverid)"; + + await connection.ExecuteAsync(sql, new + { + playerSteamid = player.SteamId.SteamId64.ToString(), + playerName = player.Name, + adminSteamid = issuer?.SteamId.SteamId64.ToString() ?? "Console", + adminName = issuer?.Name ?? "Console", + muteReason = reason, + duration = time, + ends = futureTime, + created = now, + serverid = CS2_SimpleAdmin.ServerId + }); + } + catch { }; + } + + public async Task AddWarnBySteamid(string playerSteamId, PlayerInfo? issuer, string reason, int time = 0) + { + if (string.IsNullOrEmpty(playerSteamId)) return; + + + var now = Time.ActualDateTime(); + var futureTime = now.AddMinutes(time); + + try + { + await using var connection = await database.GetConnectionAsync(); + const string sql = "INSERT INTO `sa_warns` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`) " + + "VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @serverid)"; + + await connection.ExecuteAsync(sql, new + { + playerSteamid = playerSteamId, + adminSteamid = issuer?.SteamId.ToString() ?? "Console", + adminName = issuer?.Name ?? "Console", + muteReason = reason, + duration = time, + ends = futureTime, + created = now, + serverid = CS2_SimpleAdmin.ServerId + }); + } + catch { }; + } + + public async Task> GetPlayerWarns(PlayerInfo player, bool active = true) + { + try + { + await using var connection = await database.GetConnectionAsync(); + + string sql; + + if (CS2_SimpleAdmin.Instance.Config.MultiServerMode) + { + sql = active + ? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' ORDER BY id DESC" + : "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID ORDER BY id DESC"; + } + else + { + sql = active + ? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE' ORDER BY id DESC" + : "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid ORDER BY id DESC"; + } + + var parameters = new { PlayerSteamID = player.SteamId.SteamId64.ToString(), serverid = CS2_SimpleAdmin.ServerId }; + var warns = await connection.QueryAsync(sql, parameters); + + return warns.ToList(); + } + catch (Exception) + { + return []; + } + } + + public async Task GetPlayerWarnsCount(string steamId, bool active = true) + { + try + { + await using var connection = await database.GetConnectionAsync(); + + var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? active + ? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE'" + : "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID" + : active + ? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE'" + : "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID'"; + + var muteCount = await connection.ExecuteScalarAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId }); + return muteCount; + } + catch (Exception) + { + return 0; + } + } + + public async Task UnwarnPlayer(PlayerInfo player, int warnId) + { + try + { + await using var connection = await database.GetConnectionAsync(); + + var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId" + : "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId AND server_id = @serverid"; + + await connection.ExecuteAsync(sql, new { steamid = player.SteamId.SteamId64.ToString(), warnId, serverid = CS2_SimpleAdmin.ServerId }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove warn + {ex}"); + } + } + + public async Task UnwarnPlayer(string playerPattern) + { + try + { + await using var connection = await database.GetConnectionAsync(); + + var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = (SELECT MAX(id) FROM sa_warns WHERE player_steamid = @steamid AND status = 'ACTIVE')" + : "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = (SELECT MAX(id) FROM sa_warns WHERE player_steamid = @steamid AND status = 'ACTIVE' AND server_id = @serverid)"; + + await connection.ExecuteAsync(sql, new { steamid = playerPattern, serverid = CS2_SimpleAdmin.ServerId }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove last warn + {ex}"); + } + } + + public async Task ExpireOldWarns() + { + try + { + await using var connection = await database.GetConnectionAsync(); + + var sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode + ? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime" + : "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid"; + + await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove expired warns + {ex}"); + } + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/AdminMenu.cs b/CS2-SimpleAdmin/Menus/AdminMenu.cs new file mode 100644 index 0000000..ad78c0c --- /dev/null +++ b/CS2-SimpleAdmin/Menus/AdminMenu.cs @@ -0,0 +1,69 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Menu; + +namespace CS2_SimpleAdmin.Menus; + +public static class AdminMenu +{ + public static IMenu? CreateMenu(string title) + { + return CS2_SimpleAdmin.MenuApi?.NewMenu(title); + // return CS2_SimpleAdmin.Instance.Config.UseChatMenu ? new ChatMenu(title) : new CenterHtmlMenu(title, CS2_SimpleAdmin.Instance); + } + + public static void OpenMenu(CCSPlayerController player, IMenu menu) + { + menu.Open(player); + // switch (menu) + // { + // case CenterHtmlMenu centerHtmlMenu: + // MenuManager.OpenCenterHtmlMenu(CS2_SimpleAdmin.Instance, player, centerHtmlMenu); + // break; + // case ChatMenu chatMenu: + // MenuManager.OpenChatMenu(player, chatMenu); + // break; + // } + } + + public static void OpenMenu(CCSPlayerController admin) + { + if (admin.IsValid == false) + return; + + var localizer = CS2_SimpleAdmin._localizer; + if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false) + { + admin.PrintToChat(localizer?["sa_prefix"] ?? + "[SimpleAdmin] " + + (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command") + ); + return; + } + + var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin"); + List options = + [ + new ChatMenuOptionData(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)), + new ChatMenuOptionData(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)), + new ChatMenuOptionData(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)), + ]; + + var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands; + if (customCommands.Count > 0) + { + options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin))); + } + + if (AdminManager.PlayerHasPermissions(admin, "@css/root")) + options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin))); + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled); + } + + if (menu != null) OpenMenu(admin, menu); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/ChatMenuOptionData.cs b/CS2-SimpleAdmin/Menus/ChatMenuOptionData.cs new file mode 100644 index 0000000..f2dc203 --- /dev/null +++ b/CS2-SimpleAdmin/Menus/ChatMenuOptionData.cs @@ -0,0 +1,8 @@ +namespace CS2_SimpleAdmin.Menus; + +public class ChatMenuOptionData(string name, Action action, bool disabled = false) +{ + public readonly string Name = name; + public readonly Action Action = action; + public readonly bool Disabled = disabled; +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/CustomCommandsMenu.cs b/CS2-SimpleAdmin/Menus/CustomCommandsMenu.cs new file mode 100644 index 0000000..76432c8 --- /dev/null +++ b/CS2-SimpleAdmin/Menus/CustomCommandsMenu.cs @@ -0,0 +1,50 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; + +namespace CS2_SimpleAdmin.Menus; + +public static class CustomCommandsMenu +{ + public static void OpenMenu(CCSPlayerController admin) + { + if (admin.IsValid == false) + return; + + var localizer = CS2_SimpleAdmin._localizer; + if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false) + { + admin.PrintToChat(localizer?["sa_prefix"] ?? + "[SimpleAdmin] " + + (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command") + ); + return; + } + + var menu = AdminMenu.CreateMenu(localizer?["sa_menu_custom_commands"] ?? "Custom Commands"); + List options = []; + + var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands; + options.AddRange(from customCommand in customCommands + where !string.IsNullOrEmpty(customCommand.DisplayName) && !string.IsNullOrEmpty(customCommand.Command) + let hasRights = AdminManager.PlayerHasPermissions(admin, customCommand.Flag) + where hasRights + select new ChatMenuOptionData(customCommand.DisplayName, () => + { + Helper.TryLogCommandOnDiscord(admin, customCommand.Command); + + if (customCommand.ExecuteOnClient) + admin.ExecuteClientCommandFromServer(customCommand.Command); + else + Server.ExecuteCommand(customCommand.Command); + })); + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/DurationMenu.cs b/CS2-SimpleAdmin/Menus/DurationMenu.cs new file mode 100644 index 0000000..a0cc724 --- /dev/null +++ b/CS2-SimpleAdmin/Menus/DurationMenu.cs @@ -0,0 +1,32 @@ +using CounterStrikeSharp.API.Core; +using CS2_SimpleAdmin.Models; + +namespace CS2_SimpleAdmin.Menus; + +public static class DurationMenu +{ + public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action onSelectAction) + { + var menu = AdminMenu.CreateMenu(menuName); + + foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations) + { + menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); }); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action onSelectAction) + { + var menu = AdminMenu.CreateMenu(menuName); + + foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations) + { + menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); }); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/FunActionsMenu.cs b/CS2-SimpleAdmin/Menus/FunActionsMenu.cs new file mode 100644 index 0000000..a7b1bfe --- /dev/null +++ b/CS2-SimpleAdmin/Menus/FunActionsMenu.cs @@ -0,0 +1,266 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Entities.Constants; + +namespace CS2_SimpleAdmin.Menus; + +public static class FunActionsMenu +{ + private static Dictionary? _weaponsCache; + + private static Dictionary GetWeaponsCache + { + get + { + if (_weaponsCache != null) return _weaponsCache; + + var weaponsArray = Enum.GetValues(typeof(CsItem)); + + // avoid duplicates in the menu + _weaponsCache = new Dictionary(); + foreach (CsItem item in weaponsArray) + { + if (item == CsItem.Tablet) + continue; + + _weaponsCache[(int)item] = item; + } + + return _weaponsCache; + } + } + + public static void OpenMenu(CCSPlayerController admin) + { + if (admin.IsValid == false) + return; + + var localizer = CS2_SimpleAdmin._localizer; + if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false) + { + admin.PrintToChat(localizer?["sa_prefix"] ?? + "[SimpleAdmin] " + + (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command") + ); + return; + } + + var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands"); + List options = []; + + //var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats"); + //var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay"); + + // options added in order + + if (AdminManager.CommandIsOverriden("css_god") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_god")) + : AdminManager.PlayerHasPermissions(admin, "@css/cheats")) + options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode))); + if (AdminManager.CommandIsOverriden("css_noclip") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_noclip")) + : AdminManager.PlayerHasPermissions(admin, "@css/cheats")) + options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip))); + if (AdminManager.CommandIsOverriden("css_respawn") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_respawn")) + : AdminManager.PlayerHasPermissions(admin, "@css/cheats")) + options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn))); + if (AdminManager.CommandIsOverriden("css_give") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_give")) + : AdminManager.PlayerHasPermissions(admin, "@css/cheats")) + options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu))); + + if (AdminManager.CommandIsOverriden("css_strip") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_strip")) + : AdminManager.PlayerHasPermissions(admin, "@css/slay")) + options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons))); + if (AdminManager.CommandIsOverriden("css_freeze") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_freeze")) + : AdminManager.PlayerHasPermissions(admin, "@css/slay")) + options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze))); + if (AdminManager.CommandIsOverriden("css_hp") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_hp")) + : AdminManager.PlayerHasPermissions(admin, "@css/slay")) + options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu))); + if (AdminManager.CommandIsOverriden("css_speed") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_speed")) + : AdminManager.PlayerHasPermissions(admin, "@css/slay")) + options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu))); + if (AdminManager.CommandIsOverriden("css_gravity") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gravity")) + : AdminManager.PlayerHasPermissions(admin, "@css/slay")) + options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu))); + if (AdminManager.CommandIsOverriden("css_money") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_money")) + : AdminManager.PlayerHasPermissions(admin, "@css/slay")) + options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu))); + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void GodMode(CCSPlayerController admin, CCSPlayerController player) + { + CS2_SimpleAdmin.God(admin, player); + } + + private static void NoClip(CCSPlayerController admin, CCSPlayerController player) + { + CS2_SimpleAdmin.NoClip(admin, player); + } + + private static void Respawn(CCSPlayerController? admin, CCSPlayerController player) + { + CS2_SimpleAdmin.Respawn(admin, player); + } + + private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player) + { + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}"); + + foreach (var weapon in GetWeaponsCache) + { + menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); }); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue) + { + CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue); + } + + private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player) + { + CS2_SimpleAdmin.StripWeapons(admin, player); + } + + private static void Freeze(CCSPlayerController admin, CCSPlayerController player) + { + if (!(player?.PlayerPawn.Value?.IsValid ?? false)) + return; + + if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_OBSOLETE) + CS2_SimpleAdmin.Freeze(admin, player, -1); + else + CS2_SimpleAdmin.Unfreeze(admin, player); + } + + private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player) + { + var hpArray = new[] + { + new Tuple("1", 1), + new Tuple("10", 10), + new Tuple("25", 25), + new Tuple("50", 50), + new Tuple("100", 100), + new Tuple("200", 200), + new Tuple("500", 500), + new Tuple("999", 999) + }; + + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}"); + + foreach (var (optionName, value) in hpArray) + { + menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); }); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp) + { + CS2_SimpleAdmin.SetHp(admin, player, hp); + } + + private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player) + { + var speedArray = new[] + { + new Tuple("0.1", .1f), + new Tuple("0.25", .25f), + new Tuple("0.5", .5f), + new Tuple("0.75", .75f), + new Tuple("1", 1), + new Tuple("2", 2), + new Tuple("3", 3), + new Tuple("4", 4) + }; + + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}"); + + foreach (var (optionName, value) in speedArray) + { + menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); }); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed) + { + CS2_SimpleAdmin.SetSpeed(admin, player, speed); + } + + private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player) + { + var gravityArray = new[] + { + new Tuple("0.1", .1f), + new Tuple("0.25", .25f), + new Tuple("0.5", .5f), + new Tuple("0.75", .75f), + new Tuple("1", 1), + new Tuple("2", 2) + }; + + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}"); + + foreach (var (optionName, value) in gravityArray) + { + menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); }); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity) + { + CS2_SimpleAdmin.SetGravity(admin, player, gravity); + } + + private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player) + { + var moneyArray = new[] + { + new Tuple("$0", 0), + new Tuple("$1000", 1000), + new Tuple("$2500", 2500), + new Tuple("$5000", 5000), + new Tuple("$10000", 10000), + new Tuple("$16000", 16000) + }; + + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}"); + + foreach (var (optionName, value) in moneyArray) + { + menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); }); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money) + { + CS2_SimpleAdmin.SetMoney(admin, player, money); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/ManageAdminsMenu.cs b/CS2-SimpleAdmin/Menus/ManageAdminsMenu.cs new file mode 100644 index 0000000..ea674f8 --- /dev/null +++ b/CS2-SimpleAdmin/Menus/ManageAdminsMenu.cs @@ -0,0 +1,71 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; + +namespace CS2_SimpleAdmin.Menus; + +public static class ManageAdminsMenu +{ + public static void OpenMenu(CCSPlayerController admin) + { + if (admin.IsValid == false) + return; + + var localizer = CS2_SimpleAdmin._localizer; + if (AdminManager.PlayerHasPermissions(admin, "@css/root") == false) + { + admin.PrintToChat(localizer?["sa_prefix"] ?? + "[SimpleAdmin] " + + (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command") + ); + return; + } + + var menu = AdminMenu.CreateMenu(localizer?["sa_menu_admins_manage"] ?? "Admins Manage"); + List options = + [ + new ChatMenuOptionData(localizer?["sa_admin_add"] ?? "Add Admin", + () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_admin_add"] ?? "Add Admin", AddAdminMenu)), + new ChatMenuOptionData(localizer?["sa_admin_remove"] ?? "Remove Admin", + () => PlayersMenu.OpenAdminPlayersMenu(admin, localizer?["sa_admin_remove"] ?? "Remove Admin", RemoveAdmin, + player => player != admin && admin.CanTarget(player))), + new ChatMenuOptionData(localizer?["sa_admin_reload"] ?? "Reload Admins", () => ReloadAdmins(admin)) + ]; + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void AddAdminMenu(CCSPlayerController admin, CCSPlayerController player) + { + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_admin_add"] ?? "Add Admin"}: {player.PlayerName}"); + + foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags) + { + var disabled = AdminManager.PlayerHasPermissions(player, adminFlag.Flag); + menu?.AddMenuOption(adminFlag.Name, (_, _) => { AddAdmin(admin, player, adminFlag.Flag); }, disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void AddAdmin(CCSPlayerController admin, CCSPlayerController player, string flag) + { + // TODO: Change default immunity? + CS2_SimpleAdmin.AddAdmin(admin, player.SteamID.ToString(), player.PlayerName, flag, 10); + } + + private static void RemoveAdmin(CCSPlayerController admin, CCSPlayerController player) + { + CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString()); + } + + private static void ReloadAdmins(CCSPlayerController admin) + { + CS2_SimpleAdmin.Instance.ReloadAdmins(admin); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/ManagePlayersMenu.cs b/CS2-SimpleAdmin/Menus/ManagePlayersMenu.cs new file mode 100644 index 0000000..7cb19a2 --- /dev/null +++ b/CS2-SimpleAdmin/Menus/ManagePlayersMenu.cs @@ -0,0 +1,333 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Utils; +using CS2_SimpleAdminApi; + +namespace CS2_SimpleAdmin.Menus; + +public static class ManagePlayersMenu +{ + public static void OpenMenu(CCSPlayerController admin) + { + if (admin.IsValid == false) + return; + + var localizer = CS2_SimpleAdmin._localizer; + if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false) + { + admin.PrintToChat(localizer?["sa_prefix"] ?? + "[SimpleAdmin] " + + (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command") + ); + return; + } + + var menu = AdminMenu.CreateMenu(localizer?["sa_menu_players_manage"] ?? "Manage Players"); + List options = []; + + // permissions + var hasSlay = AdminManager.CommandIsOverriden("css_slay") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_slay")) : AdminManager.PlayerHasPermissions(admin, "@css/slay"); + var hasKick = AdminManager.CommandIsOverriden("css_kick") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_kick")) : AdminManager.PlayerHasPermissions(admin, "@css/kick"); + var hasBan = AdminManager.CommandIsOverriden("css_ban") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_ban")) : AdminManager.PlayerHasPermissions(admin, "@css/ban"); + var hasChat = AdminManager.CommandIsOverriden("css_gag") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gag")) : AdminManager.PlayerHasPermissions(admin, "@css/chat"); + + // TODO: Localize options + // options added in order + + if (hasSlay) + { + options.Add(new ChatMenuOptionData(localizer?["sa_slap"] ?? "Slap", () => PlayersMenu.OpenMenu(admin, localizer?["sa_slap"] ?? "Slap", SlapMenu))); + options.Add(new ChatMenuOptionData(localizer?["sa_slay"] ?? "Slay", () => PlayersMenu.OpenMenu(admin, localizer?["sa_slay"] ?? "Slay", Slay))); + } + + if (hasKick) + { + options.Add(new ChatMenuOptionData(localizer?["sa_kick"] ?? "Kick", () => PlayersMenu.OpenMenu(admin, localizer?["sa_kick"] ?? "Kick", KickMenu))); + } + + if (AdminManager.CommandIsOverriden("css_warn") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_warn")) + : AdminManager.PlayerHasPermissions(admin, "@css/kick")) + options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, WarnMenu)))); + + if (hasBan) + options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player, BanMenu)))); + + if (hasChat) + { + if (AdminManager.CommandIsOverriden("css_gag") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_gag")) + : AdminManager.PlayerHasPermissions(admin, "@css/chat")) + options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, GagMenu)))); + if (AdminManager.CommandIsOverriden("css_mute") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_mute")) + : AdminManager.PlayerHasPermissions(admin, "@css/chat")) + options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player, MuteMenu)))); + if (AdminManager.CommandIsOverriden("css_silence") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_silence")) + : AdminManager.PlayerHasPermissions(admin, "@css/chat")) + options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, SilenceMenu)))); + } + + if (AdminManager.CommandIsOverriden("css_team") + ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_team")) + : AdminManager.PlayerHasPermissions(admin, "@css/kick")) + options.Add(new ChatMenuOptionData(localizer?["sa_team_force"] ?? "Force Team", () => PlayersMenu.OpenMenu(admin, localizer?["sa_team_force"] ?? "Force Team", ForceTeamMenu))); + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void SlapMenu(CCSPlayerController admin, CCSPlayerController player) + { + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_slap"] ?? "Slap"}: {player.PlayerName}"); + List options = + [ + // options added in order + new ChatMenuOptionData("0 hp", () => ApplySlapAndKeepMenu(admin, player, 0)), + new ChatMenuOptionData("1 hp", () => ApplySlapAndKeepMenu(admin, player, 1)), + new ChatMenuOptionData("5 hp", () => ApplySlapAndKeepMenu(admin, player, 5)), + new ChatMenuOptionData("10 hp", () => ApplySlapAndKeepMenu(admin, player, 10)), + new ChatMenuOptionData("50 hp", () => ApplySlapAndKeepMenu(admin, player, 50)), + new ChatMenuOptionData("100 hp", () => ApplySlapAndKeepMenu(admin, player, 100)), + ]; + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void ApplySlapAndKeepMenu(CCSPlayerController admin, CCSPlayerController player, int damage) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Slap(admin, player, damage); + SlapMenu(admin, player); + } + + private static void Slay(CCSPlayerController admin, CCSPlayerController player) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Slay(admin, player); + } + + private static void KickMenu(CCSPlayerController admin, CCSPlayerController player) + { + ReasonMenu.OpenMenu(admin, PenaltyType.Kick, + $"{CS2_SimpleAdmin._localizer?["sa_kick"] ?? "Kick"}: {player.PlayerName}", player, (_, _, reason) => + { + if (player is { IsValid: true }) + Kick(admin, player, reason); + }); + + // var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_kick"] ?? "Kick"}: {player?.PlayerName}"); + // + // foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons) + // { + // menu?.AddMenuOption(option, (_, _) => + // { + // if (player is { IsValid: true }) + // Kick(admin, player, option); + // }); + // } + // + // if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void Kick(CCSPlayerController admin, CCSPlayerController player, string? reason) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Instance.Kick(admin, player, reason); + } + + private static void BanMenu(CCSPlayerController admin, CCSPlayerController player, int duration) + { + ReasonMenu.OpenMenu(admin, PenaltyType.Ban, + $"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player, (_, _, reason) => + { + if (player is { IsValid: true }) + Ban(admin, player, duration, reason); + }); + + // var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player?.PlayerName}"); + // + // foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons) + // { + // menu?.AddMenuOption(option, (_, _) => + // { + // if (player is { IsValid: true }) + // Ban(admin, player, duration, option); + // }); + // } + // + // if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void Ban(CCSPlayerController admin, CCSPlayerController player, int duration, string reason) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason); + } + + private static void WarnMenu(CCSPlayerController admin, CCSPlayerController player, int duration) + { + ReasonMenu.OpenMenu(admin, PenaltyType.Warn, + $"{CS2_SimpleAdmin._localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, (_, _, reason) => + { + if (player is { IsValid: true }) + Warn(admin, player, duration, reason); + }); + + // var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_warn"] ?? "Warn"}: {player?.PlayerName}"); + // + // foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons) + // { + // menu?.AddMenuOption(option, (_, _) => + // { + // if (player is { IsValid: true }) + // Warn(admin, player, duration, option); + // }); + // } + // + // if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void Warn(CCSPlayerController admin, CCSPlayerController player, int duration, string reason) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason); + } + + private static void GagMenu(CCSPlayerController admin, CCSPlayerController player, int duration) + { + ReasonMenu.OpenMenu(admin, PenaltyType.Gag, + $"{CS2_SimpleAdmin._localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, (_, _, reason) => + { + if (player is { IsValid: true }) + Gag(admin, player, duration, reason); + }); + + // var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_gag"] ?? "Gag"}: {player?.PlayerName}"); + // + // foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons) + // { + // menu?.AddMenuOption(option, (_, _) => + // { + // if (player is { IsValid: true }) + // Gag(admin, player, duration, option); + // }); + // } + // + // if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void Gag(CCSPlayerController admin, CCSPlayerController player, int duration, string reason) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason); + } + + private static void MuteMenu(CCSPlayerController admin, CCSPlayerController player, int duration) + { + ReasonMenu.OpenMenu(admin, PenaltyType.Mute, + $"{CS2_SimpleAdmin._localizer?["sa_mute"] ?? "mute"}: {player.PlayerName}", player, (_, _, reason) => + { + if (player is { IsValid: true }) + Mute(admin, player, duration, reason); + }); + + // // TODO: Localize and make options in config? + // var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_mute"] ?? "Mute"}: {player?.PlayerName}"); + // + // foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons) + // { + // menu?.AddMenuOption(option, (_, _) => + // { + // if (player is { IsValid: true }) + // Mute(admin, player, duration, option); + // }); + // } + // + // if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void Mute(CCSPlayerController admin, CCSPlayerController player, int duration, string reason) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason); + } + + private static void SilenceMenu(CCSPlayerController admin, CCSPlayerController player, int duration) + { + ReasonMenu.OpenMenu(admin, PenaltyType.Silence, + $"{CS2_SimpleAdmin._localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, (_, _, reason) => + { + if (player is { IsValid: true }) + Silence(admin, player, duration, reason); + }); + + // // TODO: Localize and make options in config? + // var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_silence"] ?? "Silence"}: {player?.PlayerName}"); + // + // foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons) + // { + // menu?.AddMenuOption(option, (_, _) => + // { + // if (player is { IsValid: true }) + // Silence(admin, player, duration, option); + // }); + // } + // + // if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void Silence(CCSPlayerController admin, CCSPlayerController player, int duration, string reason) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason); + } + + private static void ForceTeamMenu(CCSPlayerController admin, CCSPlayerController player) + { + // TODO: Localize + var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_team_force"] ?? "Force Team"} {player.PlayerName}"); + List options = + [ + new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_ct"] ?? "CT", () => ForceTeam(admin, player, "ct", CsTeam.CounterTerrorist)), + new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_t"] ?? "T", () => ForceTeam(admin, player, "t", CsTeam.Terrorist)), + new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_swap"] ?? "Swap", () => ForceTeam(admin, player, "swap", CsTeam.Spectator)), + new ChatMenuOptionData(CS2_SimpleAdmin._localizer?["sa_team_spec"] ?? "Spec", () => ForceTeam(admin, player, "spec", CsTeam.Spectator)), + ]; + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void ForceTeam(CCSPlayerController admin, CCSPlayerController player, string teamName, CsTeam teamNum) + { + if (player is not { IsValid: true }) return; + + CS2_SimpleAdmin.ChangeTeam(admin, player, teamName, teamNum, true); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/ManageServerMenu.cs b/CS2-SimpleAdmin/Menus/ManageServerMenu.cs new file mode 100644 index 0000000..4f1e5f6 --- /dev/null +++ b/CS2-SimpleAdmin/Menus/ManageServerMenu.cs @@ -0,0 +1,77 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; + +namespace CS2_SimpleAdmin.Menus; + +public static class ManageServerMenu +{ + public static void OpenMenu(CCSPlayerController admin) + { + if (admin.IsValid == false) + return; + + var localizer = CS2_SimpleAdmin._localizer; + if (AdminManager.PlayerHasPermissions(admin, "@css/generic") == false) + { + admin.PrintToChat(localizer?["sa_prefix"] ?? + "[SimpleAdmin] " + + (localizer?["sa_no_permission"] ?? "You do not have permissions to use this command") + ); + return; + } + + var menu = AdminMenu.CreateMenu(localizer?["sa_menu_server_manage"] ?? "Server Manage"); + List options = []; + + + // permissions + var hasMap = AdminManager.CommandIsOverriden("css_map") ? AdminManager.PlayerHasPermissions(admin, AdminManager.GetPermissionOverrides("css_map")) : AdminManager.PlayerHasPermissions(admin, "@css/changemap"); + + //bool hasMap = AdminManager.PlayerHasPermissions(admin, "@css/changemap"); + + // options added in order + + if (hasMap) + { + options.Add(new ChatMenuOptionData(localizer?["sa_changemap"] ?? "Change Map", () => ChangeMapMenu(admin))); + } + + options.Add(new ChatMenuOptionData(localizer?["sa_restart_game"] ?? "Restart Game", () => CS2_SimpleAdmin.RestartGame(admin))); + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void ChangeMapMenu(CCSPlayerController admin) + { + var menu = AdminMenu.CreateMenu(CS2_SimpleAdmin._localizer?["sa_changemap"] ?? "Change Map"); + List options = []; + + var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps; + options.AddRange(maps.Select(map => new ChatMenuOptionData(map, () => ExecuteChangeMap(admin, map, false)))); + + var wsMaps = CS2_SimpleAdmin.Instance.Config.WorkshopMaps; + options.AddRange(wsMaps.Select(map => new ChatMenuOptionData($"{map.Key} (WS)", () => ExecuteChangeMap(admin, map.Value?.ToString() ?? map.Key, true)))); + + foreach (var menuOptionData in options) + { + var menuName = menuOptionData.Name; + menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + private static void ExecuteChangeMap(CCSPlayerController admin, string mapName, bool workshop) + { + if (workshop) + CS2_SimpleAdmin.Instance.ChangeWorkshopMap(admin, mapName); + else + CS2_SimpleAdmin.Instance.ChangeMap(admin, mapName); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/PlayersMenu.cs b/CS2-SimpleAdmin/Menus/PlayersMenu.cs new file mode 100644 index 0000000..092787f --- /dev/null +++ b/CS2-SimpleAdmin/Menus/PlayersMenu.cs @@ -0,0 +1,55 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using System.Web; + +namespace CS2_SimpleAdmin.Menus; + +public static class PlayersMenu +{ + public static void OpenRealPlayersMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) + { + OpenMenu(admin, menuName, onSelectAction, p => p.IsBot == false); + } + + public static void OpenAdminPlayersMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) + { + OpenMenu(admin, menuName, onSelectAction, p => AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0); + } + + public static void OpenAliveMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) + { + OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive); + } + + public static void OpenDeadMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) + { + OpenMenu(admin, menuName, onSelectAction, p => p.PawnIsAlive == false); + } + + public static void OpenMenu(CCSPlayerController admin, string menuName, Action onSelectAction, Func? enableFilter = null) + { + var menu = AdminMenu.CreateMenu(menuName); + + var players = Helper.GetValidPlayersWithBots(); + + foreach (var player in players) + { + var playerName = player != null && player.PlayerName.Length > 26 ? player.PlayerName[..26] : player?.PlayerName; + + var optionName = HttpUtility.HtmlEncode(playerName); + if (player != null && enableFilter != null && enableFilter(player) == false) + continue; + + var enabled = admin.CanTarget(player); + + if (optionName != null) + menu?.AddMenuOption(optionName, (_, _) => + { + if (player != null) onSelectAction.Invoke(admin, player); + }, + enabled == false); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Menus/ReasonMenu.cs b/CS2-SimpleAdmin/Menus/ReasonMenu.cs new file mode 100644 index 0000000..883cad6 --- /dev/null +++ b/CS2-SimpleAdmin/Menus/ReasonMenu.cs @@ -0,0 +1,50 @@ +using CounterStrikeSharp.API.Core; +using CS2_SimpleAdmin.Models; +using CS2_SimpleAdminApi; + +namespace CS2_SimpleAdmin.Menus; + +public static class ReasonMenu +{ + public static void OpenMenu(CCSPlayerController admin, PenaltyType penaltyType, string menuName, CCSPlayerController player, Action onSelectAction) + { + var menu = AdminMenu.CreateMenu(menuName); + + var reasons = penaltyType switch + { + PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons, + PenaltyType.Kick => CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons, + PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons, + PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons, + _ => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons + }; + + foreach (var reason in reasons) + { + menu?.AddMenuOption(reason, (_, _) => onSelectAction(admin, player, reason)); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } + + public static void OpenMenu(CCSPlayerController admin, PenaltyType penaltyType, string menuName, DisconnectedPlayer player, Action onSelectAction) + { + var menu = AdminMenu.CreateMenu(menuName); + + var reasons = penaltyType switch + { + PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons, + PenaltyType.Kick => CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons, + PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons, + PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons, + _ => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons + }; + + foreach (var reason in reasons) + { + menu?.AddMenuOption(reason, (_, _) => onSelectAction(admin, player, reason)); + } + + if (menu != null) AdminMenu.OpenMenu(admin, menu); + } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/Models/DisconnectedPlayer.cs b/CS2-SimpleAdmin/Models/DisconnectedPlayer.cs new file mode 100644 index 0000000..f239c39 --- /dev/null +++ b/CS2-SimpleAdmin/Models/DisconnectedPlayer.cs @@ -0,0 +1,15 @@ +using CounterStrikeSharp.API.Modules.Entities; + +namespace CS2_SimpleAdmin.Models; + +public class DisconnectedPlayer( + SteamID steamId, + string name, + string? ipAddress, + DateTime disconnectTime) +{ + public SteamID SteamId { get; } = steamId; + public string Name { get; set; } = name; + public string? IpAddress { get; set; } = ipAddress; + public DateTime DisconnectTime = disconnectTime; +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/VERSION b/CS2-SimpleAdmin/VERSION new file mode 100644 index 0000000..ba4c966 --- /dev/null +++ b/CS2-SimpleAdmin/VERSION @@ -0,0 +1 @@ +1.6.0a \ No newline at end of file diff --git a/CS2-SimpleAdmin/Variables.cs b/CS2-SimpleAdmin/Variables.cs new file mode 100644 index 0000000..e04363e --- /dev/null +++ b/CS2-SimpleAdmin/Variables.cs @@ -0,0 +1,44 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Capabilities; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CS2_SimpleAdmin.Models; +using CS2_SimpleAdminApi; +using Discord.Webhook; +using MenuManager; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using System.Collections.Concurrent; + +namespace CS2_SimpleAdmin; + +public partial class CS2_SimpleAdmin +{ + public static IStringLocalizer? _localizer; + public static readonly Dictionary VoteAnswers = []; + public static bool ServerLoaded; + private static readonly HashSet GodPlayers = []; + private static readonly HashSet SilentPlayers = []; + internal static readonly ConcurrentBag BannedPlayers = []; + internal static readonly Dictionary RenamedPlayers = []; + public static bool VoteInProgress; + public static int? ServerId = null; + public static readonly bool UnlockedCommands = CoreConfig.UnlockConCommands; + internal static readonly Dictionary PlayersInfo = []; + private static readonly List DisconnectedPlayers = []; + + internal static DiscordWebhookClient? DiscordWebhookClientLog; + + internal string DbConnectionString = string.Empty; + internal static Database.Database? Database; + internal static string IpAddress = string.Empty; + + internal static ILogger? _logger; + private static MemoryFunctionVoid? _cBasePlayerControllerSetPawnFunc; + + // Menus + internal static IMenuApi? MenuApi; + private static readonly PluginCapability MenuCapability = new("menu:nfcore"); + + // Shared + private Api.CS2_SimpleAdminApi? SimpleAdminApi { get; set; } +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/admin_help.txt b/CS2-SimpleAdmin/admin_help.txt new file mode 100644 index 0000000..e8ce7af --- /dev/null +++ b/CS2-SimpleAdmin/admin_help.txt @@ -0,0 +1,39 @@ +{GREEN}[ CS2-SimpleAdmin HELP ]{DEFAULT} +- css_who <#userid or name> - Display informations about player +- css_players - Display player list +- css_ban <#userid or name> [time in minutes/0 perm] [reason] - Ban player +- css_addban [time in minutes/0 perm] [reason] - Ban player via steamid64 +- css_banip [time in minutes/0 perm] [reason] - Ban player via IP address +- css_unban - Unban player +- css_kick <#userid or name> [reason] - Kick player +- css_gag <#userid or name> [time in minutes/0 perm] [reason] - Gag player +- css_addgag [time in minutes/0 perm] [reason] - Gag player via steamid64 +- css_unmute - Ungag player +- css_mute <#userid or name> [time in minutes/0 perm] [reason] - Mute player +- css_addmute [time in minutes/0 perm] [reason] - Mute player via steamid64 +- css_give <#userid or name> - Give player a weapon +- css_strip <#userid or name> - Takes all of the player weapons +- css_hp <#userid or name> [health] - Set player health +- css_speed <#userid or name> [speed] - Set player speed +- css_gravity <#userid or name> [gravity] - Set player gravity +- css_money <#userid or name> [money] - Set player money +- css_god <#userid or name> - Toggle player godmode +- css_slay <#userid or name> - Kill player +- css_slap <#userid or name> [damage] - Slap player +- css_vote <'Question?'> ['Answer1'] ['Answer2'] ... - Create vote +- css_map - Change map +- css_wsmap - Change workshop map +- css_asay - Say message to all admins +- css_say - Say message as admin in chat +- css_psay <#userid or name> - Sends private message to player +- css_csay - Say message as admin in center +- css_hsay - Say message as admin in hud +- css_noclip <#userid or name> - Toggle noclip for player +- css_freeze <#userid or name> [duration] - Freeze player +- css_unfreeze <#userid or name> - Unfreeze player +- css_respawn <#userid or name> - Respawn player +- css_cvar - Change cvar value +- css_rcon - Run command as server + +{Green}This is a sample admin_help.txt file +{LightRed}Write all useful information for admins here diff --git a/CS2-SimpleAdmin/lang/ar.json b/CS2-SimpleAdmin/lang/ar.json new file mode 100644 index 0000000..ac9569a --- /dev/null +++ b/CS2-SimpleAdmin/lang/ar.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "مجهول", + "sa_no_permission": "ليس لديك الصلاحيات لاستخدام هذا الأمر.", + "sa_ban_max_duration_exceeded": "مدة الحظر لا يمكن أن تتجاوز {lightred}{0}{default} دقيقة.", + "sa_ban_perm_restricted": "ليس لديك الحق في الحظر الدائم.", + + "sa_admin_add": "إضافة مسؤول", + "sa_admin_remove": "إزالة المسؤول", + "sa_admin_reload": "إعادة تحميل المسؤولين", + + "sa_godmode": "وضع الإله", + "sa_noclip": "بدون قصاصات", + "sa_respawn": "إعادة الظهور", + "sa_give_weapon": "إعطاء سلاح", + "sa_strip_weapons": "تجريد الأسلحة", + "sa_freeze": "تجميد", + "sa_set_hp": "تعيين الصحة", + "sa_set_speed": "تعيين السرعة", + "sa_set_gravity": "تعيين الجاذبية", + "sa_set_money": "تعيين المال", + + "sa_changemap": "تغيير الخريطة", + "sa_restart_game": "إعادة تشغيل اللعبة", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "تبديل", + "sa_team_spec": "المشاهدة", + + "sa_slap": "صفعة", + "sa_slay": "قتل", + "sa_kick": "طرد", + "sa_ban": "حظر", + "sa_gag": "كتم", + "sa_mute": "كتم", + "sa_silence": "صمت", + "sa_warn": "تحذير", + "sa_team_force": "فرض الفريق", + + "sa_menu_custom_commands": "الأوامر المخصصة", + "sa_menu_server_manage": "إدارة الخادم", + "sa_menu_fun_commands": "أوامر ممتعة", + "sa_menu_admins_manage": "إدارة المسؤولين", + "sa_menu_players_manage": "إدارة اللاعبين", + "sa_menu_disconnected_title": "اللاعبون الأخيرون", + "sa_menu_disconnected_action_title": "اختر الإجراء", + + "sa_player": "اللاعب", + "sa_steamid": "معرف البخار", + "sa_duration": "المدة", + "sa_reason": "السبب", + "sa_admin": "المشرف", + "sa_permanent": "دائم", + + "sa_discord_penalty_ban": "الحظر مسجل", + "sa_discord_penalty_mute": "الكتم مسجل", + "sa_discord_penalty_gag": "الصمت مسجل", + "sa_discord_penalty_silence": "الصمت مسجل", + "sa_discord_penalty_warn": "التحذير مسجل", + "sa_discord_penalty_unknown": "غير معروف مسجل", + + "sa_player_penalty_info_active_mute": "➔ كتم [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ صمت [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ سكوت [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ تحذير [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}] - السبب [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ كتم [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ صمت [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ سكوت [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ تحذير [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nعقوبات اللاعبين لـ {lightred}{0}{default},\nعدد الحظر: {lightred}{1}{default}, عدد الصمت: {lightred}{2}{default}, عدد الكتم: {lightred}{3}{default}, عدد السكوت: {lightred}{4}{default}, عدد التحذيرات: {lightred}{5}{default}\nالعقوبات النشطة:\n{6}\nالتحذيرات النشطة:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}عقوبات اللاعبين لـ {lightred}{0}{grey}, حظر: {lightred}{1}{grey}, صمت: {lightred}{2}{grey}, كتم: {lightred}{3}{grey}, سكوت: {lightred}{4}{grey}, تحذيرات: {lightred}{5}", + "sa_player_ban_message_time": "تم حظرك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!", + "sa_player_ban_message_perm": "تم حظرك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!", + "sa_player_kick_message": "تم طردك لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!", + "sa_player_gag_message_time": "تم تكميم فمك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!", + "sa_player_gag_message_perm": "تم تكميم فمك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!", + "sa_player_mute_message_time": "تم كتم صوتك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!", + "sa_player_mute_message_perm": "تم كتم صوتك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!", + "sa_player_silence_message_time": "تم إسكاتك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!", + "sa_player_silence_message_perm": "تم إسكاتك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!", + "sa_player_warn_message_time": "لقد تم تحذيرك بسبب {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة بواسطة {lightred}{2}{default}!", + "sa_player_warn_message_perm": "لقد تم تحذيرك بشكل دائم بسبب {lightred}{0}{default} بواسطة {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} حظر {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} حظر {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} طرد {lightred}{1}{default} بسبب {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} كتم {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} كتم {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} أسكت {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} أسكت {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} أسكت {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} أسكت {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} حذر {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} حذر {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} أعطى {lightred}{1}{default} {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} أخذ جميع أسلحة اللاعب {lightred}{1}{default}!", + "sa_admin_hp_message": "{lightred}{0}{default} غيّر عدد نقاط الحياة لـ {lightred}{1}{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} غيّر السرعة لـ {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} غيّر الجاذبية لـ {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} غيّر المال لـ {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} غيّر وضع الله لـ {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} قتل {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} صفع {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} غيّر الخريطة إلى {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} فعّل/ألغى نمط اللا تصادم لـ {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} جمد {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} أذاب {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} غيّر اسم {lightred}{1}{default} إلى {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} أحيى {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} نقل إلى {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} نقل إلى نفسه {lightred}{1}{default}!", + "sa_admin_team_message": "{lightred}{0}{default} نقل {lightred}{1}{default} إلى {lightred}{2}{default}!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}تحذيرات", + "sa_admin_warns_unwarn": "{lime}تم إلغاء التحذير بنجاح{default} لـ {gold}{0} {default}بسبب {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}تصويت لـ {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} بدأ التصويت لـ {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}نتائج التصويت لـ {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}الإداري: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(إداري) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(لاعب) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** أصدر الأمر `{1}` على الخادم `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/de.json b/CS2-SimpleAdmin/lang/de.json new file mode 100644 index 0000000..27045f0 --- /dev/null +++ b/CS2-SimpleAdmin/lang/de.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Unbekannt", + "sa_no_permission": "Du hast keine Berechtigung zur Verwendung dieses Befehls.", + "sa_ban_max_duration_exceeded": "Die Dauer des Banns darf {lightred}{0}{default} Minuten nicht überschreiten.", + "sa_ban_perm_restricted": "Du hast nicht die Berechtigung, einen permanenten Bann auszusprechen.", + + "sa_admin_add": "Admin hinzufügen", + "sa_admin_remove": "Admin entfernen", + "sa_admin_reload": "Admins neuladen", + + "sa_godmode": "Gottmodus", + "sa_noclip": "No Clip", + "sa_respawn": "Wiederbeleben", + "sa_give_weapon": "Waffe gegeben", + "sa_strip_weapons": "Waffen abnehmen", + "sa_freeze": "Einfrieren", + "sa_set_hp": "Lp setzen", + "sa_set_speed": "Geschwindigkeit setzen", + "sa_set_gravity": "Gravitation setzen", + "sa_set_money": "Geld setzen", + + "sa_changemap": "Map wechseln", + "sa_restart_game": "Spiel neustarten", + + "sa_team_ct": "AT", + "sa_team_t": "T", + "sa_team_swap": "Wechseln", + "sa_team_spec": "Zuschauer", + + "sa_slap": "Klaps", + "sa_slay": "töten", + "sa_kick": "Kicken", + "sa_ban": "Bann", + "sa_gag": "Chat stummschalten", + "sa_mute": "Sprachchat stummschalten", + "sa_silence": "Komplett stummschalten", + "sa_warn": "Warnen", + "sa_team_force": "Team zuweisen", + + "sa_menu_custom_commands": "Eigene Befehle", + "sa_menu_server_manage": "Server Verwalten", + "sa_menu_fun_commands": "Spaß Befehle", + "sa_menu_admins_manage": "Admins verwalten", + "sa_menu_players_manage": "Spieler verwalten", + "sa_menu_disconnected_title": "Letzte Spieler", + "sa_menu_disconnected_action_title": "Aktion auswählen", + + "sa_player": "Spieler", + "sa_steamid": "SteamID", + "sa_duration": "Dauer", + "sa_reason": "Grund", + "sa_admin": "Admin", + "sa_permanent": "Permanent", + + "sa_discord_penalty_ban": "Bann registriert", + "sa_discord_penalty_mute": "Chat-Stummschaltung registriert", + "sa_discord_penalty_gag": "Sprachchat-Stummschaltung registriert", + "sa_discord_penalty_silence": "Komplett-Stummschaltung registriert", + "sa_discord_penalty_warn": "Warnung registriert", + "sa_discord_penalty_unknown": "Unbekanntes registriert", + + "sa_player_penalty_info_active_mute": "➔ Stummschaltung [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Mundtot [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Stille [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Warnung [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}] - Grund [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Stummschaltung [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Mundtot [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Stille [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Warnung [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nSpielerstrafe für {lightred}{0}{default},\nAnzahl der Sperren: {lightred}{1}{default}, Anzahl der Mundtot: {lightred}{2}{default}, Anzahl der Stummschaltungen: {lightred}{3}{default}, Anzahl der Stille: {lightred}{4}{default}, Anzahl der Warnungen: {lightred}{5}{default}\nAktive Strafen:\n{6}\nAktive Warnungen:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Spielerstrafe für {lightred}{0}{grey}, Sperren: {lightred}{1}{grey}, Mundtot: {lightred}{2}{grey}, Stummschaltungen: {lightred}{3}{grey}, Stille: {lightred}{4}{grey}, Warnungen: {lightred}{5}", + "sa_player_ban_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} gebannt!", + "sa_player_ban_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent gebannt!", + "sa_player_kick_message": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} gekickt!", + "sa_player_gag_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} im Chat stummgeschaltet!", + "sa_player_gag_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent im Chat stummgeschaltet!", + "sa_player_mute_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} im Sprachchat stummgeschaltet!", + "sa_player_mute_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent im Sprachchat stummgeschaltet!", + "sa_player_silence_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} vollständig stummgeschaltet!", + "sa_player_silence_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent vollständig stummgeschaltet!", + "sa_player_warn_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} gewarnt!", + "sa_player_warn_message_perm": "Du wurdest dauerhaft wegen {lightred}{0}{default} von {lightred}{1}{default} gewarnt!", + "sa_admin_ban_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten gebannt!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} gebannt!", + "sa_admin_kick_message": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} gekickt!", + "sa_admin_gag_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten stummgeschaltet!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} stummgeschaltet!", + "sa_admin_mute_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten gemutet!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} gemutet!", + "sa_admin_silence_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten stummgeschaltet!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} stummgeschaltet!", + "sa_admin_warn_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten verwarnt!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} verwarnt!", + "sa_admin_give_message": "{lightred}{0}{default} hat {lightred}{1}{default} ein {lightred}{2}{default} gegeben!", + "sa_admin_strip_message": "{lightred}{0}{default} hat alle Waffen von Spieler {lightred}{1}{default} entfernt!", + "sa_admin_hp_message": "{lightred}{0}{default} hat die Lebenspunkte von {lightred}{1}{default} geändert!", + "sa_admin_speed_message": "{lightred}{0}{default} hat die Geschwindigkeit von {lightred}{1}{default} geändert!", + "sa_admin_gravity_message": "{lightred}{0}{default} hat die Schwerkraft von {lightred}{1}{default} geändert!", + "sa_admin_money_message": "{lightred}{0}{default} hat das Geld von {lightred}{1}{default} geändert!", + "sa_admin_god_message": "{lightred}{0}{default} hat den Gottmodus von {lightred}{1}{default} geändert!", + "sa_admin_slay_message": "{lightred}{0}{default} hat {lightred}{1}{default} getötet!", + "sa_admin_slap_message": "{lightred}{0}{default} hat {lightred}{1}{default} geschlagen!", + "sa_admin_changemap_message": "{lightred}{0}{default} hat die Karte zu {lightred}{1}{default} geändert!", + "sa_admin_noclip_message": "{lightred}{0}{default} hat den Noclip-Modus für {lightred}{1}{default} aktiviert/deaktiviert!", + "sa_admin_freeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} eingefroren!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} aufgetaut!", + "sa_admin_rename_message": "{lightred}{0}{default} hat den Namen von {lightred}{1}{default} in {lightred}{2}{default} geändert!", + "sa_admin_respawn_message": "{lightred}{0}{default} hat {lightred}{1}{default} wiederbelebt!", + "sa_admin_tp_message": "{lightred}{0}{default} ist zu {lightred}{1}{default} teleportiert!", + "sa_admin_bring_message": "{lightred}{0}{default} hat {lightred}{1}{default} zu sich teleportiert!", + "sa_admin_team_message": "{lightred}{0}{default} hat {lightred}{1}{default} zu {lightred}{2}{default} transferiert!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}VERWARNUNGEN", + "sa_admin_warns_unwarn": "{lime}Erfolgreich{default} Verwarnung von {gold}{0} {default}aufgehoben wegen {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}ABSTIMMUNG FÜR {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} hat eine Abstimmung für {lightred}{1}{default} gestartet", + "sa_admin_vote_message_results": "{lime}ABSTIMMUNGSERGEBNISSE FÜR {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(SPIELER) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** hat den Befehl `{1}` auf dem Server `HOSTNAME` ausgeführt" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/en.json b/CS2-SimpleAdmin/lang/en.json new file mode 100644 index 0000000..88e9cd1 --- /dev/null +++ b/CS2-SimpleAdmin/lang/en.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Unknown", + "sa_no_permission": "You do not have permissions to use this command.", + "sa_ban_max_duration_exceeded": "Ban duration cannot exceed {lightred}{0}{default} minutes.", + "sa_ban_perm_restricted": "You do not have the right to permanently ban.", + + "sa_admin_add": "Add Admin", + "sa_admin_remove": "Remove Admin", + "sa_admin_reload": "Reload Admins", + + "sa_godmode": "God Mode", + "sa_noclip": "No Clip", + "sa_respawn": "Respawn", + "sa_give_weapon": "Give Weapon", + "sa_strip_weapons": "Strip Weapons", + "sa_freeze": "Freeze", + "sa_set_hp": "Set Hp", + "sa_set_speed": "Set Speed", + "sa_set_gravity": "Set Gravity", + "sa_set_money": "Set Money", + + "sa_changemap": "Change Map", + "sa_restart_game": "Restart Game", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Swap", + "sa_team_spec": "Spec", + + "sa_slap": "Slap", + "sa_slay": "slay", + "sa_kick": "Kick", + "sa_ban": "Ban", + "sa_gag": "Gag", + "sa_mute": "Mute", + "sa_silence": "Silence", + "sa_warn": "Warn", + "sa_team_force": "Force Team", + + "sa_menu_custom_commands": "Custom Commands", + "sa_menu_server_manage": "Server Manage", + "sa_menu_fun_commands": "Fun Commands", + "sa_menu_admins_manage": "Admins Manage", + "sa_menu_players_manage": "Players Manage", + "sa_menu_disconnected_title": "Recent players", + "sa_menu_disconnected_action_title": "Select action", + + "sa_player": "Player", + "sa_steamid": "SteamID", + "sa_duration": "Duration", + "sa_reason": "Reason", + "sa_admin": "Admin", + "sa_permanent": "Permanent", + + "sa_discord_penalty_ban": "Ban registered", + "sa_discord_penalty_mute": "Mute registered", + "sa_discord_penalty_gag": "Gag registered", + "sa_discord_penalty_silence": "Silence registered", + "sa_discord_penalty_warn": "Warn registered", + "sa_discord_penalty_unknown": "Unknown registered", + + "sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Silence [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Warn [{lightred}❌{default}] - Expire [{lightred}{0}{default}] - Reason [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Mute [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Silence [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Warn [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nPlayer penalties for {lightred}{0}{default},\nNumber of bans: {lightred}{1}{default}, Number of gags: {lightred}{2}{default}, Number of mutes: {lightred}{3}{default}, Number of silences: {lightred}{4}{default}, Number of warnings: {lightred}{5}{default}\nActive penalties:\n{6}\nActive warnings:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Player penalties for {lightred}{0}{grey}, Bans: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Warns: {lightred}{5}", + "sa_player_ban_message_time": "You have been banned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!", + "sa_player_ban_message_perm": "You have been banned permanently for {lightred}{0}{default} by {lightred}{1}{default}!", + "sa_player_kick_message": "You have been kicked for {lightred}{0}{default} by {lightred}{1}{default}!", + "sa_player_gag_message_time": "You have been gagged for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!", + "sa_player_gag_message_perm": "You have been gagged permanently for {lightred}{0}{default} by {lightred}{1}{default}!", + "sa_player_mute_message_time": "You have been muted for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!", + "sa_player_mute_message_perm": "You have been muted permanently for {lightred}{0}{default} by {lightred}{1}{default}!", + "sa_player_silence_message_time": "You have been silenced for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!", + "sa_player_silence_message_perm": "You have been silenced permanently for {lightred}{0}{default} by {lightred}{1}{default}!", + "sa_player_warn_message_time": "You have been warned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!", + "sa_player_warn_message_perm": "You have been warned permanently for {lightred}{0}{default} by {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} banned {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} banned {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} kicked {lightred}{1}{default} for {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} gagged {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} gagged {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} muted {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} muted {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} silenced {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} silenced {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} warned {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} warned {lightred}{1}{default} permanently for {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} gave {lightred}{1}{default} a {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} took all of player {lightred}{1}{default} weapons!", + "sa_admin_hp_message": "{lightred}{0}{default} changed {lightred}{1}{default} hp amount{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} changed speed for {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} changed gravity for {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} changed money for {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} slayed {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} slapped {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} changed map to {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} toggled noclip for {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} froze {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} unfroze {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} changed {lightred}{1}{default} nickname to {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} respawned {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} teleported to {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} teleported to himself {lightred}{1}{default}!", + "sa_admin_team_message": "{lightred}{0}{default} transfered {lightred}{1}{default} to {lightred}{2}{default}!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}WARNS", + "sa_admin_warns_unwarn": "{lime}Successfully{default} unwarned {gold}{0} {default}for {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}VOTING FOR {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} started voting for {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}VOTING RESULTS FOR {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(PLAYER) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** issued command `{1}` on server `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/es.json b/CS2-SimpleAdmin/lang/es.json new file mode 100644 index 0000000..58d8882 --- /dev/null +++ b/CS2-SimpleAdmin/lang/es.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Desconocido", + "sa_no_permission": "No tienes permisos para usar este comando.", + "sa_ban_max_duration_exceeded": "La duración de la prohibición no puede exceder {lightred}{0}{default} minutos.", + "sa_ban_perm_restricted": "No tienes derecho a prohibir permanentemente.", + + "sa_admin_add": "Agregar Administrador", + "sa_admin_remove": "Eliminar Administrador", + "sa_admin_reload": "Recargar Administradores", + + "sa_godmode": "Modo Dios", + "sa_noclip": "Sin Colisión", + "sa_respawn": "Reaparecer", + "sa_give_weapon": "Dar Arma", + "sa_strip_weapons": "Eliminar Armas", + "sa_freeze": "Congelar", + "sa_set_hp": "Establecer Vida", + "sa_set_speed": "Establecer Velocidad", + "sa_set_gravity": "Establecer Gravedad", + "sa_set_money": "Establecer Dinero", + + "sa_changemap": "Cambiar Mapa", + "sa_restart_game": "Reiniciar Juego", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Intercambiar", + "sa_team_spec": "Espectador", + + "sa_slap": "Golpear", + "sa_slay": "Matar", + "sa_kick": "Expulsar", + "sa_ban": "Banear", + "sa_gag": "Callar", + "sa_mute": "Silenciar", + "sa_silence": "Silencio", + "sa_warn": "Advertencia", + "sa_team_force": "Forzar Equipo", + + "sa_menu_custom_commands": "Comandos Personalizados", + "sa_menu_server_manage": "Administrar Servidor", + "sa_menu_fun_commands": "Comandos Divertidos", + "sa_menu_admins_manage": "Administrar Administradores", + "sa_menu_players_manage": "Administrar Jugadores", + "sa_menu_disconnected_title": "Jugadores recientes", + "sa_menu_disconnected_action_title": "Seleccionar acción", + + "sa_player": "Jugador", + "sa_steamid": "ID de Steam", + "sa_duration": "Duración", + "sa_reason": "Motivo", + "sa_admin": "Admin", + "sa_permanent": "Permanente", + + "sa_discord_penalty_ban": "Ban registrado", + "sa_discord_penalty_mute": "Silencio registrado", + "sa_discord_penalty_gag": "Mordaza registrada", + "sa_discord_penalty_silence": "Silencio registrado", + "sa_discord_penalty_warn": "Advertencia registrada", + "sa_discord_penalty_unknown": "Registro desconocido", + + "sa_player_penalty_info_active_mute": "➔ Silenciado [{lightred}❌{default}] - Expira [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Boqueado [{lightred}❌{default}] - Expira [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Silencio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Advertencia [{lightred}❌{default}] - Expira [{lightred}{0}{default}] - Razón [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Silenciado [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Boqueado [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Silencio [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Advertencia [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nPenalizaciones del jugador para {lightred}{0}{default},\nNúmero de prohibiciones: {lightred}{1}{default}, Número de boqueos: {lightred}{2}{default}, Número de silenciamientos: {lightred}{3}{default}, Número de silencios: {lightred}{4}{default}, Número de advertencias: {lightred}{5}{default}\nPenalizaciones activas:\n{6}\nAdvertencias activas:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Penalizaciones del jugador para {lightred}{0}{grey}, Prohibiciones: {lightred}{1}{grey}, Boqueos: {lightred}{2}{grey}, Silenciamientos: {lightred}{3}{grey}, Silencios: {lightred}{4}{grey}, Advertencias: {lightred}{5}", + "sa_player_ban_message_time": "Has sido baneado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_ban_message_perm": "Has sido baneado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_kick_message": "Has sido expulsado por {lightred}{0}{default} durante {lightred}{1}{default}!", + "sa_player_gag_message_time": "Has sido silenciado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_gag_message_perm": "Has sido silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_mute_message_time": "Has sido muteado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_mute_message_perm": "Has sido muteado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_silence_message_time": "Has sido silenciado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_silence_message_perm": "Has sido silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_warn_message_time": "¡Has sido advertido por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_warn_message_perm": "¡Has sido advertido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} baneó a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} baneó a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} expulsó a {lightred}{1}{default} por {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} amordazó a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} amordazó a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} silenció a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} silenció a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} silenció a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} silenció a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} advirtió a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} advirtió a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} dio {lightred}{1}{default} un {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} quitó todas las armas del jugador {lightred}{1}{default}!", + "sa_admin_hp_message": "{lightred}{0}{default} cambió la cantidad de HP de {lightred}{1}{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} cambió la velocidad de {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} cambió la gravedad de {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} cambió el dinero de {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} cambió el modo dios de {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} mató a {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} abofeteó a {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} cambió el mapa a {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} alternó noclip para {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} congeló a {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} descongeló a {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} cambió el apodo de {lightred}{1}{default} a {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} reapareció a {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} se teletransportó a {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} teletransportó a {lightred}{1}{default} hacia sí mismo!", + "sa_admin_team_message": "{lightred}{0}{default} transfirió a {lightred}{1}{default} al {lightred}{2}{default}!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTENCIAS", + "sa_admin_warns_unwarn": "{lime}Desadvertencia{default} exitosa de {gold}{0} {default}por {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}VOTACIÓN PARA {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} inició una votación para {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}RESULTADOS DE LA VOTACIÓN PARA {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(JUGADOR) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** ejecutó el comando `{1}` en el servidor `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/fa.json b/CS2-SimpleAdmin/lang/fa.json new file mode 100644 index 0000000..2743cb0 --- /dev/null +++ b/CS2-SimpleAdmin/lang/fa.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "ناشناخته", + "sa_no_permission": "شما دسترسی برای استفاده از این دستور را ندارید.", + "sa_ban_max_duration_exceeded": "مدت ممنوعیت نمی‌تواند بیشتر از {lightred}{0}{default} دقیقه باشد.", + "sa_ban_perm_restricted": "شما اجازه ممنوعیت دائم را ندارید.", + + "sa_admin_add": "افزودن مدیر", + "sa_admin_remove": "حذف مدیر", + "sa_admin_reload": "بارگذاری مجدد مدیران", + + "sa_godmode": "حالت خدا", + "sa_noclip": "بدون بریدن", + "sa_respawn": "باززایی", + "sa_give_weapon": "دادن اسلحه", + "sa_strip_weapons": "برداشتن اسلحه", + "sa_freeze": "یخ‌زدن", + "sa_set_hp": "تنظیم پلیر", + "sa_set_speed": "تنظیم سرعت", + "sa_set_gravity": "تنظیم گرانش", + "sa_set_money": "تنظیم پول", + + "sa_changemap": "تغییر نقشه", + "sa_restart_game": "شروع مجدد بازی", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "جابه‌جایی", + "sa_team_spec": "ناظر", + + "sa_slap": "چپاد زدن", + "sa_slay": "کشتن", + "sa_kick": "اخراج", + "sa_ban": "مسدود کردن", + "sa_gag": "بی‌صدا کردن", + "sa_mute": "بی‌صدا کردن", + "sa_silence": "سکوت", + "sa_warn": "هشدار", + "sa_team_force": "اجبار تیم", + + "sa_menu_custom_commands": "دستورات سفارشی", + "sa_menu_server_manage": "مدیریت سرور", + "sa_menu_fun_commands": "دستورات جالب", + "sa_menu_admins_manage": "مدیریت مدیران", + "sa_menu_players_manage": "مدیریت بازیکنان", + "sa_menu_disconnected_title": "آخرین بازیکنان", + "sa_menu_disconnected_action_title": "انتخاب عملیات", + + "sa_player": "بازیکن", + "sa_steamid": "شناسه استیم", + "sa_duration": "مدت زمان", + "sa_reason": "دلیل", + "sa_admin": "مدیر", + "sa_permanent": "دائمی", + + "sa_discord_penalty_ban": "بن انجام شده", + "sa_discord_penalty_mute": "سکوت انجام شده", + "sa_discord_penalty_gag": "بند زدن انجام شده", + "sa_discord_penalty_silence": "سکوت انجام شده", + "sa_discord_penalty_warn": "هشدار ثبت شد", + "sa_discord_penalty_unknown": "ناشناخته انجام شده", + + "sa_player_penalty_info_active_mute": "➔ بی‌صدا [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ مسدود کردن صدا [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ سکوت [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ هشدار [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}] - دلیل [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ بی‌صدا [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ مسدود کردن صدا [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ سکوت [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ هشدار [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nتنبیهات بازیکن برای {lightred}{0}{default},\nتعداد مسدودیت‌ها: {lightred}{1}{default}, تعداد سکوت‌ها: {lightred}{2}{default}, تعداد بی‌صدا کردن‌ها: {lightred}{3}{default}, تعداد سکوت‌ها: {lightred}{4}{default}, تعداد هشدارها: {lightred}{5}{default}\nتنبیهات فعال:\n{6}\nهشدارهای فعال:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}تنبیهات بازیکن برای {lightred}{0}{grey}, مسدودیت‌ها: {lightred}{1}{grey}, سکوت‌ها: {lightred}{2}{grey}, بی‌صدا کردن‌ها: {lightred}{3}{grey}, سکوت‌ها: {lightred}{4}{grey}, هشدارها: {lightred}{5}", + "sa_player_ban_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} مسدود شده‌اید!", + "sa_player_ban_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه مسدود شده‌اید!", + "sa_player_kick_message": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} اخراج شده‌اید!", + "sa_player_gag_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} خفه شده‌اید!", + "sa_player_gag_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه خفه شده‌اید!", + "sa_player_mute_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} بی‌صدا شده‌اید!", + "sa_player_mute_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه بی‌صدا شده‌اید!", + "sa_player_silence_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} ساکت شده‌اید!", + "sa_player_silence_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه ساکت شده‌اید!", + "sa_player_warn_message_time": "شما به خاطر {lightred}{0}{default} به مدت {lightred}{1}{default} دقیقه توسط {lightred}{2}{default} هشدار داده شده\u200Cاید!", + "sa_player_warn_message_perm": "شما به طور دائم به خاطر {lightred}{0}{default} توسط {lightred}{1}{default} هشدار داده شده\u200Cاید!", + "sa_admin_ban_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} بن کرد!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} بن کرد!", + "sa_admin_kick_message": "{lightred}{0}{default} {lightred}{1}{default} را به دلیل {lightred}{2}{default} اخراج کرد!", + "sa_admin_gag_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} بی‌صدا کرد!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} بی‌صدا کرد!", + "sa_admin_mute_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} قطع صدا کرد!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} قطع صدا کرد!", + "sa_admin_silence_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} بی‌صدا کرد!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} بی‌صدا کرد!", + "sa_admin_warn_message_time": "{lightred}{0}{default} به {lightred}{1}{default} هشدار داد برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default}!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} به {lightred}{1}{default} به‌طور دائم به دلیل {lightred}{2}{default} هشدار داد!", + "sa_admin_give_message": "{lightred}{0}{default} {lightred}{2}{default} را به {lightred}{1}{default} داد!", + "sa_admin_strip_message": "{lightred}{0}{default} تمام سلاح‌های بازیکن {lightred}{1}{default} را گرفت!", + "sa_admin_hp_message": "{lightred}{0}{default} مقدار سلامت {lightred}{1}{default} را تغییر داد!", + "sa_admin_speed_message": "{lightred}{0}{default} سرعت {lightred}{1}{default} را تغییر داد!", + "sa_admin_gravity_message": "{lightred}{0}{default} جاذبه {lightred}{1}{default} را تغییر داد!", + "sa_admin_money_message": "{lightred}{0}{default} پول {lightred}{1}{default} را تغییر داد!", + "sa_admin_god_message": "{lightred}{0}{default} حالت خدا را برای {lightred}{1}{default} تغییر داد!", + "sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default} را کشت!", + "sa_admin_slap_message": "{lightred}{0}{default} به {lightred}{1}{default} سیلی زد!", + "sa_admin_changemap_message": "{lightred}{0}{default} نقشه را به {lightred}{1}{default} تغییر داد!", + "sa_admin_noclip_message": "{lightred}{0}{default} ناپدیدی را برای {lightred}{1}{default} فعال/غیرفعال کرد!", + "sa_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default} را یخ‌زده کرد!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default} را از حالت یخ خارج کرد!", + "sa_admin_rename_message": "{lightred}{0}{default} نام {lightred}{1}{default} را به {lightred}{2}{default} تغییر داد!", + "sa_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default} را دوباره زنده کرد!", + "sa_admin_tp_message": "{lightred}{0}{default} به {lightred}{1}{default} تله‌پورت شد!", + "sa_admin_bring_message": "{lightred}{0}{default} {lightred}{1}{default} را به خود تله‌پورت کرد!", + "sa_admin_team_message": "{lightred}{0}{default} {lightred}{1}{default} را به {lightred}{2}{default} انتقال داد!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}هشدارها", + "sa_admin_warns_unwarn": "{lime}هشدار با موفقیت{default} برای {gold}{0} {default}لغو شد به دلیل {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}رأی‌گیری برای {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} رأی‌گیری برای {lightred}{1}{default} را شروع کرد", + "sa_admin_vote_message_results": "{lime}نتایج رأی‌گیری برای {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}ادمین: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ادمین) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(بازیکن) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** فرمان `{1}` را در سرور `HOSTNAME` اجرا کرد" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/fr.json b/CS2-SimpleAdmin/lang/fr.json new file mode 100644 index 0000000..638a15c --- /dev/null +++ b/CS2-SimpleAdmin/lang/fr.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Inconnu", + "sa_no_permission": "Vous n'avez pas les permissions pour utiliser cette commande.", + "sa_ban_max_duration_exceeded": "La durée d'interdiction ne peut pas dépasser {lightred}{0}{default} minutes.", + "sa_ban_perm_restricted": "Vous n'avez pas le droit de bannir définitivement.", + + "sa_admin_add": "Ajouter un administrateur", + "sa_admin_remove": "Supprimer un administrateur", + "sa_admin_reload": "Recharger les administrateurs", + + "sa_godmode": "Mode Dieu", + "sa_noclip": "Mode Spectateur", + "sa_respawn": "Réapparaître", + "sa_give_weapon": "Donner une arme", + "sa_strip_weapons": "Retirer les armes", + "sa_freeze": "Geler", + "sa_set_hp": "Définir les PV", + "sa_set_speed": "Définir la vitesse", + "sa_set_gravity": "Définir la gravité", + "sa_set_money": "Définir l'argent", + + "sa_changemap": "Changer de carte", + "sa_restart_game": "Redémarrer le jeu", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Échanger", + "sa_team_spec": "Spectateur", + + "sa_slap": "Gifler", + "sa_slay": "Tuer", + "sa_kick": "Expulser", + "sa_ban": "Bannir", + "sa_gag": "Réduire au silence", + "sa_mute": "Muter", + "sa_silence": "Silence", + "sa_warn": "Avertir", + "sa_team_force": "Forcer l'équipe", + + "sa_menu_custom_commands": "Commandes personnalisées", + "sa_menu_server_manage": "Gérer le serveur", + "sa_menu_fun_commands": "Commandes amusantes", + "sa_menu_admins_manage": "Gérer les administrateurs", + "sa_menu_players_manage": "Gérer les joueurs", + "sa_menu_disconnected_title": "Derniers joueurs", + "sa_menu_disconnected_action_title": "Choisir une action", + + "sa_player": "Joueur", + "sa_steamid": "ID Steam", + "sa_duration": "Durée", + "sa_reason": "Raison", + "sa_admin": "Admin", + "sa_permanent": "Permanent", + + "sa_discord_penalty_ban": "Bannissement enregistré", + "sa_discord_penalty_mute": "Mute enregistré", + "sa_discord_penalty_gag": "Gag enregistré", + "sa_discord_penalty_silence": "Silence enregistré", + "sa_discord_penalty_warn": "Avertissement enregistré", + "sa_discord_penalty_unknown": "Inconnu enregistré", + + "sa_player_penalty_info_active_mute": "➔ Muet [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Silence [{lightred}❌{default}] - Expire [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Avertissement [{lightred}❌{default}] - Expire [{lightred}{0}{default}] - Raison [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Muet [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Silence [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Avertissement [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nPénalités du joueur pour {lightred}{0}{default},\nNombre de bannissements: {lightred}{1}{default}, Nombre de gag: {lightred}{2}{default}, Nombre de mutes: {lightred}{3}{default}, Nombre de silences: {lightred}{4}{default}, Nombre d’avertissements: {lightred}{5}{default}\nPénalités actives:\n{6}\nAvertissements actifs:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Pénalités du joueur pour {lightred}{0}{grey}, Bannissements: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Avertissements: {lightred}{5}", + "sa_player_ban_message_time": "Vous avez été banni pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!", + "sa_player_ban_message_perm": "Vous avez été banni définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!", + "sa_player_kick_message": "Vous avez été expulsé pour {lightred}{0}{default} par {lightred}{1}{default}!", + "sa_player_gag_message_time": "Vous avez été réduit au silence pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!", + "sa_player_gag_message_perm": "Vous avez été réduit au silence définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!", + "sa_player_mute_message_time": "Vous avez été réduit au silence pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!", + "sa_player_mute_message_perm": "Vous avez été réduit au silence définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!", + "sa_player_silence_message_time": "Vous avez été mis en sourdine pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!", + "sa_player_silence_message_perm": "Vous avez été mis en sourdine définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!", + "sa_player_warn_message_time": "Vous avez été averti pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!", + "sa_player_warn_message_perm": "Vous avez été averti définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} a banni {lightred}{1}{default} pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} a banni {lightred}{1}{default} définitivement pour {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} a expulsé {lightred}{1}{default} pour {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} a bâillonné {lightred}{1}{default} pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} a bâillonné {lightred}{1}{default} définitivement pour {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} a rendu {lightred}{1}{default} muet pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} a rendu {lightred}{1}{default} muet définitivement pour {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} a rendu {lightred}{1}{default} silencieux pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} a rendu {lightred}{1}{default} silencieux définitivement pour {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} a averti {lightred}{1}{default} pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} a averti {lightred}{1}{default} définitivement pour {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} a donné {lightred}{2}{default} à {lightred}{1}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} a retiré toutes les armes de {lightred}{1}{default}!", + "sa_admin_hp_message": "{lightred}{0}{default} a modifié la quantité de HP de {lightred}{1}{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} a modifié la vitesse de {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} a modifié la gravité de {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} a modifié l'argent de {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} a modifié le mode dieu de {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} a tué {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} a giflé {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} a changé la carte pour {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} a activé/désactivé le noclip pour {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} a gelé {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} a dégivré {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} a renommé {lightred}{1}{default} en {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} a réapparu {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} s'est téléporté à {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} a téléporté {lightred}{1}{default} à lui!", + "sa_admin_team_message": "{lightred}{0}{default} a transféré {lightred}{1}{default} à l'équipe {lightred}{2}{default}!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}AVERTISSEMENTS", + "sa_admin_warns_unwarn": "{lime}Avertissement annulé{default} avec succès pour {gold}{0} {default}en raison de {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}VOTE POUR {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} a lancé un vote pour {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}RÉSULTATS DU VOTE POUR {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(JOUEUR) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** a exécuté la commande `{1}` sur le serveur `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/lv.json b/CS2-SimpleAdmin/lang/lv.json new file mode 100644 index 0000000..e3876ee --- /dev/null +++ b/CS2-SimpleAdmin/lang/lv.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Nezināms", + "sa_no_permission": "Jums nav atļauju izmantot šo komandu.", + "sa_ban_max_duration_exceeded": "Aizlieguma ilgums nevar pārsniegt {lightred}{0}{default} minūtes.", + "sa_ban_perm_restricted": "Jums nav tiesību uz pastāvīgu aizliegumu.", + + "sa_admin_add": "Pievienot administratoru", + "sa_admin_remove": "Noņemt administratoru", + "sa_admin_reload": "Pārlādēt administratorus", + + "sa_godmode": "Dieva režīms", + "sa_noclip": "Bez šķēršļiem", + "sa_respawn": "Atdzimt", + "sa_give_weapon": "Dot ieroci", + "sa_strip_weapons": "Noņemt ieročus", + "sa_freeze": "Salauzt", + "sa_set_hp": "Iestatīt veselību", + "sa_set_speed": "Iestatīt ātrumu", + "sa_set_gravity": "Iestatīt gravitāciju", + "sa_set_money": "Iestatīt naudu", + + "sa_changemap": "Mainīt karti", + "sa_restart_game": "Restartēt spēli", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Mainīt", + "sa_team_spec": "Skatītājs", + + "sa_slap": "Plašs", + "sa_slay": "Nogalināt", + "sa_kick": "Izraidīt", + "sa_ban": "Bloķēt", + "sa_gag": "Izslēgt runu", + "sa_mute": "Noklusināt", + "sa_silence": "Klusums", + "sa_warn": "Brīdināt", + "sa_team_force": "Spēka komanda", + + "sa_menu_custom_commands": "Pielāgotās komandas", + "sa_menu_server_manage": "Servera pārvaldība", + "sa_menu_fun_commands": "Jautras komandas", + "sa_menu_admins_manage": "Administratoru pārvaldība", + "sa_menu_players_manage": "Spēlētāju pārvaldība", + "sa_menu_disconnected_title": "Pēdējie spēlētāji", + "sa_menu_disconnected_action_title": "Izvēlieties darbību", + + "sa_player": "Spēlētājs", + "sa_steamid": "Steam ID", + "sa_duration": "Ilgums", + "sa_reason": "Iemesls", + "sa_admin": "Admins", + "sa_permanent": "Pastāvīgs", + + "sa_discord_penalty_ban": "Bans reģistrēts", + "sa_discord_penalty_mute": "Mute reģistrēts", + "sa_discord_penalty_gag": "Gag reģistrēts", + "sa_discord_penalty_silence": "Klusums reģistrēts", + "sa_discord_penalty_warn": "Brīdinājums reģistrēts", + "sa_discord_penalty_unknown": "Nezināms reģistrēts", + + "sa_player_penalty_info_active_mute": "➔ Izslēgts [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Klusums [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Klusēšana [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Brīdinājums [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}] - Iemesls [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Izslēgts [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Klusums [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Klusēšana [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Brīdinājums [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nSpēlētāja sods priekš {lightred}{0}{default},\nAizliegumu skaits: {lightred}{1}{default}, Klusumu skaits: {lightred}{2}{default}, Izslēgšanas skaits: {lightred}{3}{default}, Klusēšanas skaits: {lightred}{4}{default}, Brīdinājumu skaits: {lightred}{5}{default}\nAktīvie sodi:\n{6}\nAktīvie brīdinājumi:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Spēlētāja sods priekš {lightred}{0}{grey}, Aizliegumi: {lightred}{1}{grey}, Klusumi: {lightred}{2}{grey}, Izslēgšana: {lightred}{3}{grey}, Klusēšana: {lightred}{4}{grey}, Brīdinājumi: {lightred}{5}", + "sa_player_ban_message_time": "Tu esi nobanots uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}!", + "sa_player_ban_message_perm": "Tevis bans ir uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!", + "sa_player_kick_message": "Tu esi izmests, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!", + "sa_player_gag_message_time": "Tev ir izliegta čata rakstīšana uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}, Admins: {lightred}{3}{default}!", + "sa_player_gag_message_perm": "Tev ir izliegta čata rakstīšana uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!", + "sa_player_mute_message_time": "Tev ir izliegta balsu rakstīšana uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}, Admins: {lightred}{3}{default}!", + "sa_player_mute_message_perm": "Tev ir izliegta balsu rakstīšana uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!", + "sa_player_silence_message_time": "Tevis balss ir izslēgta uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}, Admins: {lightred}{3}{default}!", + "sa_player_silence_message_perm": "Tevis balss ir izslēgta uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!", + "sa_player_warn_message_time": "Jums ir izteikts brīdinājums par {lightred}{0}{default} uz {lightred}{1}{default} minūtēm no {lightred}{2}{default}!", + "sa_player_warn_message_perm": "Jums ir izteikts pastāvīgs brīdinājums par {lightred}{0}{default} no {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} nobanoja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} nobanoja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} izmeta {lightred}{1}{default} par {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} izslēdza {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} izslēdza {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} brīdināja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} brīdināja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} noņēma visus {lightred}{1}{default} ieročus!", + "sa_admin_hp_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} HP daudzumu!", + "sa_admin_speed_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} ātrumu!", + "sa_admin_gravity_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} gravitāciju!", + "sa_admin_money_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} naudu!", + "sa_admin_god_message": "{lightred}{0}{default} mainīja dieva režīmu priekš {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} nogalināja {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} pa seju!", + "sa_admin_changemap_message": "{lightred}{0}{default} mainīja karti uz {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} aktivizēja/deaktivizēja noclip priekš {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} sasaldēja {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} atkausēja {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} pārdēvēja {lightred}{1}{default} uz {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} atdzīvināja {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} teleporta uz {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} teleporta {lightred}{1}{default} pie sevis!", + "sa_admin_team_message": "{lightred}{0}{default} pārvietoja {lightred}{1}{default} uz {lightred}{2}{default} komandu!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}BRĪDINĀJUMI", + "sa_admin_warns_unwarn": "{lime}Brīdinājums veiksmīgi{default} atsaukts priekš {gold}{0} {default}dēļ {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}BALSOŠANA PAR {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} uzsāka balsošanu par {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}BALSOŠANAS REZULTĀTI PAR {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(SPĒLĒTĀJS) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** izpildīja komandu `{1}` serverī `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/pl.json b/CS2-SimpleAdmin/lang/pl.json new file mode 100644 index 0000000..c22c231 --- /dev/null +++ b/CS2-SimpleAdmin/lang/pl.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Brak", + "sa_no_permission": "Nie masz uprawnień do korzystania z tej komendy.", + "sa_ban_max_duration_exceeded": "Czas bana nie może przekraczać {lightred}{0}{default} minut.", + "sa_ban_perm_restricted": "Nie masz prawa do trwałego zbanowania.", + + "sa_admin_add": "Dodaj administratora", + "sa_admin_remove": "Usuń administratora", + "sa_admin_reload": "Przeładuj administratorów", + + "sa_godmode": "Tryb Boga", + "sa_noclip": "Tryb Latania", + "sa_respawn": "Odrodzenie", + "sa_give_weapon": "Daj broń", + "sa_strip_weapons": "Usuń bronie", + "sa_freeze": "Zamroź", + "sa_set_hp": "Ustaw HP", + "sa_set_speed": "Ustaw prędkość", + "sa_set_gravity": "Ustaw grawitację", + "sa_set_money": "Ustaw pieniądze", + + "sa_changemap": "Zmień mapę", + "sa_restart_game": "Zrestartuj mapę", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Zamień", + "sa_team_spec": "Spec", + + "sa_slap": "Uderz", + "sa_slay": "Zabij", + "sa_kick": "Wyrzuć", + "sa_ban": "Zbanuj", + "sa_gag": "Zaknebluj", + "sa_mute": "Wycisz", + "sa_silence": "Ucisz", + "sa_warn": "Ostrzeżenie", + "sa_team_force": "Wymuś drużynę", + + "sa_menu_custom_commands": "Komendy niestandardowe", + "sa_menu_server_manage": "Zarządzaj serwerem", + "sa_menu_fun_commands": "Komendy rozrywkowe", + "sa_menu_admins_manage": "Zarządzaj administratorami", + "sa_menu_players_manage": "Zarządzaj graczami", + "sa_menu_disconnected_title": "Ostatni gracze", + "sa_menu_disconnected_action_title": "Wybierz akcje", + + "sa_player": "Gracz", + "sa_steamid": "SteamID", + "sa_duration": "Wygasa", + "sa_reason": "Powód", + "sa_admin": "Admin", + "sa_permanent": "Na zawsze", + + "sa_discord_penalty_ban": "Nowy ban", + "sa_discord_penalty_mute": "Nowe wyciszenie", + "sa_discord_penalty_gag": "Nowe zakneblowanie", + "sa_discord_penalty_silence": "Nowe uciszenie", + "sa_discord_penalty_warn": "Nowe ostrzeżenie", + "sa_discord_penalty_unknown": "Nowa nieznana blokada", + + "sa_player_penalty_info_active_mute": "➔ Zakneblowanie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Wyciszenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Uciszenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Ostrzeżenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}] - Powód [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Zakneblowanie [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Wyciszenie [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Uciszenie [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Ostrzeżenie [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nBlokady gracza {lightred}{0}{default},\nIlość banów: {lightred}{1}{default}, Ilość zakneblowań: {lightred}{2}{default}, Ilość wyciszeń: {lightred}{3}{default}, Ilość uciszeń: {lightred}{4}{default}Ilość ostrzeżeń: {lightred}{5}{default}\nAktywne blokady:\n{6}\nAktywne ostrzeżenia:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Blokady gracza {lightred}{0}{grey} - bany: {lightred}{1}{grey}, zakneblowania: {lightred}{2}{grey}, wyciszenia: {lightred}{3}{grey}, uciszenia: {lightred}{4}{grey}, ostrzeżenia: {lightred}{5}", + "sa_player_ban_message_time": "Zostałeś zbanowany za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!", + "sa_player_ban_message_perm": "Zostałeś zbanowany na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!", + "sa_player_kick_message": "Zostałeś wyrzucony za {lightred}{0}{default} przez {lightred}{1}{default}!", + "sa_player_gag_message_time": "Zostałeś zakneblowany za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!", + "sa_player_gag_message_perm": "Zostałeś zakneblowany na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!", + "sa_player_mute_message_time": "Zostałeś uciszony za {lightred}{0}{default} na {lightred}{1}{default} minute przez {lightred}{2}{default}!", + "sa_player_mute_message_perm": "Zostałeś uciszony na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!", + "sa_player_silence_message_time": "Zostałeś wyciszony za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!", + "sa_player_silence_message_perm": "Zostałeś wyciszony na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!", + "sa_player_warn_message_time": "Otrzymałeś ostrzeżenie za {lightred}{0}{default} na {lightred}{1}{default} minut od {lightred}{2}{default}!", + "sa_player_warn_message_perm": "Otrzymałeś ostrzeżenie za {lightred}{0}{default} od {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} zbanował {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} zbanował {lightred}{1}{default} na zawsze za {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} wyrzucił {lightred}{1}{default} za {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} zakneblował {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} zakneblował {lightred}{1}{default} na zawsze za {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} uciszył {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} uciszył {lightred}{1}{default} na zawsze za {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} wyciszył {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} wyciszył {lightred}{1}{default} na zawsze za {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} ostrzegł {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} ostrzegł {lightred}{1}{default} na zawsze za {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} dał {lightred}{1}{default} przedmiot {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} zabrał wszystkie bronie {lightred}{1}{default}!", + "sa_admin_hp_message": "{lightred}{0}{default} zmienił ilość hp dla {lightred}{1}{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} zmienił prędkość dla {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} zmienił grawitacje dla {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} zmienił pieniądze dla {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} zmienił tryb Boga dla {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} zgładził {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} uderzył {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} zmienił mapę na {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} ustawił latanie dla {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} zamroził {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} odmroził {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} zmienił nick gracza {lightred}{1}{default} na {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} odrodził {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} teleportował się do {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} teleportował do siebie {lightred}{1}{default}!", + "sa_admin_team_message": "{lightred}{0}{default} przerzucił {lightred}{1}{default} do {lightred}{2}{default}!", + "sa_admin_warns_menu_title": "{lime}OSTRZEŻENIA {gold}{0}", + "sa_admin_warns_unwarn": "{lime}Pomyślnie{default} usunięto ostrzeżenie dla {gold}{0} {default}za {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}GŁOSOWANIE NA {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} rozpoczął głosowanie na {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}WYNIKI GŁOSOWANIA {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}[{1}]", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(GRACZ) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** użył komendy `{1}` na serwerze `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/pt-BR.json b/CS2-SimpleAdmin/lang/pt-BR.json new file mode 100644 index 0000000..81c08bb --- /dev/null +++ b/CS2-SimpleAdmin/lang/pt-BR.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Desconhecido", + "sa_no_permission": "Você não tem permissão para usar este comando.", + "sa_ban_max_duration_exceeded": "A duração da proibição não pode exceder {lightred}{0}{default} minutos.", + "sa_ban_perm_restricted": "Você não tem permissão para banir permanentemente.", + + "sa_admin_add": "Adicionar Admin", + "sa_admin_remove": "Remover Admin", + "sa_admin_reload": "Recarregar Admins", + + "sa_godmode": "Modo Deus", + "sa_noclip": "Modo Espectador", + "sa_respawn": "Ressurgir", + "sa_give_weapon": "Dar Arma", + "sa_strip_weapons": "Remover Armas", + "sa_freeze": "Congelar", + "sa_set_hp": "Definir HP", + "sa_set_speed": "Definir Velocidade", + "sa_set_gravity": "Definir Gravidade", + "sa_set_money": "Definir Dinheiro", + + "sa_changemap": "Mudar Mapa", + "sa_restart_game": "Reiniciar Jogo", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Trocar", + "sa_team_spec": "Espec", + + "sa_slap": "Tapa", + "sa_slay": "Matar", + "sa_kick": "Expulsar", + "sa_ban": "Banir", + "sa_gag": "Silenciar", + "sa_mute": "Mutar", + "sa_silence": "Silêncio", + "sa_warn": "Aviso", + "sa_team_force": "Forçar Time", + + "sa_menu_custom_commands": "Comandos Personalizados", + "sa_menu_server_manage": "Gerenciar Servidor", + "sa_menu_fun_commands": "Comandos Divertidos", + "sa_menu_admins_manage": "Gerenciar Admins", + "sa_menu_players_manage": "Gerenciar Jogadores", + "sa_menu_disconnected_title": "Jogadores recentes", + "sa_menu_disconnected_action_title": "Selecionar ação", + + "sa_player": "Jogador", + "sa_steamid": "SteamID", + "sa_duration": "Duração", + "sa_reason": "Motivo", + "sa_admin": "Admin", + "sa_permanent": "Permanente", + + "sa_discord_penalty_ban": "Banimento registrado", + "sa_discord_penalty_mute": "Mute registrado", + "sa_discord_penalty_gag": "Gag registrado", + "sa_discord_penalty_silence": "Silêncio registrado", + "sa_discord_penalty_warn": "Aviso registrado", + "sa_discord_penalty_unknown": "Desconhecido registrado", + + "sa_player_penalty_info_active_mute": "➔ Mudo [{lightred}❌{default}] - Expira [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expira [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Silêncio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Aviso [{lightred}❌{default}] - Expira [{lightred}{0}{default}] - Razão [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Mudo [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Silêncio [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Aviso [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}", + "sa_player_ban_message_time": "Você foi banido por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_ban_message_perm": "Você foi banido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_kick_message": "Você foi expulso por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_gag_message_time": "Você foi silenciado por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_gag_message_perm": "Você foi silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_mute_message_time": "Você foi mutado por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_mute_message_perm": "Você foi mutado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_silence_message_time": "Você foi silenciado por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_silence_message_perm": "Você foi silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_player_warn_message_time": "Você foi advertido por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!", + "sa_player_warn_message_perm": "Você foi advertido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} baniu {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} baniu {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} expulsou {lightred}{1}{default} por {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} silenciou {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} silenciou {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} mutou {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} mutou {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} silenciou {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} silenciou {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} advertiu {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} advertiu {lightred}{1}{default} permanentemente por {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} deu {lightred}{1}{default} um(a) {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} tirou todas as armas de {lightred}{1}{default}!", + "sa_admin_hp_message": "{lightred}{0}{default} mudou a quantidade de HP de {lightred}{1}{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} mudou a velocidade de {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} matou {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} deu um tapa em {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} mudou o mapa para {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} ativou/desativou o noclip para {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} congelou {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} descongelou {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} renomeou {lightred}{1}{default} para {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} reanimou {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} teleportou para {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} teleportou {lightred}{1}{default} para si mesmo!", + "sa_admin_team_message": "{lightred}{0}{default} transferiu {lightred}{1}{default} para a equipe {lightred}{2}{default}!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTÊNCIAS", + "sa_admin_warns_unwarn": "{lime}Advertência removida com sucesso{default} para {gold}{0} {default}por {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}VOTAÇÃO PARA {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} iniciou uma votação para {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}RESULTADOS DA VOTAÇÃO PARA {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(JOGADOR) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** executou o comando `{1}` no servidor `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/ru.json b/CS2-SimpleAdmin/lang/ru.json new file mode 100644 index 0000000..63871c1 --- /dev/null +++ b/CS2-SimpleAdmin/lang/ru.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Неизвестный", + "sa_no_permission": "У вас нет прав для использования этой команды.", + "sa_ban_max_duration_exceeded": "Продолжительность бана не может превышать {lightred}{0}{default} минут.", + "sa_ban_perm_restricted": "У вас нет прав на постоянный бан.", + + "sa_admin_add": "Добавить администратора", + "sa_admin_remove": "Удалить администратора", + "sa_admin_reload": "Перезагрузить администраторов", + + "sa_godmode": "Режим бога", + "sa_noclip": "Режим бесконечного прохождения", + "sa_respawn": "Возрождение", + "sa_give_weapon": "Выдать оружие", + "sa_strip_weapons": "Удалить оружие", + "sa_freeze": "Заморозить", + "sa_set_hp": "Установить здоровье", + "sa_set_speed": "Установить скорость", + "sa_set_gravity": "Установить гравитацию", + "sa_set_money": "Установить деньги", + + "sa_changemap": "Сменить карту", + "sa_restart_game": "Перезапустить игру", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Поменять", + "sa_team_spec": "Спец", + + "sa_slap": "Шлепнуть", + "sa_slay": "Убить", + "sa_kick": "Выгнать", + "sa_ban": "Забанить", + "sa_gag": "Заглушить", + "sa_mute": "Отключить звук", + "sa_silence": "Тишина", + "sa_warn": "Предупреждение", + "sa_team_force": "Принудить к команде", + + "sa_menu_custom_commands": "Пользовательские команды", + "sa_menu_server_manage": "Управление сервером", + "sa_menu_fun_commands": "Развлекательные команды", + "sa_menu_admins_manage": "Управление администраторами", + "sa_menu_players_manage": "Управление игроками", + "sa_menu_disconnected_title": "Последние игроки", + "sa_menu_disconnected_action_title": "Выберите действие", + + "sa_player": "Игрок", + "sa_steamid": "SteamID", + "sa_duration": "Продолжительность", + "sa_reason": "Причина", + "sa_admin": "Администратор", + "sa_permanent": "Постоянный", + + "sa_discord_penalty_ban": "Бан зарегистрирован", + "sa_discord_penalty_mute": "Мут зарегистрирован", + "sa_discord_penalty_gag": "Запрет зарегистрирован", + "sa_discord_penalty_silence": "Молчание зарегистрировано", + "sa_discord_penalty_warn": "Предупреждение зарегистрировано", + "sa_discord_penalty_unknown": "Неизвестно зарегистрировано", + + "sa_player_penalty_info_active_mute": "➔ Мут [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Гэг [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Тишина [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Предупреждение [{lightred}❌{default}] - Истекает [{lightred}{0}{default}] - Причина [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Мут [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Гэг [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Тишина [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Предупреждение [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nШтрафы игрока для {lightred}{0}{default},\nКоличество банов: {lightred}{1}{default}, Количество гэгов: {lightred}{2}{default}, Количество мутов: {lightred}{3}{default}, Количество тишин: {lightred}{4}{default}, Количество предупреждений: {lightred}{5}{default}\nАктивные штрафы:\n{6}\nАктивные предупреждения:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Штрафы игрока для {lightred}{0}{grey}, Баны: {lightred}{1}{grey}, Гэги: {lightred}{2}{grey}, Муты: {lightred}{3}{grey}, Тишины: {lightred}{4}{grey}, Предупреждения: {lightred}{5}", + "sa_player_ban_message_time": "Вы были забанены по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!", + "sa_player_ban_message_perm": "Вас забанили навсегда по причине {lightred}{0}{default} администратором {lightred}{1}{default}!", + "sa_player_kick_message": "Вы были выгнаны {lightred}{0}{default} администратором {lightred}{1}{default}!", + "sa_player_gag_message_time": "Вам запрещено общаться в чате по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!", + "sa_player_gag_message_perm": "Вам навсегда запрещено общаться в чате по причине {lightred}{0}{default} администратором {lightred}{1}{default}!", + "sa_player_mute_message_time": "Вам запрещено использовать голосовой чат по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!", + "sa_player_mute_message_perm": "Вам навсегда запрещено использовать голосовой чат по причине {lightred}{0}{default} администратором {lightred}{1}{default}!", + "sa_player_silence_message_time": "Вам запрещено общаться по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!", + "sa_player_silence_message_perm": "Вам навсегда запрещено общаться по причине {lightred}{0}{default} администратором {lightred}{1}{default}!", + "sa_player_warn_message_time": "Вы получили предупреждение за {lightred}{0}{default} на {lightred}{1}{default} минут от {lightred}{2}{default}!", + "sa_player_warn_message_perm": "Вы получили постоянное предупреждение за {lightred}{0}{default} от {lightred}{1}{default}!", + "sa_admin_ban_message_time": "{lightred}{0}{default} забанил {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} забанил {lightred}{1}{default} навсегда за {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} выгнал {lightred}{1}{default} за {lightred}{2}{default}!", + "sa_admin_gag_message_time": "{lightred}{0}{default} замолчал {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} замолчал {lightred}{1}{default} навсегда за {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} отключил звук {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} отключил звук {lightred}{1}{default} навсегда за {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} замолчал {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} замолчал {lightred}{1}{default} навсегда за {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} предупредил {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} предупредил {lightred}{1}{default} навсегда за {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} дал {lightred}{1}{default} {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} забрал все оружие у {lightred}{1}{default}!", + "sa_admin_hp_message": "{lightred}{0}{default} изменил количество HP у {lightred}{1}{default}!", + "sa_admin_speed_message": "{lightred}{0}{default} изменил скорость {lightred}{1}{default}!", + "sa_admin_gravity_message": "{lightred}{0}{default} изменил гравитацию для {lightred}{1}{default}!", + "sa_admin_money_message": "{lightred}{0}{default} изменил количество денег у {lightred}{1}{default}!", + "sa_admin_god_message": "{lightred}{0}{default} изменил режим бога для {lightred}{1}{default}!", + "sa_admin_slay_message": "{lightred}{0}{default} убил {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} дал пощечину {lightred}{1}{default}!", + "sa_admin_changemap_message": "{lightred}{0}{default} изменил карту на {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} включил/выключил noclip для {lightred}{1}{default}!", + "sa_admin_freeze_message": "{lightred}{0}{default} заморозил {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} разморозил {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} изменил никнейм {lightred}{1}{default} на {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} возродил {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} телепортировался к {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} телепортировал {lightred}{1}{default} к себе!", + "sa_admin_team_message": "{lightred}{0}{default} перевел {lightred}{1}{default} в команду {lightred}{2}{default}!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}ПРЕДУПРЕЖДЕНИЯ", + "sa_admin_warns_unwarn": "{lime}Предупреждение успешно удалено{default} для {gold}{0} {default}за {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}ГОЛОСОВАНИЕ ЗА {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} начал голосование за {lightred}{1}{default}", + "sa_admin_vote_message_results": "{lime}РЕЗУЛЬТАТЫ ГОЛОСОВАНИЯ ЗА {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}АДМИН: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(АДМИН) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(ИГРОК) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** выполнил команду `{1}` на сервере `HOSTNAME`" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/tr.json b/CS2-SimpleAdmin/lang/tr.json new file mode 100644 index 0000000..602f564 --- /dev/null +++ b/CS2-SimpleAdmin/lang/tr.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "Bilinmeyen", + "sa_no_permission": "Bu komutu kullanma izniniz yok.", + "sa_ban_max_duration_exceeded": "Yasaklama süresi {lightred}{0}{default} dakikadan fazla olamaz.", + "sa_ban_perm_restricted": "Kalıcı yasaklama hakkınız yok.", + + "sa_admin_add": "Yönetici Ekle", + "sa_admin_remove": "Yönetici Kaldır", + "sa_admin_reload": "Yöneticileri Yeniden Yükle", + + "sa_godmode": "Tanrı Modu", + "sa_noclip": "Duvar Arkası Modu", + "sa_respawn": "Tekrar Doğma", + "sa_give_weapon": "Silah Ver", + "sa_strip_weapons": "Silahları Çıkart", + "sa_freeze": "Dondur", + "sa_set_hp": "HP Ayarla", + "sa_set_speed": "Hız Ayarla", + "sa_set_gravity": "Yerçekimi Ayarla", + "sa_set_money": "Para Ayarla", + + "sa_changemap": "Haritayı Değiştir", + "sa_restart_game": "Oyunu Yeniden Başlat", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "Değiştir", + "sa_team_spec": "İzleyici", + + "sa_slap": "Tokatla", + "sa_slay": "Öldür", + "sa_kick": "At", + "sa_ban": "Yasakla", + "sa_gag": "Ağzını Kapat", + "sa_mute": "Sustur", + "sa_silence": "Sessizlik", + "sa_warn": "Uyarı", + "sa_team_force": "Takımı Zorla", + + "sa_menu_custom_commands": "Özel Komutlar", + "sa_menu_server_manage": "Sunucu Yönetimi", + "sa_menu_fun_commands": "Eğlenceli Komutlar", + "sa_menu_admins_manage": "Yönetici Yönetimi", + "sa_menu_players_manage": "Oyuncu Yönetimi", + "sa_menu_disconnected_title": "Son oyuncular", + "sa_menu_disconnected_action_title": "Eylem seçin", + + "sa_player": "Oyuncu", + "sa_steamid": "SteamID", + "sa_duration": "Süre", + "sa_reason": "Neden", + "sa_admin": "Yönetici", + "sa_permanent": "Kalıcı", + + "sa_discord_penalty_ban": "Yasak kaydedildi", + "sa_discord_penalty_mute": "Susturma kaydedildi", + "sa_discord_penalty_gag": "Susturma kaydedildi", + "sa_discord_penalty_silence": "Sessizlik kaydedildi", + "sa_discord_penalty_warn": "Uyarı kaydedildi", + "sa_discord_penalty_unknown": "Bilinmeyen kaydedildi", + + "sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ Sessizlik [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ Uyarı [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}] - Sebep [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ Mute [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ Sessizlik [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ Uyarı [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\nOyuncunun cezaları {lightred}{0}{default} için,\nBan sayısı: {lightred}{1}{default}, Gag sayısı: {lightred}{2}{default}, Mute sayısı: {lightred}{3}{default}, Sessizlik sayısı: {lightred}{4}{default}, Uyarı sayısı: {lightred}{5}{default}\nAktif cezalar:\n{6}\nAktif uyarılar:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}Oyuncunun cezaları {lightred}{0}{grey}, Banlar: {lightred}{1}{grey}, Gaglar: {lightred}{2}{grey}, Mute'lar: {lightred}{3}{grey}, Sessizlikler: {lightred}{4}{grey}, Uyarılar: {lightred}{5}", + "sa_player_ban_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından yasaklandınız!", + "sa_player_ban_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından yasaklandınız!", + "sa_player_kick_message": "Senaryo nedeniyle {lightred}{0}{default} tarafından atıldınız!", + "sa_player_gag_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından susturuldunuz!", + "sa_player_gag_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından susturuldunuz!", + "sa_player_mute_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından sessize alındınız!", + "sa_player_mute_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından sessize alındınız!", + "sa_player_silence_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından susturuldunuz!", + "sa_player_silence_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından susturuldunuz!", + "sa_player_warn_message_time": "{lightred}{0}{default} nedeniyle {lightred}{1}{default} dakika boyunca {lightred}{2}{default} tarafından uyarıldınız!", + "sa_player_warn_message_perm": "{lightred}{0}{default} nedeniyle {lightred}{1}{default} tarafından kalıcı olarak uyarıldınız!", + "sa_admin_ban_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle yasakladı!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle yasakladı!", + "sa_admin_kick_message": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle atıldı!", + "sa_admin_gag_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle sessizleştirdi!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle sessizleştirdi!", + "sa_admin_mute_message_time": "{lightred}{0}{default} {lightred}{1}{default}'in sesini {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle kesti!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'in sesini kalıcı olarak {lightred}{2}{default} nedeniyle kesti!", + "sa_admin_silence_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle sessizleştirdi!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle sessizleştirdi!", + "sa_admin_warn_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle uyardı!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle uyardı!", + "sa_admin_give_message": "{lightred}{0}{default} {lightred}{1}{default}'e {lightred}{2}{default} verdi!", + "sa_admin_strip_message": "{lightred}{0}{default} {lightred}{1}{default}'in tüm silahlarını aldı!", + "sa_admin_hp_message": "{lightred}{0}{default} {lightred}{1}{default}'in HP miktarını değiştirdi!", + "sa_admin_speed_message": "{lightred}{0}{default} {lightred}{1}{default}'in hızını değiştirdi!", + "sa_admin_gravity_message": "{lightred}{0}{default} {lightred}{1}{default}'in yer çekimini değiştirdi!", + "sa_admin_money_message": "{lightred}{0}{default} {lightred}{1}{default}'in parasını değiştirdi!", + "sa_admin_god_message": "{lightred}{0}{default} {lightred}{1}{default}'in tanrı modunu değiştirdi!", + "sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default}'i öldürdü!", + "sa_admin_slap_message": "{lightred}{0}{default} {lightred}{1}{default}'e tokat attı!", + "sa_admin_changemap_message": "{lightred}{0}{default} haritayı {lightred}{1}{default} olarak değiştirdi!", + "sa_admin_noclip_message": "{lightred}{0}{default} {lightred}{1}{default} için noclip'i açtı/kapatı!", + "sa_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default}'i dondurdu!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default}'in dondurmasını çözdü!", + "sa_admin_rename_message": "{lightred}{0}{default} {lightred}{1}{default}'in takma adını {lightred}{2}{default} olarak değiştirdi!", + "sa_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default}'i yeniden doğurdu!", + "sa_admin_tp_message": "{lightred}{0}{default} {lightred}{1}{default}'e teleporto etti!", + "sa_admin_bring_message": "{lightred}{0}{default} {lightred}{1}{default}'i kendisine teleporto etti!", + "sa_admin_team_message": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} takımına transfer etti!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}UYARILAR", + "sa_admin_warns_unwarn": "{lime}Uyarı başarıyla kaldırıldı{default} {gold}{0} {default} için {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}OY VERME {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} {lightred}{1}{default} için oy kullanmaya başladı", + "sa_admin_vote_message_results": "{lime}OY SONUÇLARI {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}Yönetici: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(Yönetici) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(Oyuncu) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** `{1}` komutunu `HOSTNAME` sunucusunda gerçekleştirdi" +} \ No newline at end of file diff --git a/CS2-SimpleAdmin/lang/zh-Hans.json b/CS2-SimpleAdmin/lang/zh-Hans.json new file mode 100644 index 0000000..585efb2 --- /dev/null +++ b/CS2-SimpleAdmin/lang/zh-Hans.json @@ -0,0 +1,127 @@ +{ + "sa_title": "SimpleAdmin", + "sa_prefix": "{lightred}[SA] {default}", + + "sa_unknown": "未知", + "sa_no_permission": "您没有权限使用此命令。", + "sa_ban_max_duration_exceeded": "禁令持续时间不能超过{lightred}{0}{default}分钟。", + "sa_ban_perm_restricted": "您没有永久封禁的权限。", + + "sa_admin_add": "添加管理员", + "sa_admin_remove": "移除管理员", + "sa_admin_reload": "重新加载管理员", + + "sa_godmode": "上帝模式", + "sa_noclip": "穿墙模式", + "sa_respawn": "重生", + "sa_give_weapon": "给予武器", + "sa_strip_weapons": "剥夺武器", + "sa_freeze": "冻结", + "sa_set_hp": "设置生命值", + "sa_set_speed": "设置速度", + "sa_set_gravity": "设置重力", + "sa_set_money": "设置金钱", + + "sa_changemap": "更换地图", + "sa_restart_game": "重启游戏", + + "sa_team_ct": "CT", + "sa_team_t": "T", + "sa_team_swap": "交换", + "sa_team_spec": "观战", + + "sa_slap": "掌掴", + "sa_slay": "杀死", + "sa_kick": "踢出", + "sa_ban": "封禁", + "sa_gag": "禁言", + "sa_mute": "静音", + "sa_silence": "沉默", + "sa_warn": "警告", + "sa_team_force": "强制队伍", + + "sa_menu_custom_commands": "自定义命令", + "sa_menu_server_manage": "服务器管理", + "sa_menu_fun_commands": "娱乐命令", + "sa_menu_admins_manage": "管理员管理", + "sa_menu_players_manage": "玩家管理", + "sa_menu_disconnected_title": "最近的玩家", + "sa_menu_disconnected_action_title": "选择操作", + + "sa_player": "玩家", + "sa_steamid": "SteamID", + "sa_duration": "持续时间", + "sa_reason": "原因", + "sa_admin": "管理员", + "sa_permanent": "永久", + + "sa_discord_penalty_ban": "封禁已记录", + "sa_discord_penalty_mute": "禁言已记录", + "sa_discord_penalty_gag": "禁言已记录", + "sa_discord_penalty_silence": "禁声已记录", + "sa_discord_penalty_warn": "警告已注册", + "sa_discord_penalty_unknown": "未知已记录", + + "sa_player_penalty_info_active_mute": "➔ 静音 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]", + "sa_player_penalty_info_active_gag": "➔ 禁言 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]", + "sa_player_penalty_info_active_silence": "➔ 沉默 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}]", + "sa_player_penalty_info_active_warn": "➔ 警告 [{lightred}❌{default}] - 到期 [{lightred}{0}{default}] - 原因 [{lightred}{1}{default}]", + + "sa_player_penalty_info_no_active_mute": "➔ 静音 [{lime}✔{default}]", + "sa_player_penalty_info_no_active_gag": "➔ 禁言 [{lime}✔{default}]", + "sa_player_penalty_info_no_active_silence": "➔ 沉默 [{lime}✔{default}]", + "sa_player_penalty_info_no_active_warn": "➔ 警告 [{lime}✔{default}]", + + "sa_player_penalty_info": "===========================\n玩家处罚信息 {lightred}{0}{default},\n禁言次数: {lightred}{1}{default}, 禁言次数: {lightred}{2}{default}, 静音次数: {lightred}{3}{default}, 沉默次数: {lightred}{4}{default}, 警告次数: {lightred}{5}{default}\n当前处罚:\n{6}\n当前警告:\n{7}\n===========================", + "sa_admin_penalty_info": "{grey}玩家处罚信息 {lightred}{0}{grey}, 禁言: {lightred}{1}{grey}, 禁言: {lightred}{2}{grey}, 静音: {lightred}{3}{grey}, 沉默: {lightred}{4}{grey}, 警告: {lightred}{5}", + "sa_player_ban_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}禁止{lightred}{2}{default}分钟!", + "sa_player_ban_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁止!", + "sa_player_kick_message": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}踢出!", + "sa_player_gag_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁言{lightred}{1}{default}分钟!", + "sa_player_gag_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁言!", + "sa_player_mute_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁声{lightred}{1}{default}分钟!", + "sa_player_mute_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁声!", + "sa_player_silence_message_time": "你因为{lightred}{0}{default}的原因被{lightred}{2}{default}禁止发言{lightred}{1}{default}分钟!", + "sa_player_silence_message_perm": "你因为{lightred}{0}{default}的原因被{lightred}{1}{default}永久禁止发言!", + "sa_player_warn_message_time": "您因 {lightred}{0}{default} 被 {lightred}{1}{default} 分钟内由 {lightred}{2}{default} 警告!", + "sa_player_warn_message_perm": "您因 {lightred}{0}{default} 被 {lightred}{1}{default} 永久警告!", + "sa_admin_ban_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!", + "sa_admin_ban_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!", + "sa_admin_kick_message": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被踢出!", + "sa_admin_gag_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!", + "sa_admin_gag_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!", + "sa_admin_mute_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 的声音被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!", + "sa_admin_mute_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 的声音被永久禁言 {lightred}{2}{default}!", + "sa_admin_silence_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被 {lightred}{2}{default} 禁言 {lightred}{3}{default} 分钟!", + "sa_admin_silence_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久禁言 {lightred}{2}{default}!", + "sa_admin_warn_message_time": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被警告 {lightred}{2}{default} {lightred}{3}{default} 分钟!", + "sa_admin_warn_message_perm": "{lightred}{0}{default} 因为 {lightred}{1}{default} 被永久警告 {lightred}{2}{default}!", + "sa_admin_give_message": "{lightred}{0}{default} 给了 {lightred}{1}{default} {lightred}{2}{default}!", + "sa_admin_strip_message": "{lightred}{0}{default} 拿走了 {lightred}{1}{default} 的所有武器!", + "sa_admin_hp_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的生命值!", + "sa_admin_speed_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的速度!", + "sa_admin_gravity_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的重力!", + "sa_admin_money_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的金钱!", + "sa_admin_god_message": "{lightred}{0}{default} 改变了 {lightred}{1}{default} 的上帝模式!", + "sa_admin_slay_message": "{lightred}{0}{default} 杀死了 {lightred}{1}{default}!", + "sa_admin_slap_message": "{lightred}{0}{default} 扇了 {lightred}{1}{default} 一巴掌!", + "sa_admin_changemap_message": "{lightred}{0}{default} 更改了地图为 {lightred}{1}{default}!", + "sa_admin_noclip_message": "{lightred}{0}{default} 为 {lightred}{1}{default} 切换了 noclip!", + "sa_admin_freeze_message": "{lightred}{0}{default} 冻结了 {lightred}{1}{default}!", + "sa_admin_unfreeze_message": "{lightred}{0}{default} 解冻了 {lightred}{1}{default}!", + "sa_admin_rename_message": "{lightred}{0}{default} 更改了 {lightred}{1}{default} 的昵称为 {lightred}{2}{default}!", + "sa_admin_respawn_message": "{lightred}{0}{default} 重新生成了 {lightred}{1}{default}!", + "sa_admin_tp_message": "{lightred}{0}{default} 传送到了 {lightred}{1}{default}!", + "sa_admin_bring_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 传送到了自己身边!", + "sa_admin_team_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 转移到了 {lightred}{2}{default} 队伍!", + "sa_admin_warns_menu_title": "{gold}{0} {lime}警告", + "sa_admin_warns_unwarn": "{lime}成功{default} 取消了对 {gold}{0}{default} 的警告 {gold}{1}{default}!", + "sa_admin_vote_menu_title": "{lime}投票 {gold}{0}", + "sa_admin_vote_message": "{lightred}{0}{default} 开始为 {lightred}{1}{default} 进行投票", + "sa_admin_vote_message_results": "{lime}投票结果 {gold}{0}", + "sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}", + "sa_adminsay_prefix": "{RED}管理员: {lightred}{0}{default}", + "sa_adminchat_template_admin": "{LIME}(管理员) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_adminchat_template_player": "{SILVER}(玩家) {lightred}{0}{default}: {lightred}{1}{default}", + "sa_discord_log_command": "**{0}** 在服务器 `HOSTNAME` 上发出了 `{1}` 命令" +} \ No newline at end of file diff --git a/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj new file mode 100644 index 0000000..2338bfa --- /dev/null +++ b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + CS2_SimpleAdminApi + enable + enable + + + + + + + diff --git a/CS2-SimpleAdminApi/ICS2-SimpleAdminApi.cs b/CS2-SimpleAdminApi/ICS2-SimpleAdminApi.cs new file mode 100644 index 0000000..9cf2076 --- /dev/null +++ b/CS2-SimpleAdminApi/ICS2-SimpleAdminApi.cs @@ -0,0 +1,23 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Capabilities; +using CounterStrikeSharp.API.Modules.Entities; + +namespace CS2_SimpleAdminApi; + +public interface ICS2_SimpleAdminApi +{ + public static readonly PluginCapability PluginCapability = new("simpleadmin:api"); + + public PlayerInfo GetPlayerInfo(CCSPlayerController player); + + public string GetConnectionString(); + public string GetServerAddress(); + public int? GetServerId(); + + public Dictionary> GetPlayerMuteStatus(CCSPlayerController player); + + public event Action? OnPlayerPenaltied; + public event Action? OnPlayerPenaltiedAdded; + + public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1); +} \ No newline at end of file diff --git a/CS2-SimpleAdminApi/PenaltyType.cs b/CS2-SimpleAdminApi/PenaltyType.cs new file mode 100644 index 0000000..4cce6b8 --- /dev/null +++ b/CS2-SimpleAdminApi/PenaltyType.cs @@ -0,0 +1,11 @@ +namespace CS2_SimpleAdminApi; + +public enum PenaltyType +{ + Ban = 0, + Kick, + Mute, + Gag, + Silence, + Warn +} diff --git a/CS2-SimpleAdminApi/PlayerInfo.cs b/CS2-SimpleAdminApi/PlayerInfo.cs new file mode 100644 index 0000000..b8e82af --- /dev/null +++ b/CS2-SimpleAdminApi/PlayerInfo.cs @@ -0,0 +1,35 @@ +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.Modules.Utils; + +namespace CS2_SimpleAdminApi; + +public class PlayerInfo( + int? userId, + int slot, + SteamID steamId, + string name, + string? ipAddress, + int totalBans = 0, + int totalMutes = 0, + int totalGags = 0, + int totalSilences = 0, + int totalWarns = 0) +{ + public int? UserId { get; } = userId; + public int Slot { get; } = slot; + public SteamID SteamId { get; } = steamId; + public string Name { get; } = name; + public string? IpAddress { get; } = ipAddress; + public int TotalBans { get; set; } = totalBans; + public int TotalMutes { get; set; } = totalMutes; + public int TotalGags { get; set; } = totalGags; + public int TotalSilences { get; set; } = totalSilences; + public int TotalWarns { get; set; } = totalWarns; + public DiePosition? DiePosition { get; set; } +} + +public class DiePosition(Vector? position = null, QAngle? angle = null) +{ + public Vector? Position { get; set; } = position; + public QAngle? Angle { get; set; } = angle; +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e62ec04 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6fad578 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# CS2-SimpleAdmin + +> **Manage your Counter-Strike 2 server with simple commands!** +> CS2-SimpleAdmin is a plugin designed to help you easily manage your Counter-Strike 2 server with user-friendly commands. Whether you're banning players, managing teams, or configuring server settings, CS2-SimpleAdmin has you covered. + +## 📜 Features +- **Simple Commands**: Manage your server with an easy-to-use command system. +- **MySQL Integration**: Store and retrieve player data seamlessly with MySQL. +- **Ongoing Development**: New features and improvements are added whenever possible. +- **Performance Optimization**: Lightweight and optimized for minimal impact on server performance. +- **Extensible API**: Extend the functionality of CS2-SimpleAdmin by integrating it with your own plugins using the API. Create custom commands, automate tasks, and interact with the plugin programmatically to meet the specific needs of your server. + +## ⚙️ Requirements +**Ensure all the following dependencies are installed before proceeding** +- [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp) +- [PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2) - Required by MenuManagerCS2 +- [AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2) - Required by PlayerSettings +- [MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2) +- MySQL database + +## 🚀 Getting Started +1. **Clone or Download the Repository**: + Download or clone the repository and publish to your `addons/counterstrikesharp/plugins/` directory. + +2. **First Launch Configuration**: + On the first launch, a configuration file will be generated at: + ``` + addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json + ``` + Edit this file to customize the plugin settings according to your server needs. + +3. **Enjoy Managing Your Server!** + Use the commands provided by the plugin to easily manage your server. + +## 📁 Configuration +The configuration file (`CS2-SimpleAdmin.json`) will be auto-generated after the first launch. It contains settings for MySQL connections, command permissions, and other plugin-specific configurations. + +## 📙 Wiki +For detailed documentation, guides, and tutorials, please visit [Wiki](https://cs2-simpleadmin.daffyy.love). + +## 🛠️ Development +This project started as a base for other plugins but has grown into a standalone admin management tool. Contributions are welcome! If you'd like to help with development or have ideas for new features, feel free to submit a pull request or open an issue. + +## 💡 Credits +This project is inspired by the work of [Hackmastr](https://github.com/Hackmastr/css-basic-admin/). Thanks for laying the groundwork! + +## ❤️ Support the Project +If you find this plugin helpful and would like to support further development, consider buying me a cup of tea! Your support is greatly appreciated. + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Y8Y4THKXG) + +## 📄 License +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.