From fd69ae3a5755e6cf45bb1021d72626e4c4056084 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:50:36 +0200 Subject: [PATCH 01/37] meh --- SecretAPI/Features/Commands/CommandResult.cs | 18 +++++++++++++ SecretAPI/Features/Commands/CustomCommand.cs | 26 +++++++++++++++++++ .../Features/Commands/CustomCommandHandler.cs | 21 +++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 SecretAPI/Features/Commands/CommandResult.cs create mode 100644 SecretAPI/Features/Commands/CustomCommand.cs create mode 100644 SecretAPI/Features/Commands/CustomCommandHandler.cs diff --git a/SecretAPI/Features/Commands/CommandResult.cs b/SecretAPI/Features/Commands/CommandResult.cs new file mode 100644 index 0000000..e010621 --- /dev/null +++ b/SecretAPI/Features/Commands/CommandResult.cs @@ -0,0 +1,18 @@ +namespace SecretAPI.Features.Commands +{ + /// + /// Gets the result of . + /// + public struct CommandResult + { + /// + /// Gets a value indicating whether parsing was successful. + /// + public bool CouldParse; + + /// + /// If parsing failed, will provide the fail reason, otherwise null. + /// + public string? FailedResponse; + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs new file mode 100644 index 0000000..caf2f7c --- /dev/null +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -0,0 +1,26 @@ +namespace SecretAPI.Features.Commands +{ + using System; + using CommandSystem; + + /// + /// Defines the base of a custom . + /// + public abstract class CustomCommand : ICommand + { + /// + public abstract string Command { get; } + + /// + public abstract string[] Aliases { get; } + + /// + public abstract string Description { get; } + + /// + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + CommandResult result = CustomCommandHandler.TryCall(sender, arguments); + } + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs new file mode 100644 index 0000000..b198f82 --- /dev/null +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -0,0 +1,21 @@ +namespace SecretAPI.Features.Commands +{ + using System; + using CommandSystem; + + /// + /// Handles parsing . + /// + public static class CustomCommandHandler + { + /// + /// Attempts to pass a command message and gives a result. + /// + /// The sender of the command. + /// The arguments provided to the command. + /// The . + public static CommandResult TryCall(ICommandSender sender, ArraySegment arguments) + { + } + } +} \ No newline at end of file From 3021a014cf4ff5dcb7297fa36eb0e057b7be8a7f Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:04:24 +0200 Subject: [PATCH 02/37] idk!!!! --- ...CommandResult.cs => CommandParseResult.cs} | 6 ++--- SecretAPI/Features/Commands/CustomCommand.cs | 4 +-- .../Features/Commands/CustomCommandHandler.cs | 27 ++++++++++++++++--- 3 files changed, 28 insertions(+), 9 deletions(-) rename SecretAPI/Features/Commands/{CommandResult.cs => CommandParseResult.cs} (70%) diff --git a/SecretAPI/Features/Commands/CommandResult.cs b/SecretAPI/Features/Commands/CommandParseResult.cs similarity index 70% rename from SecretAPI/Features/Commands/CommandResult.cs rename to SecretAPI/Features/Commands/CommandParseResult.cs index e010621..6e23b3c 100644 --- a/SecretAPI/Features/Commands/CommandResult.cs +++ b/SecretAPI/Features/Commands/CommandParseResult.cs @@ -1,9 +1,9 @@ namespace SecretAPI.Features.Commands { /// - /// Gets the result of . + /// Gets the result of a . /// - public struct CommandResult + public struct CommandParseResult { /// /// Gets a value indicating whether parsing was successful. @@ -13,6 +13,6 @@ public struct CommandResult /// /// If parsing failed, will provide the fail reason, otherwise null. /// - public string? FailedResponse; + public string FailedResponse; } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index caf2f7c..e3cdf61 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -19,8 +19,6 @@ public abstract class CustomCommand : ICommand /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) - { - CommandResult result = CustomCommandHandler.TryCall(sender, arguments); - } + => CustomCommandHandler.TryCall(this, sender, arguments, out response); } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index b198f82..096f90e 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -9,13 +9,34 @@ public static class CustomCommandHandler { /// - /// Attempts to pass a command message and gives a result. + /// Attempts to call the correct command and gives a result. /// + /// The command currently being called from. /// The sender of the command. /// The arguments provided to the command. - /// The . - public static CommandResult TryCall(ICommandSender sender, ArraySegment arguments) + /// The response to give to the player. + /// Whether the command was a success. + public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySegment arguments, out string response) { + CommandParseResult parseResult = TryParse(command, arguments); + if (!parseResult.CouldParse) + { + response = parseResult.FailedResponse; + return false; + } + } + + public static CommandParseResult TryParse(CustomCommand command, ArraySegment arguments) + { + // IDK!!! + if (arguments.Count < 1) + { + return new CommandParseResult() + { + CouldParse = false, + FailedResponse = "Could not parse.", + }; + } } } } \ No newline at end of file From aec1d0b36dbc14b9e3beedf793c512f127b86273 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:18:06 +0200 Subject: [PATCH 03/37] i hate this ??? --- SecretAPI/Features/Commands/CustomCommand.cs | 5 ++ .../Features/Commands/CustomCommandHandler.cs | 81 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index e3cdf61..097d94d 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -17,6 +17,11 @@ public abstract class CustomCommand : ICommand /// public abstract string Description { get; } + /// + /// Gets an array of the sub commands for this command. + /// + public CustomCommand[] SubCommands { get; } = []; + /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) => CustomCommandHandler.TryCall(this, sender, arguments, out response); diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index 096f90e..a66c26f 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -1,6 +1,9 @@ namespace SecretAPI.Features.Commands { using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; using CommandSystem; /// @@ -28,6 +31,8 @@ public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySe public static CommandParseResult TryParse(CustomCommand command, ArraySegment arguments) { + const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; + // IDK!!! if (arguments.Count < 1) { @@ -37,6 +42,82 @@ public static CommandParseResult TryParse(CustomCommand command, ArraySegment methods = command.GetType().GetMethods(methodFlags).Where(IsValidExecuteMethod); + foreach (MethodInfo method in methods) + { + CommandParseResult result = ValidateAllMethodParameters(method, arguments); + if (result.CouldParse) + { + return new CommandParseResult() + { + CouldParse = true, + FailedResponse = string.Empty, + }; + } + } + } + + private static CommandParseResult ValidateAllMethodParameters(MethodInfo method, ArraySegment arguments) + { + for (int index = 0; index < method.GetParameters().Length; index++) + { + ParameterInfo parameter = method.GetParameters()[index]; + CommandParseResult result = ValidateParameter(parameter, arguments.ElementAtOrDefault(index)); + if (!result.CouldParse) + return result; + } + } + + private static CommandParseResult ValidateParameter(ParameterInfo parameter, string? argument) + { + // if arg doesnt exist & param is optional, then its validated + if (argument == null && parameter.IsOptional) + { + return new CommandParseResult() + { + CouldParse = true, + }; + } + + try + { + Type type = parameter.ParameterType; + + if (type.IsEnum) + { + if (Enum.TryParse(type, argument, true, out object? enumValue)) + { + } + + return false; + } + + return true; + } + catch + { + return false; + } + } + + private static bool IsValidExecuteMethod(MethodInfo method) + { + // isnt an Execute command + if (method.Name != "Execute") + return false; + + ParameterInfo[] parameters = method.GetParameters(); + + // params isnt 3, so its not default + if (parameters.Length != 3) + return true; + + // make sure params arent the types of the original default to prevent infinite loop + return !(parameters[0].ParameterType == typeof(ArraySegment) + && parameters[1].ParameterType == typeof(ICommandSender) + && parameters[2].IsOut + && parameters[2].ParameterType == typeof(string)); } } } \ No newline at end of file From e6b27fa970c07776cc4b2f1bb6f5b351a069a285 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:26:39 +0200 Subject: [PATCH 04/37] properenum validation --- .../Features/Commands/CommandParseResult.cs | 5 +++ .../Features/Commands/CustomCommandHandler.cs | 45 +++++++++---------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/SecretAPI/Features/Commands/CommandParseResult.cs b/SecretAPI/Features/Commands/CommandParseResult.cs index 6e23b3c..65fe7a0 100644 --- a/SecretAPI/Features/Commands/CommandParseResult.cs +++ b/SecretAPI/Features/Commands/CommandParseResult.cs @@ -14,5 +14,10 @@ public struct CommandParseResult /// If parsing failed, will provide the fail reason, otherwise null. /// public string FailedResponse; + + /// + /// The argument for the argument. + /// + public object? ParamArgument; } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index a66c26f..85b3134 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -29,20 +29,16 @@ public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySe } } + /// + /// + /// + /// + /// + /// public static CommandParseResult TryParse(CustomCommand command, ArraySegment arguments) { const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; - // IDK!!! - if (arguments.Count < 1) - { - return new CommandParseResult() - { - CouldParse = false, - FailedResponse = "Could not parse.", - }; - } - IEnumerable methods = command.GetType().GetMethods(methodFlags).Where(IsValidExecuteMethod); foreach (MethodInfo method in methods) { @@ -77,28 +73,31 @@ private static CommandParseResult ValidateParameter(ParameterInfo parameter, str return new CommandParseResult() { CouldParse = true, + ParamArgument = parameter.DefaultValue, }; } - try - { - Type type = parameter.ParameterType; + Type type = parameter.ParameterType; - if (type.IsEnum) + if (type.IsEnum) + { + if (Enum.TryParse(type, argument, true, out object? enumValue)) { - if (Enum.TryParse(type, argument, true, out object? enumValue)) + return new CommandParseResult() { - } - - return false; + CouldParse = true, + ParamArgument = enumValue, + }; } - return true; - } - catch - { - return false; + return new CommandParseResult() + { + CouldParse = false, + FailedResponse = $"Could not pass into valid enum value. Enum required: {type.Name}.", + }; } + + return true; } private static bool IsValidExecuteMethod(MethodInfo method) From 9516c89845c16d69e0a700437884263fbffde00d Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:54:47 +0200 Subject: [PATCH 05/37] stoopid --- .../Features/Commands/CommandParseResult.cs | 2 +- SecretAPI/Features/Commands/CommandResult.cs | 30 +++++++++++++++++++ .../Features/Commands/CustomCommandHandler.cs | 11 +++++-- 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 SecretAPI/Features/Commands/CommandResult.cs diff --git a/SecretAPI/Features/Commands/CommandParseResult.cs b/SecretAPI/Features/Commands/CommandParseResult.cs index 65fe7a0..a243616 100644 --- a/SecretAPI/Features/Commands/CommandParseResult.cs +++ b/SecretAPI/Features/Commands/CommandParseResult.cs @@ -3,7 +3,7 @@ /// /// Gets the result of a . /// - public struct CommandParseResult + internal struct CommandParseResult { /// /// Gets a value indicating whether parsing was successful. diff --git a/SecretAPI/Features/Commands/CommandResult.cs b/SecretAPI/Features/Commands/CommandResult.cs new file mode 100644 index 0000000..62ff5ce --- /dev/null +++ b/SecretAPI/Features/Commands/CommandResult.cs @@ -0,0 +1,30 @@ +namespace SecretAPI.Features.Commands +{ + using System.Reflection; + + /// + /// The result of to know what to do. + /// + internal struct CommandResult + { + /// + /// Gets a value indicating whether parsing was successful. + /// + public bool CouldParse; + + /// + /// If parsing failed, will provide the fail reason, otherwise null. + /// + public string FailedResponse; + + /// + /// If parsing succeded, the method to call with . + /// + public MethodInfo Method; + + /// + /// If parsing succeeded, the arguments provided. + /// + public object[]? ProvidedArguments; + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index 85b3134..f4abfa1 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -21,12 +21,17 @@ public static class CustomCommandHandler /// Whether the command was a success. public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySegment arguments, out string response) { - CommandParseResult parseResult = TryParse(command, arguments); + CommandResult parseResult = TryParse(command, arguments); if (!parseResult.CouldParse) { response = parseResult.FailedResponse; return false; } + + parseResult.Method.Invoke(null, parseResult.ProvidedArguments); + + // TODO: get result & put it into response + return true; } /// @@ -35,7 +40,7 @@ public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySe /// /// /// - public static CommandParseResult TryParse(CustomCommand command, ArraySegment arguments) + public static CommandResult TryParse(CustomCommand command, ArraySegment arguments) { const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; @@ -45,7 +50,7 @@ public static CommandParseResult TryParse(CustomCommand command, ArraySegment Date: Wed, 6 Aug 2025 18:30:52 +0200 Subject: [PATCH 06/37] ok slight more --- .../Attribute/ExecuteCommandAttribute.cs | 12 +++++++ SecretAPI/Features/Commands/CustomCommand.cs | 2 +- .../Features/Commands/CustomCommandHandler.cs | 32 ++++++++----------- 3 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 SecretAPI/Attribute/ExecuteCommandAttribute.cs diff --git a/SecretAPI/Attribute/ExecuteCommandAttribute.cs b/SecretAPI/Attribute/ExecuteCommandAttribute.cs new file mode 100644 index 0000000..213607f --- /dev/null +++ b/SecretAPI/Attribute/ExecuteCommandAttribute.cs @@ -0,0 +1,12 @@ +namespace SecretAPI.Attribute +{ + using System; + + /// + /// Attribute used to identify a method as a possible execution result. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class ExecuteCommandAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index 097d94d..05d6485 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -20,7 +20,7 @@ public abstract class CustomCommand : ICommand /// /// Gets an array of the sub commands for this command. /// - public CustomCommand[] SubCommands { get; } = []; + public virtual CustomCommand[] SubCommands { get; } = []; /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index f4abfa1..85a26e7 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -5,12 +5,15 @@ using System.Linq; using System.Reflection; using CommandSystem; + using SecretAPI.Attribute; /// /// Handles parsing . /// public static class CustomCommandHandler { + private static Dictionary commandExecuteMethods = new(); + /// /// Attempts to call the correct command and gives a result. /// @@ -40,12 +43,9 @@ public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySe /// /// /// - public static CommandResult TryParse(CustomCommand command, ArraySegment arguments) + private static CommandResult TryParse(CustomCommand command, ArraySegment arguments) { - const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; - - IEnumerable methods = command.GetType().GetMethods(methodFlags).Where(IsValidExecuteMethod); - foreach (MethodInfo method in methods) + foreach (MethodInfo method in GetMethods(command)) { CommandParseResult result = ValidateAllMethodParameters(method, arguments); if (result.CouldParse) @@ -105,23 +105,17 @@ private static CommandParseResult ValidateParameter(ParameterInfo parameter, str return true; } - private static bool IsValidExecuteMethod(MethodInfo method) + private static MethodInfo[] GetMethods(CustomCommand command) { - // isnt an Execute command - if (method.Name != "Execute") - return false; - - ParameterInfo[] parameters = method.GetParameters(); + const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; - // params isnt 3, so its not default - if (parameters.Length != 3) - return true; + if (!commandExecuteMethods.TryGetValue(command, out MethodInfo[] methods)) + { + methods = command.GetType().GetMethods(methodFlags).Where(m => m.GetCustomAttribute() != null).ToArray(); + commandExecuteMethods.Add(command, methods); + } - // make sure params arent the types of the original default to prevent infinite loop - return !(parameters[0].ParameterType == typeof(ArraySegment) - && parameters[1].ParameterType == typeof(ICommandSender) - && parameters[2].IsOut - && parameters[2].ParameterType == typeof(string)); + return methods; } } } \ No newline at end of file From ecf4152a3bd4b4bd15cd2ac5bb532fcb68ca3e12 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:45:25 +0200 Subject: [PATCH 07/37] rahhh --- .../Features/Commands/CustomCommandHandler.cs | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index 85a26e7..341406d 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using CommandSystem; + using LabApi.Features.Wrappers; using SecretAPI.Attribute; /// @@ -12,6 +13,11 @@ /// public static class CustomCommandHandler { + /// + /// + /// + public const string SelfPlayerName = "self"; + private static Dictionary commandExecuteMethods = new(); /// @@ -24,7 +30,9 @@ public static class CustomCommandHandler /// Whether the command was a success. public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySegment arguments, out string response) { - CommandResult parseResult = TryParse(command, arguments); + Player senderPlayer = Player.Get(sender) ?? Server.Host!; + + CommandResult parseResult = TryParse(command, senderPlayer, arguments); if (!parseResult.CouldParse) { response = parseResult.FailedResponse; @@ -37,17 +45,11 @@ public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySe return true; } - /// - /// - /// - /// - /// - /// - private static CommandResult TryParse(CustomCommand command, ArraySegment arguments) + private static CommandResult TryParse(CustomCommand command, Player sender, ArraySegment arguments) { foreach (MethodInfo method in GetMethods(command)) { - CommandParseResult result = ValidateAllMethodParameters(method, arguments); + CommandParseResult result = ValidateAllMethodParameters(method, sender, arguments); if (result.CouldParse) { return new CommandResult() @@ -59,18 +61,18 @@ private static CommandResult TryParse(CustomCommand command, ArraySegment arguments) + private static CommandParseResult ValidateAllMethodParameters(MethodInfo method, Player sender, ArraySegment arguments) { for (int index = 0; index < method.GetParameters().Length; index++) { ParameterInfo parameter = method.GetParameters()[index]; - CommandParseResult result = ValidateParameter(parameter, arguments.ElementAtOrDefault(index)); + CommandParseResult result = ValidateParameter(parameter, sender, arguments.ElementAtOrDefault(index)); if (!result.CouldParse) return result; } } - private static CommandParseResult ValidateParameter(ParameterInfo parameter, string? argument) + private static CommandParseResult ValidateParameter(ParameterInfo parameter, Player sender, string? argument) { // if arg doesnt exist & param is optional, then its validated if (argument == null && parameter.IsOptional) @@ -102,6 +104,26 @@ private static CommandParseResult ValidateParameter(ParameterInfo parameter, str }; } + if (parameter.Name == SelfPlayerName) + { + if (typeof(Player).IsAssignableFrom(parameter.ParameterType)) + { + return new CommandParseResult() + { + CouldParse = true, + ParamArgument = sender, + }; + } + else if (typeof(ReferenceHub).IsAssignableFrom(parameter.ParameterType)) + { + return new CommandParseResult() + { + CouldParse = true, + ParamArgument = sender.ReferenceHub, + }; + } + } + return true; } From ba7b32ca7c9e6ee7aba65a20f4cb2be022093561 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:06:50 +0200 Subject: [PATCH 08/37] command example --- .../Commands/ExampleExplodeCommand.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 SecretAPI.Examples/Commands/ExampleExplodeCommand.cs diff --git a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs new file mode 100644 index 0000000..7781dc8 --- /dev/null +++ b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs @@ -0,0 +1,27 @@ +namespace SecretAPI.Examples.Commands +{ + using LabApi.Features.Wrappers; + using SecretAPI.Attribute; + using SecretAPI.Features.Commands; + + /// + /// An example of a that explodes a player. + /// + public class ExampleExplodeCommand : CustomCommand + { + /// + public override string Command { get; } = "explode"; + + /// + public override string[] Aliases { get; } = []; + + /// + public override string Description { get; } = "Explodes a player"; + + [ExecuteCommand] + private void Run(Player self, Player target) + { + TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, self); + } + } +} \ No newline at end of file From 7775cd2ce970dab028e6cc3353a92705d7f7f521 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:56:37 +0200 Subject: [PATCH 09/37] idfk --- .../Commands/ExampleExplodeCommand.cs | 4 +- ...mandResult.cs => CommandArgParseResult.cs} | 15 +--- .../Commands/CommandMethodParseResult.cs | 15 ++++ .../Features/Commands/CommandParseResult.cs | 13 ++- .../Features/Commands/CustomCommandHandler.cs | 85 +++++++++++++------ 5 files changed, 92 insertions(+), 40 deletions(-) rename SecretAPI/Features/Commands/{CommandResult.cs => CommandArgParseResult.cs} (51%) create mode 100644 SecretAPI/Features/Commands/CommandMethodParseResult.cs diff --git a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs index 7781dc8..fdf2036 100644 --- a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs @@ -19,9 +19,9 @@ public class ExampleExplodeCommand : CustomCommand public override string Description { get; } = "Explodes a player"; [ExecuteCommand] - private void Run(Player self, Player target) + private void Run(Player sender, Player target) { - TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, self); + TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); } } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CommandResult.cs b/SecretAPI/Features/Commands/CommandArgParseResult.cs similarity index 51% rename from SecretAPI/Features/Commands/CommandResult.cs rename to SecretAPI/Features/Commands/CommandArgParseResult.cs index 62ff5ce..a7b9963 100644 --- a/SecretAPI/Features/Commands/CommandResult.cs +++ b/SecretAPI/Features/Commands/CommandArgParseResult.cs @@ -1,11 +1,9 @@ namespace SecretAPI.Features.Commands { - using System.Reflection; - /// - /// The result of to know what to do. + /// Gets the result of a . /// - internal struct CommandResult + internal struct CommandArgParseResult { /// /// Gets a value indicating whether parsing was successful. @@ -18,13 +16,8 @@ internal struct CommandResult public string FailedResponse; /// - /// If parsing succeded, the method to call with . - /// - public MethodInfo Method; - - /// - /// If parsing succeeded, the arguments provided. + /// The argument for the argument. /// - public object[]? ProvidedArguments; + public object ParamArgument; } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CommandMethodParseResult.cs b/SecretAPI/Features/Commands/CommandMethodParseResult.cs new file mode 100644 index 0000000..f5999f5 --- /dev/null +++ b/SecretAPI/Features/Commands/CommandMethodParseResult.cs @@ -0,0 +1,15 @@ +namespace SecretAPI.Features.Commands +{ + /// + /// Defines the return type of . + /// + internal struct CommandMethodParseResult + { +#pragma warning disable SA1600 // Elements should be documented + internal bool CouldParse; + + internal string FailedResponse; + + internal object[]? Arguments; + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CommandParseResult.cs b/SecretAPI/Features/Commands/CommandParseResult.cs index a243616..4b56236 100644 --- a/SecretAPI/Features/Commands/CommandParseResult.cs +++ b/SecretAPI/Features/Commands/CommandParseResult.cs @@ -1,7 +1,9 @@ namespace SecretAPI.Features.Commands { + using System.Reflection; + /// - /// Gets the result of a . + /// The result of to know what to do. /// internal struct CommandParseResult { @@ -16,8 +18,13 @@ internal struct CommandParseResult public string FailedResponse; /// - /// The argument for the argument. + /// If parsing succeded, the method to call with . + /// + public MethodInfo Method; + + /// + /// If parsing succeeded, the arguments provided. /// - public object? ParamArgument; + public object[]? ProvidedArguments; } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index 341406d..524781d 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -6,6 +6,7 @@ using System.Reflection; using CommandSystem; using LabApi.Features.Wrappers; + using NorthwoodLib.Pools; using SecretAPI.Attribute; /// @@ -14,9 +15,9 @@ public static class CustomCommandHandler { /// - /// + /// The name of the or argument represensing the command sender. /// - public const string SelfPlayerName = "self"; + public const string SenderPlayerName = "sender"; private static Dictionary commandExecuteMethods = new(); @@ -32,55 +33,86 @@ public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySe { Player senderPlayer = Player.Get(sender) ?? Server.Host!; - CommandResult parseResult = TryParse(command, senderPlayer, arguments); - if (!parseResult.CouldParse) + CommandParseResult parseParseResult = TryParse(command, senderPlayer, arguments); + if (!parseParseResult.CouldParse) { - response = parseResult.FailedResponse; + response = parseParseResult.FailedResponse; return false; } - parseResult.Method.Invoke(null, parseResult.ProvidedArguments); + parseParseResult.Method.Invoke(null, parseParseResult.ProvidedArguments); // TODO: get result & put it into response return true; } - private static CommandResult TryParse(CustomCommand command, Player sender, ArraySegment arguments) + private static CommandParseResult TryParse(CustomCommand command, Player sender, ArraySegment arguments) { foreach (MethodInfo method in GetMethods(command)) { - CommandParseResult result = ValidateAllMethodParameters(method, sender, arguments); + CommandMethodParseResult result = ValidateAllMethodParameters(method, sender, arguments); + + // parsed correctly, return with correct arguments if (result.CouldParse) { - return new CommandResult() + return new CommandParseResult() { CouldParse = true, FailedResponse = string.Empty, + Method = method, + ProvidedArguments = result.Arguments, }; } + + // failed to parse, return and show failure + return new CommandParseResult() + { + CouldParse = false, + FailedResponse = result.FailedResponse, + }; } } - private static CommandParseResult ValidateAllMethodParameters(MethodInfo method, Player sender, ArraySegment arguments) + private static CommandMethodParseResult ValidateAllMethodParameters(MethodInfo method, Player sender, ArraySegment arguments) { - for (int index = 0; index < method.GetParameters().Length; index++) + ParameterInfo[] parameters = method.GetParameters(); + List returns = ListPool.Shared.Rent(); + + for (int index = 0; index < parameters.Length; index++) { - ParameterInfo parameter = method.GetParameters()[index]; - CommandParseResult result = ValidateParameter(parameter, sender, arguments.ElementAtOrDefault(index)); - if (!result.CouldParse) - return result; + ParameterInfo parameter = parameters[index]; + CommandArgParseResult validateResult = ValidateParameter(parameter, sender, arguments.ElementAtOrDefault(index)); + if (!validateResult.CouldParse) + { + return new CommandMethodParseResult() + { + CouldParse = false, + FailedResponse = validateResult.FailedResponse, + }; + } + + returns.Add(validateResult.ParamArgument); } + + CommandMethodParseResult result = new() + { + CouldParse = true, + Arguments = returns.ToArray(), + }; + + ListPool.Shared.Return(returns); + return result; } - private static CommandParseResult ValidateParameter(ParameterInfo parameter, Player sender, string? argument) + private static CommandArgParseResult ValidateParameter(ParameterInfo parameter, Player sender, string? argument) { // if arg doesnt exist & param is optional, then its validated if (argument == null && parameter.IsOptional) { - return new CommandParseResult() + return new CommandArgParseResult() { CouldParse = true, - ParamArgument = parameter.DefaultValue, + ParamArgument = parameter.DefaultValue!, }; } @@ -90,25 +122,25 @@ private static CommandParseResult ValidateParameter(ParameterInfo parameter, Pla { if (Enum.TryParse(type, argument, true, out object? enumValue)) { - return new CommandParseResult() + return new CommandArgParseResult() { CouldParse = true, ParamArgument = enumValue, }; } - return new CommandParseResult() + return new CommandArgParseResult() { CouldParse = false, FailedResponse = $"Could not pass into valid enum value. Enum required: {type.Name}.", }; } - if (parameter.Name == SelfPlayerName) + if (parameter.Name == SenderPlayerName) { if (typeof(Player).IsAssignableFrom(parameter.ParameterType)) { - return new CommandParseResult() + return new CommandArgParseResult() { CouldParse = true, ParamArgument = sender, @@ -116,7 +148,7 @@ private static CommandParseResult ValidateParameter(ParameterInfo parameter, Pla } else if (typeof(ReferenceHub).IsAssignableFrom(parameter.ParameterType)) { - return new CommandParseResult() + return new CommandArgParseResult() { CouldParse = true, ParamArgument = sender.ReferenceHub, @@ -124,7 +156,12 @@ private static CommandParseResult ValidateParameter(ParameterInfo parameter, Pla } } - return true; + // all parsing failed + return new CommandArgParseResult() + { + CouldParse = false, + FailedResponse = $"Failed to parse {argument ?? null} into type of {parameter.ParameterType.Name}.", + }; } private static MethodInfo[] GetMethods(CustomCommand command) From 2fb2ca6270b2a68953dd67032fb782c81b2421a1 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:28:24 +0100 Subject: [PATCH 10/37] .Parsing & .Attributes namespaces --- .../Commands/Attributes/CommandSenderAttribute.cs | 9 +++++++++ SecretAPI/Features/Commands/CustomCommandHandler.cs | 1 + .../Commands/{ => Parsing}/CommandArgParseResult.cs | 2 +- .../Commands/{ => Parsing}/CommandMethodParseResult.cs | 2 +- .../Commands/{ => Parsing}/CommandParseResult.cs | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs rename SecretAPI/Features/Commands/{ => Parsing}/CommandArgParseResult.cs (92%) rename SecretAPI/Features/Commands/{ => Parsing}/CommandMethodParseResult.cs (88%) rename SecretAPI/Features/Commands/{ => Parsing}/CommandParseResult.cs (94%) diff --git a/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs b/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs new file mode 100644 index 0000000..8e0cdf1 --- /dev/null +++ b/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs @@ -0,0 +1,9 @@ +namespace SecretAPI.Features.Commands.Attributes +{ + using System; + + [AttributeUsage(AttributeTargets.Parameter)] + public class CommandSenderAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index 524781d..4f6a931 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -8,6 +8,7 @@ using LabApi.Features.Wrappers; using NorthwoodLib.Pools; using SecretAPI.Attribute; + using SecretAPI.Features.Commands.Parsing; /// /// Handles parsing . diff --git a/SecretAPI/Features/Commands/CommandArgParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs similarity index 92% rename from SecretAPI/Features/Commands/CommandArgParseResult.cs rename to SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs index a7b9963..bf8cc5c 100644 --- a/SecretAPI/Features/Commands/CommandArgParseResult.cs +++ b/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands +namespace SecretAPI.Features.Commands.Parsing { /// /// Gets the result of a . diff --git a/SecretAPI/Features/Commands/CommandMethodParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs similarity index 88% rename from SecretAPI/Features/Commands/CommandMethodParseResult.cs rename to SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs index f5999f5..e774185 100644 --- a/SecretAPI/Features/Commands/CommandMethodParseResult.cs +++ b/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands +namespace SecretAPI.Features.Commands.Parsing { /// /// Defines the return type of . diff --git a/SecretAPI/Features/Commands/CommandParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs similarity index 94% rename from SecretAPI/Features/Commands/CommandParseResult.cs rename to SecretAPI/Features/Commands/Parsing/CommandParseResult.cs index 4b56236..84a152d 100644 --- a/SecretAPI/Features/Commands/CommandParseResult.cs +++ b/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands +namespace SecretAPI.Features.Commands.Parsing { using System.Reflection; From b8b5e5df124bf2fd6ba9ee3756322122ace99b32 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:29:43 +0100 Subject: [PATCH 11/37] Default to no alias --- SecretAPI/Features/Commands/CustomCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index 05d6485..d3d2db3 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -12,10 +12,10 @@ public abstract class CustomCommand : ICommand public abstract string Command { get; } /// - public abstract string[] Aliases { get; } + public abstract string Description { get; } /// - public abstract string Description { get; } + public virtual string[] Aliases { get; } = []; /// /// Gets an array of the sub commands for this command. From fc9ef22d9171ddfced00b2d8da519ea15c07624d Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:28:25 +0100 Subject: [PATCH 12/37] Source generator --- .../CustomCommandGenerator.cs | 44 +++++++++++++++++++ .../SecretAPI.CodeGeneration.csproj | 22 ++++++++++ 2 files changed, 66 insertions(+) create mode 100644 SecretAPI.CodeGeneration/CustomCommandGenerator.cs create mode 100644 SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs new file mode 100644 index 0000000..9a20ece --- /dev/null +++ b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs @@ -0,0 +1,44 @@ +namespace SecretAPI.CodeGeneration; + +using System.CodeDom.Compiler; +using Microsoft.CodeAnalysis; +using System.IO; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +/// +/// Hello. +/// +[Generator] +public class CustomCommandGenerator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider classProvider = context.SyntaxProvider.CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax, + static (context, cancellationToken) => + { + if (context.Node is not ClassDeclarationSyntax classDecl) + return null; + + return context.SemanticModel.GetDeclaredSymbol(classDecl, cancellationToken) as INamedTypeSymbol; + }) + .Where(static cls => cls != null); + + context.RegisterSourceOutput(classProvider, Generate); + } + + private static void Generate(SourceProductionContext ctx, INamedTypeSymbol? symbol) + { + if (symbol == null) + return; + + using StringWriter writer = new(); + using IndentedTextWriter indentWriter = new(writer); + + indentWriter.WriteLine($"// {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + indentWriter.Write($"//{DateTime.Now::yyyy-MM-dd HH:mm:ss}"); + + // ctx.AddSource($"{symbol.ContainingNamespace}.{symbol.Name}.g.cs", writer.ToString()); + } +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj b/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj new file mode 100644 index 0000000..7e01223 --- /dev/null +++ b/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.0 + 10 + enable + enable + + + + true + false + true + Analyzer + + + + + + + + From 646d74ecd569a3cf3988ee61e0e9dc848c97a9bc Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:28:39 +0100 Subject: [PATCH 13/37] Source generator part 2 --- .../Commands/ExampleExplodeCommand.cs | 21 ++++----- .../Commands/ExampleParentCommand.cs | 30 +++++++++++++ SecretAPI.Examples/SecretAPI.Examples.csproj | 1 + SecretAPI.sln | 6 +++ .../Attributes/CommandSenderAttribute.cs | 6 +++ .../Attributes}/ExecuteCommandAttribute.cs | 2 +- SecretAPI/Features/Commands/CustomCommand.cs | 11 +++-- .../Commands/CustomCommandGenerator.cs | 44 +++++++++++++++++++ .../Features/Commands/CustomCommandHandler.cs | 4 +- .../Commands/Parsing/CommandArgParseResult.cs | 4 +- .../Parsing/CommandMethodParseResult.cs | 4 +- .../Commands/Parsing/CommandParseResult.cs | 4 +- SecretAPI/SecretAPI.csproj | 3 ++ 13 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 SecretAPI.Examples/Commands/ExampleParentCommand.cs rename SecretAPI/{Attribute => Features/Commands/Attributes}/ExecuteCommandAttribute.cs (83%) create mode 100644 SecretAPI/Features/Commands/CustomCommandGenerator.cs diff --git a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs index fdf2036..959511c 100644 --- a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs @@ -1,27 +1,22 @@ -namespace SecretAPI.Examples.Commands +/*namespace SecretAPI.Examples.Commands { using LabApi.Features.Wrappers; - using SecretAPI.Attribute; using SecretAPI.Features.Commands; + using SecretAPI.Features.Commands.Attributes; /// - /// An example of a that explodes a player. + /// An example subcommand for . /// public class ExampleExplodeCommand : CustomCommand { /// - public override string Command { get; } = "explode"; + public override string Command => "explode"; /// - public override string[] Aliases { get; } = []; - - /// - public override string Description { get; } = "Explodes a player"; + public override string Description => "Explodes a player!"; [ExecuteCommand] - private void Run(Player sender, Player target) - { - TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); - } + private void Explode(Player sender, Player target) + => TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/SecretAPI.Examples/Commands/ExampleParentCommand.cs b/SecretAPI.Examples/Commands/ExampleParentCommand.cs new file mode 100644 index 0000000..4673a51 --- /dev/null +++ b/SecretAPI.Examples/Commands/ExampleParentCommand.cs @@ -0,0 +1,30 @@ +/*namespace SecretAPI.Examples.Commands +{ + using LabApi.Features.Wrappers; + using SecretAPI.Features.Commands; + using SecretAPI.Features.Commands.Attributes; + + /// + /// An example of a that explodes a player. + /// + public class ExampleParentCommand : CustomCommand + { + /// + public override string Command => "exampleparent"; + + /// + public override string Description => "Example of a parent command, handling some sub commands."; + + /// + public override string[] Aliases { get; } = []; + + /// + public override CustomCommand[] SubCommands { get; } = [new ExampleExplodeCommand()]; + + [ExecuteCommand] + private void Run([CommandSender] Player sender, Player target) + { + TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); + } + } +}*/ \ No newline at end of file diff --git a/SecretAPI.Examples/SecretAPI.Examples.csproj b/SecretAPI.Examples/SecretAPI.Examples.csproj index dfced1d..a51d796 100644 --- a/SecretAPI.Examples/SecretAPI.Examples.csproj +++ b/SecretAPI.Examples/SecretAPI.Examples.csproj @@ -15,6 +15,7 @@ + diff --git a/SecretAPI.sln b/SecretAPI.sln index 4ed659a..4ab044c 100644 --- a/SecretAPI.sln +++ b/SecretAPI.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI", "SecretAPI\Secr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI.Examples", "SecretAPI.Examples\SecretAPI.Examples.csproj", "{0064C982-5FE1-4B65-82F9-2EEF85651188}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI.CodeGeneration", "SecretAPI.CodeGeneration\SecretAPI.CodeGeneration.csproj", "{8A490E06-9D85-43B5-A886-5B5BB14172D9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {0064C982-5FE1-4B65-82F9-2EEF85651188}.Debug|Any CPU.Build.0 = Debug|Any CPU {0064C982-5FE1-4B65-82F9-2EEF85651188}.Release|Any CPU.ActiveCfg = Release|Any CPU {0064C982-5FE1-4B65-82F9-2EEF85651188}.Release|Any CPU.Build.0 = Release|Any CPU + {8A490E06-9D85-43B5-A886-5B5BB14172D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A490E06-9D85-43B5-A886-5B5BB14172D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A490E06-9D85-43B5-A886-5B5BB14172D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A490E06-9D85-43B5-A886-5B5BB14172D9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs b/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs index 8e0cdf1..cb85204 100644 --- a/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs +++ b/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs @@ -1,7 +1,13 @@ namespace SecretAPI.Features.Commands.Attributes { using System; + using CommandSystem; + using LabApi.Features.Wrappers; + /// + /// Defines a parameter as accepting the command sender. + /// + /// this must be , or . [AttributeUsage(AttributeTargets.Parameter)] public class CommandSenderAttribute : Attribute { diff --git a/SecretAPI/Attribute/ExecuteCommandAttribute.cs b/SecretAPI/Features/Commands/Attributes/ExecuteCommandAttribute.cs similarity index 83% rename from SecretAPI/Attribute/ExecuteCommandAttribute.cs rename to SecretAPI/Features/Commands/Attributes/ExecuteCommandAttribute.cs index 213607f..5210527 100644 --- a/SecretAPI/Attribute/ExecuteCommandAttribute.cs +++ b/SecretAPI/Features/Commands/Attributes/ExecuteCommandAttribute.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Attribute +namespace SecretAPI.Features.Commands.Attributes { using System; diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index d3d2db3..1a8db9d 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands +/*namespace SecretAPI.Features.Commands { using System; using CommandSystem; @@ -6,7 +6,7 @@ /// /// Defines the base of a custom . /// - public abstract class CustomCommand : ICommand + public abstract partial class CustomCommand : ICommand { /// public abstract string Command { get; } @@ -24,6 +24,9 @@ public abstract class CustomCommand : ICommand /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) - => CustomCommandHandler.TryCall(this, sender, arguments, out response); + => ExecuteGenerated(arguments, sender, out response); // CustomCommandHandler.TryCall(this, sender, arguments, out response); + + /// + protected abstract partial bool ExecuteGenerated(ArraySegment arguments, ICommandSender sender, out string response); } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandGenerator.cs b/SecretAPI/Features/Commands/CustomCommandGenerator.cs new file mode 100644 index 0000000..fa1cd91 --- /dev/null +++ b/SecretAPI/Features/Commands/CustomCommandGenerator.cs @@ -0,0 +1,44 @@ +/*namespace SecretAPI.Features.Commands +{ + using System.CodeDom.Compiler; + using System.IO; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp.Syntax; + + /// + /// Hello. + /// + [Generator] + internal class CustomCommandGenerator : IIncrementalGenerator + { + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider classProvider = context.SyntaxProvider.CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax, + static (context, cancellationToken) => + { + if (context.Node is not ClassDeclarationSyntax classDecl) + return null; + + return context.SemanticModel.GetDeclaredSymbol(classDecl, cancellationToken) as INamedTypeSymbol; + }) + .Where(static cls => cls != null); + + context.RegisterSourceOutput(classProvider, Generate); + } + + private static void Generate(SourceProductionContext ctx, INamedTypeSymbol? symbol) + { + if (symbol == null) + return; + + using StringWriter writer = new(); + using IndentedTextWriter indentWriter = new(writer); + + indentWriter.Write("hello"); + + ctx.AddSource($"{symbol.ContainingNamespace}.{symbol.Name}.Generated.cs", writer.ToString()); + } + } +}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs index 4f6a931..3066db9 100644 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ b/SecretAPI/Features/Commands/CustomCommandHandler.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands +/*namespace SecretAPI.Features.Commands { using System; using System.Collections.Generic; @@ -178,4 +178,4 @@ private static MethodInfo[] GetMethods(CustomCommand command) return methods; } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs index bf8cc5c..27e79ca 100644 --- a/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs +++ b/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands.Parsing +/*namespace SecretAPI.Features.Commands.Parsing { /// /// Gets the result of a . @@ -20,4 +20,4 @@ internal struct CommandArgParseResult /// public object ParamArgument; } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs index e774185..19e79ee 100644 --- a/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs +++ b/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands.Parsing +/*namespace SecretAPI.Features.Commands.Parsing { /// /// Defines the return type of . @@ -12,4 +12,4 @@ internal struct CommandMethodParseResult internal object[]? Arguments; } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs index 84a152d..e2c770e 100644 --- a/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs +++ b/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands.Parsing +/*namespace SecretAPI.Features.Commands.Parsing { using System.Reflection; @@ -27,4 +27,4 @@ internal struct CommandParseResult /// public object[]? ProvidedArguments; } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj index ca6f6fa..f6fe450 100644 --- a/SecretAPI/SecretAPI.csproj +++ b/SecretAPI/SecretAPI.csproj @@ -17,6 +17,7 @@ git https://github.com/Misfiy/SecretAPI README.md + true @@ -31,6 +32,8 @@ + + From e5369ca4f2af6b4b3cce5b81d029d5bb64b7d292 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:41:43 +0100 Subject: [PATCH 14/37] Add some extra stuff (some of it works!!) --- .../CustomCommandGenerator.cs | 43 +++++++++++++-- SecretAPI.CodeGeneration/WritingUtils.cs | 53 +++++++++++++++++++ .../Commands/ExampleExplodeCommand.cs | 6 +-- .../Commands/ExampleParentCommand.cs | 6 +-- SecretAPI/Features/Commands/CustomCommand.cs | 12 +++-- 5 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 SecretAPI.CodeGeneration/WritingUtils.cs diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs index 9a20ece..72b4765 100644 --- a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs @@ -6,11 +6,14 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; /// -/// Hello. +/// Code generator for custom commands, creating validation etc. /// [Generator] public class CustomCommandGenerator : IIncrementalGenerator { + private const string CommandName = "CustomCommand"; + private const string ExecuteMethodName = "ExecuteGenerated"; + /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -33,12 +36,44 @@ private static void Generate(SourceProductionContext ctx, INamedTypeSymbol? symb if (symbol == null) return; + if (symbol.IsAbstract) + return; + + if (symbol.BaseType?.Name != CommandName) + return; + using StringWriter writer = new(); using IndentedTextWriter indentWriter = new(writer); - indentWriter.WriteLine($"// {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); - indentWriter.Write($"//{DateTime.Now::yyyy-MM-dd HH:mm:ss}"); + indentWriter.WriteGeneratedText() + .WriteNamespace(symbol, true) + .WriteUsings("System", "CommandSystem"); + + indentWriter.WriteLine($"partial class {symbol.Name}"); + indentWriter.WriteLine("{"); + indentWriter.Indent++; + + indentWriter.WriteLine($"protected override bool {ExecuteMethodName}("); + indentWriter.Indent++; + indentWriter.WriteLine("ArraySegment arguments,"); + indentWriter.WriteLine("ICommandSender sender,"); + indentWriter.WriteLine("out string response)"); + indentWriter.Indent--; + indentWriter.WriteLine("{"); + indentWriter.Indent++; + + indentWriter.WriteLine("response = \"Command not implemented.\";"); + indentWriter.WriteLine("return false;"); + + indentWriter.Indent--; + indentWriter.WriteLine("}"); + + indentWriter.Indent--; + indentWriter.WriteLine("}"); + + indentWriter.Indent--; + indentWriter.WriteLine("}"); - // ctx.AddSource($"{symbol.ContainingNamespace}.{symbol.Name}.g.cs", writer.ToString()); + ctx.AddSource($"{symbol.ContainingNamespace}.{symbol.Name}.g.cs", writer.ToString()); } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/WritingUtils.cs b/SecretAPI.CodeGeneration/WritingUtils.cs new file mode 100644 index 0000000..682f55f --- /dev/null +++ b/SecretAPI.CodeGeneration/WritingUtils.cs @@ -0,0 +1,53 @@ +namespace SecretAPI.CodeGeneration; + +using System.CodeDom.Compiler; +using Microsoft.CodeAnalysis; + +public static class WritingUtils +{ + public static string GetAccessibilityString(this Accessibility accessibility) + { + return accessibility switch + { + Accessibility.Private => "private", + Accessibility.ProtectedAndInternal => "protected internal", + Accessibility.Protected => "protected", + Accessibility.Internal => "internal", + Accessibility.Public => "public", + _ => throw new ArgumentOutOfRangeException(nameof(accessibility), accessibility, "Accessibility not supported") + }; + } + + public static IndentedTextWriter WriteGeneratedText(this IndentedTextWriter writer) + { + writer.WriteLine("// "); + writer.WriteLine($"// Generated {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + writer.WriteLine(); + return writer; + } + + public static IndentedTextWriter WriteNamespace(this IndentedTextWriter writer, INamedTypeSymbol symbol, bool startBrace) + { + writer.WriteLine($"namespace {symbol.ContainingNamespace}"); + if (startBrace) + { + writer.WriteLine("{"); + writer.Indent++; + } + + return writer; + } + + public static IndentedTextWriter WriteUsings(this IndentedTextWriter writer, params string[] usings) + { + if (usings.Length == 0) + return writer; + + foreach (string @using in usings) + writer.WriteLine($"using {@using};"); + + writer.WriteLine(); + + return writer; + } +} \ No newline at end of file diff --git a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs index 959511c..815bf54 100644 --- a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs @@ -1,4 +1,4 @@ -/*namespace SecretAPI.Examples.Commands +namespace SecretAPI.Examples.Commands { using LabApi.Features.Wrappers; using SecretAPI.Features.Commands; @@ -7,7 +7,7 @@ /// /// An example subcommand for . /// - public class ExampleExplodeCommand : CustomCommand + public partial class ExampleExplodeCommand : CustomCommand { /// public override string Command => "explode"; @@ -19,4 +19,4 @@ public class ExampleExplodeCommand : CustomCommand private void Explode(Player sender, Player target) => TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/SecretAPI.Examples/Commands/ExampleParentCommand.cs b/SecretAPI.Examples/Commands/ExampleParentCommand.cs index 4673a51..db74495 100644 --- a/SecretAPI.Examples/Commands/ExampleParentCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleParentCommand.cs @@ -1,4 +1,4 @@ -/*namespace SecretAPI.Examples.Commands +namespace SecretAPI.Examples.Commands { using LabApi.Features.Wrappers; using SecretAPI.Features.Commands; @@ -7,7 +7,7 @@ /// /// An example of a that explodes a player. /// - public class ExampleParentCommand : CustomCommand + public partial class ExampleParentCommand : CustomCommand { /// public override string Command => "exampleparent"; @@ -27,4 +27,4 @@ private void Run([CommandSender] Player sender, Player target) TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); } } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index 1a8db9d..357ed13 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -1,4 +1,4 @@ -/*namespace SecretAPI.Features.Commands +namespace SecretAPI.Features.Commands { using System; using CommandSystem; @@ -24,9 +24,13 @@ public abstract partial class CustomCommand : ICommand /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) - => ExecuteGenerated(arguments, sender, out response); // CustomCommandHandler.TryCall(this, sender, arguments, out response); + => ExecuteGenerated(arguments, sender, out response); /// - protected abstract partial bool ExecuteGenerated(ArraySegment arguments, ICommandSender sender, out string response); + protected virtual bool ExecuteGenerated(ArraySegment arguments, ICommandSender sender, out string response) + { + response = "Command not implemented."; + return false; + } } -}*/ \ No newline at end of file +} \ No newline at end of file From db54a92717135aaf66fe80120694b06cd9edeadf Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:23:07 +0100 Subject: [PATCH 15/37] Update workflows --- .github/workflows/nuget.yml | 3 ++- .github/workflows/pull_request.yml | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 64077c7..4668a51 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -11,6 +11,7 @@ jobs: env: REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip REFERENCES_PATH: ${{ github.workspace }}/References + PACKAGED_PATH: ${{ github.workspace }}/Package steps: - name: Checkout @@ -28,7 +29,7 @@ jobs: - name: Build and Pack NuGet env: SL_REFERENCES: ${{ env.REFERENCES_PATH }} - run: dotnet pack -c Release --output ${GITHUB_WORKSPACE}/nupkgs + run: dotnet pack -c Release --output ${PACKAGED_PATH} - name: Push NuGet package run: | diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9996c17..4347e74 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,6 +12,7 @@ jobs: env: REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip REFERENCES_PATH: ${{ github.workspace }}/References + BUILD_OUTPUT: ${{ github.workspace }}/Builds steps: - name: Checkout @@ -31,11 +32,11 @@ jobs: SL_REFERENCES: ${{ env.REFERENCES_PATH }} shell: pwsh run: | - dotnet build -c Release + dotnet build -c Release --output ${{ env.BUILD_OUTPUT }} - name: Upload uses: actions/upload-artifact@v4 with: name: Build Result - path: ${{ github.workspace }}\SecretAPI\bin\Release\net48\SecretAPI.dll - retention-days: 7 + path: "${{ env.BUILD_OUTPUT }}\bin\Release\net48\SecretAPI.*" + retention-days: 7 \ No newline at end of file From 6c97fcc97fed093df0751047fb38c6c73a470a75 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:26:02 +0100 Subject: [PATCH 16/37] Update workflow path --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4347e74..40b07f9 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -38,5 +38,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: Build Result - path: "${{ env.BUILD_OUTPUT }}\bin\Release\net48\SecretAPI.*" + path: ${{ env.BUILD_OUTPUT }}\bin\Release\net48\SecretAPI.* retention-days: 7 \ No newline at end of file From f015de8b16f24726911d1799fabfd18c16cebe29 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:29:45 +0100 Subject: [PATCH 17/37] Update PR workflow --- .github/workflows/pull_request.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 40b07f9..1d83fc4 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,7 +12,6 @@ jobs: env: REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip REFERENCES_PATH: ${{ github.workspace }}/References - BUILD_OUTPUT: ${{ github.workspace }}/Builds steps: - name: Checkout @@ -28,15 +27,11 @@ jobs: Expand-Archive -Path "${{ github.workspace }}/References.zip" -DestinationPath ${{ env.REFERENCES_PATH }} - name: Build - env: - SL_REFERENCES: ${{ env.REFERENCES_PATH }} shell: pwsh - run: | - dotnet build -c Release --output ${{ env.BUILD_OUTPUT }} + run: dotnet build -c Release - name: Upload uses: actions/upload-artifact@v4 with: name: Build Result - path: ${{ env.BUILD_OUTPUT }}\bin\Release\net48\SecretAPI.* - retention-days: 7 \ No newline at end of file + path: **/bin/Release/net48/SecretAPI.* \ No newline at end of file From d74b60b00829a61bcae64552cb351deeafc835ab Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:34:16 +0100 Subject: [PATCH 18/37] Update workflow for PRs --- .github/workflows/pull_request.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 1d83fc4..1af2023 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,6 +27,8 @@ jobs: Expand-Archive -Path "${{ github.workspace }}/References.zip" -DestinationPath ${{ env.REFERENCES_PATH }} - name: Build + env: + SL_REFERENCES: ${{ env.REFERENCES_PATH }} shell: pwsh run: dotnet build -c Release @@ -34,4 +36,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: Build Result - path: **/bin/Release/net48/SecretAPI.* \ No newline at end of file + path: ${{ github.workspace }}/**/bin/Release/net48/*SecretAPI*.dll + retention-days: 7 \ No newline at end of file From 53e24f8a99c682345216c9e605b77776bd170304 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:42:20 +0100 Subject: [PATCH 19/37] Minor improvements --- .../CustomCommandGenerator.cs | 18 +- SecretAPI.CodeGeneration/WritingUtils.cs | 27 ++- .../Commands/ExampleExplodeCommand.cs | 8 +- .../Commands/ExampleParentCommand.cs | 5 +- .../Attributes/ExecuteCommandAttribute.cs | 2 +- SecretAPI/Features/Commands/CustomCommand.cs | 6 +- .../Commands/CustomCommandGenerator.cs | 44 ----- .../Features/Commands/CustomCommandHandler.cs | 181 ------------------ .../Commands/Parsing/CommandArgParseResult.cs | 23 --- .../Parsing/CommandMethodParseResult.cs | 15 -- .../Commands/Parsing/CommandParseResult.cs | 30 --- 11 files changed, 43 insertions(+), 316 deletions(-) delete mode 100644 SecretAPI/Features/Commands/CustomCommandGenerator.cs delete mode 100644 SecretAPI/Features/Commands/CustomCommandHandler.cs delete mode 100644 SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs delete mode 100644 SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs delete mode 100644 SecretAPI/Features/Commands/Parsing/CommandParseResult.cs diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs index 72b4765..e962ad0 100644 --- a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs @@ -13,7 +13,7 @@ public class CustomCommandGenerator : IIncrementalGenerator { private const string CommandName = "CustomCommand"; private const string ExecuteMethodName = "ExecuteGenerated"; - + /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -47,11 +47,8 @@ private static void Generate(SourceProductionContext ctx, INamedTypeSymbol? symb indentWriter.WriteGeneratedText() .WriteNamespace(symbol, true) - .WriteUsings("System", "CommandSystem"); - - indentWriter.WriteLine($"partial class {symbol.Name}"); - indentWriter.WriteLine("{"); - indentWriter.Indent++; + .WriteUsings("System", "CommandSystem") + .WritePartialClass(symbol.Name, true); indentWriter.WriteLine($"protected override bool {ExecuteMethodName}("); indentWriter.Indent++; @@ -65,14 +62,7 @@ private static void Generate(SourceProductionContext ctx, INamedTypeSymbol? symb indentWriter.WriteLine("response = \"Command not implemented.\";"); indentWriter.WriteLine("return false;"); - indentWriter.Indent--; - indentWriter.WriteLine("}"); - - indentWriter.Indent--; - indentWriter.WriteLine("}"); - - indentWriter.Indent--; - indentWriter.WriteLine("}"); + indentWriter.FinishAllIndentations(); ctx.AddSource($"{symbol.ContainingNamespace}.{symbol.Name}.g.cs", writer.ToString()); } diff --git a/SecretAPI.CodeGeneration/WritingUtils.cs b/SecretAPI.CodeGeneration/WritingUtils.cs index 682f55f..ed2607b 100644 --- a/SecretAPI.CodeGeneration/WritingUtils.cs +++ b/SecretAPI.CodeGeneration/WritingUtils.cs @@ -28,6 +28,9 @@ public static IndentedTextWriter WriteGeneratedText(this IndentedTextWriter writ public static IndentedTextWriter WriteNamespace(this IndentedTextWriter writer, INamedTypeSymbol symbol, bool startBrace) { + if (symbol.ContainingNamespace == null) + return writer; + writer.WriteLine($"namespace {symbol.ContainingNamespace}"); if (startBrace) { @@ -42,12 +45,34 @@ public static IndentedTextWriter WriteUsings(this IndentedTextWriter writer, par { if (usings.Length == 0) return writer; - + foreach (string @using in usings) writer.WriteLine($"using {@using};"); writer.WriteLine(); + return writer; + } + + public static IndentedTextWriter WritePartialClass(this IndentedTextWriter writer, string className, bool startBrace) + { + writer.WriteLine($"partial class {className}"); + if (startBrace) + { + writer.WriteLine("{"); + writer.Indent++; + } return writer; } + + public static IndentedTextWriter FinishAllIndentations(this IndentedTextWriter writer) + { + while (writer.Indent > 0) + { + writer.Indent--; + writer.WriteLine("}"); + } + + return writer; + } } \ No newline at end of file diff --git a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs index 815bf54..3ce72ec 100644 --- a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs @@ -1,5 +1,6 @@ namespace SecretAPI.Examples.Commands { + using LabApi.Features.Console; using LabApi.Features.Wrappers; using SecretAPI.Features.Commands; using SecretAPI.Features.Commands.Attributes; @@ -16,7 +17,10 @@ public partial class ExampleExplodeCommand : CustomCommand public override string Description => "Explodes a player!"; [ExecuteCommand] - private void Explode(Player sender, Player target) - => TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); + private void Explode([CommandSender] Player sender, Player target) + { + Logger.Debug($"Example explode command run by {sender.Nickname} - Target: {target.Nickname}"); + TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); + } } } \ No newline at end of file diff --git a/SecretAPI.Examples/Commands/ExampleParentCommand.cs b/SecretAPI.Examples/Commands/ExampleParentCommand.cs index db74495..6b8554b 100644 --- a/SecretAPI.Examples/Commands/ExampleParentCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleParentCommand.cs @@ -1,5 +1,6 @@ namespace SecretAPI.Examples.Commands { + using LabApi.Features.Console; using LabApi.Features.Wrappers; using SecretAPI.Features.Commands; using SecretAPI.Features.Commands.Attributes; @@ -22,9 +23,9 @@ public partial class ExampleParentCommand : CustomCommand public override CustomCommand[] SubCommands { get; } = [new ExampleExplodeCommand()]; [ExecuteCommand] - private void Run([CommandSender] Player sender, Player target) + private void Run([CommandSender] Player sender) { - TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); + Logger.Debug($"Example parent was run by {sender.Nickname}"); } } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Attributes/ExecuteCommandAttribute.cs b/SecretAPI/Features/Commands/Attributes/ExecuteCommandAttribute.cs index 5210527..0efecd2 100644 --- a/SecretAPI/Features/Commands/Attributes/ExecuteCommandAttribute.cs +++ b/SecretAPI/Features/Commands/Attributes/ExecuteCommandAttribute.cs @@ -5,7 +5,7 @@ /// /// Attribute used to identify a method as a possible execution result. /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Method)] public class ExecuteCommandAttribute : Attribute { } diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index 357ed13..5f3ca20 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -27,10 +27,10 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s => ExecuteGenerated(arguments, sender, out response); /// - protected virtual bool ExecuteGenerated(ArraySegment arguments, ICommandSender sender, out string response) + /// This should not be overwritten except by source generation. + protected virtual bool ExecuteGenerated(ArraySegment arguments, ICommandSender commandSender, out string response) { - response = "Command not implemented."; - return false; + throw new NotImplementedException($"Command {Command} not implemented. Did source generation fail? - If this is not intentional, submit a bugreport!"); } } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandGenerator.cs b/SecretAPI/Features/Commands/CustomCommandGenerator.cs deleted file mode 100644 index fa1cd91..0000000 --- a/SecretAPI/Features/Commands/CustomCommandGenerator.cs +++ /dev/null @@ -1,44 +0,0 @@ -/*namespace SecretAPI.Features.Commands -{ - using System.CodeDom.Compiler; - using System.IO; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp.Syntax; - - /// - /// Hello. - /// - [Generator] - internal class CustomCommandGenerator : IIncrementalGenerator - { - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValuesProvider classProvider = context.SyntaxProvider.CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax, - static (context, cancellationToken) => - { - if (context.Node is not ClassDeclarationSyntax classDecl) - return null; - - return context.SemanticModel.GetDeclaredSymbol(classDecl, cancellationToken) as INamedTypeSymbol; - }) - .Where(static cls => cls != null); - - context.RegisterSourceOutput(classProvider, Generate); - } - - private static void Generate(SourceProductionContext ctx, INamedTypeSymbol? symbol) - { - if (symbol == null) - return; - - using StringWriter writer = new(); - using IndentedTextWriter indentWriter = new(writer); - - indentWriter.Write("hello"); - - ctx.AddSource($"{symbol.ContainingNamespace}.{symbol.Name}.Generated.cs", writer.ToString()); - } - } -}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CustomCommandHandler.cs b/SecretAPI/Features/Commands/CustomCommandHandler.cs deleted file mode 100644 index 3066db9..0000000 --- a/SecretAPI/Features/Commands/CustomCommandHandler.cs +++ /dev/null @@ -1,181 +0,0 @@ -/*namespace SecretAPI.Features.Commands -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using CommandSystem; - using LabApi.Features.Wrappers; - using NorthwoodLib.Pools; - using SecretAPI.Attribute; - using SecretAPI.Features.Commands.Parsing; - - /// - /// Handles parsing . - /// - public static class CustomCommandHandler - { - /// - /// The name of the or argument represensing the command sender. - /// - public const string SenderPlayerName = "sender"; - - private static Dictionary commandExecuteMethods = new(); - - /// - /// Attempts to call the correct command and gives a result. - /// - /// The command currently being called from. - /// The sender of the command. - /// The arguments provided to the command. - /// The response to give to the player. - /// Whether the command was a success. - public static bool TryCall(CustomCommand command, ICommandSender sender, ArraySegment arguments, out string response) - { - Player senderPlayer = Player.Get(sender) ?? Server.Host!; - - CommandParseResult parseParseResult = TryParse(command, senderPlayer, arguments); - if (!parseParseResult.CouldParse) - { - response = parseParseResult.FailedResponse; - return false; - } - - parseParseResult.Method.Invoke(null, parseParseResult.ProvidedArguments); - - // TODO: get result & put it into response - return true; - } - - private static CommandParseResult TryParse(CustomCommand command, Player sender, ArraySegment arguments) - { - foreach (MethodInfo method in GetMethods(command)) - { - CommandMethodParseResult result = ValidateAllMethodParameters(method, sender, arguments); - - // parsed correctly, return with correct arguments - if (result.CouldParse) - { - return new CommandParseResult() - { - CouldParse = true, - FailedResponse = string.Empty, - Method = method, - ProvidedArguments = result.Arguments, - }; - } - - // failed to parse, return and show failure - return new CommandParseResult() - { - CouldParse = false, - FailedResponse = result.FailedResponse, - }; - } - } - - private static CommandMethodParseResult ValidateAllMethodParameters(MethodInfo method, Player sender, ArraySegment arguments) - { - ParameterInfo[] parameters = method.GetParameters(); - List returns = ListPool.Shared.Rent(); - - for (int index = 0; index < parameters.Length; index++) - { - ParameterInfo parameter = parameters[index]; - CommandArgParseResult validateResult = ValidateParameter(parameter, sender, arguments.ElementAtOrDefault(index)); - if (!validateResult.CouldParse) - { - return new CommandMethodParseResult() - { - CouldParse = false, - FailedResponse = validateResult.FailedResponse, - }; - } - - returns.Add(validateResult.ParamArgument); - } - - CommandMethodParseResult result = new() - { - CouldParse = true, - Arguments = returns.ToArray(), - }; - - ListPool.Shared.Return(returns); - return result; - } - - private static CommandArgParseResult ValidateParameter(ParameterInfo parameter, Player sender, string? argument) - { - // if arg doesnt exist & param is optional, then its validated - if (argument == null && parameter.IsOptional) - { - return new CommandArgParseResult() - { - CouldParse = true, - ParamArgument = parameter.DefaultValue!, - }; - } - - Type type = parameter.ParameterType; - - if (type.IsEnum) - { - if (Enum.TryParse(type, argument, true, out object? enumValue)) - { - return new CommandArgParseResult() - { - CouldParse = true, - ParamArgument = enumValue, - }; - } - - return new CommandArgParseResult() - { - CouldParse = false, - FailedResponse = $"Could not pass into valid enum value. Enum required: {type.Name}.", - }; - } - - if (parameter.Name == SenderPlayerName) - { - if (typeof(Player).IsAssignableFrom(parameter.ParameterType)) - { - return new CommandArgParseResult() - { - CouldParse = true, - ParamArgument = sender, - }; - } - else if (typeof(ReferenceHub).IsAssignableFrom(parameter.ParameterType)) - { - return new CommandArgParseResult() - { - CouldParse = true, - ParamArgument = sender.ReferenceHub, - }; - } - } - - // all parsing failed - return new CommandArgParseResult() - { - CouldParse = false, - FailedResponse = $"Failed to parse {argument ?? null} into type of {parameter.ParameterType.Name}.", - }; - } - - private static MethodInfo[] GetMethods(CustomCommand command) - { - const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; - - if (!commandExecuteMethods.TryGetValue(command, out MethodInfo[] methods)) - { - methods = command.GetType().GetMethods(methodFlags).Where(m => m.GetCustomAttribute() != null).ToArray(); - commandExecuteMethods.Add(command, methods); - } - - return methods; - } - } -}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs deleted file mode 100644 index 27e79ca..0000000 --- a/SecretAPI/Features/Commands/Parsing/CommandArgParseResult.cs +++ /dev/null @@ -1,23 +0,0 @@ -/*namespace SecretAPI.Features.Commands.Parsing -{ - /// - /// Gets the result of a . - /// - internal struct CommandArgParseResult - { - /// - /// Gets a value indicating whether parsing was successful. - /// - public bool CouldParse; - - /// - /// If parsing failed, will provide the fail reason, otherwise null. - /// - public string FailedResponse; - - /// - /// The argument for the argument. - /// - public object ParamArgument; - } -}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs deleted file mode 100644 index 19e79ee..0000000 --- a/SecretAPI/Features/Commands/Parsing/CommandMethodParseResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -/*namespace SecretAPI.Features.Commands.Parsing -{ - /// - /// Defines the return type of . - /// - internal struct CommandMethodParseResult - { -#pragma warning disable SA1600 // Elements should be documented - internal bool CouldParse; - - internal string FailedResponse; - - internal object[]? Arguments; - } -}*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs b/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs deleted file mode 100644 index e2c770e..0000000 --- a/SecretAPI/Features/Commands/Parsing/CommandParseResult.cs +++ /dev/null @@ -1,30 +0,0 @@ -/*namespace SecretAPI.Features.Commands.Parsing -{ - using System.Reflection; - - /// - /// The result of to know what to do. - /// - internal struct CommandParseResult - { - /// - /// Gets a value indicating whether parsing was successful. - /// - public bool CouldParse; - - /// - /// If parsing failed, will provide the fail reason, otherwise null. - /// - public string FailedResponse; - - /// - /// If parsing succeded, the method to call with . - /// - public MethodInfo Method; - - /// - /// If parsing succeeded, the arguments provided. - /// - public object[]? ProvidedArguments; - } -}*/ \ No newline at end of file From b2be9b4d65285b0d3574b742bd08e5b5ecf89153 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:58:22 +0100 Subject: [PATCH 20/37] Guh --- .../CustomCommandGenerator.cs | 82 ++++++++++++------- SecretAPI.CodeGeneration/WritingUtils.cs | 48 ++++++++++- .../Commands/ExampleParentCommand.cs | 1 + SecretAPI/Features/Commands/CustomCommand.cs | 6 +- 4 files changed, 100 insertions(+), 37 deletions(-) diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs index e962ad0..d03e42b 100644 --- a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs @@ -1,6 +1,7 @@ namespace SecretAPI.CodeGeneration; using System.CodeDom.Compiler; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using System.IO; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -12,58 +13,83 @@ public class CustomCommandGenerator : IIncrementalGenerator { private const string CommandName = "CustomCommand"; - private const string ExecuteMethodName = "ExecuteGenerated"; + private const string ExecuteMethodName = "Execute"; + private const string ExecuteCommandMethodAttributeLocation = "SecretAPI.Features.Commands.Attributes.ExecuteCommandAttribute"; /// public void Initialize(IncrementalGeneratorInitializationContext context) { - IncrementalValuesProvider classProvider = context.SyntaxProvider.CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax, - static (context, cancellationToken) => - { - if (context.Node is not ClassDeclarationSyntax classDecl) - return null; + IncrementalValuesProvider<(INamedTypeSymbol?, ImmutableArray)> classProvider + = context.SyntaxProvider.CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax, + static (ctx, cancel) => + { + ClassDeclarationSyntax classSyntax = (ClassDeclarationSyntax)ctx.Node; + INamedTypeSymbol? typeSymbol = ctx.SemanticModel.GetDeclaredSymbol(classSyntax, cancel) as INamedTypeSymbol; + return (typeSymbol, GetExecuteMethods(ctx, classSyntax)); + }).Where(tuple => tuple is { typeSymbol: not null, Item2.IsEmpty: false }); - return context.SemanticModel.GetDeclaredSymbol(classDecl, cancellationToken) as INamedTypeSymbol; - }) - .Where(static cls => cls != null); + context.RegisterSourceOutput(classProvider, (ctx, tuple) => Generate(ctx, tuple.Item1!, tuple.Item2)); + } - context.RegisterSourceOutput(classProvider, Generate); + private static ImmutableArray GetExecuteMethods( + GeneratorSyntaxContext context, + ClassDeclarationSyntax classDeclarationSyntax) + { + List methods = new(); + foreach (MethodDeclarationSyntax method in classDeclarationSyntax.Members.OfType()) + { + if (!IsExecuteMethod(context, method)) + continue; + + methods.Add(method); + } + + return methods.ToImmutableArray(); } - private static void Generate(SourceProductionContext ctx, INamedTypeSymbol? symbol) + private static bool IsExecuteMethod(GeneratorSyntaxContext context, MethodDeclarationSyntax methodDeclarationSyntax) { - if (symbol == null) - return; + foreach (AttributeListSyntax attributeListSyntax in methodDeclarationSyntax.AttributeLists) + { + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) + { + ITypeSymbol? attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeTypeSymbol != null && attributeTypeSymbol.ToDisplayString() == ExecuteCommandMethodAttributeLocation) + return true; + } + } - if (symbol.IsAbstract) + return false; + } + + private static void Generate( + SourceProductionContext ctx, + INamedTypeSymbol namedClassSymbol, + ImmutableArray executeMethods) + { + if (namedClassSymbol.IsAbstract) return; - if (symbol.BaseType?.Name != CommandName) + if (namedClassSymbol.BaseType?.Name != CommandName) return; using StringWriter writer = new(); using IndentedTextWriter indentWriter = new(writer); indentWriter.WriteGeneratedText() - .WriteNamespace(symbol, true) + .WriteNamespace(namedClassSymbol, true) .WriteUsings("System", "CommandSystem") - .WritePartialClass(symbol.Name, true); - - indentWriter.WriteLine($"protected override bool {ExecuteMethodName}("); - indentWriter.Indent++; - indentWriter.WriteLine("ArraySegment arguments,"); - indentWriter.WriteLine("ICommandSender sender,"); - indentWriter.WriteLine("out string response)"); - indentWriter.Indent--; - indentWriter.WriteLine("{"); - indentWriter.Indent++; + .WritePartialClass(namedClassSymbol.Name, true) + .WriteMethod(ExecuteMethodName, "bool", true, Accessibility.Public, true, "ArraySegment arguments", + "ICommandSender sender", "out string response"); indentWriter.WriteLine("response = \"Command not implemented.\";"); indentWriter.WriteLine("return false;"); + indentWriter.WriteLine($"// {string.Join(" -> ", executeMethods.Select(m => m.Identifier))}"); indentWriter.FinishAllIndentations(); - ctx.AddSource($"{symbol.ContainingNamespace}.{symbol.Name}.g.cs", writer.ToString()); + ctx.AddSource($"{namedClassSymbol.Name}.g.cs", writer.ToString()); } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/WritingUtils.cs b/SecretAPI.CodeGeneration/WritingUtils.cs index ed2607b..24f9836 100644 --- a/SecretAPI.CodeGeneration/WritingUtils.cs +++ b/SecretAPI.CodeGeneration/WritingUtils.cs @@ -33,10 +33,7 @@ public static IndentedTextWriter WriteNamespace(this IndentedTextWriter writer, writer.WriteLine($"namespace {symbol.ContainingNamespace}"); if (startBrace) - { - writer.WriteLine("{"); - writer.Indent++; - } + WriteStartBrace(writer); return writer; } @@ -65,6 +62,49 @@ public static IndentedTextWriter WritePartialClass(this IndentedTextWriter write return writer; } + public static IndentedTextWriter WriteMethod( + this IndentedTextWriter writer, + string methodName, + string returnType, + bool isOverride, + Accessibility accessibility, + bool startBrace, + params string[] parameters) + { + writer.Write(GetAccessibilityString(accessibility)); + if (isOverride) + writer.Write(" override "); + writer.Write(returnType); + writer.Write(" " + methodName); + writer.WriteLine("("); + writer.Indent++; + + for (int index = 0; index < parameters.Length; index++) + { + string parameter = parameters[index]; + if (parameters.Length > index + 1) + writer.WriteLine(parameter + ","); + else if (!startBrace) + writer.Write(parameter + ")"); + else + writer.WriteLine(parameter + ")"); + } + + writer.Indent--; + + if (startBrace) + writer.WriteStartBrace(); + + return writer; + } + + public static IndentedTextWriter WriteStartBrace(this IndentedTextWriter writer) + { + writer.WriteLine("{"); + writer.Indent++; + return writer; + } + public static IndentedTextWriter FinishAllIndentations(this IndentedTextWriter writer) { while (writer.Indent > 0) diff --git a/SecretAPI.Examples/Commands/ExampleParentCommand.cs b/SecretAPI.Examples/Commands/ExampleParentCommand.cs index 6b8554b..98232ae 100644 --- a/SecretAPI.Examples/Commands/ExampleParentCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleParentCommand.cs @@ -1,5 +1,6 @@ namespace SecretAPI.Examples.Commands { + using System; using LabApi.Features.Console; using LabApi.Features.Wrappers; using SecretAPI.Features.Commands; diff --git a/SecretAPI/Features/Commands/CustomCommand.cs b/SecretAPI/Features/Commands/CustomCommand.cs index 5f3ca20..158c67b 100644 --- a/SecretAPI/Features/Commands/CustomCommand.cs +++ b/SecretAPI/Features/Commands/CustomCommand.cs @@ -23,12 +23,8 @@ public abstract partial class CustomCommand : ICommand public virtual CustomCommand[] SubCommands { get; } = []; /// - public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) - => ExecuteGenerated(arguments, sender, out response); - - /// /// This should not be overwritten except by source generation. - protected virtual bool ExecuteGenerated(ArraySegment arguments, ICommandSender commandSender, out string response) + public virtual bool Execute(ArraySegment arguments, ICommandSender sender, out string response) { throw new NotImplementedException($"Command {Command} not implemented. Did source generation fail? - If this is not intentional, submit a bugreport!"); } From c4c5537202a62825ec43f09b8843a705892b5b0e Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Sun, 21 Dec 2025 14:01:13 +0100 Subject: [PATCH 21/37] Use SyntaxFactory --- .../CodeBuilders/ClassBuilder.cs | 73 +++++++++++++++++++ .../CodeBuilders/MethodBuilder.cs | 20 +++++ .../CustomCommandGenerator.cs | 20 ++++- 3 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs create mode 100644 SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs diff --git a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs new file mode 100644 index 0000000..7a80d18 --- /dev/null +++ b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs @@ -0,0 +1,73 @@ +namespace SecretAPI.CodeGeneration.CodeBuilders; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +internal class ClassBuilder +{ + private NamespaceDeclarationSyntax _namespaceDeclaration; + private ClassDeclarationSyntax _classDeclaration; + private string _className; + + private readonly List _modifiers = new(); + private readonly List _usings = new(); + private readonly List _methods = new(); + + private ClassBuilder(string @namespace, string className) + { + _namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(@namespace)); + _className = className; + _classDeclaration = SyntaxFactory.ClassDeclaration(className); + } + + internal static ClassBuilder CreateBuilder(string @namespace, string className) + => new(@namespace, className); + + internal static ClassBuilder CreateBuilder(INamedTypeSymbol namedClass) + => new(namedClass.ContainingNamespace.ToDisplayString(), namedClass.Name); + + internal ClassBuilder AddUsingStatement(string usingStatement) + { + _usings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(usingStatement))); + return this; + } + + internal MethodBuilder AddMethodDefinition() + { + return new MethodBuilder(this); + } + + internal ClassBuilder AddModifiers(params SyntaxKind[] modifiers) + { + foreach (SyntaxKind token in modifiers) + AddModifier(token); + + return this; + } + + internal ClassBuilder AddModifier(SyntaxKind syntax) + { + if (!_modifiers.Any(m => m.IsKind(syntax))) + _modifiers.Add(SyntaxFactory.Token(syntax)); + + return this; + } + + internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method); + + internal CompilationUnitSyntax Build() + { + _classDeclaration = _classDeclaration.AddModifiers(_modifiers.ToArray()); + + _namespaceDeclaration = _namespaceDeclaration + /*.WithLeadingTrivia(SyntaxFactory.Comment("// "), SyntaxFactory.LineFeed, SyntaxFactory.LineFeed)*/ + .AddUsings(_usings.ToArray()) + .AddMembers(_classDeclaration); + + return SyntaxFactory.CompilationUnit() + .AddMembers(_namespaceDeclaration) + .NormalizeWhitespace() + .WithLeadingTrivia(SyntaxFactory.Comment("// "), SyntaxFactory.LineFeed, SyntaxFactory.LineFeed); + } +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs new file mode 100644 index 0000000..7f803eb --- /dev/null +++ b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs @@ -0,0 +1,20 @@ +namespace SecretAPI.CodeGeneration.CodeBuilders; + +using Microsoft.CodeAnalysis.CSharp.Syntax; + +internal class MethodBuilder +{ + private readonly ClassBuilder _classBuilder; + + internal MethodBuilder(ClassBuilder classBuilder) + { + _classBuilder = classBuilder; + } + + internal ClassBuilder FinishMethodBuild() + { + MethodDeclarationSyntax methodDeclarationSyntax; + _classBuilder.AddMethodDefinition(methodDeclarationSyntax); + return _classBuilder; + } +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs index d03e42b..0fd5ec5 100644 --- a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs @@ -4,6 +4,8 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using System.IO; +using CodeBuilders; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; /// @@ -25,7 +27,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (ctx, cancel) => { ClassDeclarationSyntax classSyntax = (ClassDeclarationSyntax)ctx.Node; - INamedTypeSymbol? typeSymbol = ctx.SemanticModel.GetDeclaredSymbol(classSyntax, cancel) as INamedTypeSymbol; + INamedTypeSymbol? typeSymbol = ModelExtensions.GetDeclaredSymbol(ctx.SemanticModel, classSyntax, cancel) as INamedTypeSymbol; return (typeSymbol, GetExecuteMethods(ctx, classSyntax)); }).Where(tuple => tuple is { typeSymbol: not null, Item2.IsEmpty: false }); @@ -54,7 +56,7 @@ private static bool IsExecuteMethod(GeneratorSyntaxContext context, MethodDeclar { foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) { - ITypeSymbol? attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + ITypeSymbol? attributeTypeSymbol = ModelExtensions.GetTypeInfo(context.SemanticModel, attributeSyntax).Type; if (attributeTypeSymbol != null && attributeTypeSymbol.ToDisplayString() == ExecuteCommandMethodAttributeLocation) return true; } @@ -74,7 +76,17 @@ private static void Generate( if (namedClassSymbol.BaseType?.Name != CommandName) return; - using StringWriter writer = new(); + CompilationUnitSyntax compilation = ClassBuilder.CreateBuilder(namedClassSymbol) + .AddUsingStatement("System") + .AddUsingStatement("System.Collections.Generic") + .AddModifier(SyntaxKind.PartialKeyword) + .AddMethodDefinition() + .FinishMethodBuild() + .Build(); + + ctx.AddSource($"{namedClassSymbol.Name}.g.cs", compilation.ToFullString()); + + /*using StringWriter writer = new(); using IndentedTextWriter indentWriter = new(writer); indentWriter.WriteGeneratedText() @@ -90,6 +102,6 @@ private static void Generate( indentWriter.FinishAllIndentations(); - ctx.AddSource($"{namedClassSymbol.Name}.g.cs", writer.ToString()); + ctx.AddSource($"{namedClassSymbol.Name}.g.cs", writer.ToString());*/ } } \ No newline at end of file From 05e4c05bf1bc7ce29a4f214260233b704e5acefb Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Sun, 21 Dec 2025 19:47:44 +0100 Subject: [PATCH 22/37] Commit some bits --- .../CodeBuilders/ClassBuilder.cs | 41 ++++++----------- .../CodeBuilders/MethodBuilder.cs | 35 ++++++++++++-- .../CustomCommandGenerator.cs | 38 ++++++++++----- SecretAPI.CodeGeneration/GlobalUsings.cs | 11 +++++ .../Utils/MethodParameter.cs | 46 +++++++++++++++++++ SecretAPI.CodeGeneration/Utils/Util.cs | 15 ++++++ .../{ => Utils}/WritingUtils.cs | 4 +- 7 files changed, 144 insertions(+), 46 deletions(-) create mode 100644 SecretAPI.CodeGeneration/GlobalUsings.cs create mode 100644 SecretAPI.CodeGeneration/Utils/MethodParameter.cs create mode 100644 SecretAPI.CodeGeneration/Utils/Util.cs rename SecretAPI.CodeGeneration/{ => Utils}/WritingUtils.cs (98%) diff --git a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs index 7a80d18..9b9453f 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs @@ -1,9 +1,5 @@ namespace SecretAPI.CodeGeneration.CodeBuilders; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - internal class ClassBuilder { private NamespaceDeclarationSyntax _namespaceDeclaration; @@ -16,9 +12,9 @@ internal class ClassBuilder private ClassBuilder(string @namespace, string className) { - _namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(@namespace)); + _namespaceDeclaration = NamespaceDeclaration(ParseName(@namespace)); _className = className; - _classDeclaration = SyntaxFactory.ClassDeclaration(className); + _classDeclaration = ClassDeclaration(className); } internal static ClassBuilder CreateBuilder(string @namespace, string className) @@ -27,47 +23,38 @@ internal static ClassBuilder CreateBuilder(string @namespace, string className) internal static ClassBuilder CreateBuilder(INamedTypeSymbol namedClass) => new(namedClass.ContainingNamespace.ToDisplayString(), namedClass.Name); - internal ClassBuilder AddUsingStatement(string usingStatement) + internal ClassBuilder AddUsingStatements(params string[] usingStatement) { - _usings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(usingStatement))); + foreach (string statement in usingStatement) + _usings.Add(UsingDirective(ParseName(statement))); + return this; } - internal MethodBuilder AddMethodDefinition() - { - return new MethodBuilder(this); - } + internal MethodBuilder StartMethodCreation(string methodName, string returnType) => new(this, methodName, returnType); + + internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method); internal ClassBuilder AddModifiers(params SyntaxKind[] modifiers) { foreach (SyntaxKind token in modifiers) - AddModifier(token); + _modifiers.Add(Token(token)); return this; } - internal ClassBuilder AddModifier(SyntaxKind syntax) - { - if (!_modifiers.Any(m => m.IsKind(syntax))) - _modifiers.Add(SyntaxFactory.Token(syntax)); - - return this; - } - - internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method); - internal CompilationUnitSyntax Build() { - _classDeclaration = _classDeclaration.AddModifiers(_modifiers.ToArray()); + _classDeclaration = _classDeclaration.AddModifiers(_modifiers.ToArray()) + .AddMembers(_methods.Cast().ToArray()); _namespaceDeclaration = _namespaceDeclaration - /*.WithLeadingTrivia(SyntaxFactory.Comment("// "), SyntaxFactory.LineFeed, SyntaxFactory.LineFeed)*/ .AddUsings(_usings.ToArray()) .AddMembers(_classDeclaration); - return SyntaxFactory.CompilationUnit() + return CompilationUnit() .AddMembers(_namespaceDeclaration) .NormalizeWhitespace() - .WithLeadingTrivia(SyntaxFactory.Comment("// "), SyntaxFactory.LineFeed, SyntaxFactory.LineFeed); + .WithLeadingTrivia(Comment("// "), LineFeed, LineFeed); } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs index 7f803eb..a82b18c 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs @@ -1,20 +1,45 @@ namespace SecretAPI.CodeGeneration.CodeBuilders; -using Microsoft.CodeAnalysis.CSharp.Syntax; - internal class MethodBuilder { private readonly ClassBuilder _classBuilder; + private readonly List _modifiers = new(); + private readonly List _parameters = new(); + private readonly string _methodName; + private readonly string _returnType; - internal MethodBuilder(ClassBuilder classBuilder) + internal MethodBuilder(ClassBuilder classBuilder, string methodName, string returnType) { _classBuilder = classBuilder; + _methodName = methodName; + _returnType = returnType; + } + + internal MethodBuilder AddParameters(params MethodParameter[] parameters) + { + foreach (MethodParameter parameter in parameters) + _parameters.Add(parameter.Syntax); + + return this; + } + + internal MethodBuilder AddModifiers(params SyntaxKind[] modifiers) + { + foreach (SyntaxKind token in modifiers) + _modifiers.Add(Token(token)); + + return this; } internal ClassBuilder FinishMethodBuild() { - MethodDeclarationSyntax methodDeclarationSyntax; - _classBuilder.AddMethodDefinition(methodDeclarationSyntax); + MethodDeclarationSyntax methodDeclaration = MethodDeclaration(ParseTypeName(_returnType), _methodName); + methodDeclaration = methodDeclaration + .AddModifiers(_modifiers.ToArray()) + .AddParameterListParameters(_parameters.ToArray()) + .WithBody(Block()); + + _classBuilder.AddMethodDefinition(methodDeclaration); return _classBuilder; } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs index 0fd5ec5..c1b5f4b 100644 --- a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs @@ -1,13 +1,5 @@ namespace SecretAPI.CodeGeneration; -using System.CodeDom.Compiler; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using System.IO; -using CodeBuilders; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - /// /// Code generator for custom commands, creating validation etc. /// @@ -18,6 +10,26 @@ public class CustomCommandGenerator : IIncrementalGenerator private const string ExecuteMethodName = "Execute"; private const string ExecuteCommandMethodAttributeLocation = "SecretAPI.Features.Commands.Attributes.ExecuteCommandAttribute"; + private static readonly MethodParameter ArgumentsParam = + new( + identifier: "arguments", + type: GetSingleGenericTypeSyntax("ArraySegment", SyntaxKind.StringKeyword) + ); + + private static readonly MethodParameter SenderParam = + new( + identifier: "sender", + type: IdentifierName("ICommandSender") + ); + + private static readonly MethodParameter ResponseParam = + new( + identifier: "response", + type: GetPredefinedTypeSyntax(SyntaxKind.StringKeyword), + modifiers: TokenList( + Token(SyntaxKind.OutKeyword)) + ); + /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -77,10 +89,12 @@ private static void Generate( return; CompilationUnitSyntax compilation = ClassBuilder.CreateBuilder(namedClassSymbol) - .AddUsingStatement("System") - .AddUsingStatement("System.Collections.Generic") - .AddModifier(SyntaxKind.PartialKeyword) - .AddMethodDefinition() + .AddUsingStatements("System", "System.Collections.Generic") + .AddUsingStatements("CommandSystem") + .AddModifiers(SyntaxKind.PartialKeyword) + .StartMethodCreation(ExecuteMethodName, "bool") + .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.OverrideKeyword) + .AddParameters(ArgumentsParam, SenderParam, ResponseParam) .FinishMethodBuild() .Build(); diff --git a/SecretAPI.CodeGeneration/GlobalUsings.cs b/SecretAPI.CodeGeneration/GlobalUsings.cs new file mode 100644 index 0000000..c853ab9 --- /dev/null +++ b/SecretAPI.CodeGeneration/GlobalUsings.cs @@ -0,0 +1,11 @@ +global using Microsoft.CodeAnalysis; +global using Microsoft.CodeAnalysis.CSharp; +global using Microsoft.CodeAnalysis.CSharp.Syntax; +global using System.Collections.Immutable; +global using Microsoft.CodeAnalysis; +global using SecretAPI.CodeGeneration.CodeBuilders; +global using SecretAPI.CodeGeneration.Utils; + +global using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +global using static Microsoft.CodeAnalysis.CSharp.SyntaxFacts; +global using static SecretAPI.CodeGeneration.Utils.Util; \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Utils/MethodParameter.cs b/SecretAPI.CodeGeneration/Utils/MethodParameter.cs new file mode 100644 index 0000000..be3df2d --- /dev/null +++ b/SecretAPI.CodeGeneration/Utils/MethodParameter.cs @@ -0,0 +1,46 @@ +namespace SecretAPI.CodeGeneration.Utils; + +/// +/// Represents a method parameter used during code generation. +/// +internal readonly struct MethodParameter +{ + private readonly SyntaxList _attributeLists; + private readonly SyntaxTokenList _modifiers; + private readonly TypeSyntax? _type; + private readonly SyntaxToken _identifier; + private readonly EqualsValueClauseSyntax? _default; + + /// + /// Creates a new instance of . + /// + /// The name of the parameter. + /// The parameter type. May be for implicitly-typed parameters. + /// Optional parameter modifiers (e.g. ref, out, in). + /// Optional attribute lists applied to the parameter. + /// Optional default value. + internal MethodParameter( + string identifier, + TypeSyntax? type = null, + SyntaxTokenList modifiers = default, + SyntaxList attributeLists = default, + EqualsValueClauseSyntax? @default = null) + { + _identifier = IsValidIdentifier(identifier) + ? Identifier(identifier) + : throw new ArgumentException("Identifier is not valid.", nameof(identifier)); + + _type = type; + _modifiers = modifiers; + _attributeLists = attributeLists; + _default = @default; + } + + public ParameterSyntax Syntax => + Parameter( + attributeLists: _attributeLists, + modifiers: _modifiers, + type: _type, + identifier: _identifier, + @default: _default); +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Utils/Util.cs b/SecretAPI.CodeGeneration/Utils/Util.cs new file mode 100644 index 0000000..86eece0 --- /dev/null +++ b/SecretAPI.CodeGeneration/Utils/Util.cs @@ -0,0 +1,15 @@ +namespace SecretAPI.CodeGeneration.Utils; + +public static class Util +{ + public static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) + => GenericName(genericName) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + PredefinedType( + Token(predefinedType))))); + + public static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind) + => PredefinedType(Token(kind)); +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/WritingUtils.cs b/SecretAPI.CodeGeneration/Utils/WritingUtils.cs similarity index 98% rename from SecretAPI.CodeGeneration/WritingUtils.cs rename to SecretAPI.CodeGeneration/Utils/WritingUtils.cs index 24f9836..113a8ba 100644 --- a/SecretAPI.CodeGeneration/WritingUtils.cs +++ b/SecretAPI.CodeGeneration/Utils/WritingUtils.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.CodeGeneration; +/*namespace SecretAPI.CodeGeneration.Utils; using System.CodeDom.Compiler; using Microsoft.CodeAnalysis; @@ -115,4 +115,4 @@ public static IndentedTextWriter FinishAllIndentations(this IndentedTextWriter w return writer; } -} \ No newline at end of file +}*/ \ No newline at end of file From 89884e48155dfc248b731854e1514ba92a34aae6 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Sun, 21 Dec 2025 21:21:20 +0100 Subject: [PATCH 23/37] Working CallOnLoadGenerator --- .../CallOnLoadGenerator.cs | 106 ++++++++++++++++++ .../CodeBuilders/ClassBuilder.cs | 8 +- .../CodeBuilders/MethodBuilder.cs | 16 ++- SecretAPI.CodeGeneration/Utils/Util.cs | 32 ++++++ SecretAPI/SecretApi.cs | 4 +- 5 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 SecretAPI.CodeGeneration/CallOnLoadGenerator.cs diff --git a/SecretAPI.CodeGeneration/CallOnLoadGenerator.cs b/SecretAPI.CodeGeneration/CallOnLoadGenerator.cs new file mode 100644 index 0000000..12580f9 --- /dev/null +++ b/SecretAPI.CodeGeneration/CallOnLoadGenerator.cs @@ -0,0 +1,106 @@ +namespace SecretAPI.CodeGeneration; + +/// +/// Code generator for CallOnLoad/CallOnUnload +/// +[Generator] +public class CallOnLoadGenerator : IIncrementalGenerator +{ + private const string PluginBaseClassName = "Plugin"; + private const string CallOnLoadAttributeLocation = "SecretAPI.Attribute.CallOnLoadAttribute"; + private const string CallOnUnloadAttributeLocation = "SecretAPI.Attribute.CallOnUnloadAttribute"; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider methodProvider = + context.SyntaxProvider.CreateSyntaxProvider( + static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 }, + static (ctx, _) => + ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as IMethodSymbol) + .Where(static m => m is not null)!; + + IncrementalValuesProvider<(IMethodSymbol method, bool isLoad, bool isUnload)> callProvider = + methodProvider.Select(static (method, _) => ( + method, + HasAttribute(method, CallOnLoadAttributeLocation), + HasAttribute(method, CallOnUnloadAttributeLocation))) + .Where(static m => m.Item2 || m.Item3); + + IncrementalValuesProvider pluginClassProvider = + context.SyntaxProvider.CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax, + static (ctx, _) => + ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol) + .Where(static c => + c is { IsAbstract: false, BaseType.Name: PluginBaseClassName })!; + + context.RegisterSourceOutput(pluginClassProvider.Combine(callProvider.Collect()), static (context, data) => + { + Generate(context, data.Left, data.Right); + }); + } + + private static bool HasAttribute(IMethodSymbol? method, string attributeLocation) + { + if (method == null) + return false; + + foreach (AttributeData attribute in method.GetAttributes()) + { + if (attribute.AttributeClass?.ToDisplayString() == attributeLocation) + return true; + } + + return false; + } + + private static int GetPriority(IMethodSymbol method, string attributeLocation) + { + AttributeData? attribute = method.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == attributeLocation); + if (attribute == null) + return 0; + + if (attribute.ConstructorArguments.Length > 0) + return (int)attribute.ConstructorArguments[0].Value!; + + return 0; + } + + private static void Generate( + SourceProductionContext context, + INamedTypeSymbol? pluginClassSymbol, + ImmutableArray<(IMethodSymbol method, bool isLoad, bool isUnload)> methods) + { + if (pluginClassSymbol == null || methods.IsEmpty) + return; + + IMethodSymbol[] loadCalls = methods + .Where(m => m.isLoad) + .Select(m => m.method) + .OrderBy(m => GetPriority(m, CallOnLoadAttributeLocation)) + .ToArray(); + + IMethodSymbol[] unloadCalls = methods + .Where(m => m.isUnload) + .Select(m => m.method) + .OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation)) + .ToArray(); + + CompilationUnitSyntax compilation = ClassBuilder.CreateBuilder(pluginClassSymbol) + .AddUsingStatements("System") + .AddModifiers(SyntaxKind.PartialKeyword) + .StartMethodCreation("OnLoad", "void") + .AddModifiers(SyntaxKind.PublicKeyword) + .AddStatements(MethodCallStatements(loadCalls)) + .FinishMethodBuild() + .StartMethodCreation("OnUnload", "void") + .AddModifiers(SyntaxKind.PublicKeyword) + .AddStatements(MethodCallStatements(unloadCalls)) + .FinishMethodBuild() + .Build(); + + context.AddSource($"{pluginClassSymbol.Name}.g.cs", compilation.ToFullString()); + } +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs index 9b9453f..80b187d 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs @@ -15,6 +15,8 @@ private ClassBuilder(string @namespace, string className) _namespaceDeclaration = NamespaceDeclaration(ParseName(@namespace)); _className = className; _classDeclaration = ClassDeclaration(className); + + AddUsingStatements("System.CodeDom.Compiler"); } internal static ClassBuilder CreateBuilder(string @namespace, string className) @@ -45,7 +47,9 @@ internal ClassBuilder AddModifiers(params SyntaxKind[] modifiers) internal CompilationUnitSyntax Build() { - _classDeclaration = _classDeclaration.AddModifiers(_modifiers.ToArray()) + _classDeclaration = _classDeclaration + .AddAttributeLists(GetGeneratedCodeAttributeListSyntax()) + .AddModifiers(_modifiers.ToArray()) .AddMembers(_methods.Cast().ToArray()); _namespaceDeclaration = _namespaceDeclaration @@ -55,6 +59,6 @@ internal CompilationUnitSyntax Build() return CompilationUnit() .AddMembers(_namespaceDeclaration) .NormalizeWhitespace() - .WithLeadingTrivia(Comment("// "), LineFeed, LineFeed); + .WithLeadingTrivia(Comment("// "), LineFeed, Comment("#pragma warning disable"), LineFeed, LineFeed); } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs index a82b18c..f3fb0ed 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs @@ -5,6 +5,7 @@ internal class MethodBuilder private readonly ClassBuilder _classBuilder; private readonly List _modifiers = new(); private readonly List _parameters = new(); + private readonly List _statements = new(); private readonly string _methodName; private readonly string _returnType; @@ -15,6 +16,12 @@ internal MethodBuilder(ClassBuilder classBuilder, string methodName, string retu _returnType = returnType; } + internal MethodBuilder AddStatements(params StatementSyntax[] statements) + { + _statements.AddRange(statements); + return this; + } + internal MethodBuilder AddParameters(params MethodParameter[] parameters) { foreach (MethodParameter parameter in parameters) @@ -33,12 +40,13 @@ internal MethodBuilder AddModifiers(params SyntaxKind[] modifiers) internal ClassBuilder FinishMethodBuild() { - MethodDeclarationSyntax methodDeclaration = MethodDeclaration(ParseTypeName(_returnType), _methodName); - methodDeclaration = methodDeclaration + BlockSyntax body = _statements.Any() ? Block(_statements) : Block(); + + MethodDeclarationSyntax methodDeclaration = MethodDeclaration(ParseTypeName(_returnType), _methodName) .AddModifiers(_modifiers.ToArray()) .AddParameterListParameters(_parameters.ToArray()) - .WithBody(Block()); - + .WithBody(body); + _classBuilder.AddMethodDefinition(methodDeclaration); return _classBuilder; } diff --git a/SecretAPI.CodeGeneration/Utils/Util.cs b/SecretAPI.CodeGeneration/Utils/Util.cs index 86eece0..71e164f 100644 --- a/SecretAPI.CodeGeneration/Utils/Util.cs +++ b/SecretAPI.CodeGeneration/Utils/Util.cs @@ -2,6 +2,21 @@ public static class Util { + private static AttributeSyntax GetGeneratedCodeAttributeSyntax() + => Attribute(IdentifierName("GeneratedCode")) + .WithArgumentList( + AttributeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.CodeGeneration"))), + Token(SyntaxKind.CommaToken), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))), + }))); + + internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() + => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax())); + public static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) => GenericName(genericName) .WithTypeArgumentList( @@ -12,4 +27,21 @@ public static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKi public static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind) => PredefinedType(Token(kind)); + + public static StatementSyntax MethodCallStatement(string typeName, string methodName) + => ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParseTypeName(typeName), IdentifierName(methodName)))); + + public static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls) + { + List statements = new(); + + foreach (IMethodSymbol methodCall in methodCalls) + statements.Add(MethodCallStatement(methodCall.ContainingType.ToDisplayString(), methodCall.Name)); + + return statements.ToArray(); + } } \ No newline at end of file diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs index 6272099..73db453 100644 --- a/SecretAPI/SecretApi.cs +++ b/SecretAPI/SecretApi.cs @@ -11,7 +11,7 @@ /// /// Main class handling loading API. /// - public class SecretApi : Plugin + public partial class SecretApi : Plugin { /// public override string Name => "SecretAPI"; @@ -49,7 +49,7 @@ public class SecretApi : Plugin public override void Enable() { Harmony = new Harmony("SecretAPI" + DateTime.Now); - CallOnLoadAttribute.Load(Assembly); + OnLoad(); } /// From 22efa3c71b6abe38d10f6c52cc7d9d4984d58e30 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+Misfiy@users.noreply.github.com> Date: Sun, 21 Dec 2025 22:43:47 +0100 Subject: [PATCH 24/37] Commit --- .github/workflows/nuget.yml | 6 +++--- .../CustomCommandGenerator.cs | 18 ------------------ SecretAPI.CodeGeneration/GlobalUsings.cs | 1 - .../SecretAPI.CodeGeneration.csproj | 1 - SecretAPI.Examples/SecretAPI.Examples.csproj | 1 - SecretAPI/SecretAPI.csproj | 19 ++++++++++++------- 6 files changed, 15 insertions(+), 31 deletions(-) diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 4668a51..abe8ebc 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -11,7 +11,7 @@ jobs: env: REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip REFERENCES_PATH: ${{ github.workspace }}/References - PACKAGED_PATH: ${{ github.workspace }}/Package + NUGET_PACKAGED_PATH: ${{ github.workspace }}/nupkgs steps: - name: Checkout @@ -29,9 +29,9 @@ jobs: - name: Build and Pack NuGet env: SL_REFERENCES: ${{ env.REFERENCES_PATH }} - run: dotnet pack -c Release --output ${PACKAGED_PATH} + run: dotnet pack -c Release --output ${NUGET_PACKAGED_PATH} - name: Push NuGet package run: | - $PackageFile = (Get-ChildItem -Path "${GITHUB_WORKSPACE}/nupkgs" -Include 'SecretAPI.*.nupkg' -Recurse | Select-Object -First 1).FullName + $PackageFile = (Get-ChildItem -Path "${NUGET_PACKAGED_PATH}" -Include 'SecretAPI.*.nupkg' -Recurse | Select-Object -First 1).FullName dotnet nuget push $PackageFile --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs index c1b5f4b..c1038f2 100644 --- a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/CustomCommandGenerator.cs @@ -99,23 +99,5 @@ private static void Generate( .Build(); ctx.AddSource($"{namedClassSymbol.Name}.g.cs", compilation.ToFullString()); - - /*using StringWriter writer = new(); - using IndentedTextWriter indentWriter = new(writer); - - indentWriter.WriteGeneratedText() - .WriteNamespace(namedClassSymbol, true) - .WriteUsings("System", "CommandSystem") - .WritePartialClass(namedClassSymbol.Name, true) - .WriteMethod(ExecuteMethodName, "bool", true, Accessibility.Public, true, "ArraySegment arguments", - "ICommandSender sender", "out string response"); - - indentWriter.WriteLine("response = \"Command not implemented.\";"); - indentWriter.WriteLine("return false;"); - indentWriter.WriteLine($"// {string.Join(" -> ", executeMethods.Select(m => m.Identifier))}"); - - indentWriter.FinishAllIndentations(); - - ctx.AddSource($"{namedClassSymbol.Name}.g.cs", writer.ToString());*/ } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/GlobalUsings.cs b/SecretAPI.CodeGeneration/GlobalUsings.cs index c853ab9..6492204 100644 --- a/SecretAPI.CodeGeneration/GlobalUsings.cs +++ b/SecretAPI.CodeGeneration/GlobalUsings.cs @@ -2,7 +2,6 @@ global using Microsoft.CodeAnalysis.CSharp; global using Microsoft.CodeAnalysis.CSharp.Syntax; global using System.Collections.Immutable; -global using Microsoft.CodeAnalysis; global using SecretAPI.CodeGeneration.CodeBuilders; global using SecretAPI.CodeGeneration.Utils; diff --git a/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj b/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj index 7e01223..f4f21ff 100644 --- a/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj +++ b/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj @@ -10,7 +10,6 @@ true false - true Analyzer diff --git a/SecretAPI.Examples/SecretAPI.Examples.csproj b/SecretAPI.Examples/SecretAPI.Examples.csproj index e386317..4f7e537 100644 --- a/SecretAPI.Examples/SecretAPI.Examples.csproj +++ b/SecretAPI.Examples/SecretAPI.Examples.csproj @@ -8,7 +8,6 @@ - diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj index a2ac285..b8b3eff 100644 --- a/SecretAPI/SecretAPI.csproj +++ b/SecretAPI/SecretAPI.csproj @@ -4,21 +4,20 @@ net48 latest enable - 2.0.3 + 2.0.4 true - + true true - Misfiy + obvEve SecretAPI API to extend SCP:SL LabAPI git - https://github.com/Misfiy/SecretAPI + https://github.com/obvEve/SecretAPI README.md MIT - true @@ -26,6 +25,14 @@ True \ + + True + analyzers/dotnet/cs + + + + + @@ -33,8 +40,6 @@ - - From 1680519b8a93e56afc430268f4383e045fa18895 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Sat, 3 Jan 2026 23:39:57 +0100 Subject: [PATCH 25/37] Validators --- .../CodeBuilders/ClassBuilder.cs | 7 ++-- .../CodeBuilders/CodeBuilder.cs | 19 +++++++++ .../CodeBuilders/MethodBuilder.cs | 11 +---- .../Attributes/CommandSenderAttribute.cs | 2 +- .../Validators/CommandValidationResult.cs | 41 +++++++++++++++++++ .../Validators/EnumArgumentValidator.cs | 15 +++++++ .../Validators/ICommandArgumentValidator.cs | 15 +++++++ 7 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 SecretAPI.CodeGeneration/CodeBuilders/CodeBuilder.cs create mode 100644 SecretAPI/Features/Commands/Validators/CommandValidationResult.cs create mode 100644 SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs create mode 100644 SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs diff --git a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs index 80b187d..7bdc000 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs @@ -1,12 +1,11 @@ namespace SecretAPI.CodeGeneration.CodeBuilders; -internal class ClassBuilder +internal class ClassBuilder : CodeBuilder { private NamespaceDeclarationSyntax _namespaceDeclaration; private ClassDeclarationSyntax _classDeclaration; private string _className; - private readonly List _modifiers = new(); private readonly List _usings = new(); private readonly List _methods = new(); @@ -37,13 +36,13 @@ internal ClassBuilder AddUsingStatements(params string[] usingStatement) internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method); - internal ClassBuilder AddModifiers(params SyntaxKind[] modifiers) + /*internal ClassBuilder AddModifiers(params SyntaxKind[] modifiers) { foreach (SyntaxKind token in modifiers) _modifiers.Add(Token(token)); return this; - } + }*/ internal CompilationUnitSyntax Build() { diff --git a/SecretAPI.CodeGeneration/CodeBuilders/CodeBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/CodeBuilder.cs new file mode 100644 index 0000000..a5110ae --- /dev/null +++ b/SecretAPI.CodeGeneration/CodeBuilders/CodeBuilder.cs @@ -0,0 +1,19 @@ +namespace SecretAPI.CodeGeneration.CodeBuilders; + +/// +/// Base of a code builder. +/// +/// The this is handling. +internal abstract class CodeBuilder + where TCodeBuilder : CodeBuilder +{ + protected readonly List _modifiers = new(); + + internal TCodeBuilder AddModifiers(params SyntaxKind[] modifiers) + { + foreach (SyntaxKind token in modifiers) + _modifiers.Add(Token(token)); + + return (TCodeBuilder)this; + } +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs index f3fb0ed..375e4d4 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs @@ -1,9 +1,8 @@ namespace SecretAPI.CodeGeneration.CodeBuilders; -internal class MethodBuilder +internal class MethodBuilder : CodeBuilder { private readonly ClassBuilder _classBuilder; - private readonly List _modifiers = new(); private readonly List _parameters = new(); private readonly List _statements = new(); private readonly string _methodName; @@ -29,14 +28,6 @@ internal MethodBuilder AddParameters(params MethodParameter[] parameters) return this; } - - internal MethodBuilder AddModifiers(params SyntaxKind[] modifiers) - { - foreach (SyntaxKind token in modifiers) - _modifiers.Add(Token(token)); - - return this; - } internal ClassBuilder FinishMethodBuild() { diff --git a/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs b/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs index cb85204..5413eea 100644 --- a/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs +++ b/SecretAPI/Features/Commands/Attributes/CommandSenderAttribute.cs @@ -7,7 +7,7 @@ /// /// Defines a parameter as accepting the command sender. /// - /// this must be , or . + /// This must be , or . [AttributeUsage(AttributeTargets.Parameter)] public class CommandSenderAttribute : Attribute { diff --git a/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs b/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs new file mode 100644 index 0000000..1c8cf9e --- /dev/null +++ b/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs @@ -0,0 +1,41 @@ +namespace SecretAPI.Features.Commands.Validators +{ + /// + /// Defines the result of a . + /// + public struct CommandValidationResult + { + /// + /// Whether the validation was successful. + /// + public readonly bool Success; + + /// + /// The error message, if any exists. + /// + public readonly string? ErrorMessage; + + /// + /// If an argument failed, then the name. Other-wise null. + /// + public readonly string? ArgumentFailName; + + /// + /// Initializes a new instance of the struct. + /// + public CommandValidationResult() + { + Success = true; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The argument that failed. + /// The error message, including how it went wrong. + public CommandValidationResult(string argument, string error) + { + Success = false; + } + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs new file mode 100644 index 0000000..eb1ff6d --- /dev/null +++ b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs @@ -0,0 +1,15 @@ +namespace SecretAPI.Features.Commands.Validators +{ + using System; + + /// + /// Validator for . + /// + public class EnumArgumentValidator : ICommandArgumentValidator + { + /// + public CommandValidationResult Validate(string argument) + { + } + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs new file mode 100644 index 0000000..df0309d --- /dev/null +++ b/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs @@ -0,0 +1,15 @@ +namespace SecretAPI.Features.Commands.Validators +{ + /// + /// Defines the base of a validator for . + /// + public interface ICommandArgumentValidator + { + /// + /// Validates the specified argument. + /// + /// The argument needed to validate. + /// The result of the validation.. + public CommandValidationResult Validate(string argument); + } +} \ No newline at end of file From db7bd31ac48e1506aea83d83ec5d0a1334866ab3 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:59:55 +0100 Subject: [PATCH 26/37] Namespace change --- .../{ => Generators}/CallOnLoadGenerator.cs | 2 +- .../{ => Generators}/CustomCommandGenerator.cs | 2 +- .../Features/Commands/Validators/EnumArgumentValidator.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename SecretAPI.CodeGeneration/{ => Generators}/CallOnLoadGenerator.cs (98%) rename SecretAPI.CodeGeneration/{ => Generators}/CustomCommandGenerator.cs (98%) diff --git a/SecretAPI.CodeGeneration/CallOnLoadGenerator.cs b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs similarity index 98% rename from SecretAPI.CodeGeneration/CallOnLoadGenerator.cs rename to SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs index 12580f9..050c8d3 100644 --- a/SecretAPI.CodeGeneration/CallOnLoadGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.CodeGeneration; +namespace SecretAPI.CodeGeneration.Generators; /// /// Code generator for CallOnLoad/CallOnUnload diff --git a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs similarity index 98% rename from SecretAPI.CodeGeneration/CustomCommandGenerator.cs rename to SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs index c1038f2..809fbe0 100644 --- a/SecretAPI.CodeGeneration/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.CodeGeneration; +namespace SecretAPI.CodeGeneration.Generators; /// /// Code generator for custom commands, creating validation etc. diff --git a/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs index eb1ff6d..121ef05 100644 --- a/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs +++ b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs @@ -1,4 +1,4 @@ -namespace SecretAPI.Features.Commands.Validators +/*namespace SecretAPI.Features.Commands.Validators { using System; @@ -12,4 +12,4 @@ public CommandValidationResult Validate(string argument) { } } -} \ No newline at end of file +}*/ \ No newline at end of file From 8ed973884a8ed882fe2708a8c65dd0f9c1affd3b Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:11:28 +0100 Subject: [PATCH 27/37] Validate using isnt already added --- .../CodeBuilders/ClassBuilder.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs index 7bdc000..4f45f00 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs @@ -24,10 +24,14 @@ internal static ClassBuilder CreateBuilder(string @namespace, string className) internal static ClassBuilder CreateBuilder(INamedTypeSymbol namedClass) => new(namedClass.ContainingNamespace.ToDisplayString(), namedClass.Name); - internal ClassBuilder AddUsingStatements(params string[] usingStatement) + internal ClassBuilder AddUsingStatements(params string[] usingStatements) { - foreach (string statement in usingStatement) - _usings.Add(UsingDirective(ParseName(statement))); + foreach (string statement in usingStatements) + { + UsingDirectiveSyntax usings = UsingDirective(ParseName(statement)); + if (!_usings.Any(existing => existing.IsEquivalentTo(usings))) + _usings.Add(usings); + } return this; } @@ -36,14 +40,6 @@ internal ClassBuilder AddUsingStatements(params string[] usingStatement) internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method); - /*internal ClassBuilder AddModifiers(params SyntaxKind[] modifiers) - { - foreach (SyntaxKind token in modifiers) - _modifiers.Add(Token(token)); - - return this; - }*/ - internal CompilationUnitSyntax Build() { _classDeclaration = _classDeclaration From 22d568f0bf0053f561e171f3999219e7447676ec Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:12:20 +0100 Subject: [PATCH 28/37] Remove unused WritingUtils --- .../Utils/WritingUtils.cs | 118 ------------------ 1 file changed, 118 deletions(-) delete mode 100644 SecretAPI.CodeGeneration/Utils/WritingUtils.cs diff --git a/SecretAPI.CodeGeneration/Utils/WritingUtils.cs b/SecretAPI.CodeGeneration/Utils/WritingUtils.cs deleted file mode 100644 index 113a8ba..0000000 --- a/SecretAPI.CodeGeneration/Utils/WritingUtils.cs +++ /dev/null @@ -1,118 +0,0 @@ -/*namespace SecretAPI.CodeGeneration.Utils; - -using System.CodeDom.Compiler; -using Microsoft.CodeAnalysis; - -public static class WritingUtils -{ - public static string GetAccessibilityString(this Accessibility accessibility) - { - return accessibility switch - { - Accessibility.Private => "private", - Accessibility.ProtectedAndInternal => "protected internal", - Accessibility.Protected => "protected", - Accessibility.Internal => "internal", - Accessibility.Public => "public", - _ => throw new ArgumentOutOfRangeException(nameof(accessibility), accessibility, "Accessibility not supported") - }; - } - - public static IndentedTextWriter WriteGeneratedText(this IndentedTextWriter writer) - { - writer.WriteLine("// "); - writer.WriteLine($"// Generated {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); - writer.WriteLine(); - return writer; - } - - public static IndentedTextWriter WriteNamespace(this IndentedTextWriter writer, INamedTypeSymbol symbol, bool startBrace) - { - if (symbol.ContainingNamespace == null) - return writer; - - writer.WriteLine($"namespace {symbol.ContainingNamespace}"); - if (startBrace) - WriteStartBrace(writer); - - return writer; - } - - public static IndentedTextWriter WriteUsings(this IndentedTextWriter writer, params string[] usings) - { - if (usings.Length == 0) - return writer; - - foreach (string @using in usings) - writer.WriteLine($"using {@using};"); - - writer.WriteLine(); - return writer; - } - - public static IndentedTextWriter WritePartialClass(this IndentedTextWriter writer, string className, bool startBrace) - { - writer.WriteLine($"partial class {className}"); - if (startBrace) - { - writer.WriteLine("{"); - writer.Indent++; - } - - return writer; - } - - public static IndentedTextWriter WriteMethod( - this IndentedTextWriter writer, - string methodName, - string returnType, - bool isOverride, - Accessibility accessibility, - bool startBrace, - params string[] parameters) - { - writer.Write(GetAccessibilityString(accessibility)); - if (isOverride) - writer.Write(" override "); - writer.Write(returnType); - writer.Write(" " + methodName); - writer.WriteLine("("); - writer.Indent++; - - for (int index = 0; index < parameters.Length; index++) - { - string parameter = parameters[index]; - if (parameters.Length > index + 1) - writer.WriteLine(parameter + ","); - else if (!startBrace) - writer.Write(parameter + ")"); - else - writer.WriteLine(parameter + ")"); - } - - writer.Indent--; - - if (startBrace) - writer.WriteStartBrace(); - - return writer; - } - - public static IndentedTextWriter WriteStartBrace(this IndentedTextWriter writer) - { - writer.WriteLine("{"); - writer.Indent++; - return writer; - } - - public static IndentedTextWriter FinishAllIndentations(this IndentedTextWriter writer) - { - while (writer.Indent > 0) - { - writer.Indent--; - writer.WriteLine("}"); - } - - return writer; - } -}*/ \ No newline at end of file From cefadc9cb248d0b37634ab5f186cdcd7e27677e4 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:58:28 +0100 Subject: [PATCH 29/37] ShouldAutoGenerate disabling + Fix SecretAPI nuget --- .../Generators/CallOnLoadGenerator.cs | 18 ++++++++++++++++-- .../SecretAPI.CodeGeneration.csproj | 3 ++- SecretAPI/Attribute/CallOnLoadAttribute.cs | 10 +++++++++- SecretAPI/SecretAPI.csproj | 5 +---- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs index 050c8d3..2003781 100644 --- a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs @@ -68,6 +68,17 @@ private static int GetPriority(IMethodSymbol method, string attributeLocation) return 0; } + private static bool ShouldAutogenerate(IMethodSymbol method, string attributeLocation) + { + AttributeData? attribute = method.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == attributeLocation); + + if (attribute is { ConstructorArguments.Length: >= 2 }) + return (bool)attribute.ConstructorArguments[1].Value!; + + return false; + } + private static void Generate( SourceProductionContext context, INamedTypeSymbol? pluginClassSymbol, @@ -77,17 +88,20 @@ private static void Generate( return; IMethodSymbol[] loadCalls = methods - .Where(m => m.isLoad) + .Where(m => m.isLoad && ShouldAutogenerate(m.method, CallOnLoadAttributeLocation)) .Select(m => m.method) .OrderBy(m => GetPriority(m, CallOnLoadAttributeLocation)) .ToArray(); IMethodSymbol[] unloadCalls = methods - .Where(m => m.isUnload) + .Where(m => m.isUnload && ShouldAutogenerate(m.method, CallOnUnloadAttributeLocation)) .Select(m => m.method) .OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation)) .ToArray(); + if (!loadCalls.Any() && !unloadCalls.Any()) + return; + CompilationUnitSyntax compilation = ClassBuilder.CreateBuilder(pluginClassSymbol) .AddUsingStatements("System") .AddModifiers(SyntaxKind.PartialKeyword) diff --git a/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj b/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj index f4f21ff..4423fec 100644 --- a/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj +++ b/SecretAPI.CodeGeneration/SecretAPI.CodeGeneration.csproj @@ -10,7 +10,8 @@ true false - Analyzer + Library + true diff --git a/SecretAPI/Attribute/CallOnLoadAttribute.cs b/SecretAPI/Attribute/CallOnLoadAttribute.cs index cb55356..72b91aa 100644 --- a/SecretAPI/Attribute/CallOnLoadAttribute.cs +++ b/SecretAPI/Attribute/CallOnLoadAttribute.cs @@ -16,9 +16,11 @@ public class CallOnLoadAttribute : Attribute, IPriority /// Initializes a new instance of the class. /// /// The priority of the load. - public CallOnLoadAttribute(int priority = 0) + /// Whether it should source generate the method call. False will make it slower during runtime. + public CallOnLoadAttribute(int priority = 0, bool shouldSourceGen = true) { Priority = priority; + ShouldSourceGen = shouldSourceGen; } /// @@ -26,6 +28,12 @@ public CallOnLoadAttribute(int priority = 0) /// public int Priority { get; } + /// + /// Gets a value indicating whether it should source generate the method call. + /// + /// If disabled, this will do runtime reflection which is slower. + public bool ShouldSourceGen { get; } + /// /// Loads and calls all . /// diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj index 216bd71..7ba6670 100644 --- a/SecretAPI/SecretAPI.csproj +++ b/SecretAPI/SecretAPI.csproj @@ -32,14 +32,11 @@ - + - From d12dfa1f4760ef42a596ac345d830e5dfb66ea59 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Tue, 6 Jan 2026 16:52:40 +0100 Subject: [PATCH 30/37] Validator start --- .../Generators/CustomCommandGenerator.cs | 37 ++++++++++++++++++- .../Attributes/ValidateArgumentAttribute.cs | 26 +++++++++++++ .../Validators/CommandValidationResult.cs | 25 +++++++------ .../Validators/EnumArgumentValidator.cs | 13 +++++-- .../Validators/ICommandArgumentValidator.cs | 7 ++-- 5 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 SecretAPI/Features/Commands/Attributes/ValidateArgumentAttribute.cs diff --git a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs index 809fbe0..084d758 100644 --- a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs @@ -100,4 +100,39 @@ private static void Generate( ctx.AddSource($"{namedClassSymbol.Name}.g.cs", compilation.ToFullString()); } -} \ No newline at end of file +} + +// ! Example of basic structure needed + +/* + +// +#pragma warning disable + +namespace SecretAPI.Examples.Commands +{ + using System.CodeDom.Compiler; + using System; + using System.Collections.Generic; + using System.Linq; + using CommandSystem; + using Features.Commands; + + partial class ExampleParentCommand + { + public override bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + if (arguments.Any()) + { + string argument = arguments.First(); + foreach (CustomCommand subCommand in SubCommands) + { + if (argument == subCommand.Command || subCommand.Aliases.Any(a => a == argument)) + subCommand.Execute(arguments, sender, out response); + } + } + } + } +} + +*/ \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Attributes/ValidateArgumentAttribute.cs b/SecretAPI/Features/Commands/Attributes/ValidateArgumentAttribute.cs new file mode 100644 index 0000000..5a1e89c --- /dev/null +++ b/SecretAPI/Features/Commands/Attributes/ValidateArgumentAttribute.cs @@ -0,0 +1,26 @@ +namespace SecretAPI.Features.Commands.Attributes +{ + using System; + using SecretAPI.Features.Commands.Validators; + + /// + /// Defines the attribute needed to auto validate with . + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class ValidateArgumentAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The of the . + public ValidateArgumentAttribute(Type type) + { + Type = type; + } + + /// + /// Gets the of the . + /// + public Type Type { get; } + } +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs b/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs index 1c8cf9e..d348136 100644 --- a/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs +++ b/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs @@ -1,9 +1,10 @@ namespace SecretAPI.Features.Commands.Validators { /// - /// Defines the result of a . + /// Defines the result of a . /// - public struct CommandValidationResult + /// The type this is validating. + public struct CommandValidationResult { /// /// Whether the validation was successful. @@ -11,30 +12,32 @@ public struct CommandValidationResult public readonly bool Success; /// - /// The error message, if any exists. + /// Gets the value when successful. /// - public readonly string? ErrorMessage; + public readonly T? Value; /// - /// If an argument failed, then the name. Other-wise null. + /// The error message, if any exists. /// - public readonly string? ArgumentFailName; + public readonly string? ErrorMessage; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - public CommandValidationResult() + /// The value that was validated. + public CommandValidationResult(T value) { + Value = value; Success = true; } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// The argument that failed. /// The error message, including how it went wrong. - public CommandValidationResult(string argument, string error) + public CommandValidationResult(string error) { + ErrorMessage = error; Success = false; } } diff --git a/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs index 121ef05..f992f4b 100644 --- a/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs +++ b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs @@ -1,15 +1,20 @@ -/*namespace SecretAPI.Features.Commands.Validators +namespace SecretAPI.Features.Commands.Validators { using System; /// /// Validator for . /// - public class EnumArgumentValidator : ICommandArgumentValidator + /// The to validate. + public class EnumArgumentValidator : ICommandArgumentValidator + where TEnum : struct, Enum { /// - public CommandValidationResult Validate(string argument) + public CommandValidationResult Validate(string argument) { + return Enum.TryParse(argument, true, out TEnum value) + ? new CommandValidationResult(value) + : new CommandValidationResult($"Argument provided was not a valid {typeof(TEnum).Name}"); } } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs index df0309d..f69504c 100644 --- a/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs +++ b/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs @@ -3,13 +3,14 @@ /// /// Defines the base of a validator for . /// - public interface ICommandArgumentValidator + /// The type this validator is for. + public interface ICommandArgumentValidator { /// /// Validates the specified argument. /// /// The argument needed to validate. - /// The result of the validation.. - public CommandValidationResult Validate(string argument); + /// The result of the validation. + public CommandValidationResult Validate(string argument); } } \ No newline at end of file From 105aacdafc0b34b35365866b7eb2298e6099f65f Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:01:06 +0100 Subject: [PATCH 31/37] Player argument validator --- .../Validators/CommandValidationResult.cs | 2 +- .../Validators/PlayerArgumentValidator.cs | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs diff --git a/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs b/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs index d348136..ed6b612 100644 --- a/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs +++ b/SecretAPI/Features/Commands/Validators/CommandValidationResult.cs @@ -4,7 +4,7 @@ /// Defines the result of a . /// /// The type this is validating. - public struct CommandValidationResult + public readonly struct CommandValidationResult { /// /// Whether the validation was successful. diff --git a/SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs new file mode 100644 index 0000000..30b33a6 --- /dev/null +++ b/SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs @@ -0,0 +1,30 @@ +namespace SecretAPI.Features.Commands.Validators +{ + using LabApi.Features.Wrappers; + + /// + /// Validates command argument for . + /// + public class PlayerArgumentValidator : ICommandArgumentValidator + { + /// + public CommandValidationResult Validate(string argument) + { + // player id + if (int.TryParse(argument, out int value) && Player.TryGet(value, out Player? found)) + return new CommandValidationResult(found); + + // player user id + if (Player.TryGet(argument, out found)) + return new CommandValidationResult(found); + + foreach (Player player in Player.List) + { + if (player.Nickname == argument || player.UserId == argument) + return new CommandValidationResult(player); + } + + return new CommandValidationResult($"{argument} is not a valid player!"); + } + } +} \ No newline at end of file From 005fc9919265af2f65c80cac3e2934dfac3bc1ab Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:20:33 +0100 Subject: [PATCH 32/37] ValidatorSingleton --- .../Generators/CustomCommandGenerator.cs | 5 ++++- .../Commands/Validators/EnumArgumentValidator.cs | 2 +- .../Validators/ICommandArgumentValidator.cs | 7 ++++++- .../Commands/Validators/PlayerArgumentValidator.cs | 2 +- .../Commands/Validators/ValidatorSingleton.cs | 14 ++++++++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs diff --git a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs index 084d758..648c02b 100644 --- a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs @@ -105,7 +105,10 @@ private static void Generate( // ! Example of basic structure needed /* - + +ValidatorSingleton.Instance.Validate(arguments.First()); +ValidatorSingleton>.Instance.Validate(arguments.First()); + // #pragma warning disable diff --git a/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs index f992f4b..979f8e8 100644 --- a/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs +++ b/SecretAPI/Features/Commands/Validators/EnumArgumentValidator.cs @@ -6,7 +6,7 @@ /// Validator for . /// /// The to validate. - public class EnumArgumentValidator : ICommandArgumentValidator + public sealed class EnumArgumentValidator : ICommandArgumentValidator where TEnum : struct, Enum { /// diff --git a/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs index f69504c..d3b39a0 100644 --- a/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs +++ b/SecretAPI/Features/Commands/Validators/ICommandArgumentValidator.cs @@ -1,10 +1,15 @@ namespace SecretAPI.Features.Commands.Validators { + /// + /// Defines the base of a validator for . + /// + public interface ICommandArgumentValidator; + /// /// Defines the base of a validator for . /// /// The type this validator is for. - public interface ICommandArgumentValidator + public interface ICommandArgumentValidator : ICommandArgumentValidator { /// /// Validates the specified argument. diff --git a/SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs b/SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs index 30b33a6..f79547b 100644 --- a/SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs +++ b/SecretAPI/Features/Commands/Validators/PlayerArgumentValidator.cs @@ -5,7 +5,7 @@ /// /// Validates command argument for . /// - public class PlayerArgumentValidator : ICommandArgumentValidator + public sealed class PlayerArgumentValidator : ICommandArgumentValidator { /// public CommandValidationResult Validate(string argument) diff --git a/SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs b/SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs new file mode 100644 index 0000000..fa118d7 --- /dev/null +++ b/SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs @@ -0,0 +1,14 @@ +namespace SecretAPI.Features.Commands.Validators; + +/// +/// Handles singleton-ing . +/// +/// The type. +public static class ValidatorSingleton + where T : class, ICommandArgumentValidator, new() +{ + /// + /// The current instance. + /// + public static readonly T Instance = new(); +} \ No newline at end of file From 379cd158350f1c5a4d68f840ad63cfcf7410a496 Mon Sep 17 00:00:00 2001 From: evelyn <85962933+Misfiy@users.noreply.github.com> Date: Wed, 7 Jan 2026 09:21:55 +0100 Subject: [PATCH 33/37] Minor changes --- .../Generators/CallOnLoadGenerator.cs | 7 ++++--- SecretAPI.CodeGeneration/Utils/Util.cs | 14 ++++++------- .../Commands/Validators/ValidatorSingleton.cs | 21 ++++++++++--------- SecretAPI/SecretApi.cs | 1 - 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs index 2003781..ffac413 100644 --- a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs @@ -6,6 +6,7 @@ [Generator] public class CallOnLoadGenerator : IIncrementalGenerator { + private const string PluginNamespace = "LabApi.Loader.Features.Plugins"; private const string PluginBaseClassName = "Plugin"; private const string CallOnLoadAttributeLocation = "SecretAPI.Attribute.CallOnLoadAttribute"; private const string CallOnUnloadAttributeLocation = "SecretAPI.Attribute.CallOnUnloadAttribute"; @@ -32,8 +33,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (node, _) => node is ClassDeclarationSyntax, static (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol) - .Where(static c => - c is { IsAbstract: false, BaseType.Name: PluginBaseClassName })!; + .Where(static c => !c.IsAbstract && c.BaseType?.Name == PluginBaseClassName && + c.BaseType.ContainingNamespace.ToDisplayString() == PluginNamespace); context.RegisterSourceOutput(pluginClassProvider.Combine(callProvider.Collect()), static (context, data) => { @@ -86,7 +87,7 @@ private static void Generate( { if (pluginClassSymbol == null || methods.IsEmpty) return; - + IMethodSymbol[] loadCalls = methods .Where(m => m.isLoad && ShouldAutogenerate(m.method, CallOnLoadAttributeLocation)) .Select(m => m.method) diff --git a/SecretAPI.CodeGeneration/Utils/Util.cs b/SecretAPI.CodeGeneration/Utils/Util.cs index 71e164f..6f40f72 100644 --- a/SecretAPI.CodeGeneration/Utils/Util.cs +++ b/SecretAPI.CodeGeneration/Utils/Util.cs @@ -1,6 +1,6 @@ namespace SecretAPI.CodeGeneration.Utils; -public static class Util +internal static class Util { private static AttributeSyntax GetGeneratedCodeAttributeSyntax() => Attribute(IdentifierName("GeneratedCode")) @@ -17,7 +17,7 @@ private static AttributeSyntax GetGeneratedCodeAttributeSyntax() internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax())); - public static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) + internal static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) => GenericName(genericName) .WithTypeArgumentList( TypeArgumentList( @@ -25,22 +25,22 @@ public static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKi PredefinedType( Token(predefinedType))))); - public static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind) + internal static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind) => PredefinedType(Token(kind)); - public static StatementSyntax MethodCallStatement(string typeName, string methodName) + internal static StatementSyntax MethodCallStatement(string typeName, string methodName) => ExpressionStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, ParseTypeName(typeName), IdentifierName(methodName)))); - public static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls) + internal static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls) { List statements = new(); - foreach (IMethodSymbol methodCall in methodCalls) - statements.Add(MethodCallStatement(methodCall.ContainingType.ToDisplayString(), methodCall.Name)); + foreach (IMethodSymbol method in methodCalls) + statements.Add(MethodCallStatement(method.ContainingType.ToDisplayString(), method.Name)); return statements.ToArray(); } diff --git a/SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs b/SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs index fa118d7..5c1e1e7 100644 --- a/SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs +++ b/SecretAPI/Features/Commands/Validators/ValidatorSingleton.cs @@ -1,14 +1,15 @@ -namespace SecretAPI.Features.Commands.Validators; - -/// -/// Handles singleton-ing . -/// -/// The type. -public static class ValidatorSingleton - where T : class, ICommandArgumentValidator, new() +namespace SecretAPI.Features.Commands.Validators { /// - /// The current instance. + /// Handles singleton-ing . /// - public static readonly T Instance = new(); + /// The type. + public static class ValidatorSingleton + where T : class, ICommandArgumentValidator, new() + { + /// + /// The current static instance. + /// + public static readonly T Instance = new(); + } } \ No newline at end of file diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs index 73db453..367d34a 100644 --- a/SecretAPI/SecretApi.cs +++ b/SecretAPI/SecretApi.cs @@ -6,7 +6,6 @@ using LabApi.Features; using LabApi.Loader.Features.Plugins; using LabApi.Loader.Features.Plugins.Enums; - using SecretAPI.Attribute; /// /// Main class handling loading API. From 545aa3b72199d92d37baef6a33430a3e697292bd Mon Sep 17 00:00:00 2001 From: evelyn <85962933+Misfiy@users.noreply.github.com> Date: Wed, 7 Jan 2026 09:42:30 +0100 Subject: [PATCH 34/37] Fix generator namespace --- .../Generators/CallOnLoadGenerator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs index ffac413..0f1d347 100644 --- a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs @@ -8,8 +8,8 @@ public class CallOnLoadGenerator : IIncrementalGenerator { private const string PluginNamespace = "LabApi.Loader.Features.Plugins"; private const string PluginBaseClassName = "Plugin"; - private const string CallOnLoadAttributeLocation = "SecretAPI.Attribute.CallOnLoadAttribute"; - private const string CallOnUnloadAttributeLocation = "SecretAPI.Attribute.CallOnUnloadAttribute"; + private const string CallOnLoadAttributeLocation = "SecretAPI.Attributes.CallOnLoadAttribute"; + private const string CallOnUnloadAttributeLocation = "SecretAPI.Attributes.CallOnUnloadAttribute"; /// public void Initialize(IncrementalGeneratorInitializationContext context) @@ -28,12 +28,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) HasAttribute(method, CallOnUnloadAttributeLocation))) .Where(static m => m.Item2 || m.Item3); - IncrementalValuesProvider pluginClassProvider = + IncrementalValuesProvider pluginClassProvider = context.SyntaxProvider.CreateSyntaxProvider( static (node, _) => node is ClassDeclarationSyntax, static (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol) - .Where(static c => !c.IsAbstract && c.BaseType?.Name == PluginBaseClassName && + .Where(static c => c != null && !c.IsAbstract && c.BaseType?.Name == PluginBaseClassName && c.BaseType.ContainingNamespace.ToDisplayString() == PluginNamespace); context.RegisterSourceOutput(pluginClassProvider.Combine(callProvider.Collect()), static (context, data) => From 0a77a00223692db015502b5a701978175c8c2e7b Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:01:49 +0100 Subject: [PATCH 35/37] Seperate utils --- SecretAPI.CodeGeneration/GlobalUsings.cs | 17 +++++-- .../Utils/GeneratedIdentifyUtils.cs | 19 ++++++++ .../Utils/GenericTypeUtils.cs | 12 +++++ SecretAPI.CodeGeneration/Utils/MethodUtils.cs | 21 +++++++++ SecretAPI.CodeGeneration/Utils/TypeUtils.cs | 7 +++ SecretAPI.CodeGeneration/Utils/Util.cs | 47 ------------------- 6 files changed, 72 insertions(+), 51 deletions(-) create mode 100644 SecretAPI.CodeGeneration/Utils/GeneratedIdentifyUtils.cs create mode 100644 SecretAPI.CodeGeneration/Utils/GenericTypeUtils.cs create mode 100644 SecretAPI.CodeGeneration/Utils/MethodUtils.cs create mode 100644 SecretAPI.CodeGeneration/Utils/TypeUtils.cs delete mode 100644 SecretAPI.CodeGeneration/Utils/Util.cs diff --git a/SecretAPI.CodeGeneration/GlobalUsings.cs b/SecretAPI.CodeGeneration/GlobalUsings.cs index 6492204..64f79cd 100644 --- a/SecretAPI.CodeGeneration/GlobalUsings.cs +++ b/SecretAPI.CodeGeneration/GlobalUsings.cs @@ -1,10 +1,19 @@ -global using Microsoft.CodeAnalysis; +//? Utils from other places +global using Microsoft.CodeAnalysis; global using Microsoft.CodeAnalysis.CSharp; global using Microsoft.CodeAnalysis.CSharp.Syntax; global using System.Collections.Immutable; -global using SecretAPI.CodeGeneration.CodeBuilders; -global using SecretAPI.CodeGeneration.Utils; +//? Static utils from other places global using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; global using static Microsoft.CodeAnalysis.CSharp.SyntaxFacts; -global using static SecretAPI.CodeGeneration.Utils.Util; \ No newline at end of file + +//? Utils from SecretAPI +global using SecretAPI.CodeGeneration.CodeBuilders; +global using SecretAPI.CodeGeneration.Utils; + +//? Static utils from SecretAPI +global using static SecretAPI.CodeGeneration.Utils.GenericTypeUtils; +global using static SecretAPI.CodeGeneration.Utils.GeneratedIdentifyUtils; +global using static SecretAPI.CodeGeneration.Utils.MethodUtils; +global using static SecretAPI.CodeGeneration.Utils.TypeUtils; \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.CodeGeneration/Utils/GeneratedIdentifyUtils.cs new file mode 100644 index 0000000..3c7b30e --- /dev/null +++ b/SecretAPI.CodeGeneration/Utils/GeneratedIdentifyUtils.cs @@ -0,0 +1,19 @@ +namespace SecretAPI.CodeGeneration.Utils; + +internal static class GeneratedIdentifyUtils +{ + private static AttributeSyntax GetGeneratedCodeAttributeSyntax() + => Attribute(IdentifierName("GeneratedCode")) + .WithArgumentList( + AttributeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.CodeGeneration"))), + Token(SyntaxKind.CommaToken), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))), + }))); + + internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() + => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax())); +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Utils/GenericTypeUtils.cs b/SecretAPI.CodeGeneration/Utils/GenericTypeUtils.cs new file mode 100644 index 0000000..d4588bd --- /dev/null +++ b/SecretAPI.CodeGeneration/Utils/GenericTypeUtils.cs @@ -0,0 +1,12 @@ +namespace SecretAPI.CodeGeneration.Utils; + +internal static class GenericTypeUtils +{ + internal static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) + => GenericName(genericName) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + PredefinedType( + Token(predefinedType))))); +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Utils/MethodUtils.cs b/SecretAPI.CodeGeneration/Utils/MethodUtils.cs new file mode 100644 index 0000000..508f774 --- /dev/null +++ b/SecretAPI.CodeGeneration/Utils/MethodUtils.cs @@ -0,0 +1,21 @@ +namespace SecretAPI.CodeGeneration.Utils; + +internal static class MethodUtils +{ + internal static StatementSyntax MethodCallStatement(string typeName, string methodName) + => ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParseTypeName(typeName), IdentifierName(methodName)))); + + internal static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls) + { + List statements = new(); + + foreach (IMethodSymbol method in methodCalls) + statements.Add(MethodCallStatement(method.ContainingType.ToDisplayString(), method.Name)); + + return statements.ToArray(); + } +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Utils/TypeUtils.cs b/SecretAPI.CodeGeneration/Utils/TypeUtils.cs new file mode 100644 index 0000000..6bfbfcd --- /dev/null +++ b/SecretAPI.CodeGeneration/Utils/TypeUtils.cs @@ -0,0 +1,7 @@ +namespace SecretAPI.CodeGeneration.Utils; + +internal static class TypeUtils +{ + internal static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind) + => PredefinedType(Token(kind)); +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Utils/Util.cs b/SecretAPI.CodeGeneration/Utils/Util.cs deleted file mode 100644 index 6f40f72..0000000 --- a/SecretAPI.CodeGeneration/Utils/Util.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace SecretAPI.CodeGeneration.Utils; - -internal static class Util -{ - private static AttributeSyntax GetGeneratedCodeAttributeSyntax() - => Attribute(IdentifierName("GeneratedCode")) - .WithArgumentList( - AttributeArgumentList( - SeparatedList( - new SyntaxNodeOrToken[] - { - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.CodeGeneration"))), - Token(SyntaxKind.CommaToken), - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))), - }))); - - internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() - => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax())); - - internal static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) - => GenericName(genericName) - .WithTypeArgumentList( - TypeArgumentList( - SingletonSeparatedList( - PredefinedType( - Token(predefinedType))))); - - internal static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind) - => PredefinedType(Token(kind)); - - internal static StatementSyntax MethodCallStatement(string typeName, string methodName) - => ExpressionStatement( - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - ParseTypeName(typeName), IdentifierName(methodName)))); - - internal static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls) - { - List statements = new(); - - foreach (IMethodSymbol method in methodCalls) - statements.Add(MethodCallStatement(method.ContainingType.ToDisplayString(), method.Name)); - - return statements.ToArray(); - } -} \ No newline at end of file From b5696dafc162729186fb85fcfd0589ace8d8b8c8 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:33:06 +0100 Subject: [PATCH 36/37] Improvements --- .../CodeBuilders/ClassBuilder.cs | 19 +++++++++---------- .../CodeBuilders/MethodBuilder.cs | 6 +++--- .../Generators/CallOnLoadGenerator.cs | 6 +++--- .../Generators/CustomCommandGenerator.cs | 2 +- SecretAPI.CodeGeneration/Utils/MethodUtils.cs | 13 ++++++------- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs index 4f45f00..09d61cb 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs @@ -4,25 +4,23 @@ internal class ClassBuilder : CodeBuilder { private NamespaceDeclarationSyntax _namespaceDeclaration; private ClassDeclarationSyntax _classDeclaration; - private string _className; private readonly List _usings = new(); private readonly List _methods = new(); - private ClassBuilder(string @namespace, string className) + private ClassBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration) { - _namespaceDeclaration = NamespaceDeclaration(ParseName(@namespace)); - _className = className; - _classDeclaration = ClassDeclaration(className); + _namespaceDeclaration = namespaceDeclaration; + _classDeclaration = classDeclaration; AddUsingStatements("System.CodeDom.Compiler"); } - - internal static ClassBuilder CreateBuilder(string @namespace, string className) - => new(@namespace, className); internal static ClassBuilder CreateBuilder(INamedTypeSymbol namedClass) - => new(namedClass.ContainingNamespace.ToDisplayString(), namedClass.Name); + => CreateBuilder(NamespaceDeclaration(ParseName(namedClass.ContainingNamespace.ToDisplayString())), ClassDeclaration(namedClass.Name)); + + internal static ClassBuilder CreateBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration) + => new(namespaceDeclaration, classDeclaration); internal ClassBuilder AddUsingStatements(params string[] usingStatements) { @@ -36,7 +34,8 @@ internal ClassBuilder AddUsingStatements(params string[] usingStatements) return this; } - internal MethodBuilder StartMethodCreation(string methodName, string returnType) => new(this, methodName, returnType); + internal MethodBuilder StartMethodCreation(string methodName, TypeSyntax returnType) => new(this, methodName, returnType); + internal MethodBuilder StartMethodCreation(string methodName, SyntaxKind returnType) => StartMethodCreation(methodName, GetPredefinedTypeSyntax(returnType)); internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method); diff --git a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs index 375e4d4..6d771dc 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs @@ -6,9 +6,9 @@ internal class MethodBuilder : CodeBuilder private readonly List _parameters = new(); private readonly List _statements = new(); private readonly string _methodName; - private readonly string _returnType; + private readonly TypeSyntax _returnType; - internal MethodBuilder(ClassBuilder classBuilder, string methodName, string returnType) + internal MethodBuilder(ClassBuilder classBuilder, string methodName, TypeSyntax returnType) { _classBuilder = classBuilder; _methodName = methodName; @@ -33,7 +33,7 @@ internal ClassBuilder FinishMethodBuild() { BlockSyntax body = _statements.Any() ? Block(_statements) : Block(); - MethodDeclarationSyntax methodDeclaration = MethodDeclaration(ParseTypeName(_returnType), _methodName) + MethodDeclarationSyntax methodDeclaration = MethodDeclaration(_returnType, _methodName) .AddModifiers(_modifiers.ToArray()) .AddParameterListParameters(_parameters.ToArray()) .WithBody(body); diff --git a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs index 0f1d347..03d04cb 100644 --- a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs @@ -106,16 +106,16 @@ private static void Generate( CompilationUnitSyntax compilation = ClassBuilder.CreateBuilder(pluginClassSymbol) .AddUsingStatements("System") .AddModifiers(SyntaxKind.PartialKeyword) - .StartMethodCreation("OnLoad", "void") + .StartMethodCreation("OnLoad", SyntaxKind.VoidKeyword) .AddModifiers(SyntaxKind.PublicKeyword) .AddStatements(MethodCallStatements(loadCalls)) .FinishMethodBuild() - .StartMethodCreation("OnUnload", "void") + .StartMethodCreation("OnUnload", SyntaxKind.VoidKeyword) .AddModifiers(SyntaxKind.PublicKeyword) .AddStatements(MethodCallStatements(unloadCalls)) .FinishMethodBuild() .Build(); - + context.AddSource($"{pluginClassSymbol.Name}.g.cs", compilation.ToFullString()); } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs index 648c02b..3df867c 100644 --- a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs @@ -92,7 +92,7 @@ private static void Generate( .AddUsingStatements("System", "System.Collections.Generic") .AddUsingStatements("CommandSystem") .AddModifiers(SyntaxKind.PartialKeyword) - .StartMethodCreation(ExecuteMethodName, "bool") + .StartMethodCreation(ExecuteMethodName, SyntaxKind.BoolKeyword) .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.OverrideKeyword) .AddParameters(ArgumentsParam, SenderParam, ResponseParam) .FinishMethodBuild() diff --git a/SecretAPI.CodeGeneration/Utils/MethodUtils.cs b/SecretAPI.CodeGeneration/Utils/MethodUtils.cs index 508f774..eec2737 100644 --- a/SecretAPI.CodeGeneration/Utils/MethodUtils.cs +++ b/SecretAPI.CodeGeneration/Utils/MethodUtils.cs @@ -2,20 +2,19 @@ internal static class MethodUtils { - internal static StatementSyntax MethodCallStatement(string typeName, string methodName) + internal static StatementSyntax MethodCallStatement(string typeName, string methodName) => + MethodCallStatement(ParseTypeName(typeName), IdentifierName(methodName)); + + internal static StatementSyntax MethodCallStatement(TypeSyntax type, IdentifierNameSyntax method) => ExpressionStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - ParseTypeName(typeName), IdentifierName(methodName)))); + type, method))); internal static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls) { - List statements = new(); - - foreach (IMethodSymbol method in methodCalls) - statements.Add(MethodCallStatement(method.ContainingType.ToDisplayString(), method.Name)); - + IEnumerable statements = methodCalls.Select(s => MethodCallStatement(s.ContainingType.ToDisplayString(), s.Name)); return statements.ToArray(); } } \ No newline at end of file From 5ac0e3491f84e46dcdd255248ad01cf2667a7727 Mon Sep 17 00:00:00 2001 From: Misfiy <85962933+obvEve@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:24:19 +0100 Subject: [PATCH 37/37] Analyse --- .../AnalyzerReleases.Shipped.md | 1 + .../AnalyzerReleases.Unshipped.md | 5 +++ .../CodeBuilders/ClassBuilder.cs | 2 ++ .../Diagnostics/CommandDiagnostics.cs | 12 +++++++ .../Generators/CallOnLoadGenerator.cs | 17 +++++----- .../Generators/CustomCommandGenerator.cs | 34 ++++++++++++++----- SecretAPI.CodeGeneration/GlobalUsings.cs | 1 + .../Utils/MethodParameter.cs | 8 +---- .../Commands/ExampleExplodeCommand.cs | 3 +- .../Commands/ExampleParentCommand.cs | 3 +- SecretAPI/Features/Commands/CommandResult.cs | 18 ++++++++++ 11 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 SecretAPI.CodeGeneration/AnalyzerReleases.Shipped.md create mode 100644 SecretAPI.CodeGeneration/AnalyzerReleases.Unshipped.md create mode 100644 SecretAPI.CodeGeneration/Diagnostics/CommandDiagnostics.cs create mode 100644 SecretAPI/Features/Commands/CommandResult.cs diff --git a/SecretAPI.CodeGeneration/AnalyzerReleases.Shipped.md b/SecretAPI.CodeGeneration/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/SecretAPI.CodeGeneration/AnalyzerReleases.Shipped.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/AnalyzerReleases.Unshipped.md b/SecretAPI.CodeGeneration/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..c302cdd --- /dev/null +++ b/SecretAPI.CodeGeneration/AnalyzerReleases.Unshipped.md @@ -0,0 +1,5 @@ +### New Rules + + Rule ID | Category | Severity | Notes +------------|----------|----------|--------------------- + SecretGen0 | Usage | Error | CA6000_AnalyzerName \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs index 09d61cb..42aa391 100644 --- a/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs +++ b/SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs @@ -55,4 +55,6 @@ internal CompilationUnitSyntax Build() .NormalizeWhitespace() .WithLeadingTrivia(Comment("// "), LineFeed, Comment("#pragma warning disable"), LineFeed, LineFeed); } + + internal void Build(SourceProductionContext context, string name) => context.AddSource(name, Build().ToFullString()); } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Diagnostics/CommandDiagnostics.cs b/SecretAPI.CodeGeneration/Diagnostics/CommandDiagnostics.cs new file mode 100644 index 0000000..61a3cb3 --- /dev/null +++ b/SecretAPI.CodeGeneration/Diagnostics/CommandDiagnostics.cs @@ -0,0 +1,12 @@ +namespace SecretAPI.CodeGeneration.Diagnostics; + +internal static class CommandDiagnostics +{ + internal static readonly DiagnosticDescriptor InvalidExecuteMethod = new( + "SecretGen0", + "Invalid ExecuteCommand method", + "Method '{0}' marked with [ExecuteCommand] is invalid: {1}", + "Usage", + DiagnosticSeverity.Error, + true); +} \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs index 03d04cb..e9b05ac 100644 --- a/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs @@ -103,19 +103,20 @@ private static void Generate( if (!loadCalls.Any() && !unloadCalls.Any()) return; - CompilationUnitSyntax compilation = ClassBuilder.CreateBuilder(pluginClassSymbol) + ClassBuilder classBuilder = ClassBuilder.CreateBuilder(pluginClassSymbol) .AddUsingStatements("System") - .AddModifiers(SyntaxKind.PartialKeyword) - .StartMethodCreation("OnLoad", SyntaxKind.VoidKeyword) + .AddModifiers(SyntaxKind.PartialKeyword); + + classBuilder.StartMethodCreation("OnLoad", SyntaxKind.VoidKeyword) .AddModifiers(SyntaxKind.PublicKeyword) .AddStatements(MethodCallStatements(loadCalls)) - .FinishMethodBuild() - .StartMethodCreation("OnUnload", SyntaxKind.VoidKeyword) + .FinishMethodBuild(); + + classBuilder.StartMethodCreation("OnUnload", SyntaxKind.VoidKeyword) .AddModifiers(SyntaxKind.PublicKeyword) .AddStatements(MethodCallStatements(unloadCalls)) - .FinishMethodBuild() - .Build(); + .FinishMethodBuild(); - context.AddSource($"{pluginClassSymbol.Name}.g.cs", compilation.ToFullString()); + classBuilder.Build(context, $"{pluginClassSymbol.Name}.g.cs"); } } \ No newline at end of file diff --git a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs index 3df867c..a80113a 100644 --- a/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs +++ b/SecretAPI.CodeGeneration/Generators/CustomCommandGenerator.cs @@ -9,19 +9,20 @@ public class CustomCommandGenerator : IIncrementalGenerator private const string CommandName = "CustomCommand"; private const string ExecuteMethodName = "Execute"; private const string ExecuteCommandMethodAttributeLocation = "SecretAPI.Features.Commands.Attributes.ExecuteCommandAttribute"; + private const string CommandResultLocation = "CommandResult"; private static readonly MethodParameter ArgumentsParam = new( identifier: "arguments", type: GetSingleGenericTypeSyntax("ArraySegment", SyntaxKind.StringKeyword) ); - + private static readonly MethodParameter SenderParam = new( identifier: "sender", type: IdentifierName("ICommandSender") ); - + private static readonly MethodParameter ResponseParam = new( identifier: "response", @@ -78,7 +79,7 @@ private static bool IsExecuteMethod(GeneratorSyntaxContext context, MethodDeclar } private static void Generate( - SourceProductionContext ctx, + SourceProductionContext context, INamedTypeSymbol namedClassSymbol, ImmutableArray executeMethods) { @@ -88,17 +89,32 @@ private static void Generate( if (namedClassSymbol.BaseType?.Name != CommandName) return; - CompilationUnitSyntax compilation = ClassBuilder.CreateBuilder(namedClassSymbol) + ClassBuilder classBuilder = ClassBuilder.CreateBuilder(namedClassSymbol) .AddUsingStatements("System", "System.Collections.Generic") .AddUsingStatements("CommandSystem") - .AddModifiers(SyntaxKind.PartialKeyword) - .StartMethodCreation(ExecuteMethodName, SyntaxKind.BoolKeyword) + .AddModifiers(SyntaxKind.PartialKeyword); + + foreach (MethodDeclarationSyntax method in executeMethods) + { + if (method.ReturnType.ToString() != CommandResultLocation) + { + context.ReportDiagnostic( + Diagnostic.Create( + CommandDiagnostics.InvalidExecuteMethod, + method.ReturnType.GetLocation(), + method.Identifier.Text, + "Return type should be of type " + CommandResultLocation + ) + ); + } + } + + classBuilder.StartMethodCreation(ExecuteMethodName, SyntaxKind.BoolKeyword) .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.OverrideKeyword) .AddParameters(ArgumentsParam, SenderParam, ResponseParam) - .FinishMethodBuild() - .Build(); + .FinishMethodBuild(); - ctx.AddSource($"{namedClassSymbol.Name}.g.cs", compilation.ToFullString()); + classBuilder.Build(context, $"{namedClassSymbol.Name}.g.cs"); } } diff --git a/SecretAPI.CodeGeneration/GlobalUsings.cs b/SecretAPI.CodeGeneration/GlobalUsings.cs index 64f79cd..d0cdcf7 100644 --- a/SecretAPI.CodeGeneration/GlobalUsings.cs +++ b/SecretAPI.CodeGeneration/GlobalUsings.cs @@ -11,6 +11,7 @@ //? Utils from SecretAPI global using SecretAPI.CodeGeneration.CodeBuilders; global using SecretAPI.CodeGeneration.Utils; +global using SecretAPI.CodeGeneration.Diagnostics; //? Static utils from SecretAPI global using static SecretAPI.CodeGeneration.Utils.GenericTypeUtils; diff --git a/SecretAPI.CodeGeneration/Utils/MethodParameter.cs b/SecretAPI.CodeGeneration/Utils/MethodParameter.cs index be3df2d..b400ff2 100644 --- a/SecretAPI.CodeGeneration/Utils/MethodParameter.cs +++ b/SecretAPI.CodeGeneration/Utils/MethodParameter.cs @@ -36,11 +36,5 @@ internal MethodParameter( _default = @default; } - public ParameterSyntax Syntax => - Parameter( - attributeLists: _attributeLists, - modifiers: _modifiers, - type: _type, - identifier: _identifier, - @default: _default); + public ParameterSyntax Syntax => Parameter(_attributeLists, _modifiers, _type, _identifier, _default); } \ No newline at end of file diff --git a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs index 3ce72ec..5d9e1eb 100644 --- a/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleExplodeCommand.cs @@ -17,10 +17,11 @@ public partial class ExampleExplodeCommand : CustomCommand public override string Description => "Explodes a player!"; [ExecuteCommand] - private void Explode([CommandSender] Player sender, Player target) + private CommandResult Explode([CommandSender] Player sender, Player target) { Logger.Debug($"Example explode command run by {sender.Nickname} - Target: {target.Nickname}"); TimedGrenadeProjectile.SpawnActive(target.Position, ItemType.GrenadeHE, sender); + return new CommandResult(true, "Success"); } } } \ No newline at end of file diff --git a/SecretAPI.Examples/Commands/ExampleParentCommand.cs b/SecretAPI.Examples/Commands/ExampleParentCommand.cs index 98232ae..d8a55c3 100644 --- a/SecretAPI.Examples/Commands/ExampleParentCommand.cs +++ b/SecretAPI.Examples/Commands/ExampleParentCommand.cs @@ -24,9 +24,10 @@ public partial class ExampleParentCommand : CustomCommand public override CustomCommand[] SubCommands { get; } = [new ExampleExplodeCommand()]; [ExecuteCommand] - private void Run([CommandSender] Player sender) + private CommandResult Run([CommandSender] Player sender) { Logger.Debug($"Example parent was run by {sender.Nickname}"); + return new CommandResult(true, "Success"); } } } \ No newline at end of file diff --git a/SecretAPI/Features/Commands/CommandResult.cs b/SecretAPI/Features/Commands/CommandResult.cs new file mode 100644 index 0000000..efcb09c --- /dev/null +++ b/SecretAPI/Features/Commands/CommandResult.cs @@ -0,0 +1,18 @@ +namespace SecretAPI.Features.Commands +{ + /// + /// Defines the result of a . + /// + public readonly struct CommandResult(bool success, string response) + { + /// + /// Whether the command succeeded. + /// + public readonly bool Success = success; + + /// + /// The response to give after command use. + /// + public readonly string Response = response; + } +} \ No newline at end of file