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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] .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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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/24] 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 @@ - -