diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 22d2763..4c82bbd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: .NET 7 Build and Release +name: .NET 8 Build and Release on: pull_request: diff --git a/CustomCommands/CustomCommands.cs b/CustomCommands/CustomCommands.cs index 2296fbe..b5b7879 100644 --- a/CustomCommands/CustomCommands.cs +++ b/CustomCommands/CustomCommands.cs @@ -9,24 +9,26 @@ namespace CustomCommands; public partial class CustomCommands : BasePlugin, IPluginConfig { public override string ModuleName => "CustomCommands"; - public override string ModuleVersion => "2.1.0"; + public override string ModuleVersion => "3.0.0"; public override string ModuleAuthor => "HerrMagic"; public override string ModuleDescription => "Create your own commands per config"; public CustomCommandsConfig Config { get; set; } = new(); - private readonly IRegisterCommands RegisterCommands; - private readonly IPluginGlobals PluginGlobals; - private readonly ILoadJson LoadJson; - private readonly IEventManager EventManager; + private readonly IRegisterCommands _registerCommands; + private readonly IPluginGlobals _pluginGlobals; + private readonly ILoadJson _loadJson; + private readonly IEventManager _eventManager; + private readonly IReplaceTagsFunctions _replaceTagsFunctions; public CustomCommands(IRegisterCommands RegisterCommands, ILogger Logger, - IPluginGlobals PluginGlobals, ILoadJson LoadJson, IEventManager EventManager) + IPluginGlobals PluginGlobals, ILoadJson LoadJson, IEventManager EventManager, IReplaceTagsFunctions ReplaceTagsFunctions) { this.Logger = Logger; - this.RegisterCommands = RegisterCommands; - this.PluginGlobals = PluginGlobals; - this.LoadJson = LoadJson; - this.EventManager = EventManager; + _registerCommands = RegisterCommands; + _pluginGlobals = PluginGlobals; + _loadJson = LoadJson; + _eventManager = EventManager; + _replaceTagsFunctions = ReplaceTagsFunctions; } public void OnConfigParsed(CustomCommandsConfig config) @@ -45,9 +47,10 @@ public override void Load(bool hotReload) Logger.LogInformation( $"{ModuleName} loaded!"); - PluginGlobals.Config = Config; + _pluginGlobals.Config = Config; + Config.Prefix = _replaceTagsFunctions.ReplaceColorTags(Config.Prefix); - var comms = LoadJson.GetCommandsFromJsonFiles(ModuleDirectory); + var comms = Task.Run(async () => await _loadJson.GetCommandsFromJsonFiles(ModuleDirectory)).Result; if (comms == null) { @@ -55,16 +58,18 @@ public override void Load(bool hotReload) return; } - EventManager.RegisterListeners(); + _eventManager.RegisterListeners(); if (comms != null) { - PluginGlobals.CustomCommands = comms; + _pluginGlobals.CustomCommands = comms; + + _registerCommands.CheckForDuplicateCommands(); + _registerCommands.ConvertingCommandsForRegister(); - comms = RegisterCommands.CheckForDuplicateCommands(comms); // Add commands from the JSON file to the server - foreach (var com in comms) - RegisterCommands.AddCommands(com); + foreach (var cmd in _pluginGlobals.CustomCommands) + _registerCommands.AddCommands(cmd); } } } \ No newline at end of file diff --git a/CustomCommands/CustomCommandsConfig.cs b/CustomCommands/CustomCommandsConfig.cs index 763fd61..fd1791b 100644 --- a/CustomCommands/CustomCommandsConfig.cs +++ b/CustomCommands/CustomCommandsConfig.cs @@ -6,6 +6,8 @@ namespace CustomCommands; public class CustomCommandsConfig : BasePluginConfig { + public override int Version { get; set; } = 2; + [JsonPropertyName("IsPluginEnabled")] public bool IsPluginEnabled { get; set; } = true; @@ -14,4 +16,7 @@ public class CustomCommandsConfig : BasePluginConfig [JsonPropertyName("Prefix")] public string Prefix { get; set; } = $"[{ChatColors.Yellow}Info{ChatColors.Default}] "; + + [JsonPropertyName("RegisterCommandsAsCSSFramework")] + public bool RegisterCommandsAsCSSFramework { get; set; } = true; } \ No newline at end of file diff --git a/CustomCommands/CustomCommandsServiceCollection.cs b/CustomCommands/CustomCommandsServiceCollection.cs index 30e50c0..517b451 100644 --- a/CustomCommands/CustomCommandsServiceCollection.cs +++ b/CustomCommands/CustomCommandsServiceCollection.cs @@ -9,6 +9,7 @@ public class CustomCommandsServiceCollection : IPluginServiceCollection scan .FromAssemblyOf() .AddClasses() diff --git a/CustomCommands/Interfaces/ILoadJson.cs b/CustomCommands/Interfaces/ILoadJson.cs index e564de8..5d865fc 100644 --- a/CustomCommands/Interfaces/ILoadJson.cs +++ b/CustomCommands/Interfaces/ILoadJson.cs @@ -4,11 +4,52 @@ namespace CustomCommands.Interfaces; public interface ILoadJson { - List GetCommandsFromJsonFiles(string path); + /// + /// Retrieves a list of commands from JSON files located in the specified path. + /// + /// The path where the JSON files are located. + /// A list of commands. + Task> GetCommandsFromJsonFiles(string path); + + /// + /// Checks if an .example file exists in the specified path and creates a default config file if it doesn't exist. + /// + /// The path to check for the example file. void CheckForExampleFile(string path); - bool IsValidJsonSyntax(string jsonString); + + /// + /// Checks if the JSON file has a valid syntax. + /// + /// The path to the JSON file. + /// The json string. + /// True if the JSON syntax is valid, false otherwise. + bool IsValidJsonSyntax(string json, string path); + + /// + /// Validates the list of commands loaded from a JSON file. + /// + /// The list of commands to validate. + /// The path of the JSON file. + /// True if all commands are valid, false otherwise. bool ValidateObject(List? comms, string path); + + /// + /// Logs the details of a command. + /// + /// The command to log. void LogCommandDetails(Commands comms); + + /// + /// Validates the PrintTo property of the Commands object and checks if the required message properties are set based on the PrintTo value. + /// + /// The Commands object to validate. + /// True if the PrintTo property is valid and the required message properties are set; otherwise, false. bool PrintToCheck(Commands comms); + + /// + /// Validates a dynamic message by checking if it is a string or an array. + /// + /// The dynamic message to validate. + /// True if the message is a string or an array, otherwise false. bool ValidateMessage(dynamic message); } \ No newline at end of file diff --git a/CustomCommands/Interfaces/IMessageManager.cs b/CustomCommands/Interfaces/IMessageManager.cs index c2be909..edf7fbf 100644 --- a/CustomCommands/Interfaces/IMessageManager.cs +++ b/CustomCommands/Interfaces/IMessageManager.cs @@ -5,6 +5,11 @@ namespace CustomCommands.Interfaces; public interface IMessageManager { + /// + /// Sends a message based on the specified command and target receiver. + /// + /// The player who triggered the command. + /// The command containing the message and target receiver. void SendMessage(CCSPlayerController player, Commands cmd); void PrintToCenterClient(CCSPlayerController player, Commands cmd); void PrintToAllCenter(Commands cmd); diff --git a/CustomCommands/Interfaces/IPluginUtilities.cs b/CustomCommands/Interfaces/IPluginUtilities.cs index feb444f..7d4c378 100644 --- a/CustomCommands/Interfaces/IPluginUtilities.cs +++ b/CustomCommands/Interfaces/IPluginUtilities.cs @@ -5,7 +5,49 @@ namespace CustomCommands.Interfaces; public interface IPluginUtilities { + /// + /// Adds the css_ prefix to each alias. + /// This will help cs# tell this command belongs to the framework. + /// + /// + /// + string[] AddCSSTagsToAliases(List input); + + /// + /// Splits a string by comma, semicolon, or whitespace characters. + /// + /// The string to be split. + /// An array of strings containing the split substrings. string[] SplitStringByCommaOrSemicolon(string str); + + /// + /// Executes the server commands from the command object + /// + /// + /// void ExecuteServerCommands(Commands cmd, CCSPlayerController player); + + /// + /// Issue the specified command to the specified client (mimics that client typing the command at the console). + /// Note: Only works for some commands, marked with the FCVAR_CLIENT_CAN_EXECUTE flag (not many). + /// + /// + /// + void ExecuteClientCommands(Commands cmd, CCSPlayerController player); + + /// + /// Issue the specified command directly from the server (mimics the server executing the command with the given player context). + /// Works with server commands like `kill`, `explode`, `noclip`, etc. + /// + /// + /// + void ExecuteClientCommandsFromServer(Commands cmd, CCSPlayerController player); + + /// + /// Checks if the player has the required permissions to execute the command + /// + /// + /// + /// bool RequiresPermissions(CCSPlayerController player, Permission permissions); } diff --git a/CustomCommands/Interfaces/IRegisterCommands.cs b/CustomCommands/Interfaces/IRegisterCommands.cs index 2680673..f698073 100644 --- a/CustomCommands/Interfaces/IRegisterCommands.cs +++ b/CustomCommands/Interfaces/IRegisterCommands.cs @@ -4,6 +4,23 @@ namespace CustomCommands.Interfaces; public interface IRegisterCommands { + /// + /// Adds custom commands to the plugin. + /// + /// The command to add. void AddCommands(Commands cmd); - List CheckForDuplicateCommands(List comms); + + /// + /// Checks for duplicate commands in the provided list and removes them. + /// + void CheckForDuplicateCommands(); + + /// + /// Converts custom commands stored in the global plugin state by processing each command string, + /// splitting it by commas or semicolons, and adjusting the command structure. + /// + /// The method clones each command, assigns a new unique ID, and processes its arguments. + /// Each split command is then added back to the global list with updated formatting. + /// + void ConvertingCommandsForRegister(); } diff --git a/CustomCommands/Interfaces/IReplaceTagsFunctions.cs b/CustomCommands/Interfaces/IReplaceTagsFunctions.cs index 419e19e..0d6f85b 100644 --- a/CustomCommands/Interfaces/IReplaceTagsFunctions.cs +++ b/CustomCommands/Interfaces/IReplaceTagsFunctions.cs @@ -4,9 +4,40 @@ namespace CustomCommands.Interfaces; public interface IReplaceTagsFunctions { - string[] ReplaceTags(string[] input, CCSPlayerController player); + /// + /// Replaces tags in the input array with their corresponding values. + /// + /// The array of strings containing tags to be replaced. + /// The CCSPlayerController object used for tag replacement. + /// The array of strings with tags replaced. + string[] ReplaceTags(dynamic input, CCSPlayerController player); + + /// + /// Replaces language tags in the input string with the corresponding localized value. + /// Language tags are defined within curly braces, e.g. "{LANG=LocalizerTag}". + /// If a language tag is found, it is replaced with the localized value from the CustomCommands plugin's Localizer. + /// If the localized value is not found, a default message is returned. + /// + /// The input string to process. + /// The input string with language tags replaced with localized values. string ReplaceLanguageTags(string input); + + /// + /// Replaces tags in the input string with corresponding values based on the provided player information. + /// + /// The input string containing tags to be replaced. + /// The CCSPlayerController object representing the player. + /// A boolean value indicating whether to replace the {PLAYERNAME} tag. Default is true. + /// The modified string with replaced tags. string ReplaceMessageTags(string input, CCSPlayerController player, bool safety = true); string ReplaceColorTags(string input); - string[] WrappedLine(dynamic message); + + /// + /// Splits the input into an array of strings. If the input is a string, it will be split by newlines. If the input is an array, each element will be split by newlines. + /// + /// This should be a string[] or a string + /// An array of strings representing the lines of the input. + List WrappedLine(dynamic message); + + string ReplaceRandomTags(string message); } \ No newline at end of file diff --git a/CustomCommands/Model/CommandsConfig.cs b/CustomCommands/Model/CommandsConfig.cs index 1cc96b1..fbf6f8f 100644 --- a/CustomCommands/Model/CommandsConfig.cs +++ b/CustomCommands/Model/CommandsConfig.cs @@ -1,8 +1,9 @@ namespace CustomCommands.Model; -public class Commands +public class Commands : ICloneable { public Guid ID { get; set; } = Guid.NewGuid(); + public string? Argument { get; set; } public string Title { get; set; } = ""; public string Description { get; set; } = "Description"; public string Command { get; set; } = ""; @@ -11,7 +12,31 @@ public class Commands public CenterElement CenterMessage { get; set; } = new(); public Sender PrintTo { get; set; } = Sender.ClientChat; public List ServerCommands { get; set; } = new(); - public Permission Permission { get; set; } = new(); + public List ClientCommands { get; set; } = new(); + public List ClientCommandsFromServer { get; set; } = new(); + public Permission? Permission { get; set; } = new(); + public bool IsRegisterable { get; set; } = true; + + public object Clone() + { + return new Commands() + { + ID = ID, + Argument = Argument, + Title = Title, + Description = Description, + Command = Command, + Cooldown = Cooldown, + Message = Message, + CenterMessage = CenterMessage, + PrintTo = PrintTo, + ServerCommands = new List(ServerCommands), + ClientCommands = new List(ClientCommands), + ClientCommandsFromServer = new List(ClientCommandsFromServer), + Permission = Permission?.Clone() as Permission, + IsRegisterable = IsRegisterable + }; + } } public class Cooldown { @@ -19,10 +44,16 @@ public class Cooldown public bool IsGlobal { get; set; } = false; public string CooldownMessage { get; set; } = ""; } -public class Permission +public class Permission : ICloneable { public bool RequiresAllPermissions { get; set; } = false; public List PermissionList { get; set; } = new(); + + public object Clone() + { + return MemberwiseClone(); + } + } public class CenterElement { diff --git a/CustomCommands/Services/CooldownManager.cs b/CustomCommands/Services/CooldownManager.cs index 40f6730..36d1434 100644 --- a/CustomCommands/Services/CooldownManager.cs +++ b/CustomCommands/Services/CooldownManager.cs @@ -6,12 +6,13 @@ namespace CustomCommands.Interfaces; public class CooldownManager : ICooldownManager { - public IPluginGlobals PluginGlobals { get; } - public IReplaceTagsFunctions ReplaceTagsFunctions { get; } + public IPluginGlobals _pluginGlobals { get; } + public IReplaceTagsFunctions _replaceTagsFunctions { get; } + public CooldownManager(IPluginGlobals PluginGlobals, IReplaceTagsFunctions ReplaceTagsFunctions) { - this.PluginGlobals = PluginGlobals; - this.ReplaceTagsFunctions = ReplaceTagsFunctions; + _pluginGlobals = PluginGlobals; + _replaceTagsFunctions = ReplaceTagsFunctions; } /// @@ -33,32 +34,38 @@ public bool IsCommandOnCooldown(CCSPlayerController player, Commands cmd) return false; } + /// + /// Checks if a command is on cooldown based on a given condition. + /// + /// The condition to check for each cooldown timer. + /// The player controller. + /// The command. + /// True if the command is on cooldown, false otherwise. public bool IsCommandOnCooldownWithCondition(Func predicate, CCSPlayerController player, Commands cmd) { - int index = PluginGlobals.CooldownTimer.FindIndex(x => predicate(x) && x.CooldownTime > DateTime.Now); + var index = _pluginGlobals.CooldownTimer.FindIndex(x => predicate(x) && x.CooldownTime > DateTime.Now); if (index != -1) { - double totalSeconds = PluginGlobals.CooldownTimer[index].CooldownTime.Subtract(DateTime.Now).TotalSeconds; - int totalSecondsRounded = (int)Math.Round(totalSeconds); - string timeleft = totalSecondsRounded.ToString(); - - string message = ""; + var totalSeconds = (double)_pluginGlobals.CooldownTimer[index].CooldownTime.Subtract(DateTime.Now).TotalSeconds; + var totalSecondsRounded = (int)Math.Round(totalSeconds); + var timeleft = totalSecondsRounded.ToString(); + var message = ""; // This is ugly as fuck try { - Cooldown cooldown = JsonSerializer.Deserialize(cmd.Cooldown.GetRawText()); + var cooldown = JsonSerializer.Deserialize(cmd.Cooldown.GetRawText()); Console.WriteLine(cooldown.CooldownMessage); string[] replaceTimeleft = {cooldown.CooldownMessage.Replace("{TIMELEFT}", timeleft)}; - message = ReplaceTagsFunctions.ReplaceTags(replaceTimeleft, player)[0]; + message = _replaceTagsFunctions.ReplaceTags(replaceTimeleft, player)[0]; } catch (JsonException) { message = $"This command is for {timeleft} seconds on cooldown"; } - player.PrintToChat($"{PluginGlobals.Config.Prefix}{message}"); + player.PrintToChat($"{_pluginGlobals.Config.Prefix}{message}"); return true; } @@ -83,25 +90,25 @@ public void AddToCooldownList(bool isGlobal, int playerID, Guid commandID, int c if (isGlobal) { - int index = PluginGlobals.CooldownTimer.FindIndex(x => + int index = _pluginGlobals.CooldownTimer.FindIndex(x => x.IsGlobal == true && x.CommandID == commandID); if (index != -1) - PluginGlobals.CooldownTimer[index].CooldownTime = timer.CooldownTime; + _pluginGlobals.CooldownTimer[index].CooldownTime = timer.CooldownTime; else - PluginGlobals.CooldownTimer.Add(timer); + _pluginGlobals.CooldownTimer.Add(timer); } else { timer.PlayerID = playerID; - int index = PluginGlobals.CooldownTimer.FindIndex(x => + int index = _pluginGlobals.CooldownTimer.FindIndex(x => x.PlayerID == playerID && x.CommandID == commandID); if (index != -1) - PluginGlobals.CooldownTimer[index].CooldownTime = timer.CooldownTime; + _pluginGlobals.CooldownTimer[index].CooldownTime = timer.CooldownTime; else - PluginGlobals.CooldownTimer.Add(timer); + _pluginGlobals.CooldownTimer.Add(timer); } } @@ -118,7 +125,7 @@ public void SetCooldown(CCSPlayerController player, Commands cmd) { case JsonValueKind.Number: - int cooldown = cmd.Cooldown.GetInt32(); + var cooldown = cmd.Cooldown.GetInt32(); if (cooldown == 0) break; @@ -129,7 +136,6 @@ public void SetCooldown(CCSPlayerController player, Commands cmd) var cooldownObject = JsonSerializer.Deserialize(cmd.Cooldown.GetRawText()); AddToCooldownList(cooldownObject.IsGlobal, player.UserId ?? 0, cmd.ID, cooldownObject.CooldownTime); - break; default: diff --git a/CustomCommands/Services/EventManager.cs b/CustomCommands/Services/EventManager.cs index 08a49a6..441af7a 100644 --- a/CustomCommands/Services/EventManager.cs +++ b/CustomCommands/Services/EventManager.cs @@ -8,36 +8,37 @@ namespace CustomCommands.Services; public class EventManager : IEventManager { - private readonly IPluginGlobals PluginGlobals; - private readonly PluginContext PluginContext; - private readonly IReplaceTagsFunctions ReplaceTagsFunctions; + private readonly IPluginGlobals _pluginGlobals; + private readonly PluginContext _pluginContext; + private readonly IReplaceTagsFunctions _replaceTagsFunctions; public EventManager(IPluginGlobals PluginGlobals, IPluginContext PluginContext, IReplaceTagsFunctions ReplaceTagsFunctions) { - this.PluginGlobals = PluginGlobals; - this.PluginContext = (PluginContext as PluginContext)!; - this.ReplaceTagsFunctions = ReplaceTagsFunctions; + _pluginGlobals = PluginGlobals; + _pluginContext = (PluginContext as PluginContext)!; + _replaceTagsFunctions = ReplaceTagsFunctions; } [GameEventHandler] public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo _) { - PluginGlobals.centerClientOn.RemoveAll(p => p.ClientId == @event.Userid.UserId); - PluginGlobals.CooldownTimer.RemoveAll(p => p.PlayerID == @event.Userid.UserId); + _pluginGlobals.centerClientOn.RemoveAll(p => p.ClientId == @event.Userid.UserId); + _pluginGlobals.CooldownTimer.RemoveAll(p => p.PlayerID == @event.Userid.UserId); return HookResult.Continue; } public void RegisterListeners() { - CustomCommands plugin = (PluginContext.Plugin as CustomCommands)!; + var plugin = (_pluginContext.Plugin as CustomCommands)!; + // Register the OnTick event for PrintToCenterHtml duration plugin.RegisterListener(() => { // Client Print To Center - if(PluginGlobals.centerClientOn != null && PluginGlobals.centerClientOn.Count !> 0 ) + if(_pluginGlobals.centerClientOn != null && _pluginGlobals.centerClientOn.Count !> 0 ) { - foreach (var player in PluginGlobals.centerClientOn) + foreach (var player in _pluginGlobals.centerClientOn) { var targetPlayer = Utilities.GetPlayerFromUserid(player.ClientId); if (player != null && targetPlayer != null) @@ -48,14 +49,14 @@ public void RegisterListeners() } // Server Print To Center - if (PluginGlobals.centerServerOn.IsRunning) + if (_pluginGlobals.centerServerOn.IsRunning) { Utilities.GetPlayers().ForEach(controller => { if (!controller.IsValid || controller.SteamID == 0) return; - string message = ReplaceTagsFunctions.ReplaceLanguageTags(PluginGlobals.centerServerOn.Message); - message = ReplaceTagsFunctions.ReplaceMessageTags(message, controller); + var message = _replaceTagsFunctions.ReplaceLanguageTags(_pluginGlobals.centerServerOn.Message); + message = _replaceTagsFunctions.ReplaceMessageTags(message, controller); controller.PrintToCenterHtml(message, 1); }); } @@ -63,8 +64,8 @@ public void RegisterListeners() plugin.RegisterListener(() => { - PluginGlobals.centerClientOn.Clear(); - PluginGlobals.CooldownTimer.Clear(); + _pluginGlobals.centerClientOn.Clear(); + _pluginGlobals.CooldownTimer.Clear(); }); } } \ No newline at end of file diff --git a/CustomCommands/Services/LoadJson.cs b/CustomCommands/Services/LoadJson.cs index 954583c..7b5a7b2 100644 --- a/CustomCommands/Services/LoadJson.cs +++ b/CustomCommands/Services/LoadJson.cs @@ -1,3 +1,4 @@ +using System.Text; using System.Text.Json; using CustomCommands.Interfaces; using CustomCommands.Model; @@ -7,18 +8,18 @@ namespace CustomCommands.Services; public class LoadJson : ILoadJson { - private readonly ILogger Logger; + private readonly ILogger _logger; public LoadJson(ILogger Logger) { - this.Logger = Logger; + _logger = Logger; } - public List GetCommandsFromJsonFiles(string path) + public async Task> GetCommandsFromJsonFiles(string path) { var comms = new List(); - string defaultconfigpath = Path.Combine(path, "Commands.json"); + var defaultconfigpath = Path.Combine(path, "Commands.json"); CheckForExampleFile(path); @@ -33,29 +34,43 @@ public List GetCommandsFromJsonFiles(string path) if (File.Exists(defaultconfigpath)) { files.Add(defaultconfigpath); - Logger.LogInformation("Found default config file."); + _logger.LogInformation("Found default config file."); } else if (!File.Exists(defaultconfigpath) && files.Count == 0) { - Logger.LogWarning("No Config file found. Please create plugins/CustomCommands/Commands.json or in plugins/CustomCommands/Commands/.json"); + _logger.LogWarning("No Config file found. Please create plugins/CustomCommands/Commands.json or in plugins/CustomCommands/Commands/.json"); return comms; } - foreach (var file in files) + // Create a list of tasks to handle each file asynchronously + var tasks = files.Select(async file => { - var json = File.ReadAllText(file); + var json = string.Empty; + + // Read Unicode Characters asynchronously + using (var sr = new StreamReader(file, Encoding.UTF8)) + json = await sr.ReadToEndAsync(); // Validate the JSON file - if (!IsValidJsonSyntax(file)) - continue; + if (!IsValidJsonSyntax(json, file)) + return; + // Deserialize and validate the commands var commands = JsonSerializer.Deserialize>(json); if (ValidateObject(commands, file)) - comms.AddRange(commands!); - } + { + lock (comms) // Ensure thread-safety while adding to the shared list + { + comms.AddRange(commands!); + } + } + }); + + await Task.WhenAll(tasks); + return comms; } - // Check if the Command.json file exists. If not replace it with the example file + public void CheckForExampleFile(string path) { if (Directory.Exists(Path.Combine(path, "Commands"))) @@ -70,51 +85,52 @@ public void CheckForExampleFile(string path) if (!File.Exists(defaultconfigpath)) { File.Copy(exampleconfigpath, defaultconfigpath); - Logger.LogInformation("Created default config file."); + _logger.LogInformation("Created default config file."); } } - public bool IsValidJsonSyntax(string path) + + public bool IsValidJsonSyntax(string json, string path) { try { - var json = File.ReadAllText(path); var document = JsonDocument.Parse(json); return true; } catch (JsonException ex) { - Logger.LogError($"Invalid JSON syntax in {path}. Please check the docs on how to create a valid JSON file"); - Logger.LogError(ex.Message); + _logger.LogError($"Invalid JSON syntax in {path}. Please check the docs on how to create a valid JSON file"); + _logger.LogError(ex.Message); return false; } } + public bool ValidateObject(List? comms, string path) { if (comms == null) { - Logger.LogError($"Invalid JSON format in {path}. Please check the docs on how to create a valid JSON file"); + _logger.LogError($"Invalid JSON format in {path}. Please check the docs on how to create a valid JSON file"); return false; } - bool commandstatus = true; + var commandstatus = true; for (int i = 0; i < comms.Count; i++) { commandstatus = true; // Title if (string.IsNullOrEmpty(comms[i].Title)) { - Logger.LogWarning($"Title not set in {path}. Title is not required but recommended"); + _logger.LogWarning($"Title not set in {path}. Title is not required but recommended"); commandstatus = false; } // Description if (string.IsNullOrEmpty(comms[i].Description)) { - Logger.LogWarning($"Description not set in {path}. Description is not required but recommended. This will be shown in the help command"); + _logger.LogWarning($"Description not set in {path}. Description is not required but recommended. This will be shown in the help command"); commandstatus = false; } // Command if (string.IsNullOrEmpty(comms[i].Command)) { - Logger.LogError($"Command not set in {path}"); + _logger.LogError($"Command not set in {path}"); commandstatus = false; } if (!PrintToCheck(comms[i])) @@ -122,7 +138,7 @@ public bool ValidateObject(List? comms, string path) if (!commandstatus) { - Logger.LogError($"Command {comms[i].Command} will not be loaded. Index: {i}"); + _logger.LogError($"Command {comms[i].Command} will not be loaded. Index: {i}"); LogCommandDetails(comms[i]); } } @@ -133,16 +149,16 @@ public bool ValidateObject(List? comms, string path) public void LogCommandDetails(Commands comms) { - Logger.LogInformation($"-- Title: {comms.Title}"); - Logger.LogInformation($"-- Description: {comms.Description}"); - Logger.LogInformation($"-- Command: {comms.Command}"); - Logger.LogInformation($"-- Message: {comms.Message}"); - Logger.LogInformation($"-- CenterMessage: {comms.CenterMessage.Message}"); - Logger.LogInformation($"-- CenterMessageTime: {comms.CenterMessage.Time}"); - Logger.LogInformation($"-- PrintTo: {comms.PrintTo}"); - Logger.LogInformation($"-- ServerCommands: {JsonSerializer.Serialize(comms.ServerCommands)}"); - Logger.LogInformation($"-- PermissionList: {JsonSerializer.Serialize(comms.Permission)}"); - Logger.LogInformation("--------------------------------------------------"); + _logger.LogInformation($"-- Title: {comms.Title}"); + _logger.LogInformation($"-- Description: {comms.Description}"); + _logger.LogInformation($"-- Command: {comms.Command}"); + _logger.LogInformation($"-- Message: {comms.Message}"); + _logger.LogInformation($"-- CenterMessage: {comms.CenterMessage.Message}"); + _logger.LogInformation($"-- CenterMessageTime: {comms.CenterMessage.Time}"); + _logger.LogInformation($"-- PrintTo: {comms.PrintTo}"); + _logger.LogInformation($"-- ServerCommands: {JsonSerializer.Serialize(comms.ServerCommands)}"); + _logger.LogInformation($"-- PermissionList: {JsonSerializer.Serialize(comms.Permission)}"); + _logger.LogInformation("--------------------------------------------------"); } public bool PrintToCheck(Commands comms) @@ -152,7 +168,7 @@ public bool PrintToCheck(Commands comms) if (!ValidateMessage(comms.Message)) { - Logger.LogError($"Message not set but needs to be set because PrintTo is set to {comms.PrintTo}"); + _logger.LogError($"Message not set but needs to be set because PrintTo is set to {comms.PrintTo}"); return false; } } @@ -160,7 +176,7 @@ public bool PrintToCheck(Commands comms) { if (string.IsNullOrEmpty(comms.CenterMessage.Message)) { - Logger.LogError($"CenterMessage is not set but needs to be set because PrintTo is set to {comms.PrintTo}"); + _logger.LogError($"CenterMessage is not set but needs to be set because PrintTo is set to {comms.PrintTo}"); return false; } } @@ -168,7 +184,7 @@ public bool PrintToCheck(Commands comms) { if (!ValidateMessage(comms.Message) && string.IsNullOrEmpty(comms.CenterMessage.Message)) { - Logger.LogError($"Message and CenterMessage are not set but needs to be set because PrintTo is set to {comms.PrintTo}"); + _logger.LogError($"Message and CenterMessage are not set but needs to be set because PrintTo is set to {comms.PrintTo}"); return false; } } diff --git a/CustomCommands/Services/MessageManager.cs b/CustomCommands/Services/MessageManager.cs index bf54ff9..3389cd3 100644 --- a/CustomCommands/Services/MessageManager.cs +++ b/CustomCommands/Services/MessageManager.cs @@ -7,15 +7,15 @@ namespace CustomCommands.Services; public class MessageManager : IMessageManager { - private readonly IPluginGlobals PluginGlobals; - private readonly IReplaceTagsFunctions ReplaceTagsFunctions; - private readonly PluginContext PluginContext; + private readonly IPluginGlobals _pluginGlobals; + private readonly IReplaceTagsFunctions _replaceTagsFunctions; + private readonly PluginContext _pluginContext; public MessageManager(IPluginGlobals PluginGlobals, IReplaceTagsFunctions ReplaceTagsFunctions, IPluginContext PluginContext) { - this.PluginGlobals = PluginGlobals; - this.ReplaceTagsFunctions = ReplaceTagsFunctions; - this.PluginContext = (PluginContext as PluginContext)!; + _pluginGlobals = PluginGlobals; + _replaceTagsFunctions = ReplaceTagsFunctions; + _pluginContext = (PluginContext as PluginContext)!; } public void SendMessage(CCSPlayerController player, Commands cmd) @@ -53,31 +53,28 @@ public void SendMessage(CCSPlayerController player, Commands cmd) public void PrintToCenterClient(CCSPlayerController player, Commands cmd) { - CustomCommands plugin = (PluginContext.Plugin as CustomCommands)!; - - - string message = ReplaceTagsFunctions.ReplaceLanguageTags(cmd.CenterMessage.Message); - message = ReplaceTagsFunctions.ReplaceMessageTags(message, player); + var context = (_pluginContext.Plugin as CustomCommands)!; + var message = _replaceTagsFunctions.ReplaceLanguageTags(cmd.CenterMessage.Message); + message = _replaceTagsFunctions.ReplaceMessageTags(message, player); var CenterClientElement = new CenterClientElement { ClientId = player.UserId!.Value, Message = message }; - PluginGlobals.centerClientOn.Add(CenterClientElement); - plugin.AddTimer(cmd.CenterMessage.Time, () => PluginGlobals.centerClientOn.Remove(CenterClientElement)); + _pluginGlobals.centerClientOn.Add(CenterClientElement); + context.AddTimer(cmd.CenterMessage.Time, () => _pluginGlobals.centerClientOn.Remove(CenterClientElement)); } public void PrintToAllCenter(Commands cmd) { - CustomCommands plugin = (PluginContext.Plugin as CustomCommands)!; - - PluginGlobals.centerServerOn.Message = cmd.CenterMessage.Message; - PluginGlobals.centerServerOn.IsRunning = true; + var context = (_pluginContext.Plugin as CustomCommands)!; + _pluginGlobals.centerServerOn.Message = cmd.CenterMessage.Message; + _pluginGlobals.centerServerOn.IsRunning = true; - plugin.AddTimer(cmd.CenterMessage.Time, () => + context.AddTimer(cmd.CenterMessage.Time, () => { - PluginGlobals.centerServerOn.IsRunning = false; + _pluginGlobals.centerServerOn.IsRunning = false; }); } @@ -95,8 +92,7 @@ public void PrintToChatAndAllCenter(Receiver receiver, CCSPlayerController playe public void PrintToChat(Receiver printToChat, CCSPlayerController player, dynamic message) { - string[] msg = ReplaceTagsFunctions.WrappedLine(message); - msg = ReplaceTagsFunctions.ReplaceTags(msg, player); + var msg = _replaceTagsFunctions.ReplaceTags(message, player); switch (printToChat) { diff --git a/CustomCommands/Services/PluginGlobals.cs b/CustomCommands/Services/PluginGlobals.cs index 6719506..c293d00 100644 --- a/CustomCommands/Services/PluginGlobals.cs +++ b/CustomCommands/Services/PluginGlobals.cs @@ -4,9 +4,24 @@ namespace CustomCommands.Services; public class PluginGlobals : IPluginGlobals { + /// + /// List of clients that have a message printed to their center. + /// public List centerClientOn { get; set; } = new(); + /// + /// The message that is printed to all players' center. + /// public CenterServerElement centerServerOn { get; set; } = new(); + /// + /// The configuration for the plugin. + /// public CustomCommandsConfig Config { get; set; } = new(); + /// + /// List of cooldown timers for each player. + /// public List CooldownTimer { get; set; } = new(); + /// + /// List of custom commands. + /// public List CustomCommands { get; set; } = new(); } diff --git a/CustomCommands/Services/PluginUtilities.cs b/CustomCommands/Services/PluginUtilities.cs index fb546dc..f13b6c0 100644 --- a/CustomCommands/Services/PluginUtilities.cs +++ b/CustomCommands/Services/PluginUtilities.cs @@ -9,34 +9,44 @@ namespace CustomCommands.Services; -public class PluginUtilities : IPluginUtilities +public partial class PluginUtilities : IPluginUtilities { - private readonly IPluginGlobals PluginGlobals; - private readonly IReplaceTagsFunctions ReplaceTagsFunctions; - private readonly ILogger Logger; - + private readonly IPluginGlobals _pluginGlobals; + private readonly IReplaceTagsFunctions _replaceTagsFunctions; + private readonly ILogger _logger; + public PluginUtilities(IPluginGlobals PluginGlobals, IReplaceTagsFunctions ReplaceTagsFunctions, ILogger Logger) { - this.PluginGlobals = PluginGlobals; - this.ReplaceTagsFunctions = ReplaceTagsFunctions; - this.Logger = Logger; + _pluginGlobals = PluginGlobals; + _replaceTagsFunctions = ReplaceTagsFunctions; + _logger = Logger; + } + + public string[] AddCSSTagsToAliases(List input) + { + for (int i = 0; i < input.Count; i++) + { + if (!input[i].StartsWith("css_")) + input[i] = "css_" + input[i]; + } + return input.ToArray(); } + + [GeneratedRegex("[,;]")] + private static partial Regex SplitStringByCommaOrSemicolonRegex(); public string[] SplitStringByCommaOrSemicolon(string str) { - return Regex.Split(str, ",|;|\\s") + return SplitStringByCommaOrSemicolonRegex().Split(str) .Where(s => !string.IsNullOrEmpty(s)) .ToArray(); } - /// - /// Executes the server commands from the command object - /// - /// - /// - public void ExecuteServerCommands(Commands cmd, CCSPlayerController player) + + public void ExecuteServerCommands(Commands cmd, CCSPlayerController player) { - if (cmd.ServerCommands.Count == 0) return; + if (cmd.ServerCommands.Count == 0) + return; foreach (var serverCommand in cmd.ServerCommands) { @@ -47,52 +57,72 @@ public void ExecuteServerCommands(Commands cmd, CCSPlayerController player) continue; } - Server.ExecuteCommand(ReplaceTagsFunctions.ReplaceMessageTags(serverCommand, player)); + + Server.ExecuteCommand(_replaceTagsFunctions.ReplaceMessageTags(serverCommand, player)); } } + + public void ExecuteClientCommands(Commands cmd, CCSPlayerController player) + { + if (cmd.ClientCommands.Count == 0) + return; + + foreach (var clientCommand in cmd.ClientCommands) + { + player.ExecuteClientCommand(_replaceTagsFunctions.ReplaceMessageTags(clientCommand, player)); + } + } + + public void ExecuteClientCommandsFromServer(Commands cmd, CCSPlayerController player) + { + if (cmd.ClientCommandsFromServer.Count == 0) + return; + + foreach (var clientCommandsFromServer in cmd.ClientCommandsFromServer) + { + player.ExecuteClientCommandFromServer(_replaceTagsFunctions.ReplaceMessageTags(clientCommandsFromServer, player)); + } + } + /// /// Handles the toggle command /// /// private void HandleToggleCommand(string serverCommand) { - string commandWithoutToggle = serverCommand.Replace("toggle ", ""); + var commandWithoutToggle = serverCommand.Replace("toggle ", ""); var commandCvar = ConVar.Find(commandWithoutToggle); + if (commandCvar != null) { - if(commandCvar.GetPrimitiveValue()) + if (commandCvar.GetPrimitiveValue()) Server.ExecuteCommand($"{commandWithoutToggle} 0"); else Server.ExecuteCommand($"{commandWithoutToggle} 1"); } else { - Logger.LogError($"Couldn't toggle {commandWithoutToggle}. Please check if this command is toggleable"); + _logger.LogError($"Couldn't toggle {commandWithoutToggle}. Please check if this command is toggleable"); } } - /// - /// Checks if the player has the required permissions to execute the command - /// - /// - /// - /// + public bool RequiresPermissions(CCSPlayerController player, Permission permissions) { if (!permissions.RequiresAllPermissions) { foreach (var permission in permissions.PermissionList) { - if (AdminManager.PlayerHasPermissions(player, new string[]{permission})) + if (AdminManager.PlayerHasPermissions(player, new string[] { permission })) return true; } - player.PrintToChat($"{PluginGlobals.Config.Prefix}You don't have the required permissions to execute this command"); + player.PrintToChat($"{_pluginGlobals.Config.Prefix}You don't have the required permissions to execute this command"); return false; } else { if (!AdminManager.PlayerHasPermissions(player, permissions.PermissionList.ToArray())) { - player.PrintToChat($"{PluginGlobals.Config.Prefix}You don't have the required permissions to execute this command"); + player.PrintToChat($"{_pluginGlobals.Config.Prefix}You don't have the required permissions to execute this command"); return false; } return true; diff --git a/CustomCommands/Services/RegisterCommands.cs b/CustomCommands/Services/RegisterCommands.cs index b908021..7d0f1dc 100644 --- a/CustomCommands/Services/RegisterCommands.cs +++ b/CustomCommands/Services/RegisterCommands.cs @@ -1,5 +1,5 @@ -using System.Text.Json; using CounterStrikeSharp.API.Core.Plugin; +using CounterStrikeSharp.API.Modules.Commands; using CustomCommands.Interfaces; using CustomCommands.Model; using Microsoft.Extensions.Logging; @@ -7,84 +7,108 @@ namespace CustomCommands.Services; public class RegisterCommands : IRegisterCommands { - private readonly ILogger Logger; - private readonly IMessageManager MessageManager; - private readonly IPluginGlobals PluginGlobals; - private readonly PluginContext PluginContext; - private readonly IPluginUtilities PluginUtilities; - private readonly ICooldownManager CooldownManager; + private readonly ILogger _logger; + private readonly IMessageManager _messageManager; + private readonly IPluginGlobals _pluginGlobals; + private readonly PluginContext _pluginContext; + private readonly IPluginUtilities _pluginUtilities; + private readonly ICooldownManager _cooldownManager; public RegisterCommands(ILogger Logger, IMessageManager MessageManager, IPluginGlobals PluginGlobals, IPluginContext PluginContext, IPluginUtilities PluginUtilities, ICooldownManager CooldownManager) { - this.Logger = Logger; - this.MessageManager = MessageManager; - this.PluginGlobals = PluginGlobals; - this.PluginContext = (PluginContext as PluginContext)!; - this.PluginUtilities = PluginUtilities; - this.CooldownManager = CooldownManager; + _logger = Logger; + _messageManager = MessageManager; + _pluginGlobals = PluginGlobals; + _pluginContext = (PluginContext as PluginContext)!; + _pluginUtilities = PluginUtilities; + _cooldownManager = CooldownManager; } - public void AddCommands(Commands com) + public void AddCommands(Commands cmd) { - CustomCommands plugin = (PluginContext.Plugin as CustomCommands)!; - - string[] aliases = PluginUtilities.SplitStringByCommaOrSemicolon(com.Command); + if (!cmd.IsRegisterable) + return; - for (int i = 0; i < aliases.Length; i++) + var context = (_pluginContext.Plugin as CustomCommands)!; + + context.AddCommand(cmd.Command, cmd.Description, + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + (player, info) => { - string alias = aliases[i]; - plugin.AddCommand(alias, com.Description, (player, info) => - { - if (player == null) return; - - var command = com; + if (player == null) + return; - // Check if the command has arguments and if it does, check if the command exists and is the right one - if (info.ArgCount > 1) - { - var findcommand = PluginGlobals.CustomCommands.Find(x => x.Command.Contains(alias + $" {info.ArgString}")); - // Check if the command is the right one with the right arguments - if (findcommand! != command) - return; + var command = cmd; - command = findcommand; + // Check if the command has arguments and if it does, check if the command exists and is the right one + if (info.ArgCount > 1) + { + var findcommand = _pluginGlobals.CustomCommands.Find(x => x.Command == command.Command && x.Argument == info.ArgString); + // Check if the command is the right one with the right arguments + if (findcommand is null) + { + player.PrintToChat("This command requires no Arguments or you added to many"); + return; } - // This will exit the command if the command has arguments but the client didn't provide any - if (command!.Command.Contains(alias + " ") && info.ArgCount <= 1) + + if (!findcommand.Argument!.Equals(info.ArgString)) + { + player.PrintToChat("Wrong Arguments"); return; + } - if (command!.Permission.PermissionList.Count > 0 && command.Permission != null) - if (!PluginUtilities.RequiresPermissions(player, command.Permission)) - return; + command = findcommand; + } + + // This will exit the command if the command has arguments but the client didn't provide any + if (info.ArgCount <= 1 && !string.IsNullOrEmpty(command.Argument!)) + { + player.PrintToChat("This command requires Arguments"); + return; + } - if(CooldownManager.IsCommandOnCooldown(player, command)) return; + // Check if the player has the permission to use the command + if (command!.Permission?.PermissionList.Count > 0 && command.Permission != null) + if (!_pluginUtilities.RequiresPermissions(player, command.Permission)) + return; + + // Check if the command is on cooldown + if(_cooldownManager.IsCommandOnCooldown(player, command)) return; - CooldownManager.SetCooldown(player, command); + // Set the cooldown for the command if it has a cooldown set + _cooldownManager.SetCooldown(player, command); - MessageManager.SendMessage(player, command); + // Sending the message to the player + _messageManager.SendMessage(player, command); - PluginUtilities.ExecuteServerCommands(command, player); - }); - } + // Execute the client commands + _pluginUtilities.ExecuteClientCommands(command, player); + + // Execute the client commands from the server + _pluginUtilities.ExecuteClientCommandsFromServer(command, player); + + // Execute the server commands + _pluginUtilities.ExecuteServerCommands(command, player); + }); } - public List CheckForDuplicateCommands(List comms) + public void CheckForDuplicateCommands() { - List duplicateCommands = new(); - List commands = new(); - List commandNames = new(); + var comms = _pluginGlobals.CustomCommands; + var duplicateCommands = new List(); + var commandNames = new List(); - foreach (var com in comms) + foreach (var cmd in comms) { - string[] aliases = com.Command.Split(','); + string[] aliases = cmd.Command.Split(','); foreach (var alias in aliases) { if (commandNames.Contains(alias.ToLower())) { - duplicateCommands.Add(com); + duplicateCommands.Add(cmd); continue; } commandNames.Add(alias.ToLower()); @@ -92,25 +116,63 @@ public List CheckForDuplicateCommands(List comms) } if (duplicateCommands.Count == 0) - return comms; + return; - Logger.LogError($"------------------------------------------------------------------------"); - Logger.LogError($"{PluginGlobals.Config.LogPrefix} Duplicate commands found, removing them from the list. Please check your config file for duplicate commands and remove them."); + // Log the duplicate commands + _logger.LogError($"------------------------------------------------------------------------"); + _logger.LogError($"{_pluginGlobals.Config.LogPrefix} Duplicate commands found, removing them from the list. Please check your config file for duplicate commands and remove them."); for (int i = 0; i < comms.Count; i++) { if(duplicateCommands.Contains(comms[i])) { - Logger.LogError($"{PluginGlobals.Config.LogPrefix} Duplicate command found index {i+1}: "); - Logger.LogError($"{PluginGlobals.Config.LogPrefix} - {comms[i].Title} "); - Logger.LogError($"{PluginGlobals.Config.LogPrefix} - {comms[i].Description}"); - Logger.LogError($"{PluginGlobals.Config.LogPrefix} - {comms[i].Command}"); + _logger.LogError($"{_pluginGlobals.Config.LogPrefix} Duplicate command found index {i+1}: "); + _logger.LogError($"{_pluginGlobals.Config.LogPrefix} - {comms[i].Title} "); + _logger.LogError($"{_pluginGlobals.Config.LogPrefix} - {comms[i].Description}"); + _logger.LogError($"{_pluginGlobals.Config.LogPrefix} - {comms[i].Command}"); continue; } - commands.Add(comms[i]); + comms.Add(comms[i]); + } + _logger.LogError($"------------------------------------------------------------------------"); + } + + public void ConvertingCommandsForRegister() + { + var newCmds = new List(); + + foreach (var cmd in _pluginGlobals.CustomCommands) + { + var splitCommands = _pluginUtilities.SplitStringByCommaOrSemicolon(cmd.Command); + splitCommands = _pluginUtilities.AddCSSTagsToAliases(splitCommands.ToList()); + + foreach (var split in splitCommands) + { + var args = split.Split(' '); + + if(args.Length == 1) + { + var newCmd = cmd.Clone() as Commands; + newCmd!.ID = Guid.NewGuid(); + newCmd.Command = split.Trim(); + newCmds.Add(newCmd); + } + else if (args.Length > 1) + { + var newCmd = cmd.Clone() as Commands; + + if (newCmds.Any(p => p.Command.Contains(args[0]))) + newCmd!.IsRegisterable = false; + + newCmd!.ID = Guid.NewGuid(); + newCmd.Command = args[0].Trim(); + args[0] = ""; + newCmd.Argument = string.Join(" ", args).Trim(); + newCmds.Add(newCmd); + } + } } - Logger.LogError($"------------------------------------------------------------------------"); - return commands; + _pluginGlobals.CustomCommands = newCmds; } } \ No newline at end of file diff --git a/CustomCommands/Services/ReplaceTagsFunction.cs b/CustomCommands/Services/ReplaceTagsFunction.cs index efe300b..b52d067 100644 --- a/CustomCommands/Services/ReplaceTagsFunction.cs +++ b/CustomCommands/Services/ReplaceTagsFunction.cs @@ -10,50 +10,57 @@ using CounterStrikeSharp.API.Core.Plugin; namespace CustomCommands.Services; -public class ReplaceTagsFunctions : IReplaceTagsFunctions +public partial class ReplaceTagsFunctions : IReplaceTagsFunctions { - private readonly IPluginGlobals PluginGlobals; - private readonly PluginContext PluginContext; - private readonly ILogger Logger; + private readonly IPluginGlobals _pluginGlobals; + private readonly PluginContext _pluginContext; + private readonly ILogger _logger; + + private static readonly Random _random = new Random(); public ReplaceTagsFunctions(IPluginGlobals PluginGlobals, IPluginContext PluginContext, ILogger Logger) { - this.PluginGlobals = PluginGlobals; - this.PluginContext = (PluginContext as PluginContext)!; - this.Logger = Logger; + _pluginGlobals = PluginGlobals; + _pluginContext = (PluginContext as PluginContext)!; + _logger = Logger; } - public string[] ReplaceTags(string[] input, CCSPlayerController player) + public string[] ReplaceTags(dynamic input, CCSPlayerController player) { - string[] output = new string[input.Length]; + var output = WrappedLine(input); + + for (int i = 0; i < output.Count; i++) + output[i] = ReplaceLanguageTags(output[i]); - for (int i = 0; i < input.Length; i++) + output = WrappedLine(output.ToArray()); + + for (int i = 0; i < output.Count; i++) { - output[i] = ReplaceLanguageTags(input[i]); output[i] = ReplaceMessageTags(output[i], player, false); + output[i] = ReplaceRandomTags(output[i]); output[i] = ReplaceColorTags(output[i]); } - return output; + return output.ToArray(); } + [GeneratedRegex(@"\{LANG=(.*?)\}")] + private static partial Regex ReplaceLanguageTagsRegex(); + public string ReplaceLanguageTags(string input) { - CustomCommands plugin = (PluginContext.Plugin as CustomCommands)!; - - // Define the regex pattern to find "{LANG=...}" - string pattern = @"\{LANG=(.*?)\}"; - // Use Regex to find matches - Match match = Regex.Match(input, pattern); + var match = ReplaceLanguageTagsRegex().Match(input); // Check if a match is found if (match.Success) { // Return the group captured in the regex, which is the string after "=" - string lang = match.Groups[1].Value; - return input.Replace(match.Value, plugin.Localizer[lang] ?? " not found>"); + var lang = match.Groups[1].Value; + var context = (_pluginContext.Plugin as CustomCommands)!; + + return input.Replace(match.Value, context.Localizer[lang] ?? " not found>"); } else { @@ -61,13 +68,83 @@ public string ReplaceLanguageTags(string input) return input; } } + + /// + /// Use regex to find the RNDNO tag pattern {RNDNO={min, max}} + /// + /// + [GeneratedRegex(@"\{RNDNO=\((\d+(?:\.\d+)?),\s*(\d+(?:\.\d+)?)\)\}")] + private static partial Regex ReplaceRandomTagsRegex(); + + public string ReplaceRandomTags(string message) + { + // Replace all occurrences of the RNDNO tag in the message + var match = ReplaceRandomTagsRegex().Match(message); + + // Check if the match is successful + if (!match.Success) + { + return message; // Return original message if no match is found + } + + // Extract min and max from the regex match groups + string minStr = match.Groups[1].Value; + string maxStr = match.Groups[2].Value; + + // Check for empty strings + if (string.IsNullOrWhiteSpace(minStr) || string.IsNullOrWhiteSpace(maxStr)) + { + return message; // Return original message if min or max is empty + } + + // Determine if the min and max are integers or floats + bool isMinFloat = float.TryParse(minStr, out float minFloat); + bool isMaxFloat = float.TryParse(maxStr, out float maxFloat); + + if (isMinFloat && isMaxFloat) + { + // Generate a random float between min and max (inclusive) + float randomFloat = (float)(_random.NextDouble() * (maxFloat - minFloat) + minFloat); + + // Determine the maximum precision from the min and max values + int maxDecimalPlaces = Math.Max(GetDecimalPlaces(minStr), GetDecimalPlaces(maxStr)); + + // Use the determined precision to format the float + message = message.Replace(match.Value, randomFloat.ToString($"F{maxDecimalPlaces}")); + } + else if (int.TryParse(minStr, out int min) && int.TryParse(maxStr, out int max)) + { + /// Generate a random integer between min and max (inclusive) + int randomValue = _random.Next(min, max + 1); // max is exclusive, so add 1 + message = message.Replace(match.Value, randomValue.ToString()); + } + else + { + // If neither min nor max is valid, return the original message + return message; + } + + return message; + } + + // Method to get the number of decimal places in a number string + private static int GetDecimalPlaces(string numberStr) + { + int decimalIndex = numberStr.IndexOf('.'); + if (decimalIndex == -1) + { + return 0; // No decimal point, return 0 + } + return numberStr.Length - decimalIndex - 1; // Count digits after the decimal point + } + public string ReplaceMessageTags(string input, CCSPlayerController player, bool safety = true) { - SteamID steamId = new SteamID(player.SteamID); + var steamId = new SteamID(player.SteamID); Dictionary replacements = new() { - {"{PREFIX}", PluginGlobals.Config.Prefix ?? ""}, + {"{PREFIX}", _pluginGlobals.Config.Prefix ?? ""}, {"{MAP}", NativeAPI.GetMapName() ?? ""}, {"{TIME}", DateTime.Now.ToString("HH:mm:ss") ?? "