From 0f32daa4c0a7e17eae5bb5acfd4fa64d88196427 Mon Sep 17 00:00:00 2001 From: daffyyyy Date: Sun, 3 Dec 2023 19:22:34 +0100 Subject: [PATCH] KickTime in config, and gag --- BanManager.cs | 3 +- CS2-SimpleAdmin.cs | 184 +++++++++++++++++++++++++++++++++++++++++++-- Config.cs | 11 ++- Events.cs | 54 ++++++++++++- Helper.cs | 5 +- MuteManager.cs | 137 +++++++++++++++++++++++++++++++++ README.md | 4 +- 7 files changed, 387 insertions(+), 11 deletions(-) create mode 100644 MuteManager.cs diff --git a/BanManager.cs b/BanManager.cs index 1eb7662..5d6fe3e 100644 --- a/BanManager.cs +++ b/BanManager.cs @@ -53,6 +53,7 @@ public void AddBanBySteamid(string playerSteamId, CCSPlayerController? issuer, s var sql = "INSERT INTO `sa_bans` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`) " + "VALUES (@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created)"; + _dbConnection.Execute(sql, new { playerSteamid = playerSteamId, @@ -90,7 +91,7 @@ public void UnbanPlayer(string playerPattern) _dbConnection.Open(); - string sqlUnban = "UPDATE sa_bans SET status = 'UNBANNED' WHERE player_steamid = @pattern OR player_name = @pattern"; + string sqlUnban = "UPDATE sa_bans SET status = 'UNBANNED' WHERE player_steamid = @pattern OR player_name = @pattern AND status = 'ACTIVE'"; _dbConnection.Execute(sqlUnban, new { pattern = playerPattern }); _dbConnection.Close(); diff --git a/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin.cs index 002c1fd..32843e6 100644 --- a/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin.cs @@ -4,6 +4,7 @@ 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.Utils; using Microsoft.Extensions.Logging; @@ -12,6 +13,8 @@ namespace CS2_SimpleAdmin; public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig { + public List gaggedPlayers = new List(); + internal string dbConnectionString = string.Empty; public override string ModuleName => "CS2-SimpleAdmin"; public override string ModuleDescription => ""; @@ -70,6 +73,24 @@ public void OnConfigParsed(CS2_SimpleAdminConfig config) MySqlCommand command = new MySqlCommand(sql, connection); command.ExecuteNonQuery(); + sql = @"CREATE TABLE IF NOT EXISTS `sa_mutes` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `player_steamid` varchar(64) NOT NULL, + `player_name` varchar(128) 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, + `created` timestamp NOT NULL, + `type` enum('GAG','MUTE','') NOT NULL DEFAULT 'GAG', + `status` enum('ACTIVE','UNMUTED','EXPIRED','') NOT NULL DEFAULT 'ACTIVE', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"; + + command = new MySqlCommand(sql, connection); + command.ExecuteNonQuery(); + connection.Close(); } @@ -105,7 +126,7 @@ public void OnKickCommand(CCSPlayerController? caller, CommandInfo command) return; player!.Pawn.Value!.Freeze(); - string reason = "Brak powodu"; + string reason = "Unknown"; if (command.ArgCount >= 2) reason = command.GetArg(2); @@ -113,17 +134,168 @@ public void OnKickCommand(CCSPlayerController? caller, CommandInfo command) if (command.ArgCount >= 2) { player!.PrintToCenter($"{Config.Messages.PlayerKickMessage}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); - AddTimer(10.0f, () => Helper.KickPlayer(player!.UserId, reason)); + AddTimer(Config.KickTime, () => Helper.KickPlayer(player!.UserId, reason)); } else { - AddTimer(10.0f, () => Helper.KickPlayer(player!.UserId)); + AddTimer(Config.KickTime, () => Helper.KickPlayer(player!.UserId)); } Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminKickMessage}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); } + [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 (!GetTarget(command, out var player)) + return; + if (command.ArgCount < 2) + return; + + int time = 0; + string reason = "Unknown"; + + MuteManager _muteManager = new(dbConnectionString); + + int.TryParse(command.GetArg(2), out time); + + if (command.ArgCount >= 3) + reason = command.GetArg(3); + + _muteManager.MutePlayer(player, caller, reason, time, 0); + + if (!gaggedPlayers.Contains((int)player!.Index)) + gaggedPlayers.Add((int)player.Index); + + if (time == 0) + { + player!.PrintToCenter($"{Config.Messages.PlayerGagMessagePerm}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); + Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminGagMessagePerm}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); + } + else + { + player!.PrintToCenter($"{Config.Messages.PlayerGagMessageTime}".Replace("{REASON}", reason).Replace("{TIME}", time.ToString()).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); + Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminGagMessageTime}".Replace("{REASON}", reason).Replace("{TIME}", time.ToString()).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); + } + } + + [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 (command.ArgCount < 2) + return; + if (string.IsNullOrEmpty(command.GetArg(1))) return; + + string steamid = command.GetArg(1); + + if (!Helper.IsValidSteamID64(steamid)) + { + command.ReplyToCommand($"Invalid SteamID64."); + return; + } + + int time = 0; + string reason = "Unknown"; + + MuteManager _muteManager = new(dbConnectionString); + + int.TryParse(command.GetArg(2), out time); + + if (command.ArgCount >= 3) + reason = command.GetArg(3); + + _muteManager.AddMuteBySteamid(steamid, caller, reason, time, 0); + + List matches = Helper.GetPlayerFromSteamid64(steamid); + if (matches.Count == 1) + { + CCSPlayerController? player = matches.FirstOrDefault(); + if (player != null) + { + if (time == 0) + { + player!.PrintToCenter($"{Config.Messages.PlayerGagMessagePerm}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); + Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminGagMessagePerm}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); + } + else + { + player!.PrintToCenter($"{Config.Messages.PlayerGagMessageTime}".Replace("{REASON}", reason).Replace("{TIME}", time.ToString()).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); + Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminGagMessageTime}".Replace("{REASON}", reason).Replace("{TIME}", time.ToString()).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); + } + + if (!gaggedPlayers.Contains((int)player.Index)) + gaggedPlayers.Add((int)player.Index); + } + } + command.ReplyToCommand($"Gagged player with steamid {steamid}."); + } + + [ConsoleCommand("css_unmute")] + [RequiresPermissions("@css/chat")] + [CommandHelper(minArgs: 1, usage: " ", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void OnUnmuteCommand(CCSPlayerController? caller, CommandInfo command) + { + if (command.GetArg(1).Length <= 1) + { + command.ReplyToCommand($"Too short pattern to search."); + return; + } + + string pattern = command.GetArg(1); + + MuteManager _muteManager = new(dbConnectionString); + + if (Helper.IsValidSteamID64(pattern)) + { + List matches = Helper.GetPlayerFromSteamid64(pattern); + if (matches.Count == 1) + { + CCSPlayerController? player = matches.FirstOrDefault(); + if (player != null) + { + gaggedPlayers.Remove((int)player.Index); + } + } + } + else + { + List matches = Helper.GetPlayerFromName(pattern); + if (matches.Count == 1) + { + CCSPlayerController? player = matches.FirstOrDefault(); + if (player != null) + { + gaggedPlayers.Remove((int)player.Index); + } + } + } + + if (command.ArgCount >= 3) + { + string? action = command.GetArg(2)?.ToLower(); + + if (action == "gag") + { + _muteManager.UnmutePlayer(pattern, 0); // Unmute by type 0 (gag) + } + else if (action == "mute") + { + _muteManager.UnmutePlayer(pattern, 1); // Unmute by type 1 (mute) + } + } + else + { + _muteManager.UnmutePlayer(pattern, 2); // Default unmute (all types) + } + + command.ReplyToCommand($"Unmuted player with pattern {pattern}."); + } + [ConsoleCommand("css_ban")] [RequiresPermissions("@css/ban")] [CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] @@ -159,7 +331,7 @@ public void OnBanCommand(CCSPlayerController? caller, CommandInfo command) Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminBanMessageTime}".Replace("{REASON}", reason).Replace("{TIME}", time.ToString()).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); } - AddTimer(10.0f, () => Helper.KickPlayer(player!.UserId)); + AddTimer(Config.KickTime, () => Helper.KickPlayer(player!.UserId)); } [ConsoleCommand("css_addban")] @@ -197,6 +369,8 @@ public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command) CCSPlayerController? player = matches.FirstOrDefault(); if (player != null) { + player!.Pawn.Value!.Freeze(); + if (time == 0) { player!.PrintToCenter($"{Config.Messages.PlayerBanMessagePerm}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); @@ -208,7 +382,7 @@ public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command) Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminBanMessageTime}".Replace("{REASON}", reason).Replace("{TIME}", time.ToString()).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); } - AddTimer(10.0f, () => Helper.KickPlayer(player.UserId)); + AddTimer(Config.KickTime, () => Helper.KickPlayer(player.UserId)); } } command.ReplyToCommand($"Banned player with steamid {steamid}."); diff --git a/Config.cs b/Config.cs index 569b434..fccdaa6 100644 --- a/Config.cs +++ b/Config.cs @@ -11,13 +11,20 @@ public class Messages public string PlayerBanMessagePerm { get; set; } = "You have been banned permanently for {REASON} by {ADMIN}!"; [JsonPropertyName("PlayerKickMessage")] public string PlayerKickMessage { get; set; } = "You have been kicked for {REASON} by {ADMIN}!"; + [JsonPropertyName("PlayerGagMessageTime")] + public string PlayerGagMessageTime { get; set; } = "You have been gagged for {REASON} for {TIME} minutes by {ADMIN}!"; + [JsonPropertyName("PlayerGagMessagePerm")] + public string PlayerGagMessagePerm { get; set; } = "You have been gagged permanently for {REASON} by {ADMIN}!"; [JsonPropertyName("AdminBanMessageTime")] public string AdminBanMessageTime { get; set; } = "Admin {ADMIN} banned {PLAYER} for {REASON} for {TIME} minutes!"; [JsonPropertyName("AdminBanMessagePerm")] public string AdminBanMessagePerm { get; set; } = "Admin {ADMIN} banned {PLAYER} permanently for {REASON}"; - [JsonPropertyName("AdminKickMessage")] public string AdminKickMessage { get; set; } = "Admin {ADMIN} kicked {PLAYER} for {REASON}!"; + [JsonPropertyName("AdminGagMessageTime")] + public string AdminGagMessageTime { get; set; } = "Admin {ADMIN} gagged {PLAYER} for {REASON} for {TIME} minutes!"; + [JsonPropertyName("AdminGagMessagePerm")] + public string AdminGagMessagePerm { get; set; } = "Admin {ADMIN} gagged {PLAYER} permanently for {REASON}"; [JsonPropertyName("AdminSlayMessage")] public string AdminSlayMessage { get; set; } = "Admin {ADMIN} slayed {PLAYER}!"; [JsonPropertyName("AdminSlapMessage")] @@ -63,6 +70,8 @@ public class CS2_SimpleAdminConfig : BasePluginConfig [JsonPropertyName("Prefix")] public string Prefix { get; set; } = "{GREEN}[SimpleAdmin]"; + [JsonPropertyName("KickTime")] + public int KickTime { get; set; } = 10; [JsonPropertyName("Messages")] public Messages Messages { get; set; } = new Messages(); diff --git a/Events.cs b/Events.cs index 6ed895b..cce3bf4 100644 --- a/Events.cs +++ b/Events.cs @@ -1,6 +1,8 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Commands; using CounterStrikeSharp.API.Modules.Entities; +using static CounterStrikeSharp.API.Core.Listeners; namespace CS2_SimpleAdmin { @@ -8,8 +10,23 @@ public partial class CS2_SimpleAdmin { private void registerEvents() { - RegisterListener(OnClientAuthorized); - RegisterListener(OnMapStart); + RegisterListener(OnClientAuthorized); + RegisterListener(OnClientDisconnect); + RegisterListener(OnMapStart); + AddCommandListener("say", OnCommandSay); + AddCommandListener("say_team", OnCommandSay); + } + + private HookResult OnCommandSay(CCSPlayerController? player, CommandInfo info) + { + if (player == null || !player.IsValid || info.GetArg(1).Length == 0) return HookResult.Continue; + + if (gaggedPlayers.Contains((int)player.Index)) + { + return HookResult.Handled; + } + + return HookResult.Continue; } private void OnClientAuthorized(int playerSlot, SteamID steamID) @@ -28,20 +45,53 @@ private void OnClientAuthorized(int playerSlot, SteamID steamID) } BanManager _banManager = new(dbConnectionString); + MuteManager _muteManager = new(dbConnectionString); bool isBanned = _banManager.IsPlayerBanned(player.AuthorizedSteamID.SteamId64.ToString()); + List activeMutes = _muteManager.IsPlayerMuted(player.AuthorizedSteamID.SteamId64.ToString()); + if (activeMutes.Count > 0) + { + // Player is muted, handle mute + foreach (var mute in activeMutes) + { + string muteType = mute.type; + + if (muteType == "GAG") + { + if (!gaggedPlayers.Contains((int)player.Index)) + gaggedPlayers.Add((int)player.Index); + } + else + { + continue; + } + } + } + + // Player is banned, kick him if (isBanned) { Helper.KickPlayer(player.UserId, "Banned"); } } + private void OnClientDisconnect(int playerSlot) + { + CCSPlayerController? player = Utilities.GetPlayerFromSlot(playerSlot); + + if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) return; + + gaggedPlayers.Remove((int)player.Index); + } + private void OnMapStart(string mapName) { AddTimer(120.0f, () => { BanManager _banManager = new(dbConnectionString); _banManager.ExpireOldBans(); + MuteManager _muteManager = new(dbConnectionString); + _muteManager.ExpireOldMutes(); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT | CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); } } diff --git a/Helper.cs b/Helper.cs index 7868106..fb534bf 100644 --- a/Helper.cs +++ b/Helper.cs @@ -18,7 +18,10 @@ public static List GetPlayerFromName(string name) public static List GetPlayerFromSteamid64(string steamid) { - return Utilities.GetPlayers().FindAll(x => x.AuthorizedSteamID!.SteamId64.ToString().Equals(steamid)); + return Utilities.GetPlayers().FindAll(x => + x.AuthorizedSteamID != null && + x.AuthorizedSteamID.SteamId64.ToString().Equals(steamid, StringComparison.OrdinalIgnoreCase) + ); } public static bool IsValidSteamID64(string input) diff --git a/MuteManager.cs b/MuteManager.cs new file mode 100644 index 0000000..470cc36 --- /dev/null +++ b/MuteManager.cs @@ -0,0 +1,137 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Entities; +using Dapper; +using MySqlConnector; +using System.Data; +using System.Xml.Linq; + +namespace CS2_SimpleAdmin +{ + public class MuteManager + { + private readonly IDbConnection _dbConnection; + + public MuteManager(string connectionString) + { + _dbConnection = new MySqlConnection(connectionString); + } + + public void MutePlayer(CCSPlayerController? player, CCSPlayerController? issuer, string reason, int time = 0, int type = 0) + { + _dbConnection.Open(); + + if (player == null || !player.IsValid || player.AuthorizedSteamID == null) return; + + DateTime now = DateTime.Now; + DateTime futureTime = now.AddMinutes(time); + + string muteType = "GAG"; + + if (type == 1) + muteType = "MUTE"; + + var sql = "INSERT INTO `sa_mutes` (`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`) " + + "VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @type)"; + + _dbConnection.Execute(sql, new + { + playerSteamid = player.AuthorizedSteamID.SteamId64.ToString(), + playerName = player.PlayerName, + adminSteamid = issuer == null ? "Console" : issuer?.AuthorizedSteamID?.SteamId64.ToString(), + adminName = issuer == null ? "Console" : issuer.PlayerName, + banReason = reason, + duration = time, + ends = futureTime, + created = now, + type = muteType, + }); + + _dbConnection.Close(); + } + + public void AddMuteBySteamid(string playerSteamId, CCSPlayerController? issuer, string reason, int time = 0, int type = 0) + { + if (string.IsNullOrEmpty(playerSteamId)) return; + + _dbConnection.Open(); + + DateTime now = DateTime.Now; + DateTime futureTime = now.AddMinutes(time); + + string muteType = "GAG"; + + if (type == 1) + muteType = "MUTE"; + + var sql = "INSERT INTO `sa_mutes` (`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`) " + + "VALUES (@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @type)"; + + _dbConnection.Execute(sql, new + { + playerSteamid = playerSteamId, + adminSteamid = issuer == null ? "Console" : issuer?.AuthorizedSteamID?.SteamId64.ToString(), + adminName = issuer == null ? "Console" : issuer.PlayerName, + banReason = reason, + duration = time, + ends = futureTime, + created = now, + type = muteType + }); + + _dbConnection.Close(); + } + + public List IsPlayerMuted(string steamId) + { + _dbConnection.Open(); + + DateTime now = DateTime.Now; + + string sql = "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"; + List activeMutes = _dbConnection.Query(sql, new { PlayerSteamID = steamId, CurrentTime = now }).ToList(); + + _dbConnection.Close(); + + return activeMutes; + } + + public void UnmutePlayer(string playerPattern, int type = 0) + { + if (playerPattern == null || playerPattern.Length <= 1) + { + return; + } + + if (type == 2) + { + string _unbanSql = "UPDATE sa_mutes SET status = 'UNMUTED' WHERE (player_steamid = @pattern OR player_name = @pattern) AND status = 'ACTIVE'"; + _dbConnection.Execute(_unbanSql, new { pattern = playerPattern }); + _dbConnection.Close(); + + return; + } + + string muteType = "GAG"; + + if (type == 1) + muteType = "MUTE"; + + _dbConnection.Open(); + + string sqlUnban = "UPDATE sa_mutes SET status = 'UNMUTED' WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE'"; + _dbConnection.Execute(sqlUnban, new { pattern = playerPattern, muteType }); + _dbConnection.Close(); + } + + public void ExpireOldMutes() + { + _dbConnection.Open(); + string sql = "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"; + _dbConnection.Execute(sql, new { CurrentTime = DateTime.Now }); + //int affectedRows = _dbConnection.Execute(sql, new { CurrentTime = DateTime.Now }); + + _dbConnection.Close(); + } + + } +} diff --git a/README.md b/README.md index 87a9044..2122681 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ It's only plugin base, I don't have much time for more extensive development, so - css_addban [time in minutes/0 perm] [reason] - Ban player via steamid64 // @css/ban - css_unban - Unban player // @css/unban - css_kick <#userid or name> [reason] - Kick player / @css/kick +- css_gag <#userid or name> [time in minutes/0 perm] [reason] - Gag player // @css/chat +- css_addgag [time in minutes/0 perm] [reason] - Gag player via steamid64 // @css/chat +- css_unmute - Kill player // @css/slay - css_slap <#userid or name> [damage] - Slap player // @css/slay - css_map - Change map // @css/changemap @@ -26,7 +29,6 @@ It's only plugin base, I don't have much time for more extensive development, so - css_cvar - Change cvar value // @css/cvar - css_rcon - Run command as server // @css/rcon - ### Requirments [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/) **tested on v90**