From 5900d7b0857e0350f36227a1c40f3cd3f86801f0 Mon Sep 17 00:00:00 2001 From: Esmaybe Date: Sat, 7 Jun 2025 16:04:08 +0200 Subject: [PATCH 1/3] Added proper brigadier --- .../minestom/AnnotationCommand.java | 393 ++++++++++++++---- 1 file changed, 315 insertions(+), 78 deletions(-) diff --git a/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java b/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java index f94b6ed..3639a4e 100644 --- a/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java +++ b/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java @@ -1,9 +1,23 @@ package com.jazzkuh.commandlib.minestom; -import com.jazzkuh.commandlib.common.*; +import com.jazzkuh.commandlib.common.AnnotationCommandImpl; +import com.jazzkuh.commandlib.common.AnnotationCommandParser; +import com.jazzkuh.commandlib.common.AnnotationCommandSender; +import com.jazzkuh.commandlib.common.AnnotationSubCommand; +import com.jazzkuh.commandlib.common.annotations.Completion; +import com.jazzkuh.commandlib.common.annotations.Greedy; import com.jazzkuh.commandlib.common.annotations.Main; +import com.jazzkuh.commandlib.common.annotations.Optional; import com.jazzkuh.commandlib.common.annotations.Subcommand; -import com.jazzkuh.commandlib.common.exception.*; +import com.jazzkuh.commandlib.common.exception.ArgumentException; +import com.jazzkuh.commandlib.common.exception.CommandException; +import com.jazzkuh.commandlib.common.exception.ContextResolverException; +import com.jazzkuh.commandlib.common.exception.ErrorException; +import com.jazzkuh.commandlib.common.exception.ParameterException; +import com.jazzkuh.commandlib.common.exception.PermissionException; +import com.jazzkuh.commandlib.common.resolvers.CompletionResolver; +import com.jazzkuh.commandlib.common.resolvers.ContextResolver; +import com.jazzkuh.commandlib.common.resolvers.Resolvers; import com.jazzkuh.commandlib.minestom.terminal.LoggingConsoleSender; import com.jazzkuh.commandlib.minestom.utils.StringUtils; import com.jazzkuh.commandlib.minestom.utils.permission.Permissable; @@ -16,24 +30,44 @@ import net.minestom.server.command.ConsoleSender; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.Argument; +import net.minestom.server.command.builder.arguments.ArgumentBoolean; +import net.minestom.server.command.builder.arguments.ArgumentString; import net.minestom.server.command.builder.arguments.ArgumentStringArray; +import net.minestom.server.command.builder.arguments.ArgumentWord; +import net.minestom.server.command.builder.arguments.number.ArgumentDouble; +import net.minestom.server.command.builder.arguments.number.ArgumentFloat; +import net.minestom.server.command.builder.arguments.number.ArgumentInteger; +import net.minestom.server.command.builder.arguments.number.ArgumentLong; import net.minestom.server.command.builder.suggestion.SuggestionEntry; import net.minestom.server.entity.Player; import org.codehaus.plexus.util.ReflectionUtils; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.*; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class AnnotationCommand extends Command implements AnnotationCommandImpl { + private static final ComponentLogger LOGGER = ComponentLogger.logger("CommandLibrary"); protected String commandName; protected AnnotationSubCommand mainCommand = null; protected final List subCommands = new ArrayList<>(); + protected final Map>> commandArguments = new HashMap<>(); public AnnotationCommand(String commandName) { super(commandName); + this.commandName = commandName; this.init(); } @@ -58,6 +92,7 @@ private void init() { if (mainCommands.size() > 1) { throw new IllegalArgumentException("There can only be one main command per class"); } + mainCommands.forEach(method -> this.mainCommand = AnnotationCommandParser.parse(this, method)); Field aliasesField = ReflectionUtils.getFieldByNameIncludingSuperclasses("aliases", Command.class); @@ -75,24 +110,16 @@ private void init() { List subcommandMethods = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Subcommand.class)).toList(); subcommandMethods.forEach(method -> this.subCommands.add(AnnotationCommandParser.parse(this, method))); - ArgumentStringArray arguments = new ArgumentStringArray("[...]"); - arguments.setDefaultValue(new String[0]); - arguments.setSuggestionCallback((sender, context, suggestionCallback) -> { - String[] args = this.fixArguments(context.get(arguments)); + for (AnnotationSubCommand subCommand : this.subCommands) + this.createBrigadierSyntax(subCommand); - List suggestions = this.suggest(sender, args); - if (suggestions.isEmpty()) return; - for (String suggestion : suggestions) { - SuggestionEntry suggestionEntry = new SuggestionEntry(suggestion); - if (suggestionCallback.getEntries().contains(suggestionEntry)) continue; - suggestionCallback.addEntry(suggestionEntry); - } - }); + if (this.mainCommand != null) + this.createBrigadierSyntax(this.mainCommand); + + this.setDefaultExecutor(this::executeDefault); - setDefaultExecutor(this::execute); - addSyntax(this::execute, arguments); - if (this.mainCommand.getPermission() != null) { - setCondition((commandSender, s) -> { + if (this.mainCommand != null && this.mainCommand.getPermission() != null) { + this.setCondition((commandSender, s) -> { Permissable permissable = new Permissable(null); if (commandSender instanceof Player player) { permissable = new Permissable(player.getUuid()); @@ -103,52 +130,207 @@ private void init() { } } - private String[] fixArguments(String[] args) { - // Minestom set null character to end of array - if (args.length > 0 && args[args.length - 1].equals("\u0000")) { - args = Arrays.copyOf(args, args.length); + private void createBrigadierSyntax(AnnotationSubCommand command) { + Method method = command.getMethod(); + Parameter[] parameters = method.getParameters(); + List> arguments = new ArrayList<>(); + + List usageParamNames = this.parseUsageParameterNames(command); + + for (int i = 1; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + String paramName = usageParamNames.size() >= i ? usageParamNames.get(i - 1) : parameter.getName(); + + Argument argument = this.createArgumentForParameter(parameter, paramName); + arguments.add(argument); + } + + this.commandArguments.put(command, arguments); + + if (method.isAnnotationPresent(Subcommand.class)) { + ArgumentWord subcommandArg = new ArgumentWord(command.getName() + "_sub"); + subcommandArg.from(command.getName()); + for (String alias : command.getAliases()) { + subcommandArg.from(alias); + } + + if (arguments.isEmpty()) { + this.addSyntax((sender, context) -> executeSubcommand(command, sender, context), subcommandArg); + return; + } - args[args.length - 1] = ""; + this.createOptionalSyntaxCombinations(command, subcommandArg, arguments); + return; } - return args; + if (arguments.isEmpty()) return; + this.createOptionalSyntaxCombinations(command, null, arguments); } + private void createOptionalSyntaxCombinations(AnnotationSubCommand command, ArgumentWord subcommandArg, + List> arguments) { + Parameter[] parameters = command.getMethod().getParameters(); - @Override - public String getCommandName() { - return this.commandName; + List> requiredArgs = new ArrayList<>(); + List> optionalArgs = new ArrayList<>(); + + for (int i = 0; i < arguments.size(); i++) { + Parameter param = parameters[i + 1]; + if (param.isAnnotationPresent(Optional.class)) optionalArgs.add(arguments.get(i)); + else requiredArgs.add(arguments.get(i)); + } + + int totalOptional = optionalArgs.size(); + for (int optionalCount = 0; optionalCount <= totalOptional; optionalCount++) { + List> syntaxArgs = new ArrayList<>(); + if (subcommandArg != null) + syntaxArgs.add(subcommandArg); + + syntaxArgs.addAll(requiredArgs); + syntaxArgs.addAll(optionalArgs.subList(0, optionalCount)); + + Argument[] argsArray = syntaxArgs.toArray(new Argument[0]); + addSyntax((sender, context) -> { + if (subcommandArg != null) { + this.executeSubcommand(command, sender, context); + return; + } + + this.executeMainCommand(command, sender, context); + }, argsArray); + } } - public void execute(CommandSender sender, CommandContext context) { - String[] args = Arrays.stream(context.getInput().split(" ")).skip(1).toArray(String[]::new); + private Argument createArgumentForParameter(Parameter parameter, String name) { + Class type = parameter.getType(); - if (args.length < 1) { - if (this.mainCommand == null) { - this.formatUsage(sender); - return; + if (parameter.isAnnotationPresent(Greedy.class) && type == String.class) { + ArgumentStringArray greedyArg = new ArgumentStringArray(name); + this.setupCompletionForArgument(greedyArg, parameter); + return greedyArg; + } + + if (type == String.class) { + ArgumentString stringArg = new ArgumentString(name); + this.setupCompletionForArgument(stringArg, parameter); + return stringArg; + } else if (type == Integer.class || type == int.class) { + return new ArgumentInteger(name); + } else if (type == Double.class || type == double.class) { + return new ArgumentDouble(name); + } else if (type == Float.class || type == float.class) { + return new ArgumentFloat(name); + } else if (type == Long.class || type == long.class) { + return new ArgumentLong(name); + } else if (type == Boolean.class || type == boolean.class) { + return new ArgumentBoolean(name); + } else if (type == UUID.class) { + ArgumentString uuidArg = new ArgumentString(name); + this.setupCompletionForArgument(uuidArg, parameter); + return uuidArg; + } else if (type.isEnum()) { + ArgumentString enumArg = new ArgumentString(name); + this.setupEnumCompletion(enumArg, (Class) type); + return enumArg; + } else { + ArgumentString customArg = new ArgumentString(name); + this.setupCompletionForArgument(customArg, parameter); + return customArg; + } + } + + private > void setupCompletionForArgument(T argument, Parameter parameter) { + argument.setSuggestionCallback((sender, context, suggestionCallback) -> { + String currentInput = ""; + + if (argument instanceof ArgumentString) { + currentInput = context.getOrDefault((ArgumentString) argument, ""); + } else if (argument instanceof ArgumentStringArray) { + String[] array = context.getOrDefault((ArgumentStringArray) argument, new String[0]); + currentInput = array.length > 0 ? array[array.length - 1] : ""; } - this.executeCommand(this.mainCommand, sender, args); - return; + List suggestions = getParameterCompletions(sender, parameter, currentInput); + for (String suggestion : suggestions) { + SuggestionEntry entry = new SuggestionEntry(suggestion); + if (suggestionCallback.getEntries().contains(entry)) + continue; + + suggestionCallback.addEntry(entry); + } + }); + } + + @SuppressWarnings("unchecked") + private void setupEnumCompletion(ArgumentString argument, Class enumClass) { + argument.setSuggestionCallback((sender, context, suggestionCallback) -> { + String currentInput = context.getOrDefault(argument, ""); + List enumValues = EnumSet.allOf(enumClass).stream() + .map(e -> e.toString().toLowerCase()) + .toList(); + + List matches = currentInput.isEmpty() ? enumValues : StringUtils.copyPartialMatches(currentInput, enumValues, new ArrayList<>()); + for (String match : matches) { + suggestionCallback.addEntry(new SuggestionEntry(match)); + } + }); + } + + private List getParameterCompletions(CommandSender sender, Parameter parameter, String currentInput) { + AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); + + if (parameter.isAnnotationPresent(Completion.class)) { + Completion completion = parameter.getAnnotation(Completion.class); + CompletionResolver resolver = Resolvers.completion(completion.value()); + if (resolver != null) { + List allCompletions = resolver.resolve(commandSender, currentInput); + + if (currentInput.isEmpty()) { + return allCompletions; + } + + return StringUtils.copyPartialMatches(currentInput, allCompletions, new ArrayList<>()); + } } - for (AnnotationSubCommand subCommand : subCommands) { - if (!args[0].equalsIgnoreCase(subCommand.getName()) && !subCommand.getAliases().contains(args[0].toLowerCase())) - continue; - this.executeCommand(subCommand, sender, args); + CompletionResolver completionResolver = Resolvers.completion(parameter.getType()); + if (completionResolver != null) { + List allCompletions = completionResolver.resolve(commandSender, currentInput); + + if (currentInput.isEmpty()) { + return allCompletions; + } + + return StringUtils.copyPartialMatches(currentInput, allCompletions, new ArrayList<>()); + } + + return new ArrayList<>(); + } + + private void executeDefault(CommandSender sender, CommandContext context) { + if (this.mainCommand == null) { + this.formatUsage(sender); return; } - this.executeCommand(this.mainCommand, sender, args); + this.executeMainCommand(this.mainCommand, sender, context); } - private void executeCommand(AnnotationSubCommand subCommand, CommandSender sender, String[] args) { + private void executeMainCommand(AnnotationSubCommand command, CommandSender sender, CommandContext context) { + this.executeCommandWithContext(command, sender, context); + } + + private void executeSubcommand(AnnotationSubCommand command, CommandSender sender, CommandContext context) { + this.executeCommandWithContext(command, sender, context); + } + + private void executeCommandWithContext(AnnotationSubCommand subCommand, CommandSender sender, CommandContext context) { Permissable permissable = new Permissable(null); - if (sender instanceof ConsoleSender) sender = new LoggingConsoleSender(); + if (sender instanceof ConsoleSender) + sender = new LoggingConsoleSender(); if (sender instanceof Player player) { permissable = new Permissable(player.getUuid()); - LOGGER.info("Command executed by {}: {} {}", player.getUsername(), this.getCommandName(), String.join(" ", args)); + LOGGER.info("Command executed by {}: {} {}", player.getUsername(), this.getCommandName(), context.getInput()); } if (subCommand.getPermission() != null && !(sender instanceof ConsoleSender) && !permissable.hasPermission(subCommand.getPermission())) { @@ -157,11 +339,8 @@ private void executeCommand(AnnotationSubCommand subCommand, CommandSender sende return; } - AnnotationCommandExecutor commandExecutor = new AnnotationCommandExecutor<>(subCommand, this); - AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); - try { - commandExecutor.execute(commandSender, args); + this.executeMethodWithBrigadierContext(subCommand, sender, context); } catch (CommandException commandException) { if (commandException instanceof ArgumentException) { this.formatUsage(sender); @@ -177,49 +356,107 @@ private void executeCommand(AnnotationSubCommand subCommand, CommandSender sende } } - public List suggest(CommandSender sender, String[] args) { - AnnotationCommandExecutor mainCommandExecutor = new AnnotationCommandExecutor<>(this.mainCommand, this); - AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); - Permissable permissable = new Permissable(null); - if (sender instanceof Player player) { - permissable = new Permissable(player.getUuid()); + private void executeMethodWithBrigadierContext(AnnotationSubCommand subCommand, CommandSender sender, + CommandContext context) throws CommandException { + Method method = subCommand.getMethod(); + Parameter[] parameters = method.getParameters(); + List> arguments = this.commandArguments.get(subCommand); + + Object[] resolvedParameters = new Object[parameters.length]; + resolvedParameters[0] = sender; + + for (int i = 1; i < parameters.length; i++) { + resolvedParameters[i] = this.resolveParameter(parameters[i], i - 1, arguments, context); } - List options = new ArrayList<>(mainCommandExecutor.complete(commandSender, args)); + try { + method.setAccessible(true); + method.invoke(this, resolvedParameters); + } catch (Exception e) { + e.printStackTrace(); + throw new ErrorException(e.getMessage()); + } + } - if (args.length == 1 && !this.subCommands.isEmpty()) { - for (AnnotationSubCommand subCommand : this.subCommands) { - if (subCommand.getPermission() == null) { - options.add(subCommand.getName()); - continue; - } + private Object resolveParameter(Parameter parameter, int argIndex, List> arguments, + CommandContext context) throws CommandException { + if (argIndex >= arguments.size()) { + return handleMissingArgument(parameter); + } - if (permissable.hasPermission(subCommand.getPermission())) - options.add(subCommand.getName()); - } + Argument argument = arguments.get(argIndex); + Object value = context.get(argument); - return StringUtils.copyPartialMatches(args[0], options, new ArrayList<>(options.size())); + if (value != null) { + return this.resolveParameterValue(parameter, value, parameter.getType()); } - for (AnnotationSubCommand subCommand : this.subCommands) { - if (args.length < 1) { - if (subCommand.getPermission() == null) { - options.add(subCommand.getName()); - continue; + return this.handleMissingArgument(parameter); + } + + private Object handleMissingArgument(Parameter parameter) throws ArgumentException { + if (parameter.isAnnotationPresent(Optional.class)) { + return null; + } + + throw new ArgumentException(); + } + + private Object resolveParameterValue(Parameter parameter, Object value, Class paramClass) throws CommandException { + if (parameter.isAnnotationPresent(Greedy.class) && paramClass == String.class && value instanceof String[]) { + return String.join(" ", (String[]) value); + } + + if (paramClass.isInstance(value)) { + return value; + } + + if (value instanceof String stringValue) { + if (paramClass.isEnum()) { + try { + return Enum.valueOf((Class) paramClass, stringValue.toUpperCase()); + } catch (Exception e) { + throw new ParameterException("Cannot resolve parameter " + stringValue + " for type " + paramClass.getSimpleName()); + } + } + + ContextResolver contextResolver = Resolvers.context(paramClass); + if (contextResolver != null) { + Object resolved = contextResolver.resolve(stringValue); + if (resolved == null) { + throw new ParameterException("Cannot resolve parameter " + stringValue + " for type " + paramClass.getSimpleName()); } - if (permissable.hasPermission(subCommand.getPermission())) - options.add(subCommand.getName()); - continue; + return resolved; } + } + + return value; + } + + private List parseUsageParameterNames(AnnotationSubCommand command) { + List paramNames = new ArrayList<>(); - if (!args[0].equalsIgnoreCase(subCommand.getName()) && !subCommand.getAliases().contains(args[0].toLowerCase())) - continue; - AnnotationCommandExecutor subCommandExecutor = new AnnotationCommandExecutor<>(subCommand, this); - options.addAll(subCommandExecutor.complete(commandSender, args)); + if (command.usage() == null || command.usage().isEmpty()) { + return paramNames; } - return options; + String usage = command.usage().trim(); + + Pattern pattern = java.util.regex.Pattern.compile("[<\\[]([^>\\]]+)[>\\]]"); + Matcher matcher = pattern.matcher(usage); + + while (matcher.find()) { + String paramName = matcher.group(1).trim(); + paramNames.add(paramName); + } + + return paramNames; + } + + @Override + public String getCommandName() { + return this.commandName; } public void register(CommandManager commandManager) { From 062816aacce797b5f49a709ac8862fa90e126529 Mon Sep 17 00:00:00 2001 From: Esmaybe Date: Sat, 7 Jun 2025 16:16:00 +0200 Subject: [PATCH 2/3] Added the option to have multiple main commands --- .../minestom/AnnotationCommand.java | 112 ++++++++++++------ 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java b/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java index 3639a4e..621cc87 100644 --- a/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java +++ b/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java @@ -61,7 +61,7 @@ public class AnnotationCommand extends Command implements AnnotationCommandImpl private static final ComponentLogger LOGGER = ComponentLogger.logger("CommandLibrary"); protected String commandName; - protected AnnotationSubCommand mainCommand = null; + protected final List mainCommands = new ArrayList<>(); protected final List subCommands = new ArrayList<>(); protected final Map>> commandArguments = new HashMap<>(); @@ -88,44 +88,57 @@ private void init() { nameField.setAccessible(true); nameField.set(this, this.commandName); - List mainCommands = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Main.class)).toList(); - if (mainCommands.size() > 1) { - throw new IllegalArgumentException("There can only be one main command per class"); - } + List mainCommandMethods = Arrays.stream(this.getClass().getMethods()) + .filter(method -> method.isAnnotationPresent(Main.class)) + .toList(); + + mainCommandMethods.forEach(method -> this.mainCommands.add(AnnotationCommandParser.parse(this, method))); - mainCommands.forEach(method -> this.mainCommand = AnnotationCommandParser.parse(this, method)); + List allAliases = new ArrayList<>(); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + allAliases.addAll(mainCommand.getAliases()); + } Field aliasesField = ReflectionUtils.getFieldByNameIncludingSuperclasses("aliases", Command.class); aliasesField.setAccessible(true); - aliasesField.set(this, this.mainCommand.getAliases().toArray(new String[0])); + aliasesField.set(this, allAliases.toArray(new String[0])); List names = new ArrayList<>(); names.add(this.commandName); - names.addAll(this.mainCommand.getAliases()); + names.addAll(allAliases); Field namesField = ReflectionUtils.getFieldByNameIncludingSuperclasses("names", Command.class); namesField.setAccessible(true); namesField.set(this, names.toArray(new String[0])); - List subcommandMethods = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Subcommand.class)).toList(); + List subcommandMethods = Arrays.stream(this.getClass().getMethods()) + .filter(method -> method.isAnnotationPresent(Subcommand.class)) + .toList(); subcommandMethods.forEach(method -> this.subCommands.add(AnnotationCommandParser.parse(this, method))); - for (AnnotationSubCommand subCommand : this.subCommands) + // We register subcommands first to make sure they don't get overridden by main commands + for (AnnotationSubCommand subCommand : this.subCommands) { this.createBrigadierSyntax(subCommand); + } - if (this.mainCommand != null) - this.createBrigadierSyntax(this.mainCommand); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + this.createBrigadierSyntax(mainCommand); + } this.setDefaultExecutor(this::executeDefault); - if (this.mainCommand != null && this.mainCommand.getPermission() != null) { + boolean allMainCommandsHavePermissions = !this.mainCommands.isEmpty() && + this.mainCommands.stream().allMatch(cmd -> cmd.getPermission() != null); + + if (allMainCommandsHavePermissions) { this.setCondition((commandSender, s) -> { Permissable permissable = new Permissable(null); if (commandSender instanceof Player player) { permissable = new Permissable(player.getUuid()); } - return permissable.hasPermission(this.mainCommand.getPermission()); + Permissable finalPermissable = permissable; + return this.mainCommands.stream().anyMatch(cmd -> finalPermissable.hasPermission(cmd.getPermission())); }); } } @@ -163,12 +176,15 @@ private void createBrigadierSyntax(AnnotationSubCommand command) { return; } - if (arguments.isEmpty()) return; + if (arguments.isEmpty()) { + this.addSyntax((sender, context) -> executeMainCommand(command, sender, context)); + return; + } + this.createOptionalSyntaxCombinations(command, null, arguments); } - private void createOptionalSyntaxCombinations(AnnotationSubCommand command, ArgumentWord subcommandArg, - List> arguments) { + private void createOptionalSyntaxCombinations(AnnotationSubCommand command, ArgumentWord subcommandArg, List> arguments) { Parameter[] parameters = command.getMethod().getParameters(); List> requiredArgs = new ArrayList<>(); @@ -308,12 +324,21 @@ private List getParameterCompletions(CommandSender sender, Parameter par } private void executeDefault(CommandSender sender, CommandContext context) { - if (this.mainCommand == null) { + if (this.mainCommands.isEmpty()) { this.formatUsage(sender); return; } - this.executeMainCommand(this.mainCommand, sender, context); + if (this.mainCommands.size() == 1) { + AnnotationSubCommand singleMain = this.mainCommands.get(0); + List> args = this.commandArguments.get(singleMain); + if (args.isEmpty()) { + this.executeMainCommand(singleMain, sender, context); + return; + } + } + + this.formatUsage(sender); } private void executeMainCommand(AnnotationSubCommand command, CommandSender sender, CommandContext context) { @@ -356,8 +381,7 @@ private void executeCommandWithContext(AnnotationSubCommand subCommand, CommandS } } - private void executeMethodWithBrigadierContext(AnnotationSubCommand subCommand, CommandSender sender, - CommandContext context) throws CommandException { + private void executeMethodWithBrigadierContext(AnnotationSubCommand subCommand, CommandSender sender, CommandContext context) throws CommandException { Method method = subCommand.getMethod(); Parameter[] parameters = method.getParameters(); List> arguments = this.commandArguments.get(subCommand); @@ -378,8 +402,7 @@ private void executeMethodWithBrigadierContext(AnnotationSubCommand subCommand, } } - private Object resolveParameter(Parameter parameter, int argIndex, List> arguments, - CommandContext context) throws CommandException { + private Object resolveParameter(Parameter parameter, int argIndex, List> arguments, CommandContext context) throws CommandException { if (argIndex >= arguments.size()) { return handleMissingArgument(parameter); } @@ -403,13 +426,11 @@ private Object handleMissingArgument(Parameter parameter) throws ArgumentExcepti } private Object resolveParameterValue(Parameter parameter, Object value, Class paramClass) throws CommandException { - if (parameter.isAnnotationPresent(Greedy.class) && paramClass == String.class && value instanceof String[]) { + if (parameter.isAnnotationPresent(Greedy.class) && paramClass == String.class && value instanceof String[]) return String.join(" ", (String[]) value); - } - if (paramClass.isInstance(value)) { + if (paramClass.isInstance(value)) return value; - } if (value instanceof String stringValue) { if (paramClass.isEnum()) { @@ -437,9 +458,8 @@ private Object resolveParameterValue(Parameter parameter, Object value, Class private List parseUsageParameterNames(AnnotationSubCommand command) { List paramNames = new ArrayList<>(); - if (command.usage() == null || command.usage().isEmpty()) { + if (command.usage() == null || command.usage().isEmpty()) return paramNames; - } String usage = command.usage().trim(); @@ -477,21 +497,35 @@ public void formatUsage(CommandSender sender) { permissable = new Permissable(player.getUuid()); } - if (mainCommand.getUsage() != null && !this.mainCommand.getUsage().isEmpty() && this.subCommands.isEmpty()) { - sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax is: ", TextColor.fromHexString("#FBFB00"))); - sender.sendMessage(Component.text("/" + this.getCommandName() + this.mainCommand.getUsage() + " - " + this.mainCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); - return; - } + List usageMessages = new ArrayList<>(); - sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax's are:", TextColor.fromHexString("#FBFB00"))); - if (mainCommand.getUsage() != null && !mainCommand.getUsage().isEmpty()) { - sender.sendMessage(Component.text("/" + this.getCommandName() + this.mainCommand.getUsage() + " - " + this.mainCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + if (mainCommand.getPermission() == null || permissable.hasPermission(mainCommand.getPermission())) { + String usage = "/" + this.getCommandName() + mainCommand.getUsage() + " - " + mainCommand.getDescription(); + usageMessages.add(usage); + } } for (AnnotationSubCommand subCommand : this.subCommands) { if (subCommand.getPermission() == null || permissable.hasPermission(subCommand.getPermission())) { - sender.sendMessage(Component.text("/" + this.getCommandName() + " " + subCommand.getName() + subCommand.getUsage() + " - " + subCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); + String usage = "/" + this.getCommandName() + " " + subCommand.getName() + subCommand.getUsage() + " - " + subCommand.getDescription(); + usageMessages.add(usage); + } + } + + if (usageMessages.isEmpty()) { + sender.sendMessage(Component.text("No available command syntaxes.", TextColor.fromHexString("#FF6B6B"))); + return; + } + + if (usageMessages.size() == 1) { + sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax is: ", TextColor.fromHexString("#FBFB00"))); + sender.sendMessage(Component.text(usageMessages.get(0), TextColor.fromHexString("#FBFB00"))); + } else { + sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax's are:", TextColor.fromHexString("#FBFB00"))); + for (String usage : usageMessages) { + sender.sendMessage(Component.text(usage, TextColor.fromHexString("#FBFB00"))); } } } -} +} \ No newline at end of file From 177e96e82bae52b1b663b489a23f5f79b34fbab8 Mon Sep 17 00:00:00 2001 From: Esmaybe Date: Sun, 8 Jun 2025 13:27:21 +0200 Subject: [PATCH 3/3] Spigot & Velocity as well --- .../commandlib/spigot/AnnotationCommand.java | 98 +++++++++++++------ .../velocity/AnnotationCommand.java | 90 ++++++++++++----- 2 files changed, 133 insertions(+), 55 deletions(-) diff --git a/spigot/src/main/java/com/jazzkuh/commandlib/spigot/AnnotationCommand.java b/spigot/src/main/java/com/jazzkuh/commandlib/spigot/AnnotationCommand.java index a6a5a84..ab1f7d7 100644 --- a/spigot/src/main/java/com/jazzkuh/commandlib/spigot/AnnotationCommand.java +++ b/spigot/src/main/java/com/jazzkuh/commandlib/spigot/AnnotationCommand.java @@ -22,11 +22,11 @@ import java.util.List; public class AnnotationCommand extends Command implements AnnotationCommandImpl { + protected final String commandName; - protected AnnotationSubCommand mainCommand = null; + protected final List mainCommands = new ArrayList<>(); protected final List subCommands = new ArrayList<>(); - public AnnotationCommand(String commandName) { super(commandName); @@ -47,11 +47,8 @@ public AnnotationCommand() { } private void init() { - List mainCommands = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Main.class)).toList(); - if (mainCommands.size() > 1) { - throw new IllegalArgumentException("There can only be one main command per class"); - } - mainCommands.forEach(method -> this.mainCommand = AnnotationCommandParser.parse(this, method)); + List mainCommandMethods = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Main.class)).toList(); + mainCommandMethods.forEach(method -> this.mainCommands.add(AnnotationCommandParser.parse(this, method))); List subcommandMethods = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Subcommand.class)).toList(); subcommandMethods.forEach(method -> this.subCommands.add(AnnotationCommandParser.parse(this, method))); @@ -65,12 +62,17 @@ public String getCommandName() { @Override public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { if (args.length < 1) { - if (this.mainCommand == null) { + if (this.mainCommands.isEmpty()) { this.formatUsage(sender); return true; } - this.executeCommand(this.mainCommand, sender, args); + if (this.mainCommands.size() == 1) { + this.executeCommand(this.mainCommands.get(0), sender, args); + return true; + } + + this.formatUsage(sender); return true; } @@ -80,7 +82,17 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String label, @No return true; } - this.executeCommand(this.mainCommand, sender, args); + if (!this.mainCommands.isEmpty()) { + if (this.mainCommands.size() == 1) { + this.executeCommand(this.mainCommands.get(0), sender, args); + return true; + } + + this.formatUsage(sender); + return true; + } + + this.formatUsage(sender); return true; } @@ -114,12 +126,17 @@ private void executeCommand(AnnotationSubCommand subCommand, CommandSender sende @Override @NotNull public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) { - AnnotationCommandExecutor mainCommandExecutor = new AnnotationCommandExecutor<>(this.mainCommand, this); + List options = new ArrayList<>(); AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); - List options = new ArrayList<>(mainCommandExecutor.complete(commandSender, args)); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + if (mainCommand.getPermission() == null || sender.hasPermission(mainCommand.getPermission())) { + AnnotationCommandExecutor mainCommandExecutor = new AnnotationCommandExecutor<>(mainCommand, this); + options.addAll(mainCommandExecutor.complete(commandSender, args)); + } + } - if (args.length == 1 && this.subCommands.size() >= 1) { + if (args.length == 1 && !this.subCommands.isEmpty()) { for (AnnotationSubCommand subCommand : this.subCommands) { if (subCommand.getPermission() == null) { options.add(subCommand.getName()); @@ -141,14 +158,21 @@ public List tabComplete(@NotNull CommandSender sender, @NotNull String a return options; } - public void register(JavaPlugin plugin) { try { - if (this.mainCommand.getPermission() != null) { - this.setPermission(this.mainCommand.getPermission()); + List allAliases = new ArrayList<>(); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + allAliases.addAll(mainCommand.getAliases()); + } + this.setAliases(allAliases); + + boolean allMainCommandsHavePermissions = !this.mainCommands.isEmpty() && + this.mainCommands.stream().allMatch(cmd -> cmd.getPermission() != null); + + if (allMainCommandsHavePermissions) { + this.setPermission(this.mainCommands.get(0).getPermission()); this.permissionMessage(Component.text("You do not have permission to use this command.", TextColor.fromHexString("#FB465C"))); } - this.setAliases(this.mainCommand.getAliases()); Field bukkitCommandMap = Bukkit.getServer().getClass().getDeclaredField("commandMap"); bukkitCommandMap.setAccessible(true); @@ -157,8 +181,8 @@ public void register(JavaPlugin plugin) { commandMap.register(plugin.getName(), this); plugin.getLogger().info("Registered command: " + this.getCommandName()); - if (!this.mainCommand.getAliases().isEmpty()) { - plugin.getLogger().info("- Registered aliases: " + String.join(", ", this.getAliases())); + if (!allAliases.isEmpty()) { + plugin.getLogger().info("- Registered aliases: " + String.join(", ", allAliases)); } } catch (Exception exception) { plugin.getLogger().severe("Unable to register command: " + this.getCommandName()); @@ -166,23 +190,35 @@ public void register(JavaPlugin plugin) { } protected void formatUsage(CommandSender sender) { - if (mainCommand.getUsage() != null && !this.mainCommand.getUsage().isEmpty() && this.subCommands.isEmpty()) { - sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax is: " + this.getCommandName() + this.mainCommand.getUsage(), TextColor.fromHexString("#FBFB00"))); - return; + List usageMessages = new ArrayList<>(); + + for (AnnotationSubCommand mainCommand : this.mainCommands) { + if (mainCommand.getPermission() == null || sender.hasPermission(mainCommand.getPermission())) { + String usage = "/" + this.getCommandName() + mainCommand.getUsage() + " - " + mainCommand.getDescription(); + usageMessages.add(usage); + } } - sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax's are:", TextColor.fromHexString("#FBFB00"))); - if (mainCommand.getUsage() != null && !mainCommand.getUsage().isEmpty()) { - sender.sendMessage(Component.text("/" + this.getCommandName() + this.mainCommand.getUsage() + " - " + this.mainCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); + for (AnnotationSubCommand subCommand : this.subCommands) { + if (subCommand.getPermission() == null || sender.hasPermission(subCommand.getPermission())) { + String usage = "/" + this.getCommandName() + " " + subCommand.getName() + subCommand.getUsage() + " - " + subCommand.getDescription(); + usageMessages.add(usage); + } } - List sortedSubCommands = new ArrayList<>(this.subCommands); - sortedSubCommands.sort(Comparator.comparingInt(cmd -> -(cmd.getName() + cmd.getUsage()).length())); + if (usageMessages.isEmpty()) { + sender.sendMessage(Component.text("No available command syntaxes.", TextColor.fromHexString("#FF6B6B"))); + return; + } - for (AnnotationSubCommand subCommand : sortedSubCommands) { - if (subCommand.getPermission() == null || sender.hasPermission(subCommand.getPermission())) { - sender.sendMessage(Component.text("/" + this.getCommandName() + " " + subCommand.getName() + subCommand.getUsage() + " - " + subCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); + if (usageMessages.size() == 1) { + sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax is: ", TextColor.fromHexString("#FBFB00"))); + sender.sendMessage(Component.text(usageMessages.get(0), TextColor.fromHexString("#FBFB00"))); + } else { + sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax's are:", TextColor.fromHexString("#FBFB00"))); + for (String usage : usageMessages) { + sender.sendMessage(Component.text(usage, TextColor.fromHexString("#FBFB00"))); } } } -} +} \ No newline at end of file diff --git a/velocity/src/main/java/com/jazzkuh/commandlib/velocity/AnnotationCommand.java b/velocity/src/main/java/com/jazzkuh/commandlib/velocity/AnnotationCommand.java index b8227ae..ee79097 100644 --- a/velocity/src/main/java/com/jazzkuh/commandlib/velocity/AnnotationCommand.java +++ b/velocity/src/main/java/com/jazzkuh/commandlib/velocity/AnnotationCommand.java @@ -18,7 +18,7 @@ public class AnnotationCommand implements AnnotationCommandImpl, SimpleCommand { protected final String commandName; - protected AnnotationSubCommand mainCommand = null; + protected final List mainCommands = new ArrayList<>(); protected final List subCommands = new ArrayList<>(); public AnnotationCommand() { @@ -28,11 +28,8 @@ public AnnotationCommand() { this.commandName = this.getClass().getAnnotation(com.jazzkuh.commandlib.common.annotations.Command.class).value(); - List mainCommands = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Main.class)).toList(); - if (mainCommands.size() > 1) { - throw new IllegalArgumentException("There can only be one main command per class"); - } - mainCommands.forEach(method -> this.mainCommand = AnnotationCommandParser.parse(this, method)); + List mainCommandMethods = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Main.class)).toList(); + mainCommandMethods.forEach(method -> this.mainCommands.add(AnnotationCommandParser.parse(this, method))); List subcommandMethods = Arrays.stream(this.getClass().getMethods()).filter(method -> method.isAnnotationPresent(Subcommand.class)).toList(); subcommandMethods.forEach(method -> this.subCommands.add(AnnotationCommandParser.parse(this, method))); @@ -49,12 +46,17 @@ public void execute(Invocation invocation) { CommandSource sender = invocation.source(); if (args.length < 1) { - if (this.mainCommand == null) { + if (this.mainCommands.isEmpty()) { this.formatUsage(sender); return; } - this.executeCommand(this.mainCommand, sender, args); + if (this.mainCommands.size() == 1) { + this.executeCommand(this.mainCommands.get(0), sender, args); + return; + } + + this.formatUsage(sender); return; } @@ -64,7 +66,17 @@ public void execute(Invocation invocation) { return; } - this.executeCommand(this.mainCommand, sender, args); + if (!this.mainCommands.isEmpty()) { + if (this.mainCommands.size() == 1) { + this.executeCommand(this.mainCommands.get(0), sender, args); + return; + } + + this.formatUsage(sender); + return; + } + + this.formatUsage(sender); } private void executeCommand(AnnotationSubCommand subCommand, CommandSource sender, String[] args) { @@ -98,12 +110,17 @@ private void executeCommand(AnnotationSubCommand subCommand, CommandSource sende public List suggest(Invocation invocation) { String[] args = invocation.arguments(); CommandSource sender = invocation.source(); - AnnotationCommandExecutor mainCommandExecutor = new AnnotationCommandExecutor<>(this.mainCommand, this); + List options = new ArrayList<>(); AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); - List options = new ArrayList<>(mainCommandExecutor.complete(commandSender, args)); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + if (mainCommand.getPermission() == null || sender.hasPermission(mainCommand.getPermission())) { + AnnotationCommandExecutor mainCommandExecutor = new AnnotationCommandExecutor<>(mainCommand, this); + options.addAll(mainCommandExecutor.complete(commandSender, args)); + } + } - if (args.length == 1 && this.subCommands.size() >= 1) { + if (args.length == 1 && !this.subCommands.isEmpty()) { for (AnnotationSubCommand subCommand : this.subCommands) { if (subCommand.getPermission() == null) { options.add(subCommand.getName()); @@ -129,6 +146,7 @@ public List suggest(Invocation invocation) { if (!args[0].equalsIgnoreCase(subCommand.getName()) && !subCommand.getAliases().contains(args[0].toLowerCase())) continue; AnnotationCommandExecutor subCommandExecutor = new AnnotationCommandExecutor<>(subCommand, this); + if (subCommand.getPermission() != null && !commandSender.getSender().hasPermission(subCommand.getPermission())) continue; options.addAll(subCommandExecutor.complete(commandSender, args)); } @@ -137,29 +155,53 @@ public List suggest(Invocation invocation) { @Override public boolean hasPermission(Invocation invocation) { - return this.mainCommand.getPermission() == null || invocation.source().hasPermission(this.mainCommand.getPermission()); + if (this.mainCommands.isEmpty()) { + return true; + } + + return this.mainCommands.stream().anyMatch(cmd -> + cmd.getPermission() == null || invocation.source().hasPermission(cmd.getPermission())); } public void register(CommandManager commandManager) { - commandManager.register(commandName, this, this.mainCommand.getAliases().toArray(new String[0])); + List allAliases = new ArrayList<>(); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + allAliases.addAll(mainCommand.getAliases()); + } + + commandManager.register(commandName, this, allAliases.toArray(new String[0])); } protected void formatUsage(CommandSource sender) { - if (mainCommand.getUsage() != null && this.mainCommand.getUsage().length() > 0 && this.subCommands.isEmpty()) { - sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax is: ", TextColor.fromHexString("#FBFB00"))); - sender.sendMessage(Component.text("/" + this.getCommandName() + this.mainCommand.getUsage() + " - " + this.mainCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); - return; - } + List usageMessages = new ArrayList<>(); - sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax's are:", TextColor.fromHexString("#FBFB00"))); - if (mainCommand.getUsage() != null && mainCommand.getUsage().length() > 0) { - sender.sendMessage(Component.text("/" + this.getCommandName() + this.mainCommand.getUsage() + " - " + this.mainCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); + for (AnnotationSubCommand mainCommand : this.mainCommands) { + if (mainCommand.getPermission() == null || sender.hasPermission(mainCommand.getPermission())) { + String usage = "/" + this.getCommandName() + mainCommand.getUsage() + " - " + mainCommand.getDescription(); + usageMessages.add(usage); + } } for (AnnotationSubCommand subCommand : this.subCommands) { if (subCommand.getPermission() == null || sender.hasPermission(subCommand.getPermission())) { - sender.sendMessage(Component.text("/" + this.getCommandName() + " " + subCommand.getName() + subCommand.getUsage() + " - " + subCommand.getDescription(), TextColor.fromHexString("#FBFB00"))); + String usage = "/" + this.getCommandName() + " " + subCommand.getName() + subCommand.getUsage() + " - " + subCommand.getDescription(); + usageMessages.add(usage); + } + } + + if (usageMessages.isEmpty()) { + sender.sendMessage(Component.text("No available command syntaxes.", TextColor.fromHexString("#FF6B6B"))); + return; + } + + if (usageMessages.size() == 1) { + sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax is: ", TextColor.fromHexString("#FBFB00"))); + sender.sendMessage(Component.text(usageMessages.get(0), TextColor.fromHexString("#FBFB00"))); + } else { + sender.sendMessage(Component.text("Invalid command syntax. Correct command syntax's are:", TextColor.fromHexString("#FBFB00"))); + for (String usage : usageMessages) { + sender.sendMessage(Component.text(usage, TextColor.fromHexString("#FBFB00"))); } } } -} +} \ No newline at end of file