From 6bcb10c5cfdbd063c06c0e2e361f4541995b6d27 Mon Sep 17 00:00:00 2001 From: daffyyyy Date: Sat, 2 Dec 2023 22:44:11 +0100 Subject: [PATCH] Initial commit --- .gitignore | 4 + BanManager.cs | 67 +++++++++++++++ CS2-SimpleAdmin.cs | 187 +++++++++++++++++++++++++++++++++++++++++ CS2-SimpleAdmin.csproj | 17 ++++ CS2-SimpleAdmin.sln | 25 ++++++ Config.cs | 53 ++++++++++++ Events.cs | 48 +++++++++++ Helper.cs | 63 ++++++++++++++ PlayerUtils.cs | 71 ++++++++++++++++ README.md | 43 ++++++++++ TargetResult.cs | 8 ++ 11 files changed, 586 insertions(+) create mode 100644 .gitignore create mode 100644 BanManager.cs create mode 100644 CS2-SimpleAdmin.cs create mode 100644 CS2-SimpleAdmin.csproj create mode 100644 CS2-SimpleAdmin.sln create mode 100644 Config.cs create mode 100644 Events.cs create mode 100644 Helper.cs create mode 100644 PlayerUtils.cs create mode 100644 README.md create mode 100644 TargetResult.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48166c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin/ +obj/ +.vs/ +.git \ No newline at end of file diff --git a/BanManager.cs b/BanManager.cs new file mode 100644 index 0000000..2a6213f --- /dev/null +++ b/BanManager.cs @@ -0,0 +1,67 @@ +using CounterStrikeSharp.API.Core; +using Dapper; +using MySqlConnector; +using System.Data; + +namespace CS2_SimpleAdmin +{ + internal class BanManager + { + private readonly IDbConnection _dbConnection; + + public BanManager(string connectionString) + { + _dbConnection = new MySqlConnection(connectionString); + } + + public void BanPlayer(CCSPlayerController? player, CCSPlayerController? issuer, string reason, int time = 0) + { + _dbConnection.Open(); + + if (player == null || !player.IsValid || player.AuthorizedSteamID == null) return; + + DateTime now = DateTime.Now; + DateTime futureTime = now.AddMinutes(time); + + var sql = "INSERT INTO `sa_bans` (`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `duration`, `ends`, `created`) " + + "VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @duration, @ends, @created)"; + _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, + duration = time, + ends = futureTime, + created = now + }); + + _dbConnection.Close(); + } + + public bool IsPlayerBanned(string steamId) + { + _dbConnection.Open(); + + DateTime now = DateTime.Now; + + string sql = "SELECT COUNT(*) FROM sa_bans WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"; + int banCount = _dbConnection.ExecuteScalar(sql, new { PlayerSteamID = steamId, CurrentTime = now }); + + _dbConnection.Close(); + + return banCount > 0; + } + + public void ExpireOldBans() + { + _dbConnection.Open(); + + string sql = "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"; + int affectedRows = _dbConnection.Execute(sql, new { CurrentTime = DateTime.Now }); + + _dbConnection.Close(); + } + + } +} diff --git a/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin.cs new file mode 100644 index 0000000..4645fce --- /dev/null +++ b/CS2-SimpleAdmin.cs @@ -0,0 +1,187 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using MySqlConnector; +using static System.Net.Mime.MediaTypeNames; + +namespace CS2_SimpleAdmin; +public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig +{ + internal string dbConnectionString = string.Empty; + public override string ModuleName => "CS2-SimpleAdmin"; + public override string ModuleDescription => ""; + public override string ModuleAuthor => "daffyy"; + public override string ModuleVersion => "1.0.0"; + + public CS2_SimpleAdminConfig Config { get; set; } = new(); + + public override void Load(bool hotReload) + { + registerEvents(); + + if (hotReload) + { + OnMapStart(string.Empty); + } + } + + 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!"); + } + + MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder + { + Server = config.DatabaseHost, + Database = config.DatabaseName, + UserID = config.DatabaseUser, + Password = config.DatabasePassword, + Port = (uint)config.DatabasePort, + }; + + dbConnectionString = builder.ConnectionString; + + try + { + using (var connection = new MySqlConnection(dbConnectionString)) + { + connection.Open(); + connection.Close(); + } + + } + catch (MySqlException ex) + { + throw new Exception("[CS2-SimpleAdmin] Unable to connect to Database!" + ex.Message); + } + + Config = config; + } + + [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) + { + if (!GetTarget(command, out var player)) + return; + + player!.Pawn.Value!.Freeze(); + string reason = "Brak powodu"; + + if (command.ArgCount >= 2) + reason = command.GetArg(2); + + 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)); + + } + else + { + AddTimer(10.0f, () => 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_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) + { + if (!GetTarget(command, out var player)) + return; + if (command.ArgCount < 2) + return; + + int time = 0; + string reason = "Unknown"; + + player!.Pawn.Value!.Freeze(); + + BanManager _banManager = new(dbConnectionString); + + int.TryParse(command.GetArg(2), out time); + + if (command.ArgCount >= 3) + reason = command.GetArg(3); + + _banManager.BanPlayer(player, caller, reason, time); + + if (time == 0) + { + player!.PrintToCenter($"{Config.Messages.PlayerBanMessagePerm}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); + Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminBanMessagePerm}".Replace("{REASON}", reason).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); + } + else + { + player!.PrintToCenter($"{Config.Messages.PlayerBanMessageTime}".Replace("{REASON}", reason).Replace("{TIME}", time.ToString()).Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName)); + 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)); + + } + + [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) + { + if (!GetTarget(command, out CCSPlayerController? player)) + return; + if (!player!.PawnIsAlive) + return; + + player!.CommitSuicide(false, true); + + Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminSlayMessage}".Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); + } + + [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) + { + if (!GetTarget(command, out CCSPlayerController? player)) + return; + if (!player!.PawnIsAlive) + return; + + int damage = 0; + + + if (command.ArgCount >= 2) + { + int.TryParse(command.GetArg(2), out damage); + } + + player!.Pawn.Value!.Slap(damage); + + Server.PrintToChatAll(Helper.ReplaceTags($" {Config.Prefix} {Config.Messages.AdminSlapMessage}".Replace("{ADMIN}", caller?.PlayerName == null ? "Console" : caller.PlayerName).Replace("{PLAYER}", player.PlayerName))); + } + + private static bool GetTarget(CommandInfo info, out CCSPlayerController? player) + { + var matches = Helper.GetTarget(info.GetArg(1), out player); + + switch (matches) + { + case TargetResult.None: + info.ReplyToCommand($"Target {info.GetArg(1)} not found."); + return false; + case TargetResult.Multiple: + info.ReplyToCommand($"Multiple targets found for \"{info.GetArg(1)}\"."); + return false; + } + + return true; + } +} + diff --git a/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin.csproj new file mode 100644 index 0000000..e0ae93f --- /dev/null +++ b/CS2-SimpleAdmin.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + CS2_SimpleAdmin + enable + enable + true + + + + + + + + + diff --git a/CS2-SimpleAdmin.sln b/CS2-SimpleAdmin.sln new file mode 100644 index 0000000..0f0c027 --- /dev/null +++ b/CS2-SimpleAdmin.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-SimpleAdmin", "CS2-SimpleAdmin.csproj", "{0DB90496-262F-4BF9-9B55-1490EBF4D49F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0DB90496-262F-4BF9-9B55-1490EBF4D49F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DB90496-262F-4BF9-9B55-1490EBF4D49F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DB90496-262F-4BF9-9B55-1490EBF4D49F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DB90496-262F-4BF9-9B55-1490EBF4D49F}.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/Config.cs b/Config.cs new file mode 100644 index 0000000..6179565 --- /dev/null +++ b/Config.cs @@ -0,0 +1,53 @@ +using CounterStrikeSharp.API.Core; +using System.Text.Json.Serialization; + +namespace CS2_SimpleAdmin +{ + public class Messages + { + [JsonPropertyName("PlayerBanMessageTime")] + public string PlayerBanMessageTime { get; set; } = "You have been banned for {REASON} for {TIME} minutes by {ADMIN}!"; + [JsonPropertyName("PlayerBanMessagePerm")] + 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("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("AdminSlayMessage")] + public string AdminSlayMessage { get; set; } = "Admin {ADMIN} slayed {PLAYER}!"; + [JsonPropertyName("AdminSlapMessage")] + public string AdminSlapMessage { get; set; } = "Admin {ADMIN} slapped {PLAYER}!"; + } + + public class CS2_SimpleAdminConfig : BasePluginConfig + { + public override int Version { get; set; } = 1; + + [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("Prefix")] + public string Prefix { get; set; } = "{GREEN}[SimpleAdmin]"; + + [JsonPropertyName("Messages")] + public Messages Messages { get; set; } = new Messages(); + } + +} \ No newline at end of file diff --git a/Events.cs b/Events.cs new file mode 100644 index 0000000..6ed895b --- /dev/null +++ b/Events.cs @@ -0,0 +1,48 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Entities; + +namespace CS2_SimpleAdmin +{ + public partial class CS2_SimpleAdmin + { + private void registerEvents() + { + RegisterListener(OnClientAuthorized); + RegisterListener(OnMapStart); + } + + private void OnClientAuthorized(int playerSlot, SteamID steamID) + { + int playerIndex = playerSlot + 1; + + CCSPlayerController? player = Utilities.GetPlayerFromIndex(playerIndex); + + if (player.AuthorizedSteamID == null) + { + AddTimer(3.0f, () => + { + OnClientAuthorized(playerSlot, steamID); + }); + return; + } + + BanManager _banManager = new(dbConnectionString); + bool isBanned = _banManager.IsPlayerBanned(player.AuthorizedSteamID.SteamId64.ToString()); + + if (isBanned) + { + Helper.KickPlayer(player.UserId, "Banned"); + } + } + + private void OnMapStart(string mapName) + { + AddTimer(120.0f, () => + { + BanManager _banManager = new(dbConnectionString); + _banManager.ExpireOldBans(); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT | CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + } +} diff --git a/Helper.cs b/Helper.cs new file mode 100644 index 0000000..c6491d9 --- /dev/null +++ b/Helper.cs @@ -0,0 +1,63 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API; +using MySqlConnector; +using CounterStrikeSharp.API.Modules.Utils; +using System.Reflection; + +namespace CS2_SimpleAdmin +{ + internal class Helper + { + internal static CS2_SimpleAdminConfig? Config { get; set; } + + public static List GetPlayerFromName(string name) + { + return Utilities.GetPlayers().FindAll(x => x.PlayerName.Contains(name, StringComparison.OrdinalIgnoreCase)); + } + + public static TargetResult GetTarget(string target, out CCSPlayerController? player) + { + player = null; + + if (target.StartsWith("#") && int.TryParse(target.AsSpan(1), out var userid)) + { + player = Utilities.GetPlayerFromUserid(userid); + } + else + { + var matches = GetPlayerFromName(target); + if (matches.Count > 1) + return TargetResult.Multiple; + + player = matches.FirstOrDefault(); + } + + return player?.IsValid == true ? TargetResult.Single : TargetResult.None; + } + + public static void KickPlayer(int? userId, string? reason = null) + { + Server.ExecuteCommand($"kickid {userId} {reason}"); + } + + internal static string ReplaceTags(string message) + { + if (message.Contains('{')) + { + string modifiedValue = message; + foreach (FieldInfo field in typeof(ChatColors).GetFields()) + { + string pattern = $"{{{field.Name}}}"; + if (message.Contains(pattern, StringComparison.OrdinalIgnoreCase)) + { + modifiedValue = modifiedValue.Replace(pattern, field.GetValue(null)!.ToString(), StringComparison.OrdinalIgnoreCase); + } + } + return modifiedValue; + } + + return message; + } + + } +} diff --git a/PlayerUtils.cs b/PlayerUtils.cs new file mode 100644 index 0000000..b5e4295 --- /dev/null +++ b/PlayerUtils.cs @@ -0,0 +1,71 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; + +namespace CS2_SimpleAdmin; + +public static class PlayerUtils +{ + public static void Slap(this CBasePlayerPawn pawn, int damage = 0) + { + PerformSlap(pawn, damage); + } + + 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_NONE; + } + + public static void Unfreeze(this CBasePlayerPawn pawn) + { + pawn.MoveType = MoveType_t.MOVETYPE_WALK; + } + + public static void ToggleNoclip(this CBasePlayerPawn pawn) + { + if (pawn.MoveType == MoveType_t.MOVETYPE_NOCLIP) + pawn.MoveType = MoveType_t.MOVETYPE_WALK; + else + pawn.MoveType = MoveType_t.MOVETYPE_NOCLIP; + } + + 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.Teleport(pawn.AbsOrigin!, pawn.AbsRotation!, vel); + + if (damage <= 0) + return; + + pawn.Health -= damage; + + if (pawn.Health <= 0) + pawn.CommitSuicide(true, true); + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1557cb8 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# CS2-SimpleAdmin + +### Description +Manage your Counter-Strike 2 server by simple commands :) + +### Info +It's only plugin base, I don't have much time for more extensive development, so if you want to help, do it :) + +### Commands +- css_ban <#userid or name> [time in minutes/0 perm] [reason] - Ban player +- css_kick <#userid or name> [reason] - Kick player +- css_slay <#userid or name> - Kill player +- css_slap <#userid or name> [damage] - Slap player + +### Requirments +[CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/) **tested on v90** + +### Configuration +After first launch, u need to configure plugin in addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json + +### Colors +``` + public static char Default = '\x01'; + public static char White = '\x01'; + public static char Darkred = '\x02'; + public static char Green = '\x04'; + public static char LightYellow = '\x03'; + public static char LightBlue = '\x03'; + public static char Olive = '\x05'; + public static char Lime = '\x06'; + public static char Red = '\x07'; + public static char Purple = '\x03'; + public static char Grey = '\x08'; + public static char Yellow = '\x09'; + public static char Gold = '\x10'; + public static char Silver = '\x0A'; + public static char Blue = '\x0B'; + public static char DarkBlue = '\x0C'; + public static char BlueGrey = '\x0D'; + public static char Magenta = '\x0E'; + public static char LightRed = '\x0F'; +``` +Use color name for e.g. {LightRed} \ No newline at end of file diff --git a/TargetResult.cs b/TargetResult.cs new file mode 100644 index 0000000..39ed99e --- /dev/null +++ b/TargetResult.cs @@ -0,0 +1,8 @@ +namespace CS2_SimpleAdmin; + +internal enum TargetResult +{ + None, + Multiple, + Single +}