From 6517ca5a229e65b494e12dec4966396841cf7d50 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Sat, 2 Sep 2023 17:00:09 -0400 Subject: [PATCH 01/42] Initial rewrite of command building system Precursor work for https://github.com/JorelAli/CommandAPI/issues/483 Notable changes: - No more flattening everything into a CommandAPICommand - CommandTrees are not flattened - CommandMetaData and Execution removed (only used to flatten CommandTrees) - MultiLiteralArgument and subcommands are no longer converted into LiteralArguments - Optional arguments are not chopped up into multiple arrays - In general, a single call to `ExecutableCommand#register` now creates the entire Brigadier tree for that command instance - Should only be converting Arguments to Brigadier nodes once instead of multiple times - CommandAPIHandler no longer creates Brigadier nodes - Delegated to AbstractCommandAPICommand, AbstractCommandTree, AbstractArgumentTree.java, and AbstractArgument - Problems that prevent a command from being registered have been unified under CommandRegistrationException - Exception messages tweaked - Bukkit-specific features removed from `commandapi-core` - Removed `isConverted` property of CommandAPICommand - Moved logic for flattening EntitySelectorArgument from `CommandAPIHandler#generateBrigadierCommand` to Converter - Moved special Bukkit command permission stuff into CommandAPIBukkit/SpigotCommandRegistration - Taking advantage of Brigadier redirects - Command alias nodes redirect to main command name - MultiLiteralArgument literal nodes use redirects - Instead of duplicating the argument node structure after these arguments, they can reference the same path TODO: - The checks performed by `CommandAPIHandler#hasCommandConflict` have not been reimplemented (I'm not sure if they did anything?) - MultiLiteralArgument is currently broken. See https://github.com/Mojang/brigadier/issues/137 - Many changes not covered by tests (could be sneaky behavioral differences) --- .../commandapi/AbstractArgumentTree.java | 84 +- .../commandapi/AbstractCommandAPICommand.java | 318 ++--- .../jorel/commandapi/AbstractCommandTree.java | 95 +- .../java/dev/jorel/commandapi/Brigadier.java | 58 +- .../jorel/commandapi/CommandAPIHandler.java | 1059 +++++------------ .../jorel/commandapi/CommandAPIPlatform.java | 41 +- .../dev/jorel/commandapi/CommandMetaData.java | 80 -- .../jorel/commandapi/ExecutableCommand.java | 262 ++-- .../java/dev/jorel/commandapi/Execution.java | 47 - .../jorel/commandapi/RegisteredCommand.java | 35 +- .../arguments/AbstractArgument.java | 268 ++++- .../jorel/commandapi/arguments/Literal.java | 39 +- .../commandapi/arguments/MultiLiteral.java | 144 ++- .../MultiLiteralArgumentBuilder.java | 55 + .../arguments/MultiLiteralCommandNode.java | 82 ++ .../CommandRegistrationException.java | 27 + .../DuplicateNodeNameException.java | 33 + .../exceptions/GreedyArgumentException.java | 40 +- .../MissingCommandExecutorException.java | 44 +- .../exceptions/OptionalArgumentException.java | 52 +- .../jorel/commandapi/CommandAPIBukkit.java | 41 +- .../jorel/commandapi/CommandAPICommand.java | 23 +- .../CommandRegistrationStrategy.java | 5 +- .../dev/jorel/commandapi/CommandTree.java | 24 +- .../java/dev/jorel/commandapi/Converter.java | 98 +- .../commandapi/PaperCommandRegistration.java | 24 +- .../commandapi/SpigotCommandRegistration.java | 52 +- .../arguments/EntitySelectorArgument.java | 16 +- .../arguments/FlattenableArgument.java | 15 + .../commandapi/arguments/LiteralArgument.java | 27 +- .../arguments/MultiLiteralArgument.java | 39 +- .../test/CommandUnregisterTests.java | 10 +- .../jorel/commandapi/test/OnEnableTests.java | 56 +- .../arguments/ArgumentMultiLiteralTests.java | 6 + .../jorel/commandapi/CommandAPICommand.java | 14 - .../jorel/commandapi/CommandAPIVelocity.java | 26 +- .../commandapi/arguments/LiteralArgument.java | 27 +- .../arguments/MultiLiteralArgument.java | 38 +- 38 files changed, 1894 insertions(+), 1510 deletions(-) delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/CommandMetaData.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/Execution.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgumentBuilder.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralCommandNode.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlattenableArgument.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 6024256e0e..155394e04d 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -1,6 +1,10 @@ package dev.jorel.commandapi; +import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.GreedyArgument; +import dev.jorel.commandapi.exceptions.GreedyArgumentException; +import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; import java.util.List; @@ -30,10 +34,10 @@ public abstract class AbstractArgumentTree) { + if (this instanceof AbstractArgument) { this.argument = (Argument) this; } else { - throw new IllegalArgumentException("Implicit inherited constructor must be from Argument"); + throw new IllegalArgumentException("Implicit inherited constructor must be from AbstractArgument"); } } @@ -60,19 +64,73 @@ public Impl then(final AbstractArgumentTree tree) { return instance(); } - List> getExecutions() { - List> executions = new ArrayList<>(); - // If this is executable, add its execution - if (this.executor.hasAnyExecutors()) { - executions.add(new Execution<>(List.of(this.argument), this.executor)); + /** + * Builds the Brigadier {@link CommandNode} structure for this argument tree. + * + * @param previousNode The {@link CommandNode} to add this argument tree onto. + * @param previousArguments A List of CommandAPI arguments that came before this argument tree. + * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. + * @param The Brigadier Source object for running commands. + */ + public void buildBrigadierNode(CommandNode previousNode, List previousArguments, List previousNonLiteralArgumentNames) { + // Check preconditions + if (argument instanceof GreedyArgument && !arguments.isEmpty()) { + throw new GreedyArgumentException(previousArguments, argument, getBranchesAsList()); + } + if (!executor.hasAnyExecutors() && arguments.isEmpty()) { + throw new MissingCommandExecutorException(previousArguments, argument); + } + + // Create node for this argument + CommandNode rootNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); + + // Add our branches as children to the node + for (AbstractArgumentTree child : arguments) { + // We need a new list for each branch of the tree + List newPreviousArguments = new ArrayList<>(previousArguments); + List newPreviousArgumentNames = new ArrayList<>(previousNonLiteralArgumentNames); + + child.buildBrigadierNode(rootNode, newPreviousArguments, newPreviousArgumentNames); + } + } + + /** + * @return A list of paths that represent the possible branches of this argument tree as Strings, starting with the + * base argument held by this tree. + */ + public List> getBranchesAsStrings() { + List> baseArgumentPaths = new ArrayList<>(); + baseArgumentPaths.add(new ArrayList<>()); + argument.appendToCommandPaths(baseArgumentPaths); + + List> argumentStrings = new ArrayList<>(); + for (AbstractArgumentTree child : arguments) { + for (List subArgs : child.getBranchesAsStrings()) { + for (List basePath : baseArgumentPaths) { + List mergedPaths = new ArrayList<>(); + mergedPaths.addAll(basePath); + mergedPaths.addAll(subArgs); + argumentStrings.add(mergedPaths); + } + } } - // Add all executions from all arguments - for (AbstractArgumentTree tree : arguments) { - for (Execution execution : tree.getExecutions()) { - // Prepend this argument to the arguments of the executions - executions.add(execution.prependedBy(this.argument)); + + return argumentStrings; + } + + /** + * @return A list of paths that represent the possible branches of this argument tree as Argument objects. + */ + protected List> getBranchesAsList() { + List> branchesList = new ArrayList<>(); + for (AbstractArgumentTree branch : arguments) { + for (List subBranchList : branch.getBranchesAsList()) { + List newBranchList = new ArrayList<>(); + newBranchList.add(branch.argument); + newBranchList.addAll(subBranchList); + branchesList.add(newBranchList); } } - return executions; + return branchesList; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 2bf2a9b701..08f1a597f8 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -20,18 +20,19 @@ *******************************************************************************/ package dev.jorel.commandapi; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.function.Predicate; - +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.GreedyArgument; import dev.jorel.commandapi.exceptions.GreedyArgumentException; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import dev.jorel.commandapi.exceptions.OptionalArgumentException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * A builder used to create commands to be registered by the CommandAPI. * @@ -51,7 +52,6 @@ public abstract class AbstractCommandAPICommand arguments = new ArrayList<>(); protected List subcommands = new ArrayList<>(); - protected boolean isConverted; /** * Creates a new command builder @@ -60,17 +60,6 @@ public abstract class AbstractCommandAPICommand metaData) { - super(metaData); - this.isConverted = false; } /** @@ -148,7 +137,8 @@ public Impl withSubcommand(Impl subcommand) { * @param subcommands the subcommands to add as children of this command * @return this command builder */ - public Impl withSubcommands(@SuppressWarnings("unchecked") Impl... subcommands) { + @SafeVarargs + public final Impl withSubcommands(Impl... subcommands) { this.subcommands.addAll(Arrays.asList(subcommands)); return instance(); } @@ -189,197 +179,151 @@ public void setSubcommands(List subcommands) { this.subcommands = subcommands; } - /** - * Returns whether this command is an automatically converted command - * - * @return whether this command is an automatically converted command - */ - public boolean isConverted() { - return isConverted; - } - - /** - * Sets a command as "converted". This tells the CommandAPI that this command - * was converted by the CommandAPI's Converter. This should not be used outside - * of the CommandAPI's internal API - * - * @param isConverted whether this command is converted or not - * @return this command builder - */ - Impl setConverted(boolean isConverted) { - this.isConverted = isConverted; - return instance(); - } - - // Expands subcommands into arguments. This method should be static (it - // shouldn't be accessing/depending on any of the contents of the current class instance) - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static , Argument extends AbstractArgument, CommandSender> - void flatten(Impl rootCommand, List prevArguments, Impl subcommand, String namespace) { - // Get the list of literals represented by the current subcommand. This - // includes the subcommand's name and any aliases for this subcommand - String[] literals = new String[subcommand.meta.aliases.length + 1]; - literals[0] = subcommand.meta.commandName; - System.arraycopy(subcommand.meta.aliases, 0, literals, 1, subcommand.meta.aliases.length); - - // Create a MultiLiteralArgument using the subcommand information - Argument literal = (Argument) CommandAPIHandler.getInstance().getPlatform().newConcreteMultiLiteralArgument(subcommand.meta.commandName, literals); - - literal.withPermission(subcommand.meta.permission) - .withRequirement((Predicate) subcommand.meta.requirements) - .setListed(false); - - prevArguments.add(literal); - - if (subcommand.executor.hasAnyExecutors()) { - // Create the new command. The new command: - // - starts at the root command node - // - has all of the previously declared arguments (i.e. not itself) - // - uses the subcommand's executor - // - has no subcommands(?) - // Honestly, if you're asking how or why any of this works, I don't - // know because I just trialled random code until it started working - rootCommand.arguments = prevArguments; - rootCommand.withArguments(subcommand.arguments); - rootCommand.executor = subcommand.executor; - rootCommand.subcommands = new ArrayList<>(); - rootCommand.register(namespace); - } - - for (Impl subsubcommand : subcommand.getSubcommands()) { - flatten(rootCommand, new ArrayList<>(prevArguments), subsubcommand, namespace); - } - } - - boolean hasAnyExecutors() { - if (this.executor.hasAnyExecutors()) { - return true; - } else { - for(Impl subcommand : this.subcommands) { - if (subcommand.hasAnyExecutors()) { - return true; - } - } - } - return false; - } - - private void checkHasExecutors() { - if(!hasAnyExecutors()) { - throw new MissingCommandExecutorException(this.meta.commandName); - } - } - - /** - * Registers the command with a given namespace - * - * @param namespace The namespace of this command. This cannot be null - * @throws NullPointerException if the namespace is null - */ @Override - public void register(String namespace) { - if (namespace == null) { - // Only reachable through Velocity - throw new NullPointerException("Parameter 'namespace' was null when registering command /" + this.meta.commandName + "!"); + public List> getArgumentsAsStrings() { + // Return an empty list if we have no arguments + if (arguments.isEmpty() && subcommands.isEmpty()) { + // Note: the inner list needs to be mutable in the case that this is a subcommand/sub-subcommand... + // In that case, the parent subcommands will be built backwards inside this list + return List.of(new ArrayList<>()); } - @SuppressWarnings("unchecked") - Argument[] argumentsArray = (Argument[]) (arguments == null ? new AbstractArgument[0] : arguments.toArray(AbstractArgument[]::new)); - - // Check GreedyArgument constraints - checkGreedyArgumentConstraints(argumentsArray); - checkHasExecutors(); - - // Assign the command's permissions to arguments if the arguments don't already - // have one - for (Argument argument : argumentsArray) { - if (argument.getArgumentPermission() == null) { - argument.withPermission(meta.permission); + + List> argumentStrings = new ArrayList<>(); + + if (!arguments.isEmpty()) { + // Build main path + List> currentPaths = new ArrayList<>(); + currentPaths.add(new ArrayList<>()); + boolean foundOptional = arguments.get(0).isOptional(); + for (int i = 0; i < arguments.size(); i++) { + Argument argument = arguments.get(i); + argument.appendToCommandPaths(currentPaths); + + // Non-optional argument after an optional argument + // This state is invalid, so we cannot continue + boolean nextIsOptional = i == arguments.size() - 1 || arguments.get(i + 1).isOptional(); + if (foundOptional && !nextIsOptional) + throw new OptionalArgumentException(name, arguments.subList(0, i), argument); + foundOptional = nextIsOptional; + + // If this is the last argument, or the next argument is optional, then the current path should be included by itself + if (nextIsOptional) argumentStrings.addAll(currentPaths); } } - if (executor.hasAnyExecutors()) { - // Need to cast handler to the right CommandSender type so that argumentsArray and executor are accepted - @SuppressWarnings("unchecked") - CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - - // Create a List that is used to register optional arguments - for (Argument[] args : getArgumentsToRegister(argumentsArray)) { - handler.register(meta, args, executor, isConverted, namespace); + // Add subcommands + for (Impl subCommand : subcommands) { + String subCommandArgument = subCommand.name + ":LiteralArgument"; + for (List subArgs : subCommand.getArgumentsAsStrings()) { + subArgs.add(0, subCommandArgument); + argumentStrings.add(subArgs); } } - // Convert subcommands into multiliteral arguments - for (Impl subcommand : this.subcommands) { - flatten(this.copy(), new ArrayList<>(), subcommand, namespace); - } + return argumentStrings; } - // Checks that greedy arguments don't have any other arguments at the end, - // and only zero or one greedy argument is present in an array of arguments - private void checkGreedyArgumentConstraints(Argument[] argumentsArray) { - for (int i = 0; i < argumentsArray.length; i++) { - // If we've seen a greedy argument that isn't at the end, then that - // also covers the case of seeing more than one greedy argument, as - // if there are more than one greedy arguments, one of them must not - // be at the end! - if (argumentsArray[i] instanceof GreedyArgument && i != argumentsArray.length - 1) { - throw new GreedyArgumentException(argumentsArray); - } + @Override + Nodes createCommandNodes() { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Check preconditions + if (!executor.hasAnyExecutors() && (subcommands.isEmpty() || !arguments.isEmpty())) { + // If we don't have any executors then: + // No subcommands is bad because this path can't be run at all + // Having arguments is bad because developer intended this path to be executable with arguments + throw new MissingCommandExecutorException(name); } - } - public Impl copy() { - Impl command = newConcreteCommandAPICommand(new CommandMetaData<>(this.meta)); - command.arguments = new ArrayList<>(this.arguments); - command.subcommands = new ArrayList<>(this.subcommands); - command.isConverted = this.isConverted; - return command; - } + // Create node + LiteralArgumentBuilder rootBuilder = LiteralArgumentBuilder.literal(name); - protected abstract Impl newConcreteCommandAPICommand(CommandMetaData metaData); + // Add permission and requirements + rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); - private List getArgumentsToRegister(Argument[] argumentsArray) { - List argumentsToRegister = new ArrayList<>(); - List currentCommand = new ArrayList<>(); + // Add our executor if this is the last node, or the next argument is optional + if ((arguments.isEmpty() || arguments.get(0).isOptional()) && executor.hasAnyExecutors()) { + rootBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); + } - Iterator argumentIterator = List.of(argumentsArray).iterator(); + // Register main node + LiteralCommandNode rootNode = rootBuilder.build(); + + // Create arguments + if (!arguments.isEmpty()) { + CommandNode previousNode = rootNode; + List previousArguments = new ArrayList<>(); + List previousArgumentNames = new ArrayList<>(); + + // The previous arguments include an unlisted MultiLiteral representing the command name and aliases + // This doesn't affect how the command acts, but it helps represent the command path in exceptions + String[] literals = new String[aliases.length + 1]; + literals[0] = name; + System.arraycopy(aliases, 0, literals, 1, aliases.length); + Argument commandNames = handler.getPlatform().newConcreteMultiLiteralArgument(name, literals); + commandNames.setListed(false); + + previousArguments.add(commandNames); + + boolean foundOptional = arguments.get(0).isOptional(); + for (int i = 0; i < arguments.size(); i++) { + Argument argument = arguments.get(i); + + boolean nextIsOptional = i == arguments.size() - 1 || arguments.get(i + 1).isOptional(); + // Non-optional argument after an optional argument + // This state is invalid, so we cannot continue + if (foundOptional && !nextIsOptional) throw new OptionalArgumentException(previousArguments, argument); + foundOptional = nextIsOptional; + + previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousArgumentNames, + // If this is the last argument, or the next argument is optional, add the executor + nextIsOptional ? executor : null); + } - // Collect all required arguments, adding them as a command once finding the first optional - while(argumentIterator.hasNext()) { - Argument next = argumentIterator.next(); - if(next.isOptional()) { - argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0])); - currentCommand.addAll(unpackCombinedArguments(next)); - break; + // Check greedy argument constraint + // We need to check it down here so that all the combined arguments are properly considered after unpacking + for (int i = 0; i < previousArguments.size() - 1 /* Minus one since we don't need to check last argument */; i++) { + Argument argument = previousArguments.get(i); + if (argument instanceof GreedyArgument) { + throw new GreedyArgumentException( + previousArguments.subList(0, i), // Arguments before this + argument, + List.of(previousArguments.subList(i + 1, previousArguments.size())) // Arguments after this + ); + } } - currentCommand.addAll(unpackCombinedArguments(next)); } - // Collect the optional arguments, adding each one as a valid command - while (argumentIterator.hasNext()) { - Argument next = argumentIterator.next(); - if(!next.isOptional()) { - throw new OptionalArgumentException(meta.commandName); // non-optional argument after optional + // Add subcommands + for (Impl subCommand : subcommands) { + Nodes nodes = subCommand.createCommandNodes(); + rootNode.addChild(nodes.rootNode()); + for (LiteralCommandNode aliasNode : nodes.aliasNodes()) { + rootNode.addChild(aliasNode); } - argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0])); - currentCommand.addAll(unpackCombinedArguments(next)); } - // All the arguments expanded, also handles when there are no optional arguments - argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0])); - return argumentsToRegister; - } + // Generate alias nodes + List> aliasNodes = new ArrayList<>(); + for (String alias : aliases) { + // Create node + LiteralArgumentBuilder aliasBuilder = LiteralArgumentBuilder.literal(alias); - private List unpackCombinedArguments(Argument argument) { - if (!argument.hasCombinedArguments()) { - return List.of(argument); - } - List combinedArguments = new ArrayList<>(); - combinedArguments.add(argument); - for (Argument subArgument : argument.getCombinedArguments()) { - subArgument.copyPermissionsAndRequirements(argument); - combinedArguments.addAll(unpackCombinedArguments(subArgument)); + // Add permission and requirements + aliasBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); + + // Add our executor + if ((arguments.isEmpty() || arguments.get(0).isOptional()) && executor.hasAnyExecutors()) { + aliasBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); + } + + // Redirect to rootNode so all its arguments come after this node + aliasBuilder.redirect(rootNode); + + // Register alias node + aliasNodes.add(aliasBuilder.build()); } - return combinedArguments; + + return new Nodes<>(rootNode, aliasNodes); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index b8d010759b..c1fbe660f2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -1,6 +1,9 @@ package dev.jorel.commandapi; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; import java.util.List; @@ -43,27 +46,85 @@ public Impl then(final AbstractArgumentTree tree) { return instance(); } - /** - * Registers the command with a given namespace - * - * @param namespace The namespace of this command. This cannot be null - * @throws NullPointerException if the namespace is null - */ + public List> getArguments() { + return arguments; + } + @Override - public void register(String namespace) { - if (namespace == null) { - // Only reachable through Velocity - throw new NullPointerException("Parameter 'namespace' was null when registering command /" + this.meta.commandName + "!"); + public List> getArgumentsAsStrings() { + if (arguments.isEmpty()) return List.of(List.of()); + + List> argumentStrings = new ArrayList<>(); + argumentStrings.add(new ArrayList<>()); + for (AbstractArgumentTree argument : arguments) { + argumentStrings.addAll(argument.getBranchesAsStrings()); } - List> executions = new ArrayList<>(); - if (this.executor.hasAnyExecutors()) { - executions.add(new Execution<>(List.of(), this.executor)); + + return argumentStrings; + } + + @Override + Nodes createCommandNodes() { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Check preconditions + if (!executor.hasAnyExecutors() && arguments.isEmpty()) { + throw new MissingCommandExecutorException(name); } - for (AbstractArgumentTree tree : arguments) { - executions.addAll(tree.getExecutions()); + + // Create node + LiteralArgumentBuilder rootBuilder = LiteralArgumentBuilder.literal(name); + + // Add permission and requirements + rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); + + // Add our executor + if (executor.hasAnyExecutors()) { + rootBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); + } + + // Register main node + LiteralCommandNode rootNode = rootBuilder.build(); + + // Add our arguments as children to the node + // The previous arguments include an unlisted MultiLiteral representing the command name and aliases + // This doesn't affect how the command acts, but it helps represent the command path in exceptions + String[] literals = new String[aliases.length + 1]; + literals[0] = name; + System.arraycopy(aliases, 0, literals, 1, aliases.length); + Argument commandNames = handler.getPlatform().newConcreteMultiLiteralArgument(name, literals); + commandNames.setListed(false); + + for (AbstractArgumentTree argument : arguments) { + // We need new previousArguments lists for each branch + List previousArguments = new ArrayList<>(); + List previousArgumentNames = new ArrayList<>(); + previousArguments.add(commandNames); + + argument.buildBrigadierNode(rootNode, previousArguments, previousArgumentNames); } - for (Execution execution : executions) { - execution.register(this.meta, namespace); + + // Generate alias nodes + List> aliasNodes = new ArrayList<>(); + for (String alias : aliases) { + // Create node + LiteralArgumentBuilder aliasBuilder = LiteralArgumentBuilder.literal(alias); + + // Add permission and requirements + aliasBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); + + // Add our executor + if (executor.hasAnyExecutors()) { + aliasBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); + } + + // Redirect to rootNode so all its arguments come after this node + aliasBuilder.redirect(rootNode); + + // Register alias node + aliasNodes.add(aliasBuilder.build()); } + + return new Nodes<>(rootNode, aliasNodes); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java b/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java index 92b89151d6..99b0a6e423 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java @@ -30,6 +30,7 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.ArgumentSuggestions; import dev.jorel.commandapi.arguments.Literal; import dev.jorel.commandapi.commandsenders.AbstractCommandSender; @@ -80,10 +81,13 @@ public static RootCommandNode getRootNode() { * @param literalArgument the LiteralArgument to convert from * @return a LiteralArgumentBuilder that represents the literal */ - public static > - LiteralArgumentBuilder fromLiteralArgument(Literal literalArgument) { - CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - return handler.getLiteralArgumentBuilderArgument(literalArgument.getLiteral(), literalArgument.instance().getArgumentPermission(), literalArgument.instance().getRequirements()); + public static > + LiteralArgumentBuilder fromLiteralArgument(Literal literalArgument) { + Argument argument = (Argument) literalArgument; + LiteralArgumentBuilder rootBuilder = (LiteralArgumentBuilder) argument.createArgumentBuilder(List.of(), List.of()); + argument.finishBuildingNode(rootBuilder, List.of(), null); + + return rootBuilder; } /** @@ -122,8 +126,8 @@ RedirectModifier fromPredicate(BiPredicate predicate, L public static , CommandSender> Command fromCommand(AbstractCommandAPICommand command) { // Need to cast base handler to make it realize we're using the same CommandSender class - CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - return handler.generateCommand((Argument[]) command.getArguments().toArray(AbstractArgument[]::new), command.getExecutor(), command.isConverted()); + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + return handler.generateBrigadierCommand(command.getArguments(), command.getExecutor()); } /** @@ -136,15 +140,17 @@ Command fromCommand(AbstractCommandAPICommand comman * * RequiredArgumentBuilder argBuilder = Brigadier.fromArguments(arguments, "hello"); * - * - * @param args the List of arguments which you typically declare for - * commands - * @param argument the argument you want to specify + * + * @param previousArguments the List of arguments which you typically declare for + * commands + * @param argument the argument you want to specify * @return a RequiredArgumentBuilder that represents the provided argument */ - public static > RequiredArgumentBuilder fromArgument(List args, Argument argument) { - CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - return handler.getRequiredArgumentBuilderDynamic((Argument[]) args.toArray(AbstractArgument[]::new), argument); + public static > RequiredArgumentBuilder fromArgument(List previousArguments, Argument argument) { + RequiredArgumentBuilder rootBuilder = (RequiredArgumentBuilder) argument.createArgumentBuilder(previousArguments, List.of()); + argument.finishBuildingNode(rootBuilder, previousArguments, null); + + return rootBuilder; } /** @@ -154,22 +160,21 @@ Command fromCommand(AbstractCommandAPICommand comman * @return a RequiredArgumentBuilder that represents the provided argument */ public static > RequiredArgumentBuilder fromArgument(Argument argument) { - CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - return handler.getRequiredArgumentBuilderDynamic((Argument[]) new AbstractArgument[] { argument }, argument); + return fromArgument(List.of(), argument); } /** * Converts an argument and a list of arguments to a Brigadier * SuggestionProvider - * - * @param argument the argument to convert to suggestions - * @param args the list of arguments + * + * @param argument the argument to convert to suggestions + * @param previousArguments the list of arguments that came before the given argument * @return a SuggestionProvider that suggests the overridden suggestions for the - * specified argument + * specified argument */ - public static > SuggestionProvider toSuggestions(Argument argument, List args) { - CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - return handler.toSuggestions(argument, (Argument[]) args.toArray(AbstractArgument[]::new), true); + public static , CommandSender> SuggestionProvider toSuggestions(Argument argument, List previousArguments) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + return handler.generateBrigadierSuggestions(previousArguments, argument.getOverriddenSuggestions().orElse(ArgumentSuggestions.empty())); } /** @@ -181,12 +186,12 @@ Command fromCommand(AbstractCommandAPICommand comman * @param cmdCtx the command context used to parse the command arguments * @param args the list of arguments to parse * @return an array of Objects which hold the results of the argument parsing - * step + * step * @throws CommandSyntaxException if there was an error during parsing */ public static > Object[] parseArguments(CommandContext cmdCtx, List args) throws CommandSyntaxException { CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - return handler.argsToCommandArgs(cmdCtx, (Argument[]) args.toArray(AbstractArgument[]::new)).args(); + return handler.argsToCommandArgs(cmdCtx, args).args(); } /** @@ -197,14 +202,13 @@ Command fromCommand(AbstractCommandAPICommand comman * @param sender the Bukkit CommandSender to convert into a Brigadier source * object * @return a Brigadier source object representing the provided Bukkit - * CommandSender + * CommandSender */ public static Object getBrigadierSourceFromCommandSender(CommandSender sender) { CommandAPIPlatform platform = (CommandAPIPlatform) CommandAPIHandler.getInstance().getPlatform(); return platform.getBrigadierSourceFromCommandSender(platform.wrapCommandSender(sender)); } - /** * Returns a Bukkit CommandSender from a Brigadier CommandContext * @@ -217,4 +221,4 @@ public static CommandSender getCommandSenderFromContext(CommandC AbstractCommandSender abstractSender = platform.getSenderForCommand(cmdCtx, false); return abstractSender.getSource(); } -} \ No newline at end of file +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index c8f34841c8..73d535d6ed 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -20,43 +20,28 @@ *******************************************************************************/ package dev.jorel.commandapi; -import java.awt.Component; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import java.util.regex.Pattern; import com.mojang.brigadier.Command; -import com.mojang.brigadier.builder.ArgumentBuilder; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; - import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.ArgumentSuggestions; -import dev.jorel.commandapi.arguments.CustomProvidedArgument; -import dev.jorel.commandapi.arguments.Literal; -import dev.jorel.commandapi.arguments.MultiLiteral; import dev.jorel.commandapi.arguments.PreviewInfo; import dev.jorel.commandapi.arguments.Previewable; import dev.jorel.commandapi.commandsenders.AbstractCommandSender; @@ -69,9 +54,9 @@ * The "brains" behind the CommandAPI. * Handles command registration * - * @param The implementation of AbstractArgument being used + * @param The implementation of AbstractArgument being used * @param The class for running platform commands - * @param The class for running Brigadier commands + * @param The class for running Brigadier commands */ @RequireField(in = CommandContext.class, name = "arguments", ofType = Map.class) public class CommandAPIHandler the command source type - * @param cmdCtx the command context which is used to run this - * command - * @param key the node name for the argument - * @return the raw input string for this argument - */ - public static String getRawArgumentInput(CommandContext cmdCtx, String key) { - final ParsedArgument parsedArgument = commandContextArguments.get(cmdCtx).get(key); - - // TODO: Issue #310: Parsing this argument via /execute run doesn't have the value in - // the arguments for this command context (most likely because it's a redirected command). - // We need to figure out how to handle this case. - if (parsedArgument != null) { - // Sanity check: See https://github.com/JorelAli/CommandAPI/wiki/Implementation-details#chatcomponentargument-raw-arguments - StringRange range = parsedArgument.getRange(); - if (range.getEnd() > cmdCtx.getInput().length()) { - range = StringRange.between(range.getStart(), cmdCtx.getInput().length()); - } - return range.get(cmdCtx.getInput()); - } else { - return ""; - } - } - // TODO: Need to ensure this can be safely "disposed of" when we're done (e.g. on reloads). // I hiiiiiiighly doubt we're storing class caches of classes that can be unloaded at runtime, // but this IS a generic class caching system and we don't want derpy memory leaks private static final Map FIELDS = new HashMap<>(); final CommandAPIPlatform platform; - final TreeMap registeredPermissions = new TreeMap<>(); final List registeredCommands; // Keep track of what has been registered for type checking final Map, Previewable> previewableArguments; // Arguments with previewable chat static final Pattern NAMESPACE_PATTERN = Pattern.compile("[0-9a-z_.-]+"); private static CommandAPIHandler instance; + //////////////////// + // SECTION: Setup // + //////////////////// + protected CommandAPIHandler(CommandAPIPlatform platform) { this.platform = platform; this.registeredCommands = new ArrayList<>(); @@ -160,14 +119,15 @@ public void onDisable() { platform.onDisable(); CommandAPIHandler.resetInstance(); } - + private static void resetInstance() { CommandAPIHandler.instance = null; } - public static CommandAPIHandler getInstance() { - if(CommandAPIHandler.instance != null) { - return CommandAPIHandler.instance; + public static , CommandSender, Source> + CommandAPIHandler getInstance() { + if (CommandAPIHandler.instance != null) { + return (CommandAPIHandler) CommandAPIHandler.instance; } else { throw new IllegalStateException("Tried to access CommandAPIHandler instance, but it was null! Are you using CommandAPI features before calling CommandAPI#onLoad?"); } @@ -177,22 +137,148 @@ public CommandAPIPlatform getPlatform() { return this.platform; } + //////////////////////////////// + // SECTION: Creating commands // + //////////////////////////////// + + void registerCommand(ExecutableCommand command, String namespace) { + platform.preCommandRegistration(command.getName()); + + List registeredCommandInformation = RegisteredCommand.fromExecutableCommand(command, namespace); + registeredCommands.addAll(registeredCommandInformation); + + for (RegisteredCommand singleCommand : registeredCommandInformation) { + CommandAPI.logInfo("Registering command /" + command.getName() + " " + String.join(" ", singleCommand.argsAsStr())); + } + + // Create command nodes + ExecutableCommand.Nodes nodes = command.createCommandNodes(); + LiteralCommandNode resultantNode = nodes.rootNode(); + List> aliasNodes = nodes.aliasNodes(); + + // Register rootNode + platform.registerCommandNode(resultantNode, namespace); + + // Register aliasNodes + for (LiteralCommandNode aliasNode : aliasNodes) { + platform.registerCommandNode(aliasNode, namespace); + } + +// TODO: Do something when ambiguities are found +// platform.getBrigadierDispatcher().findAmbiguities( +// (CommandNode parent, +// CommandNode child, +// CommandNode sibling, +// Collection inputs) -> { +// if(resultantNode.equals(parent)) { +// // Byeeeeeeeeeeeeeeeeeeeee~ +// } +// }); + + // We never know if this is "the last command" and we want dynamic (even if + // partial) command registration. Generate the dispatcher file! + writeDispatcherToFile(); + + platform.postCommandRegistration(registeredCommandInformation, resultantNode, aliasNodes); + } + +// // Builds a command then registers it +// // TODO: Move into new register method +// void register(CommandMetaData meta, final Argument[] args, +// CommandAPIExecutor> executor, boolean converted) { +// +// // TODO: precondition, might need it if a step that depends on it is used +// // Although, kinda hard to get the same syntax +// // Create the human-readable command syntax of arguments +// final String humanReadableCommandArgSyntax; +// { +// StringBuilder builder = new StringBuilder(); +// for (Argument arg : args) { +// builder.append(arg.toString()).append(" "); +// } +// humanReadableCommandArgSyntax = builder.toString().trim(); +// } +// +// // TODO: Unclear if this step is necessary +// // Handle command conflicts +// boolean hasRegisteredCommand = false; +// for (int i = 0, size = registeredCommands.size(); i < size && !hasRegisteredCommand; i++) { +// hasRegisteredCommand |= registeredCommands.get(i).commandName().equals(commandName); +// } +// +// if (hasRegisteredCommand && hasCommandConflict(commandName, args, humanReadableCommandArgSyntax)) { +// return; +// } +// } +// +// // Prevent nodes of the same name but with different types: +// // allow /race invite player +// // disallow /race invite player +// // Return true if conflict was present, otherwise return false +// private boolean hasCommandConflict(String commandName, Argument[] args, String argumentsAsString) { +// // TODO: This code only checks if the last argument of two commands have the same name +// // I'm not sure this actually prevents any relevant problems +// List regArgs = new ArrayList<>(); +// for (RegisteredCommand rCommand : registeredCommands) { +// if (rCommand.commandName().equals(commandName)) { +// for (String str : rCommand.argsAsStr()) { +// regArgs.add(str.split(":")); +// } +// // We just find the first entry that causes a conflict. If this +// // were some industry-level code, we would probably generate a +// // list of all commands first, then check for command conflicts +// // all in one go so we can display EVERY command conflict for +// // all commands, but this works perfectly and isn't important. +// break; +// } +// } +// for (int i = 0; i < args.length; i++) { +// // Avoid IAOOBEs and ensure all node names are the same +// if ((regArgs.size() == i && regArgs.size() < args.length) || (!regArgs.get(i)[0].equals(args[i].getNodeName()))) { +// break; +// } +// // This only applies to the last argument +// if (i == args.length - 1 && !regArgs.get(i)[1].equals(args[i].getClass().getSimpleName())) { +// // Command it conflicts with +// StringBuilder builder2 = new StringBuilder(); +// for (String[] arg : regArgs) { +// builder2.append(arg[0]).append("<").append(arg[1]).append("> "); +// } +// +// // TODO: Error message isn't very clear as to what a command conflict is +// CommandAPI.logError(""" +// Failed to register command: +// +// %s %s +// +// Because it conflicts with this previously registered command: +// +// %s %s +// """.formatted(commandName, argumentsAsString, commandName, builder2.toString())); +// return true; +// } +// } +// return false; +// } + /** - * Generates a command to be registered by the CommandAPI. - * - * @param args set of ordered argument pairs which contain the prompt text - * and their argument types - * @param executor code to be ran when the command is executed - * @param converted True if this command is being converted from another plugin, and false otherwise - * @return a brigadier command which is registered internally - * @throws CommandSyntaxException if an error occurs when the command is ran + * Generates a Brigadier {@link Command} using the given CommandAPI objects. + * + * @param args A list of Arguments that have been defined for this command. + * @param executor Code to run when the command is executed. + * @return A Brigadier Command object that runs the given execution with the given arguments as input. */ - Command generateCommand(Argument[] args, CommandAPIExecutor> executor, boolean converted) { - + public Command generateBrigadierCommand(List args, CommandAPIExecutor> executor) { + // We need to make sure our arguments list is never changed + // If we just used the reference to the list, the caller might add arguments that aren't actually previous + // arguments for this suggestion node, and we would be confused because the new arguments don't exist + List immutableArguments = List.copyOf(args); // Generate our command from executor return cmdCtx -> { + // Construct the execution info AbstractCommandSender sender = platform.getSenderForCommand(cmdCtx, executor.isForceNative()); - CommandArguments commandArguments = argsToCommandArgs(cmdCtx, args); + CommandArguments commandArguments = argsToCommandArgs(cmdCtx, immutableArguments); + ExecutionInfo> executionInfo = new ExecutionInfo<>() { @Override public CommandSender sender() { @@ -209,538 +295,101 @@ public CommandArguments args() { return commandArguments; } }; - if (converted) { - int resultValue = 0; - - // Return a String[] of arguments for converted commands - String[] argsAndCmd = cmdCtx.getRange().get(cmdCtx.getInput()).split(" "); - String[] result = new String[argsAndCmd.length - 1]; - ExecutionInfo> convertedExecutionInfo = new ExecutionInfo<>() { - @Override - public CommandSender sender() { - return sender.getSource(); - } - - @Override - public AbstractCommandSender senderWrapper() { - return sender; - } - - @Override - public CommandArguments args() { - return new CommandArguments(result, new LinkedHashMap<>(), result, new LinkedHashMap<>(), "/" + cmdCtx.getInput()); - } - }; - - System.arraycopy(argsAndCmd, 1, result, 0, argsAndCmd.length - 1); - - // As stupid as it sounds, it's more performant and safer to use - // a List[] instead of a List>, due to NPEs and AIOOBEs. - @SuppressWarnings("unchecked") - List[] entityNamesForArgs = new List[args.length]; - for (int i = 0; i < args.length; i++) { - entityNamesForArgs[i] = args[i].getEntityNames(commandArguments.get(i)); - } - List> product = CartesianProduct.getDescartes(Arrays.asList(entityNamesForArgs)); - - // These objects in obj are List - for (List strings : product) { - // We assume result.length == strings.size - if (result.length == strings.size()) { - for (int i = 0; i < result.length; i++) { - if (strings.get(i) != null) { - result[i] = strings.get(i); - } - } - } - resultValue += executor.execute(convertedExecutionInfo); - } - return resultValue; - } else { - return executor.execute(executionInfo); - } + // Apply the executor + return executor.execute(executionInfo); }; } /** - * Converts the List<Argument> into a {@link CommandArguments} for command execution - * - * @param cmdCtx the command context that will execute this command - * @param args the map of strings to arguments - * @return an CommandArguments object which can be used in (sender, args) -> - * @throws CommandSyntaxException - */ - CommandArguments argsToCommandArgs(CommandContext cmdCtx, Argument[] args) - throws CommandSyntaxException { - // Array for arguments for executor - List argList = new ArrayList<>(); - - // LinkedHashMap for arguments for executor - Map argsMap = new LinkedHashMap<>(); - - // List for raw arguments - List rawArguments = new ArrayList<>(); - - // LinkedHashMap for raw arguments - Map rawArgumentsMap = new LinkedHashMap<>(); - - // Populate array - for (Argument argument : args) { - if (argument.isListed()) { - Object parsedArgument = parseArgument(cmdCtx, argument.getNodeName(), argument, new CommandArguments(argList.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + cmdCtx.getInput())); - - // Add the parsed argument - argList.add(parsedArgument); - argsMap.put(argument.getNodeName(), parsedArgument); - - // Add the raw argument - String rawArgumentString = getRawArgumentInput(cmdCtx, argument.getNodeName()); - - rawArguments.add(rawArgumentString); - rawArgumentsMap.put(argument.getNodeName(), rawArgumentString); - } - } - - return new CommandArguments(argList.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + cmdCtx.getInput()); - } - - /** - * Parses an argument and converts it into its object + * Generates a Brigadier {@link SuggestionProvider} using the given CommandAPI objects. * - * @param cmdCtx the command context - * @param key the key (declared in arguments) - * @param value the value (the argument declared in arguments) - * @return the Argument's corresponding object - * @throws CommandSyntaxException when the input for the argument isn't formatted correctly + * @param previousArguments A list of Arguments that came before the argument using these suggestions. These arguments + * will be available in the {@link SuggestionInfo} when providing suggestions. + * @param suggestions An {@link ArgumentSuggestions} object that should be used to generate the suggestions. + * @return A Brigadier SuggestionProvider object that generates suggestions using with the given arguments as input. */ - Object parseArgument(CommandContext cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException { - if (value.isListed()) { - return value.parseArgument(cmdCtx, key, previousArgs); - } else { - return null; - } - } + public SuggestionProvider generateBrigadierSuggestions(List previousArguments, ArgumentSuggestions suggestions) { + // We need to make sure our arguments list is never changed + // If we just used the reference to the list, the caller might add arguments that aren't actually previous + // arguments for this suggestion node, and we would be confused because the new arguments don't exist + List immutableArguments = List.copyOf(previousArguments); + return (context, builder) -> { + // Construct the suggestion info + SuggestionInfo suggestionInfo = new SuggestionInfo<>( + platform.getCommandSenderFromCommandSource(context.getSource()).getSource(), + argsToCommandArgs(context, immutableArguments), builder.getInput(), builder.getRemaining() + ); - ////////////////////////////////////////////////////////////////////////////////////////////////////// - // SECTION: Permissions // - ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Apply the suggestions + return suggestions.suggest(suggestionInfo, builder); + }; + } /** - * This permission generation setup ONLY works iff: - *
    - *
  • You register the parent permission node FIRST.
  • - *
  • Example:
    - * /mycmd - permission node: my.perm
    - * /mycmd <arg> - permission node: my.perm.other
  • - *
+ * Generates a {@link Predicate} that evaluates a Brigadier source object using the given CommandAPI objects. * - * The my.perm.other permission node is revoked for the COMMAND - * REGISTRATION, however: - *
    - *
  • The permission node IS REGISTERED.
  • - *
  • The permission node, if used for an argument (as in this case), will be - * used for suggestions for said argument
  • - *
- * + * @param permission The {@link CommandPermission} to check that the source object satisfies. * @param requirements An arbitrary additional check to perform on the CommandSender - * after the permissions check - */ - Predicate generatePermissions(String commandName, CommandPermission permission, Predicate requirements, String namespace) { - // If namespace:commandName was already registered, always use the first permission used - String namespacedCommand = namespace.isEmpty() - ? commandName.toLowerCase() - : namespace.toLowerCase() + ":" + commandName.toLowerCase(); - if (registeredPermissions.containsKey(namespacedCommand)) { - permission = registeredPermissions.get(namespacedCommand); - } else { - registeredPermissions.put(namespacedCommand, permission); - // The first command to be registered determines the permission for the `commandName` version of the command - registeredPermissions.putIfAbsent(commandName.toLowerCase(), permission); - } - - // Register permission to the platform's registry, if both exist - permission.getPermission().ifPresent(platform::registerPermission); - - // Generate predicate for the permission and requirement check - CommandPermission finalPermission = permission; - return (Source css) -> permissionCheck(platform.getCommandSenderFromCommandSource(css), finalPermission, - requirements); - } - - /** - * Checks if a sender has a given permission. - * - * @param sender the sender to check permissions of - * @param permission the CommandAPI CommandPermission permission to check - * @return true if the sender satisfies the provided permission + * after the permissions check + * @return A Predicate that makes sure a Brigadier source object satisfies the given permission and arbitrary requirements. */ - static boolean permissionCheck(AbstractCommandSender sender, CommandPermission permission, Predicate requirements) { - boolean satisfiesPermissions; - if (sender == null) { - satisfiesPermissions = true; + public Predicate generateBrigadierRequirements(CommandPermission permission, Predicate requirements) { + // Find the intial check for the given CommandPermission + Predicate> senderCheck; + if (permission.equals(CommandPermission.NONE)) { + // No permissions always passes + senderCheck = null; + } else if (permission.equals(CommandPermission.OP)) { + // Check op status + senderCheck = AbstractCommandSender::isOp; } else { - if (permission.equals(CommandPermission.NONE)) { - // No permission set - satisfiesPermissions = true; - } else if (permission.equals(CommandPermission.OP)) { - // Op permission set - satisfiesPermissions = sender.isOp(); + Optional permissionStringWrapper = permission.getPermission(); + if (permissionStringWrapper.isPresent()) { + String permissionString = permissionStringWrapper.get(); + // check permission + senderCheck = sender -> sender.hasPermission(permissionString); } else { - final Optional optionalPerm = permission.getPermission(); - if(optionalPerm.isPresent()) { - satisfiesPermissions = sender.hasPermission(optionalPerm.get()); - } else { - satisfiesPermissions = true; - } - } - } - if (permission.isNegated()) { - satisfiesPermissions = !satisfiesPermissions; - } - return satisfiesPermissions && requirements.test(sender == null ? null : sender.getSource()); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////// - // SECTION: Registration // - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - /* - * Expands multiliteral arguments and registers all expansions of - * MultiLiteralArguments throughout the provided command. Returns true if - * multiliteral arguments were present (and expanded) and returns false if - * multiliteral arguments were not present. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - private boolean expandMultiLiterals(CommandMetaData meta, final Argument[] args, - CommandAPIExecutor> executor, boolean converted, String namespace) { - - // "Expands" our MultiLiterals into Literals - for (int index = 0; index < args.length; index++) { - // Find the first multiLiteral in the for loop - if (args[index] instanceof MultiLiteral) { - MultiLiteral superArg = (MultiLiteral) args[index]; - - String nodeName = superArg.instance().getNodeName(); - - // Add all of its entries - for (String literal: superArg.getLiterals()) { - // TODO: We only expect nodeName to be null here because the constructor for a MultiLiteralArgument - // without a nodeName is currently deprecated but not removed. Once that constructor is removed, - // this `nodeName == null` statement can probably be removed as well - Argument litArg = platform.newConcreteLiteralArgument(nodeName == null ? literal : nodeName, literal); - - litArg.setListed(superArg.instance().isListed()) - .withPermission(superArg.instance().getArgumentPermission()) - .withRequirement((Predicate) superArg.instance().getRequirements()); - - // Reconstruct the list of arguments and place in the new literals - Argument[] newArgs = Arrays.copyOf(args, args.length); - newArgs[index] = litArg; - register(meta, newArgs, executor, converted, namespace); - } - return true; - } - } - return false; - } - - // Prevent nodes of the same name but with different types: - // allow /race invite player - // disallow /race invite player - // Return true if conflict was present, otherwise return false - private boolean hasCommandConflict(String commandName, Argument[] args, String argumentsAsString) { - List regArgs = new ArrayList<>(); - for (RegisteredCommand rCommand : registeredCommands) { - if (rCommand.commandName().equals(commandName)) { - for (String str : rCommand.argsAsStr()) { - regArgs.add(str.split(":")); - } - // We just find the first entry that causes a conflict. If this - // were some industry-level code, we would probably generate a - // list of all commands first, then check for command conflicts - // all in one go so we can display EVERY command conflict for - // all commands, but this works perfectly and isn't important. - break; - } - } - for (int i = 0; i < args.length; i++) { - // Avoid IAOOBEs and ensure all node names are the same - if ((regArgs.size() == i && regArgs.size() < args.length) || (!regArgs.get(i)[0].equals(args[i].getNodeName()))) { - break; - } - // This only applies to the last argument - if (i == args.length - 1 && !regArgs.get(i)[1].equals(args[i].getClass().getSimpleName())) { - // Command it conflicts with - StringBuilder builder2 = new StringBuilder(); - for (String[] arg : regArgs) { - builder2.append(arg[0]).append("<").append(arg[1]).append("> "); - } - - CommandAPI.logError(""" - Failed to register command: - - %s %s - - Because it conflicts with this previously registered command: - - %s %s - """.formatted(commandName, argumentsAsString, commandName, builder2.toString())); - return true; + // No permission always passes + senderCheck = null; } } - return false; - } - - // Links arg -> Executor - private ArgumentBuilder generateInnerArgument(Command command, Argument[] args) { - Argument innerArg = args[args.length - 1]; - - // Handle Literal arguments - if (innerArg instanceof Literal) { - @SuppressWarnings("unchecked") - Literal literalArgument = (Literal) innerArg; - return getLiteralArgumentBuilderArgument(literalArgument.getLiteral(), innerArg.getArgumentPermission(), - innerArg.getRequirements()).executes(command); - } - - // Handle arguments with built-in suggestion providers - else if (innerArg instanceof CustomProvidedArgument customProvidedArg && innerArg.getOverriddenSuggestions().isEmpty()) { - return getRequiredArgumentBuilderWithProvider(innerArg, args, - platform.getSuggestionProvider(customProvidedArg.getSuggestionProvider())).executes(command); - } - - // Handle every other type of argument - else { - return getRequiredArgumentBuilderDynamic(args, innerArg).executes(command); - } - } - - // Links arg1 -> arg2 -> ... argN -> innermostArgument - private ArgumentBuilder generateOuterArguments(ArgumentBuilder innermostArgument, Argument[] args) { - ArgumentBuilder outer = innermostArgument; - for (int i = args.length - 2; i >= 0; i--) { - Argument outerArg = args[i]; - - // Handle Literal arguments - if (outerArg instanceof Literal) { - @SuppressWarnings("unchecked") - Literal literalArgument = (Literal) outerArg; - outer = getLiteralArgumentBuilderArgument(literalArgument.getLiteral(), - outerArg.getArgumentPermission(), outerArg.getRequirements()).then(outer); - } - - // Handle arguments with built-in suggestion providers - else if (outerArg instanceof CustomProvidedArgument customProvidedArg - && outerArg.getOverriddenSuggestions().isEmpty()) { - outer = getRequiredArgumentBuilderWithProvider(outerArg, args, - platform.getSuggestionProvider(customProvidedArg.getSuggestionProvider())).then(outer); - } - // Handle every other type of argument - else { - outer = getRequiredArgumentBuilderDynamic(args, outerArg).then(outer); - } - } - return outer; - } - - /** - * Handles previewable arguments. This stores the path to previewable arguments - * in {@link CommandAPIHandler#previewableArguments} for runtime resolving - * - * @param commandName the name of the command - * @param args the declared arguments - * @param aliases the command's aliases - */ - private void handlePreviewableArguments(String commandName, Argument[] args, String[] aliases) { - if (args.length > 0 && args[args.length - 1] instanceof Previewable previewable) { - List path = new ArrayList<>(); - - path.add(commandName); - for (Argument arg : args) { - path.add(arg.getNodeName()); - } - previewableArguments.put(List.copyOf(path), previewable); - - // And aliases - for (String alias : aliases) { - path.set(0, alias); - previewableArguments.put(List.copyOf(path), previewable); - } - } - } - - // Builds a command then registers it - void register(CommandMetaData meta, final Argument[] args, - CommandAPIExecutor> executor, boolean converted, String namespace) { - // "Expands" our MultiLiterals into Literals - if (expandMultiLiterals(meta, args, executor, converted, namespace)) { - return; - } - - // Create the human-readable command syntax of arguments - final String humanReadableCommandArgSyntax; - { - StringBuilder builder = new StringBuilder(); - for (Argument arg : args) { - builder.append(arg.toString()).append(" "); - } - humanReadableCommandArgSyntax = builder.toString().trim(); - } - - // #312 Safeguard against duplicate node names. This only applies to - // required arguments (i.e. not literal arguments) - if(!checkForDuplicateArgumentNodeNames(args, humanReadableCommandArgSyntax, meta.commandName)) { - return; - } - - // Expand metaData into named variables - String commandName = meta.commandName; - CommandPermission permission = meta.permission; - String[] aliases = meta.aliases; - Predicate requirements = meta.requirements; - Optional shortDescription = meta.shortDescription; - Optional fullDescription = meta.fullDescription; - Optional usageDescription = meta.usageDescription; - Optional helpTopic = meta.helpTopic; - - // Handle command conflicts - boolean hasRegisteredCommand = false; - for (int i = 0, size = registeredCommands.size(); i < size && !hasRegisteredCommand; i++) { - hasRegisteredCommand |= registeredCommands.get(i).commandName().equals(commandName); - } - - if (hasRegisteredCommand && hasCommandConflict(commandName, args, humanReadableCommandArgSyntax)) { - return; - } - - List argumentsString = new ArrayList<>(); - for (Argument arg : args) { - argumentsString.add(arg.getNodeName() + ":" + arg.getClass().getSimpleName()); - } - RegisteredCommand registeredCommandInformation = new RegisteredCommand(commandName, argumentsString, List.of(args), shortDescription, - fullDescription, usageDescription, helpTopic, aliases, permission, namespace); - registeredCommands.add(registeredCommandInformation); - - // Handle previewable arguments - handlePreviewableArguments(commandName, args, aliases); - - platform.preCommandRegistration(commandName); - - String namespacedCommandName = namespace.isEmpty() ? commandName : namespace + ":" + commandName; - CommandAPI.logInfo("Registering command /" + namespacedCommandName + " " + humanReadableCommandArgSyntax); - - // Generate the actual command - Command command = generateCommand(args, executor, converted); - - /* - * The innermost argument needs to be connected to the executor. Then that - * argument needs to be connected to the previous argument etc. Then the first - * argument needs to be connected to the command name, so we get: CommandName -> - * Args1 -> Args2 -> ... -> ArgsN -> Executor - */ - LiteralCommandNode resultantNode; - List> aliasNodes = new ArrayList<>(); - if (args.length == 0) { - // Link command name to the executor - resultantNode = platform.registerCommandNode(getLiteralArgumentBuilder(commandName) - .requires(generatePermissions(commandName, permission, requirements, namespace)).executes(command), namespace); - - // Register aliases - for (String alias : aliases) { - CommandAPI.logInfo("Registering alias /" + alias + " -> " + resultantNode.getName()); - aliasNodes.add(platform.registerCommandNode(getLiteralArgumentBuilder(alias) - .requires(generatePermissions(alias, permission, requirements, namespace)).executes(command), namespace)); - } - } else { - - // Generate all of the arguments, following each other and finally linking to - // the executor - ArgumentBuilder commandArguments = generateOuterArguments( - generateInnerArgument(command, args), args); - - // Link command name to first argument and register - resultantNode = platform.registerCommandNode(getLiteralArgumentBuilder(commandName) - .requires(generatePermissions(commandName, permission, requirements, namespace)).then(commandArguments), namespace); - - // Register aliases - for (String alias : aliases) { - if (CommandAPI.getConfiguration().hasVerboseOutput()) { - CommandAPI.logInfo("Registering alias /" + alias + " -> " + resultantNode.getName()); - } - - aliasNodes.add(platform.registerCommandNode(getLiteralArgumentBuilder(alias) - .requires(generatePermissions(alias, permission, requirements, namespace)).then(commandArguments), namespace)); + if (senderCheck == null) { + // Short circuit permissions check if it doesn't depend on source + if (permission.isNegated()) { + // A negated NONE permission never passes + return source -> false; + } else { + // Only need to check the requirements + return source -> requirements.test(platform.getCommandSenderFromCommandSource(source).getSource()); } } -// TODO: Do something when ambiguities are found -// platform.getBrigadierDispatcher().findAmbiguities( -// (CommandNode parent, -// CommandNode child, -// CommandNode sibling, -// Collection inputs) -> { -// if(resultantNode.equals(parent)) { -// // Byeeeeeeeeeeeeeeeeeeeee~ -// } -// }); - // We never know if this is "the last command" and we want dynamic (even if - // partial) command registration. Generate the dispatcher file! - writeDispatcherToFile(); + // Negate permission check if appropriate + Predicate> finalSenderCheck = permission.isNegated() ? senderCheck.negate() : senderCheck; - platform.postCommandRegistration(registeredCommandInformation, resultantNode, aliasNodes); + // Merge permission check and requirements + return source -> { + AbstractCommandSender sender = platform.getCommandSenderFromCommandSource(source); + return finalSenderCheck.test(sender) && requirements.test(sender.getSource()); + }; } - /** - * Checks for duplicate argument node names and logs them as errors in the - * console - * - * @param args the list of arguments - * @param humanReadableCommandArgSyntax the human readable command argument - * syntax - * @param commandName the name of the command - * @return true if there were no duplicate argument node names, false otherwise - */ - private boolean checkForDuplicateArgumentNodeNames(Argument[] args, String humanReadableCommandArgSyntax, String commandName) { - Set argumentNames = new HashSet<>(); - for (Argument arg : args) { - // We shouldn't find MultiLiteralArguments at this point, only LiteralArguments - if (!(arg instanceof Literal)) { - if (argumentNames.contains(arg.getNodeName())) { - CommandAPI.logError(""" - Failed to register command: - - %s %s - - Because the following argument shares the same node name as another argument: - - %s - """.formatted(commandName, humanReadableCommandArgSyntax, arg.toString())); - return false; - } else { - argumentNames.add(arg.getNodeName()); - } - } - } - return true; - } - public void writeDispatcherToFile() { File file = CommandAPI.getConfiguration().getDispatcherFile(); if (file != null) { try { + // Make sure the file exists file.getParentFile().mkdirs(); - if (file.createNewFile()) { - // Cool, we've created the file - assert true; - } + file.createNewFile(); } catch (IOException e) { CommandAPI.logError("Failed to create the required directories for " + file.getName() + ": " + e.getMessage()); return; } try { + // Write the dispatcher json platform.createDispatcherFile(file, platform.getBrigadierDispatcher()); } catch (IOException e) { CommandAPI.logError("Failed to write command registration info to " + file.getName() + ": " + e.getMessage()); @@ -748,9 +397,9 @@ public void writeDispatcherToFile() { } } - LiteralCommandNode namespaceNode(LiteralCommandNode original, String namespace) { + LiteralCommandNode namespaceNode(LiteralCommandNode original, String namespace) { // Adapted from a section of `CraftServer#syncCommands` - LiteralCommandNode clone = new LiteralCommandNode<>( + LiteralCommandNode clone = new LiteralCommandNode<>( namespace + ":" + original.getLiteral(), original.getCommand(), original.getRequirement(), @@ -759,95 +408,70 @@ LiteralCommandNode namespaceNode(LiteralCommandNo original.isFork() ); - for (CommandNode child : original.getChildren()) { + for (CommandNode child : original.getChildren()) { clone.addChild(child); } return clone; } - ////////////////////////////////////////////////////////////////////////////////////////////////////// - // SECTION: Argument Builders // - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Creates a literal for a given name. - * - * @param commandName the name of the literal to create - * @return a brigadier LiteralArgumentBuilder representing a literal - */ - LiteralArgumentBuilder getLiteralArgumentBuilder(String commandName) { - return LiteralArgumentBuilder.literal(commandName); - } + //////////////////////////////// + // SECTION: Parsing arguments // + //////////////////////////////// /** - * Creates a literal for a given name that requires a specified permission. - * - * @param commandName the name fo the literal to create - * @param permission the permission required to use this literal - * @return a brigadier LiteralArgumentBuilder representing a literal + * Returns the raw input for an argument for a given command context and its + * key. This effectively returns the string value that is currently typed for + * this argument + * + * @param the command source type + * @param cmdCtx the command context which is used to run this + * command + * @param key the node name for the argument + * @return the raw input string for this argument */ - LiteralArgumentBuilder getLiteralArgumentBuilderArgument(String commandName, CommandPermission permission, Predicate requirements) { - LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal(commandName); - return builder.requires((Source css) -> permissionCheck(platform.getCommandSenderFromCommandSource(css), - permission, requirements)); - } - - // Gets a RequiredArgumentBuilder for a DynamicSuggestedStringArgument - RequiredArgumentBuilder getRequiredArgumentBuilderDynamic(final Argument[] args, Argument argument) { + public static String getRawArgumentInput(CommandContext cmdCtx, String key) { + final ParsedArgument parsedArgument = commandContextArguments.get(cmdCtx).get(key); - final SuggestionProvider suggestions; + // TODO: Issue #310: Parsing this argument via /execute run doesn't have the value in + // the arguments for this command context (most likely because it's a redirected command). + // We need to figure out how to handle this case. - if (argument.getOverriddenSuggestions().isPresent()) { - suggestions = toSuggestions(argument, args, true); - } else if (argument.getIncludedSuggestions().isPresent()) { - // TODO(#317): Merge the suggestions included here instead? - suggestions = (cmdCtx, builder) -> argument.getRawType().listSuggestions(cmdCtx, builder); + // TODO: What is this talking about? https://github.com/JorelAli/CommandAPI/issues/310 + + // TODO: Oh, I might have figured out what's wrong + // https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/CommandDispatcher.java#L239 + // Redirects work by adding children onto a context builder + // Seen in that line, the source of the command is copied onto the context, but the arguments are not + // The child context is the one used to run the commands, so the argument doesn't exist when the command is being run + // This is currently also affecting MultiLiteralArguments since they use redirects now + // I feel like this is a bug in Brigadier, but maybe there is a reason for this? + // I hope there is at least a work around + // https://github.com/Mojang/brigadier/issues/137 + if (parsedArgument != null) { + // Sanity check: See https://github.com/JorelAli/CommandAPI/wiki/Implementation-details#chatcomponentargument-raw-arguments + StringRange range = parsedArgument.getRange(); + if (range.getEnd() > cmdCtx.getInput().length()) { + range = StringRange.between(range.getStart(), cmdCtx.getInput().length()); + } + return range.get(cmdCtx.getInput()); } else { - suggestions = null; - } - - return getRequiredArgumentBuilderWithProvider(argument, args, suggestions); - } - - // Gets a RequiredArgumentBuilder for an argument, given a SuggestionProvider - RequiredArgumentBuilder getRequiredArgumentBuilderWithProvider(Argument argument, Argument[] args, SuggestionProvider provider) { - SuggestionProvider newSuggestionsProvider = provider; - - // If we have suggestions to add, combine provider with the suggestions - if (argument.getIncludedSuggestions().isPresent() && argument.getOverriddenSuggestions().isEmpty()) { - SuggestionProvider addedSuggestions = toSuggestions(argument, args, false); - - newSuggestionsProvider = (cmdCtx, builder) -> { - // Heavily inspired by CommandDispatcher#listSuggestions, with combining - // multiple CompletableFuture into one. - - CompletableFuture addedSuggestionsFuture = addedSuggestions.getSuggestions(cmdCtx, - builder); - CompletableFuture providerSuggestionsFuture = provider.getSuggestions(cmdCtx, builder); - CompletableFuture result = new CompletableFuture<>(); - CompletableFuture.allOf(addedSuggestionsFuture, providerSuggestionsFuture).thenRun(() -> { - List suggestions = new ArrayList<>(); - suggestions.add(addedSuggestionsFuture.join()); - suggestions.add(providerSuggestionsFuture.join()); - result.complete(Suggestions.merge(cmdCtx.getInput(), suggestions)); - }); - return result; - }; + return ""; } - - RequiredArgumentBuilder requiredArgumentBuilder = RequiredArgumentBuilder - .argument(argument.getNodeName(), argument.getRawType()); - - return requiredArgumentBuilder.requires(css -> permissionCheck(platform.getCommandSenderFromCommandSource(css), - argument.getArgumentPermission(), argument.getRequirements())).suggests(newSuggestionsProvider); } - CommandArguments generatePreviousArguments(CommandContext context, Argument[] args, String nodeName) - throws CommandSyntaxException { - // Populate Object[], which is our previously filled arguments - List previousArguments = new ArrayList<>(); + /** + * Converts the List<Argument> into a {@link CommandArguments} for command execution + * + * @param cmdCtx the command context that will execute this command + * @param args the map of strings to arguments + * @return an CommandArguments object which can be used in (sender, args) -> + * @throws CommandSyntaxException If an argument is improperly formatted and cannot be parsed + */ + CommandArguments argsToCommandArgs(CommandContext cmdCtx, List args) throws CommandSyntaxException { + // Array for arguments for executor + List argList = new ArrayList<>(); - // LinkedHashMap for arguments + // LinkedHashMap for arguments for executor Map argsMap = new LinkedHashMap<>(); // List for raw arguments @@ -856,53 +480,71 @@ CommandArguments generatePreviousArguments(CommandContext context, Argum // LinkedHashMap for raw arguments Map rawArgumentsMap = new LinkedHashMap<>(); - for (Argument arg : args) { - if (arg.getNodeName().equals(nodeName) && !(arg instanceof Literal)) { - break; - } + // Populate array + for (Argument argument : args) { + if (argument.isListed()) { + Object parsedArgument = parseArgument(cmdCtx, argument.getNodeName(), argument, new CommandArguments(argList.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + cmdCtx.getInput())); - Object result; - try { - result = parseArgument(context, arg.getNodeName(), arg, new CommandArguments(previousArguments.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + context.getInput())); - } catch (IllegalArgumentException e) { - /* - * Redirected commands don't parse previous arguments properly. Simplest way to - * determine what we should do is simply set it to null, since there's nothing - * else we can do. I thought about letting this simply be an empty array, but - * then it's even more annoying to deal with - I wouldn't expect an array of - * size n to suddenly, randomly be 0, but I would expect random NPEs because - * let's be honest, this is Java we're dealing with. - */ - result = null; - } - if (arg.isListed()) { // Add the parsed argument - previousArguments.add(result); - argsMap.put(arg.getNodeName(), result); + argList.add(parsedArgument); + argsMap.put(argument.getNodeName(), parsedArgument); // Add the raw argument - String rawArgumentString = getRawArgumentInput(context, arg.getNodeName()); + String rawArgumentString = getRawArgumentInput(cmdCtx, argument.getNodeName()); rawArguments.add(rawArgumentString); - rawArgumentsMap.put(arg.getNodeName(), rawArgumentString); + rawArgumentsMap.put(argument.getNodeName(), rawArgumentString); } } - return new CommandArguments(previousArguments.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + context.getInput()); + + return new CommandArguments(argList.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + cmdCtx.getInput()); } - SuggestionProvider toSuggestions(Argument theArgument, Argument[] args, - boolean overrideSuggestions) { - return (CommandContext context, SuggestionsBuilder builder) -> { - // Construct the suggestion info - SuggestionInfo suggestionInfo = new SuggestionInfo<>(platform.getCommandSenderFromCommandSource(context.getSource()).getSource(), - generatePreviousArguments(context, args, theArgument.getNodeName()), builder.getInput(), builder.getRemaining()); - - // Get the suggestions - Optional> suggestionsToAddOrOverride = overrideSuggestions - ? theArgument.getOverriddenSuggestions() - : theArgument.getIncludedSuggestions(); - return suggestionsToAddOrOverride.orElse(ArgumentSuggestions.empty()).suggest(suggestionInfo, builder); - }; + /** + * Parses an argument and converts it into its object + * + * @param cmdCtx the command context + * @param key the key (declared in arguments) + * @param value the value (the argument declared in arguments) + * @return the Argument's corresponding object + * @throws CommandSyntaxException when the input for the argument isn't formatted correctly + */ + Object parseArgument(CommandContext cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException { + if (value.isListed()) { + return value.parseArgument(cmdCtx, key, previousArgs); + } else { + return null; + } + } + + //////////////////////////////////// + // SECTION: Previewable Arguments // + //////////////////////////////////// + + /** + * Handles a previewable argument. This stores the path to the previewable argument + * in {@link CommandAPIHandler#previewableArguments} for runtime resolving + * + * @param previousArguments The list of arguments that came before this argument + * @param previewableArgument The {@link Previewable} argument + */ + public void addPreviewableArgument(List previousArguments, Argument previewableArgument) { + if (!(previewableArgument instanceof Previewable previewable)) { + throw new IllegalArgumentException("An argument must implement Previewable to be added as previewable argument"); + } + + // Generate all paths to the argument + List> paths = new ArrayList<>(); + paths.add(new ArrayList<>()); + for (Argument argument : previousArguments) { + argument.appendToCommandPaths(paths); + } + previewableArgument.appendToCommandPaths(paths); + + // Insert paths to out map + for (List path : paths) { + previewableArguments.put(path, previewable); + } } /** @@ -910,12 +552,12 @@ SuggestionProvider toSuggestions(Argument theArgument, Argument[] args, * command tree. This is a method internal to the CommandAPI and isn't expected * to be used by plugin developers (but you're more than welcome to use it as * you see fit). - * + * * @param path a list of Strings representing the path (names of command nodes) * to (and including) the previewable argument - * @return a function that takes in a {@link PreviewInfo} and returns a - * {@link Component}. If such a function is not available, this will - * return a function that always returns null. + * @return a {@link PreviewableFunction} that takes in a {@link PreviewInfo} and returns a + * text Component. If such a function is not available, this will + * return a function that always returns null. */ @SuppressWarnings("unchecked") public Optional> lookupPreviewable(List path) { @@ -928,7 +570,6 @@ public Optional> lookupPreviewable(List path) { } /** - * * @param path a list of Strings representing the path (names of command nodes) * to (and including) the previewable argument * @return Whether a previewable is legacy (non-Adventure) or not @@ -949,7 +590,7 @@ public boolean lookupPreviewableLegacyStatus(List path) { /** * Caches a field using reflection if it is not already cached, then return the * field of a given class. This will also make the field accessible. - * + * * @param clazz the class where the field is declared * @param name the name of the field * @return a Field reference @@ -961,9 +602,9 @@ public static Field getField(Class clazz, String name) { /** * Caches a field using reflection if it is not already cached, then return the * field of a given class. This will also make the field accessible. - * - * @param clazz the class where the field is declared - * @param name the name of the field + * + * @param clazz the class where the field is declared + * @param name the name of the field * @param mojangMappedName the name of a field under Mojang mappings * @return a Field reference */ @@ -985,10 +626,6 @@ public static Field getField(Class clazz, String name, String mojangMappedNam } } - ////////////////////////////// - // SECTION: Private classes // - ////////////////////////////// - /** * Class to store cached methods and fields *

@@ -997,56 +634,4 @@ public static Field getField(Class clazz, String name, String mojangMappedNam */ private record ClassCache(Class clazz, String name, String mojangMappedName) { } - - /** - * A class to compute the Cartesian product of a number of lists. Source: - * https://www.programmersought.com/article/86195393650/ - */ - private static final class CartesianProduct { - - // Shouldn't be instantiated - private CartesianProduct() { - } - - /** - * Returns the Cartesian product of a list of lists - * - * @param the underlying type of the list of lists - * @param list the list to calculate the Cartesian product of - * @return a List of lists which represents the Cartesian product of all - * elements of the input - */ - public static List> getDescartes(List> list) { - List> returnList = new ArrayList<>(); - descartesRecursive(list, 0, returnList, new ArrayList()); - return returnList; - } - - /** - * Recursive implementation Principle: traverse sequentially from 0 of the - * original list to the end - * - * @param the underlying type of the list of lists - * @param originalList original list - * @param position The position of the current recursion in the original - * list - * @param returnList return result - * @param cacheList temporarily saved list - */ - private static void descartesRecursive(List> originalList, int position, - List> returnList, List cacheList) { - List originalItemList = originalList.get(position); - for (int i = 0; i < originalItemList.size(); i++) { - // The last one reuses cacheList to save memory - List childCacheList = (i == originalItemList.size() - 1) ? cacheList : new ArrayList<>(cacheList); - childCacheList.add(originalItemList.get(i)); - if (position == originalList.size() - 1) {// Exit recursion to the end - returnList.add(childCacheList); - continue; - } - descartesRecursive(originalList, position + 1, returnList, childCacheList); - } - } - - } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index 981e97d654..44846542e5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -80,10 +80,6 @@ public interface CommandAPIPlatform wrapCommandSender(CommandSender sender); - // Registers a permission. Bukkit's permission system requires permissions to be "registered" - // before they can be used. - public abstract void registerPermission(String string); - // Some commands have existing suggestion providers public abstract SuggestionProvider getSuggestionProvider(SuggestionProviders suggestionProvider); @@ -98,25 +94,41 @@ public interface CommandAPIPlatform registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes); + + /** + * Builds and registers a Brigadier command node. + * + * @param node The Brigadier {@link LiteralArgumentBuilder} to build and register. + * @param namespace The namespace to register the command with. + * @return The built node. */ - public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); + default LiteralCommandNode registerCommandNode(LiteralArgumentBuilder node, String namespace) { + LiteralCommandNode built = node.build(); + registerCommandNode(built, namespace); + return built; + } /** - * Registers a Brigadier command node and returns the built node. + * Registers a Brigadier command node. + * + * @param node The Brigadier {@link LiteralArgumentBuilder} to register. + * @param namespace The namespace to register the command with. */ - public abstract LiteralCommandNode registerCommandNode(LiteralArgumentBuilder node, String namespace); + public abstract void registerCommandNode(LiteralCommandNode node, String namespace); /** * Unregisters a command from the CommandGraph so it can't be run anymore. * - * @param commandName the name of the command to unregister + * @param commandName the name of the command to unregister * @param unregisterNamespaces whether the unregistration system should attempt to remove versions of the - * command that start with a namespace. Eg. `minecraft:command`, `bukkit:command`, - * or `plugin:command` + * command that start with a namespace. Eg. `minecraft:command`, `bukkit:command`, + * or `plugin:command` */ public abstract void unregister(String commandName, boolean unregisterNamespaces); @@ -180,9 +192,6 @@ public void severe(String message, Throwable throwable) { */ public abstract void updateRequirements(AbstractPlayer player); - // Create the concrete instances of objects implemented by the platform - public abstract AbstractCommandAPICommand newConcreteCommandAPICommand(CommandMetaData meta); - public abstract Argument newConcreteMultiLiteralArgument(String nodeName, String[] literals); public abstract Argument newConcreteLiteralArgument(String nodeName, String literal); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandMetaData.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandMetaData.java deleted file mode 100644 index 5893510bf6..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandMetaData.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.jorel.commandapi; - -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.exceptions.InvalidCommandNameException; - -import java.util.Arrays; -import java.util.Optional; -import java.util.function.Predicate; - -/** - * This class stores metadata about a command - */ -final class CommandMetaData { - - /** - * The command's name - */ - final String commandName; - - /** - * The command's permission - */ - CommandPermission permission = CommandPermission.NONE; - - /** - * The command's aliases - */ - String[] aliases = new String[0]; - - /** - * A predicate that a {@link AbstractCommandSender} must pass in order to execute the command - */ - Predicate requirements = s -> true; - - /** - * An optional short description for the command - */ - Optional shortDescription = Optional.empty(); - - /** - * An optional full description for the command - */ - Optional fullDescription = Optional.empty(); - - /** - * An optional usage description for the command - */ - Optional usageDescription = Optional.empty(); - - /** - * An optional HelpTopic object for the command (for Bukkit) - */ - Optional helpTopic = Optional.empty(); - - /** - * Create command metadata - * @param commandName The command's name - * - * @throws InvalidCommandNameException if the command name is not valid - */ - CommandMetaData(final String commandName) { - if(commandName == null || commandName.isEmpty() || commandName.contains(" ")) { - throw new InvalidCommandNameException(commandName); - } - - this.commandName = commandName; - } - - public CommandMetaData(CommandMetaData original) { - this(original.commandName); - this.permission = original.permission; - this.aliases = Arrays.copyOf(original.aliases, original.aliases.length); - this.requirements = original.requirements; - this.shortDescription = original.shortDescription.isPresent() ? Optional.of(original.shortDescription.get()) : Optional.empty(); - this.fullDescription = original.fullDescription.isPresent() ? Optional.of(original.fullDescription.get()) : Optional.empty(); - this.usageDescription = original.usageDescription.isPresent() ? Optional.of(original.usageDescription.get()) : Optional.empty(); - this.helpTopic = original.helpTopic.isPresent() ? Optional.of(original.helpTopic.get()) : Optional.empty(); - } - -} \ No newline at end of file diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 3f1b41e4a8..464d097682 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -1,6 +1,10 @@ package dev.jorel.commandapi; -import java.util.Optional; +import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.jorel.commandapi.commandsenders.AbstractCommandSender; +import dev.jorel.commandapi.exceptions.InvalidCommandNameException; + +import java.util.List; import java.util.function.Predicate; /** @@ -14,65 +18,110 @@ public abstract class ExecutableCommand /// @endcond , CommandSender> extends Executable { + // Command names + /** + * The command's name + */ + protected final String name; + /** + * The command's aliases + */ + protected String[] aliases = new String[0]; + // Command requirements /** - * The Command's meta-data for this executable command + * The command's permission */ - protected final CommandMetaData meta; + protected CommandPermission permission = CommandPermission.NONE; + /** + * A predicate that a {@link AbstractCommandSender} must pass in order to execute the command + */ + protected Predicate requirements = s -> true; - ExecutableCommand(final String commandName) { - this.meta = new CommandMetaData<>(commandName); - } + // Command help + /** + * An optional short description for the command + */ + protected String shortDescription = null; + /** + * An optional full description for the command + */ + protected String fullDescription = null; + /** + * An optional usage description for the command + */ + protected String[] usageDescription = null; + // TODO: Bukkit specific fields probably should not be in platform agnostic classes + // Either make HelpTopic platform agnostic or move this field into bukkit-core + /** + * An optional HelpTopic object for the command (for Bukkit) + */ + protected Object helpTopic = null; + + ExecutableCommand(final String name) { + if (name == null || name.isEmpty() || name.contains(" ")) { + throw new InvalidCommandNameException(name); + } - protected ExecutableCommand(final CommandMetaData meta) { - this.meta = meta; + this.name = name; } + ///////////////////// + // Builder methods // + ///////////////////// + /** - * Returns the name of this command - * @return the name of this command + * Adds an array of aliases to the current command builder + * + * @param aliases An array of aliases which can be used to execute this command + * @return this command builder */ - public String getName() { - return meta.commandName; + public Impl withAliases(String... aliases) { + this.aliases = aliases; + return instance(); } /** * Applies a permission to the current command builder + * * @param permission The permission node required to execute this command * @return this command builder */ public Impl withPermission(CommandPermission permission) { - this.meta.permission = permission; + this.permission = permission; return instance(); } /** * Applies a permission to the current command builder + * * @param permission The permission node required to execute this command * @return this command builder */ public Impl withPermission(String permission) { - this.meta.permission = CommandPermission.fromString(permission); + this.permission = CommandPermission.fromString(permission); return instance(); } /** * Applies a permission to the current command builder + * * @param permission The permission node required to execute this command * @return this command builder */ public Impl withoutPermission(CommandPermission permission) { - this.meta.permission = permission.negate(); + this.permission = permission.negate(); return instance(); } /** * Applies a permission to the current command builder + * * @param permission The permission node required to execute this command * @return this command builder */ public Impl withoutPermission(String permission) { - this.meta.permission = CommandPermission.fromString(permission).negate(); + this.permission = CommandPermission.fromString(permission).negate(); return instance(); } @@ -85,147 +134,176 @@ public Impl withoutPermission(String permission) { * @return this command builder */ public Impl withRequirement(Predicate requirement) { - this.meta.requirements = this.meta.requirements.and(requirement); + this.requirements = this.requirements.and(requirement); return instance(); } /** - * Adds an array of aliases to the current command builder - * @param aliases An array of aliases which can be used to execute this command + * Sets the short description for this command. This is the help which is + * shown in the main /help menu. + * + * @param description the short description for this command * @return this command builder */ - public Impl withAliases(String... aliases) { - this.meta.aliases = aliases; + public Impl withShortDescription(String description) { + this.shortDescription = description; return instance(); } + /** + * Sets the full description for this command. This is the help which is + * shown in the specific /help page for this command (e.g. /help mycommand). + * + * @param description the full description for this command + * @return this command builder + */ + public Impl withFullDescription(String description) { + this.fullDescription = description; + return instance(); + } + /** + * Sets the short and full description for this command. This is a short-hand + * for the {@link ExecutableCommand#withShortDescription} and + * {@link ExecutableCommand#withFullDescription} methods. + * + * @param shortDescription the short description for this command + * @param fullDescription the full description for this command + * @return this command builder + */ + public Impl withHelp(String shortDescription, String fullDescription) { + this.shortDescription = shortDescription; + this.fullDescription = fullDescription; + return instance(); + } /** - * Returns the permission associated with this command - * @return the permission associated with this command + * Sets the full usage for this command. This is the usage which is + * shown in the specific /help page for this command (e.g. /help mycommand). + * + * @param usage the full usage for this command + * @return this command builder */ - public CommandPermission getPermission() { - return this.meta.permission; + public Impl withUsage(String... usage) { + this.usageDescription = usage; + return instance(); } + ///////////////////////// + // Getters and setters // + ///////////////////////// + /** - * Sets the permission required to run this command - * @param permission the permission required to run this command + * Returns the name of this command + * + * @return the name of this command */ - public void setPermission(CommandPermission permission) { - this.meta.permission = permission; + public String getName() { + return name; + } + + /** + * Sets the aliases for this command + * + * @param aliases the aliases for this command + */ + public void setAliases(String[] aliases) { + this.aliases = aliases; } /** * Returns an array of aliases that can be used to run this command + * * @return an array of aliases that can be used to run this command */ public String[] getAliases() { - return meta.aliases; + return aliases; } /** - * Sets the aliases for this command - * @param aliases the aliases for this command + * Returns the permission associated with this command + * + * @return the permission associated with this command */ - public void setAliases(String[] aliases) { - this.meta.aliases = aliases; + public CommandPermission getPermission() { + return this.permission; + } + + /** + * Sets the permission required to run this command + * + * @param permission the permission required to run this command + */ + public void setPermission(CommandPermission permission) { + this.permission = permission; } /** * Returns the requirements that must be satisfied to run this command + * * @return the requirements that must be satisfied to run this command */ public Predicate getRequirements() { - return this.meta.requirements; + return this.requirements; } /** * Sets the requirements that must be satisfied to run this command + * * @param requirements the requirements that must be satisfied to run this command */ public void setRequirements(Predicate requirements) { - this.meta.requirements = requirements; + this.requirements = requirements; } - + /** * Returns the short description for this command + * * @return the short description for this command */ public String getShortDescription() { - return this.meta.shortDescription.orElse(null); + return this.shortDescription; } - /** - * Sets the short description for this command. This is the help which is - * shown in the main /help menu. - * @param description the short description for this command - * @return this command builder - */ - public Impl withShortDescription(String description) { - this.meta.shortDescription = Optional.ofNullable(description); - return instance(); - } - /** * Returns the full description for this command + * * @return the full description for this command */ public String getFullDescription() { - return this.meta.fullDescription.orElse(null); - } - - /** - * Sets the full description for this command. This is the help which is - * shown in the specific /help page for this command (e.g. /help mycommand). - * @param description the full description for this command - * @return this command builder - */ - public Impl withFullDescription(String description) { - this.meta.fullDescription = Optional.ofNullable(description); - return instance(); - } - - /** - * Sets the full usage for this command. This is the usage which is - * shown in the specific /help page for this command (e.g. /help mycommand). - * @param usage the full usage for this command - * @return this command builder - */ - public Impl withUsage(String... usage) { - this.meta.usageDescription = Optional.ofNullable(usage); - return instance(); + return this.fullDescription; } /** * Returns the usage for this command + * * @return the usage for this command */ public String[] getUsage() { - return this.meta.usageDescription.orElse(null); + return this.usageDescription; } /** - * Sets the short and full description for this command. This is a short-hand - * for the {@link ExecutableCommand#withShortDescription} and - * {@link ExecutableCommand#withFullDescription} methods. - * @param shortDescription the short description for this command - * @param fullDescription the full description for this command - * @return this command builder + * Returns the {@code HelpTopic} object assigned to this command (For Bukkit) + * + * @return the {@code HelpTopic} object assigned to this command (For Bukkit) */ - public Impl withHelp(String shortDescription, String fullDescription) { - this.meta.shortDescription = Optional.ofNullable(shortDescription); - this.meta.fullDescription = Optional.ofNullable(fullDescription); - return instance(); + public Object getHelpTopic() { + return helpTopic; } + ////////////////// + // Registration // + ////////////////// + + public abstract List> getArgumentsAsStrings(); + /** * Overrides a command. Effectively the same as unregistering the command using * CommandAPI.unregister() and then registering the command using .register() */ public void override() { - CommandAPI.unregister(this.meta.commandName, true); + CommandAPI.unregister(this.name, true); register(); } @@ -237,8 +315,20 @@ public void register() { } /** - * Registers this command with a custom {@link String} namespace + * Registers the command with a given namespace. + * + * @param namespace The namespace of this command. This cannot be null + * @throws NullPointerException if the namespace is null */ - public abstract void register(String namespace); + public void register(String namespace) { + if (namespace == null) { + throw new NullPointerException("Parameter 'namespace' was null when registering command /" + this.name + "!"); + } + ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); + } + abstract Nodes createCommandNodes(); + + record Nodes(LiteralCommandNode rootNode, List> aliasNodes) { + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/Execution.java b/commandapi-core/src/main/java/dev/jorel/commandapi/Execution.java deleted file mode 100644 index 1f6f55feda..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/Execution.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.jorel.commandapi; - -import dev.jorel.commandapi.arguments.AbstractArgument; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; - -import java.util.ArrayList; -import java.util.List; - -/** - * A list of arguments which results in an execution. This is used for building branches in a {@link AbstractCommandTree} - */ -public class Execution -/// @endcond -> { - - private final List arguments; - private final CommandAPIExecutor> executor; - - public Execution(List arguments, CommandAPIExecutor> executor) { - this.arguments = arguments; - this.executor = executor; - } - - /** - * Register a command with the given arguments and executor to brigadier, by converting it into a {@link AbstractCommandAPICommand} - * - * @param meta The metadata to register the command with - * @param namespace The namespace to use for this command - */ - public void register(CommandMetaData meta, String namespace) { - @SuppressWarnings("unchecked") - CommandAPIPlatform platform = (CommandAPIPlatform) CommandAPIHandler.getInstance().getPlatform(); - AbstractCommandAPICommand command = platform.newConcreteCommandAPICommand(meta); - command.withArguments(this.arguments); - command.setExecutor(this.executor); - command.register(namespace); - } - - public Execution prependedBy(Argument argument) { - List args = new ArrayList<>(); - args.add(argument); - args.addAll(this.arguments); - return new Execution<>(args, this.executor); - } -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java index ebaa9adea1..2aa91dcb60 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java @@ -2,6 +2,7 @@ import dev.jorel.commandapi.arguments.AbstractArgument; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -49,8 +50,10 @@ public record RegisteredCommand( * @return An {@link Optional} containing this command's help's * usage */ - Optional usageDescription, - + Optional usageDescription, + + // TODO: Bukkit specific fields probably should not be in platform agnostic classes + // Either make HelpTopic platform agnostic or move this field into bukkit-core /** * @return An {@link Optional} containing this command's help topic (for Bukkit) */ @@ -70,6 +73,31 @@ public record RegisteredCommand( * @return The namespace for this command */ String namespace) { + public static List fromExecutableCommand(ExecutableCommand command, String namespace) { + // Unpack command parameters + String commandName = command.getName(); + List> argumentsAsStrings = command.getArgumentsAsStrings(); + + Optional shortDescription = Optional.ofNullable(command.getShortDescription()); + Optional fullDescription = Optional.ofNullable(command.getFullDescription()); + Optional usageDescription = Optional.ofNullable(command.getUsage()); + Optional helpTopic = Optional.ofNullable(command.getHelpTopic()); + + String[] aliases = command.getAliases(); + CommandPermission permission = command.getPermission(); + + List result = new ArrayList<>(argumentsAsStrings.size()); + for (List argumentString : argumentsAsStrings) { + result.add(new RegisteredCommand( + commandName, argumentString, null, // TODO: Need to fix this. Trying to rebase changes from https://github.com/JorelAli/CommandAPI/pull/537 into `dev/command-build-rewrite` + shortDescription, fullDescription, usageDescription, + helpTopic, aliases, permission, namespace + )); + } + + return result; + } + // As https://stackoverflow.com/a/32083420 mentions, Optional's hashCode, equals, and toString method don't work if the // Optional wraps an array, like `Optional usageDescription`, so we have to use the Arrays methods ourselves @@ -104,5 +132,4 @@ public String toString() { + ", usageDescription=" + (usageDescription.isPresent() ? "Optional[" + Arrays.toString(usageDescription.get()) + "]" : "Optional.empty") + ", aliases=" + Arrays.toString(aliases) + ", permission=" + permission + ", namespace=" + namespace + ", helpTopic=" + helpTopic + "]"; } - -} \ No newline at end of file +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index decd3ed6c2..b1c7f3ac69 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -21,24 +21,34 @@ package dev.jorel.commandapi.arguments; import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.AbstractArgumentTree; +import dev.jorel.commandapi.CommandAPIExecutor; +import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.commandsenders.AbstractCommandSender; +import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; import dev.jorel.commandapi.executors.CommandArguments; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; /** * The core abstract class for Command API arguments - * - * @param The type of the underlying object that this argument casts to - * @param The class extending this class, used as the return type for chain calls - * @param The implementation of Argument used by the class extending this class + * + * @param The type of the underlying object that this argument casts to + * @param The class extending this class, used as the return type for chain calls + * @param The implementation of Argument used by the class extending this class * @param The CommandSender class used by the class extending this class */ public abstract class AbstractArgument> suggestions = Optional.empty(); - private Optional> addedSuggestions = Optional.empty(); + private ArgumentSuggestions replaceSuggestions = null; + private ArgumentSuggestions includedSuggestions = null; /** * Include suggestions to add to the list of default suggestions represented by this argument. @@ -131,7 +141,7 @@ public final String getNodeName() { * @return the current argument */ public Impl includeSuggestions(ArgumentSuggestions suggestions) { - this.addedSuggestions = Optional.of(suggestions); + this.includedSuggestions = suggestions; return instance(); } @@ -142,7 +152,7 @@ public Impl includeSuggestions(ArgumentSuggestions suggestions) { * @return An Optional containing a function which generates suggestions */ public Optional> getIncludedSuggestions() { - return addedSuggestions; + return Optional.ofNullable(includedSuggestions); } @@ -153,9 +163,8 @@ public Optional> getIncludedSuggestions() { * ArgumentSuggestions to create these. * @return the current argument */ - public Impl replaceSuggestions(ArgumentSuggestions suggestions) { - this.suggestions = Optional.of(suggestions); + this.replaceSuggestions = suggestions; return instance(); } @@ -167,7 +176,7 @@ public Impl replaceSuggestions(ArgumentSuggestions suggestions) { * are no overridden suggestions. */ public final Optional> getOverriddenSuggestions() { - return suggestions; + return Optional.ofNullable(replaceSuggestions); } ///////////////// @@ -235,13 +244,6 @@ public final Impl withRequirement(Predicate requirement) { return instance(); } - /** - * Resets the requirements for this command - */ - void resetRequirements() { - this.requirements = s -> true; - } - ///////////////// // Listability // ///////////////// @@ -316,7 +318,7 @@ public boolean hasCombinedArguments() { /** * Adds combined arguments to this argument. Combined arguments are used to have required arguments after optional arguments * by ignoring they exist until they are added to the arguments array for registration. - * + *

* This method also causes permissions and requirements from this argument to be copied over to the arguments you want to combine * this argument with. Their permissions and requirements will be ignored. * @@ -325,48 +327,216 @@ public boolean hasCombinedArguments() { */ @SafeVarargs public final Impl combineWith(Argument... combinedArguments) { - for (Argument argument : combinedArguments) { - this.combinedArguments.add(argument); - } + this.combinedArguments.addAll(Arrays.asList(combinedArguments)); return instance(); } - /////////// - // Other // - /////////// + ////////////////////// + // Command Building // + ////////////////////// + + public String getHelpString() { + return "<" + this.getNodeName() + ">"; + } + + @Override + public String toString() { + return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">"; + } /** - * Gets a list of entity names for the current provided argument. This is - * expected to be implemented by {@code EntitySelectorArgument} in Bukkit, see - * {@code EntitySelectorArgument#getEntityNames(Object)} + * Adds this argument to the end of the all the current possible paths given. The added entry is built as {code nodeName:argumentClass}. * - * @param argument a parsed (Bukkit) object representing the entity selector - * type. This is either a List, an Entity or a Player - * @return a list of strings representing the names of the entity or entities - * from {@code argument} + * @param argumentStrings A list of possible paths to this argument so far. */ - public List getEntityNames(Object argument) { - return Collections.singletonList(null); + public void appendToCommandPaths(List> argumentStrings) { + // Create paths for this argument + String argumentString = nodeName + ":" + getClass().getSimpleName(); + for (List path : argumentStrings) { + path.add(argumentString); + } + + // Add combined arguments + for (Argument subArgument : combinedArguments) { + subArgument.appendToCommandPaths(argumentStrings); + } } /** - * Copies permissions and requirements from the provided argument to this argument - * This also resets additional permissions and requirements. + * Builds the Brigadier {@link CommandNode} structure for this argument. Note that the Brigadier node structure may + * contain multiple nodes, for example if {@link #combineWith(AbstractArgument[])} was called for this argument to + * merge it with other arguments. + *

+ * This process is broken up into 4 other methods for the convenience of defining special node structures for specific + * arguments. Those methods are: + *

    + *
  • {@link #checkPreconditions(List, List)}
  • + *
  • {@link #createArgumentBuilder(List, List)}
  • + *
  • {@link #finishBuildingNode(ArgumentBuilder, List, CommandAPIExecutor)}
  • + *
  • {@link #linkNode(CommandNode, CommandNode, List, List, CommandAPIExecutor)}
  • + *
* - * @param argument The argument to copy permissions and requirements from + * @param previousNode The {@link CommandNode} to add this argument onto. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. + * @param terminalExecutor The {@link CommandAPIExecutor} to apply at the end of the node structure. + * This parameter can be null to indicate that this argument should not be + * executable. + * @param The Brigadier Source object for running commands. + * @return The last node in the Brigadier node structure for this argument. */ - public void copyPermissionsAndRequirements(Argument argument) { - this.resetRequirements(); - this.withRequirement(argument.getRequirements()); - this.withPermission(argument.getArgumentPermission()); + public CommandNode addArgumentNodes(CommandNode previousNode, List previousArguments, List previousNonLiteralArgumentNames, + CommandAPIExecutor> terminalExecutor) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Check preconditions + checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + + // Handle previewable argument + if (this instanceof Previewable) { + handler.addPreviewableArgument(previousArguments, (Argument) this); + } + + // Create node + ArgumentBuilder rootBuilder = createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + + // Finish building node + CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalExecutor); + + // Link node to previous + previousNode = linkNode(previousNode, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + + // Return last node + return previousNode; } - public String getHelpString() { - return "<" + this.getNodeName() + ">"; + /** + * Checks for any conditions that mean this argument cannot be build properly. + * + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. + */ + public void checkPreconditions(List previousArguments, List previousNonLiteralArgumentNames) { + if (previousNonLiteralArgumentNames.contains(nodeName)) { + throw new DuplicateNodeNameException(previousArguments, (Argument) this); + } } - @Override - public String toString() { - return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">"; + /** + * Creates a Brigadier {@link ArgumentBuilder} representing this argument. Note: calling this method will also add + * this argument and its name to the end of the given {@code previousArguments} and {@code previousNonLiteralArgumentNames} + * lists. + * + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. + * @param The Brigadier Source object for running commands. + * @return The {@link ArgumentBuilder} for this argument. + */ + public ArgumentBuilder createArgumentBuilder(List previousArguments, List previousNonLiteralArgumentNames) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Create node + RequiredArgumentBuilder rootBuilder = RequiredArgumentBuilder.argument(nodeName, rawType); + + // Add suggestions + if (replaceSuggestions != null) { + // Overridden suggestions take precedence + rootBuilder.suggests(handler.generateBrigadierSuggestions(previousArguments, replaceSuggestions)); + } else if (includedSuggestions != null) { + // Insert additional defined suggestions + SuggestionProvider defaultSuggestions; + if (this instanceof CustomProvidedArgument cPA) { + defaultSuggestions = handler.getPlatform().getSuggestionProvider(cPA.getSuggestionProvider()); + } else { + defaultSuggestions = rawType::listSuggestions; + } + + SuggestionProvider includedSuggestions = handler.generateBrigadierSuggestions(previousArguments, this.includedSuggestions); + + rootBuilder.suggests((cmdCtx, builder) -> { + // Heavily inspired by CommandDispatcher#getCompletionSuggestions, with combining + // multiple CompletableFuture into one. + CompletableFuture defaultSuggestionsFuture = defaultSuggestions.getSuggestions(cmdCtx, builder); + CompletableFuture includedSuggestionsFuture = includedSuggestions.getSuggestions(cmdCtx, builder); + + CompletableFuture result = new CompletableFuture<>(); + CompletableFuture.allOf(defaultSuggestionsFuture, includedSuggestionsFuture).thenRun(() -> { + List suggestions = new ArrayList<>(); + suggestions.add(defaultSuggestionsFuture.join()); + suggestions.add(includedSuggestionsFuture.join()); + result.complete(Suggestions.merge(cmdCtx.getInput(), suggestions)); + }); + return result; + }); + } else if (this instanceof CustomProvidedArgument cPA) { + // Handle arguments with built-in suggestion providers + rootBuilder.suggests(handler.getPlatform().getSuggestionProvider(cPA.getSuggestionProvider())); + } + + // Add argument to previousArgument lists + // Note: this argument should not be in the previous arguments list when doing suggestions, + // since this argument is not going to be present in the cmdCtx while being suggested + previousArguments.add((Argument) this); + previousNonLiteralArgumentNames.add(nodeName); + + return rootBuilder; + } + + /** + * Finishes building the Brigadier {@link ArgumentBuilder} representing this argument. + * + * @param rootBuilder The {@link ArgumentBuilder} to finish building. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param terminalExecutor The {@link CommandAPIExecutor} to apply at the end of the node structure. This parameter + * can be null to indicate that this argument should not be executable. + * @param The Brigadier Source object for running commands. + * @return The {@link CommandNode} representing this argument created by building the given {@link ArgumentBuilder}. + */ + public CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, CommandAPIExecutor> terminalExecutor) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Add permission and requirements + rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); + + // Add the given executor if we are the last node + if (combinedArguments.isEmpty() && terminalExecutor != null && terminalExecutor.hasAnyExecutors()) { + rootBuilder.executes(handler.generateBrigadierCommand(previousArguments, terminalExecutor)); + } + + return rootBuilder.build(); + } + + /** + * Links this argument into the Brigadier {@link CommandNode} structure. + * + * @param previousNode The {@link CommandNode} to add this argument onto. + * @param rootNode The {@link CommandNode} representing this argument. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. + * @param terminalExecutor The {@link CommandAPIExecutor} to apply at the end of the node structure. + * This parameter can be null to indicate that this argument should not be + * executable. + * @param The Brigadier Source object for running commands. + * @return The last node in the Brigadier {@link CommandNode} structure representing this Argument. Note that this + * is not necessarily the {@code rootNode} for this argument, since the Brigadier node structure may contain multiple + * nodes. This might happen if {@link #combineWith(AbstractArgument[])} was called for this argument to merge it with + * other arguments. + */ + public CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List previousArguments, List previousNonLiteralArgumentNames, + CommandAPIExecutor> terminalExecutor) { + // Add rootNode to the previous + previousNode.addChild(rootNode); + + // Add combined arguments + previousNode = rootNode; + for (int i = 0; i < combinedArguments.size(); i++) { + Argument subArgument = combinedArguments.get(i); + previousNode = subArgument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, + // Only apply the `terminalExecutor` to the last argument + i == combinedArguments.size() - 1 ? terminalExecutor : null); + } + + // Return last node + return previousNode; } -} \ No newline at end of file +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java index 9953538269..5afb6a4f8b 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java @@ -1,15 +1,18 @@ package dev.jorel.commandapi.arguments; -import dev.jorel.commandapi.ChainableBuilder; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; + +import java.util.List; /** * An interface representing literal-based arguments */ -public interface Literal +extends AbstractArgument /// @endcond -> extends ChainableBuilder { +, CommandSender> { // Literal specific information /** @@ -18,4 +21,32 @@ public interface Literal + * Normally, Arguments cannot be built if their node name is found in {@code previousNonLiteralArgumentNames} list. + * However, Literals do not have this problem, so we want to skip that check. + */ + default void checkPreconditions(List previousArguments, List previousNonLiteralArgumentNames) { + + } + + /** + * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. + *

+ * Normally, Arguments will use Brigadier's RequiredArgumentBuilder. However, Literals use LiteralArgumentBuilders. + * Arguments also usually add their name to the {@code previousNonLiteralArgumentNames} list, but literal node names + * do not conflict with required argument node names. + */ + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousNonLiteralArgumentNames) { + previousArguments.add((Argument) this); + + return LiteralArgumentBuilder.literal(getLiteral()); + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index 91d9ba5744..c39115b3f2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -1,21 +1,153 @@ package dev.jorel.commandapi.arguments; -import dev.jorel.commandapi.ChainableBuilder; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandAPIExecutor; +import dev.jorel.commandapi.commandsenders.AbstractCommandSender; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; /** * An interface representing arguments with multiple literal string definitions */ -public interface MultiLiteral +extends AbstractArgument /// @endcond -> extends ChainableBuilder { +, CommandSender> { // MultiLiteral specific information /** - * Returns the literals that are present in this argument + * Returns the literals that are present in this argument. * - * @return the literals that are present in this argument + * @return the literals that are present in this argument. + * @apiNote A MultiLiteral argument must have at least one literal. E.g. the array returned by this method should + * never be empty. */ String[] getLiterals(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // LINKED METHODS // + // These automatically link to methods in AbstractArgument (make sure they have the same signature) // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Links to {@link AbstractArgument#getNodeName()}. + */ + String getNodeName(); + + /** + * Links to {@link AbstractArgument#getCombinedArguments()}. + */ + List getCombinedArguments(); + + /** + * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, CommandAPIExecutor)}. + */ + CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, CommandAPIExecutor> terminalExecutor); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDING METHODS // + // A MultiLiteral has special logic that should override the implementations in AbstractArgument // + //////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Overrides {@link AbstractArgument#checkPreconditions(List, List)}. + *

+ * Normally, Arguments cannot be built if their node name is found in {@code previousNonLiteralArgumentNames} list. + * However, LiteralArguments do not have this problem, so we want to skip that check. + */ + default void checkPreconditions(List previousArguments, List previousNonLiteralArgumentNames) { + + } + + /** + * Overrides {@link AbstractArgument#appendToCommandPaths(List)}. + *

+ * Normally, Arguments only represent a single branching path. However, a MultiLiteral represents multiple literal + * node paths, so we need to branch out the current paths for each literal. + */ + default void appendToCommandPaths(List> argumentStrings) { + // Create paths for this argument + Iterator literals = Arrays.asList(getLiterals()).iterator(); + String firstLiteralArgumentString = literals.next() + ":LiteralArgument"; + + List> newPaths = new ArrayList<>(); + while (literals.hasNext()) { + String literalArgumentString = literals.next() + ":LiteralArgument"; + for (List path : argumentStrings) { + List newPath = new ArrayList<>(path); + newPath.add(literalArgumentString); + newPaths.add(newPath); + } + } + + for (List path : argumentStrings) { + path.add(firstLiteralArgumentString); + } + argumentStrings.addAll(newPaths); + + // Add combined arguments + for (Argument argument : getCombinedArguments()) { + argument.appendToCommandPaths(argumentStrings); + } + } + + /** + * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. + *

+ * Normally, Arguments will use Brigadier's RequiredArgumentBuilder. However, MultiLiterals use LiteralArgumentBuilders. + * Arguments also usually add their name to the {@code previousNonLiteralArgumentNames} list, but literal node names + * do not conflict with required argument node names. + */ + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousNonLiteralArgumentNames) { + previousArguments.add((Argument) this); + + return MultiLiteralArgumentBuilder.multiLiteral(getNodeName(), getLiterals()[0]); + } + + /** + * Overrides {@link AbstractArgument#linkNode(CommandNode, CommandNode, List, List, CommandAPIExecutor)}. + *

+ * Normally, Arguments are only represented by a single node, and so only need to link one node. However, a MultiLiteral + * represents multiple literal node paths, which also need to be generated and inserted into the node structure. + */ + default CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List previousArguments, List previousNonLiteralArgumentNames, + CommandAPIExecutor> terminalExecutor) { + // Generate nodes for other literals + Iterator literals = Arrays.asList(getLiterals()).iterator(); + literals.next(); // Skip first literal; that was handled by `#createArgumentBuilder` + while (literals.hasNext()) { + // Create node + MultiLiteralArgumentBuilder literalBuilder = MultiLiteralArgumentBuilder.multiLiteral(getNodeName(), literals.next()); + + // Redirect to root node so all its arguments come after this + literalBuilder.redirect(rootNode); + + // Finish building node + CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalExecutor); + + // Link node to previous + previousNode.addChild(literalNode); + } + + // Add root node to previous + previousNode.addChild(rootNode); + + // Add combined arguments + previousNode = rootNode; + List combinedArguments = getCombinedArguments(); + for (int i = 0; i < combinedArguments.size(); i++) { + Argument subArgument = combinedArguments.get(i); + previousNode = subArgument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, + // Only apply the `terminalExecutor` to the last argument + i == combinedArguments.size() - 1 ? terminalExecutor : null); + } + + // Return last node + return previousNode; + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgumentBuilder.java new file mode 100644 index 0000000000..533965d84a --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgumentBuilder.java @@ -0,0 +1,55 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; + +/** + * A special type of {@link LiteralArgumentBuilder} for {@link MultiLiteral}. Compared to the {@link LiteralArgumentBuilder}, + * this class also has a {code nodeName} and builds a {@link MultiLiteralCommandNode}. + * + * @param The Brigadier Source object for running commands. + */ +public class MultiLiteralArgumentBuilder extends LiteralArgumentBuilder { + private final String nodeName; + + /** + * Creates a new MultiLiteralArgumentBuilder with the given nodeName and literal. + * + * @param nodeName the string that identifies the parsed command node in the CommandContext. + * @param literal the literal that identifies the built command node in the CommandDispatcher + */ + protected MultiLiteralArgumentBuilder(String nodeName, String literal) { + super(literal); + this.nodeName = nodeName; + } + + /** + * A factory method to create a new builder for a {@link MultiLiteralCommandNode}. + * + * @param nodeName the string that identifies the parsed command node in the CommandContext. + * @param literal the literal that identifies the built command node in the CommandDispatcher + * @param The Brigadier Source object for running commands. + * @return the created MultiLiteralArgumentBuilder. + */ + public static MultiLiteralArgumentBuilder multiLiteral(String nodeName, String literal) { + return new MultiLiteralArgumentBuilder<>(nodeName, literal); + } + + /** + * @return the node name that the built command node will be identified by. + */ + public String getNodeName() { + return nodeName; + } + + @Override + public MultiLiteralCommandNode build() { + MultiLiteralCommandNode result = new MultiLiteralCommandNode<>(getNodeName(), getLiteral(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork()); + + for (CommandNode argument : getArguments()) { + result.addChild(argument); + } + + return result; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralCommandNode.java new file mode 100644 index 0000000000..7a72432220 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralCommandNode.java @@ -0,0 +1,82 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.RedirectModifier; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedArgument; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; + +import java.util.function.Predicate; + +/** + * A special type of {@link LiteralCommandNode} for {@link MultiLiteral}. Compared to the {@link LiteralCommandNode}, + * this class also has a {@code nodeName}. When this node is parsed, it will add its literal value as an argument in + * the CommandContext, allowing a MultiLiteralArgument to know which literal was selected. + * + * @param The Brigadier Source object for running commands. + */ +public class MultiLiteralCommandNode extends LiteralCommandNode { + private final String nodeName; + + public MultiLiteralCommandNode(String nodeName, String literal, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks) { + super(literal, command, requirement, redirect, modifier, forks); + this.nodeName = nodeName; + } + + /** + * @return the node name this command represents. + */ + public String getNodeName() { + return nodeName; + } + + // A MultiLiteralCommandNode is mostly identical to a LiteralCommandNode + // The only difference is that when a MultiLiteral is parsed, it adds its literal as an argument based on the nodeName + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + int start = reader.getCursor(); + super.parse(reader, contextBuilder); + + contextBuilder.withArgument(getNodeName(), new ParsedArgument<>(start, reader.getCursor(), getLiteral())); + } + + // Typical LiteralCommandNode methods, adding in the nodeName parameter + // Mostly copied and inspired by the implementations for these methods in LiteralCommandNode + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof MultiLiteralCommandNode other)) return false; + + + if (!nodeName.equals(other.nodeName)) return false; + return super.equals(obj); + } + + @Override + public int hashCode() { + int result = nodeName.hashCode(); + result = 31 * result + super.hashCode(); + return result; + } + + @Override + public MultiLiteralArgumentBuilder createBuilder() { + MultiLiteralArgumentBuilder builder = MultiLiteralArgumentBuilder.multiLiteral(this.nodeName, getLiteral()); + + builder.requires(getRequirement()); + builder.forward(getRedirect(), getRedirectModifier(), isFork()); + if (getCommand() != null) { + builder.executes(getCommand()); + } + return builder; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java new file mode 100644 index 0000000000..df6cb672aa --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java @@ -0,0 +1,27 @@ +package dev.jorel.commandapi.exceptions; + +import dev.jorel.commandapi.arguments.AbstractArgument; + +import java.util.List; + +/** + * An exception that happens while registering a command + */ +public class CommandRegistrationException extends RuntimeException { + public CommandRegistrationException(String message) { + super(message); + } + + protected static > void addArgumentList(StringBuilder builder, List arguments) { + builder.append("["); + for (AbstractArgument argument : arguments) { + addArgument(builder, argument); + builder.append(" "); + } + builder.setCharAt(builder.length() - 1, ']'); + } + + protected static > void addArgument(StringBuilder builder, Argument argument) { + builder.append(argument.getNodeName()).append("<").append(argument.getClass().getSimpleName()).append(">"); + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java new file mode 100644 index 0000000000..5351aa5c94 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java @@ -0,0 +1,33 @@ +package dev.jorel.commandapi.exceptions; + +import dev.jorel.commandapi.arguments.AbstractArgument; + +import java.util.List; + +/** + * An exception caused when two arguments conflict due to sharing the same node name. Note that literal nodes are allowed + * to share node names with other literals and arguments. + */ +public class DuplicateNodeNameException extends CommandRegistrationException { + /** + * Creates a new DuplicateNodeNameException. + * + * @param previousArguments The arguments that led up to the invalid argument. + * @param argument The argument that was incorrectly given the same name as a previous argument. + */ + public > DuplicateNodeNameException(List previousArguments, Argument argument) { + super(buildMessage(previousArguments, argument)); + } + + private static > String buildMessage(List previousArguments, Argument argument) { + StringBuilder builder = new StringBuilder(); + + builder.append("Duplicate node names for non-literal arguments are not allowed! Going down the "); + addArgumentList(builder, previousArguments); + builder.append(" branch, found "); + addArgument(builder, argument); + builder.append(", which had a duplicate node name"); + + return builder.toString(); + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java index c48a3d7f27..3300a1cbac 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java @@ -22,28 +22,42 @@ import dev.jorel.commandapi.arguments.AbstractArgument; +import java.util.List; + /** - * An exception caused when a greedy argument is not declared at the end of a - * List + * An exception caused when a greedy argument is not declared at the end of a List. */ -@SuppressWarnings("serial") -public class GreedyArgumentException extends RuntimeException { +public class GreedyArgumentException extends CommandRegistrationException { /** * Creates a GreedyArgumentException - * - * @param arguments the list of arguments that have been used for this command - * (including the greedy string argument) + * + * @param previousArguments The arguments that came before the greedy argument + * @param argument The greedy argument that is in an invalid spot + * @param followingBranches The branches following the greedy argument that weren't supposed to be there + * @param The Argument class being used */ - public GreedyArgumentException(AbstractArgument[] arguments) { - super("Only one GreedyStringArgument or ChatArgument can be declared, at the end of a List. Found arguments: " - + buildArgsStr(arguments)); + public > GreedyArgumentException( + List previousArguments, Argument argument, List> followingBranches) { + super(buildMessage(previousArguments, argument, followingBranches)); } - private static String buildArgsStr(AbstractArgument[] arguments) { + private static > String buildMessage( + List previousArguments, Argument argument, List> followingBranches) { StringBuilder builder = new StringBuilder(); - for (AbstractArgument arg : arguments) { - builder.append(arg.getNodeName()).append("<").append(arg.getClass().getSimpleName()).append("> "); + + builder.append("A greedy argument must be declared at the end of a command. Going down the "); + addArgumentList(builder, previousArguments); + builder.append(" branch, found "); + addArgument(builder, argument); + builder.append(" followed by "); + + for (List branch : followingBranches) { + addArgumentList(builder, branch); + builder.append(" and "); } + + builder.setLength(builder.length() - 5); + return builder.toString(); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/MissingCommandExecutorException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/MissingCommandExecutorException.java index 721e799f41..4c21185d32 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/MissingCommandExecutorException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/MissingCommandExecutorException.java @@ -20,18 +20,46 @@ *******************************************************************************/ package dev.jorel.commandapi.exceptions; +import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.arguments.AbstractArgument; + +import java.util.List; + /** - * An exception caused when a command does not declare any executors + * An exception caused when a command does not declare any executors. */ -@SuppressWarnings("serial") -public class MissingCommandExecutorException extends RuntimeException { - +public class MissingCommandExecutorException extends CommandRegistrationException { /** - * Creates a MissingCommandExecutorException - * - * @param commandName the name of the command that was being registered + * Creates a MissingCommandExecutorException. + * + * @param commandName The name of the command that could not be executed. */ public MissingCommandExecutorException(String commandName) { - super("/" + commandName + " does not declare any executors or executable subcommands!"); + this(List.of(), CommandAPIHandler.getInstance().getPlatform().newConcreteLiteralArgument(commandName, commandName)); + } + + /** + * Creates a MissingCommandExecutorException. + * + * @param previousArguments The arguments that led up to the un-executable argument. + * @param argument The argument that is missing an executor. + */ + public > MissingCommandExecutorException( + List previousArguments, Argument argument) { + super(buildMessage(previousArguments, argument)); + } + + private static > String buildMessage(List previousArguments, Argument argument) { + StringBuilder builder = new StringBuilder(); + + builder.append("The command path "); + if (!previousArguments.isEmpty()) { + addArgumentList(builder, previousArguments); + builder.append(" ending with "); + } + addArgument(builder, argument); + builder.append(" is not executable!"); + + return builder.toString(); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java index 2dbea9adfb..8244acd8b5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java @@ -1,10 +1,54 @@ package dev.jorel.commandapi.exceptions; -@SuppressWarnings("serial") -public class OptionalArgumentException extends RuntimeException { +import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.arguments.AbstractArgument; - public OptionalArgumentException(String commandName) { - super("Failed to register command /" + commandName + " because an unlinked required argument cannot follow an optional argument!"); +import java.util.ArrayList; +import java.util.List; + +/** + * An exception caused when a required (non-optional) argument follows an optional argument. + */ +public class OptionalArgumentException extends CommandRegistrationException { + /** + * Creates an OptionalArgumentException + * + * @param commandName The name of the command that had a required argument after an optional argument. + * @param previousArguments The arguments that led up to the invalid required argument. + * @param argument The required argument that incorrectly came after an optional argument. + */ + public > OptionalArgumentException(String commandName, List previousArguments, Argument argument) { + this(insertCommandName(commandName, previousArguments), argument); + } + + private static > List insertCommandName(String commandName, List previousArguments) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + Argument commandNameArgument = handler.getPlatform().newConcreteLiteralArgument(commandName, commandName); + List newArguments = new ArrayList<>(List.of(commandNameArgument)); + newArguments.addAll(previousArguments); + return newArguments; + } + + /** + * Creates an OptionalArgumentException + * + * @param previousArguments The arguments that led up to the invalid required argument, starting with an Argument representing + * the initial command node. + * @param argument The required argument that incorrectly came after an optional argument. + */ + public > OptionalArgumentException(List previousArguments, Argument argument) { + super(buildMessage(previousArguments, argument)); } + private static > String buildMessage(List previousArguments, Argument argument) { + StringBuilder builder = new StringBuilder(); + + builder.append("Uncombined required arguments following optional arguments are not allowed! Going down the "); + addArgumentList(builder, previousArguments); + builder.append(" branch, found a required argument "); + addArgument(builder, argument); + builder.append(" after an optional argument"); + + return builder.toString(); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index e18a59a98a..dd1539c9a0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -41,7 +41,6 @@ import org.bukkit.plugin.java.JavaPlugin; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.SuggestionProvider; @@ -420,15 +419,6 @@ public BukkitCommandSender wrapCommandSender(CommandSen throw new RuntimeException("Failed to wrap CommandSender " + sender + " to a CommandAPI-compatible BukkitCommandSender"); } - @Override - public void registerPermission(String string) { - try { - Bukkit.getPluginManager().addPermission(new Permission(string)); - } catch (IllegalArgumentException e) { - assert true; // nop, not an error. - } - } - @Override @Unimplemented(because = REQUIRES_MINECRAFT_SERVER) public abstract SuggestionProvider getSuggestionProvider(SuggestionProviders suggestionProvider); @@ -454,12 +444,28 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { - commandRegistrationStrategy.postCommandRegistration(registeredCommand, resultantNode, aliasNodes); + public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { + commandRegistrationStrategy.postCommandRegistration(registeredCommands, resultantNode, aliasNodes); + + // Using registeredCommands.get(0) as representation for most command features. + // This is fine, because the only difference between the commands in the list is their argument strings. + RegisteredCommand commonCommandInformation = registeredCommands.get(0); + + // Register the command's permission node to Bukkit's manager, if it exists + CommandPermission permission = commonCommandInformation.permission(); + Optional wrappedPermissionString = permission.getPermission(); + if (wrappedPermissionString.isPresent()) { + try { + Bukkit.getPluginManager().addPermission(new Permission(wrappedPermissionString.get())); + } catch (IllegalArgumentException ignored) { + // Exception is thrown if we attempt to register a permission that already exists + // If it already exists, that's totally fine, so just ignore the exception + } + } if (!CommandAPI.canRegister()) { // Adding the command to the help map usually happens in `CommandAPIBukkit#onEnable` - updateHelpForCommands(List.of(registeredCommand)); + updateHelpForCommands(registeredCommands); // Sending command dispatcher packets usually happens when Players join the server for (Player p : Bukkit.getOnlinePlayers()) { @@ -469,8 +475,8 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal } @Override - public LiteralCommandNode registerCommandNode(LiteralArgumentBuilder node, String namespace) { - return commandRegistrationStrategy.registerCommandNode(node, namespace); + public void registerCommandNode(LiteralCommandNode node, String namespace) { + commandRegistrationStrategy.registerCommandNode(node, namespace); } @Override @@ -562,11 +568,6 @@ public Argument newConcreteLiteralArgument(String nodeName, String liter return new LiteralArgument(nodeName, literal); } - @Override - public CommandAPICommand newConcreteCommandAPICommand(CommandMetaData meta) { - return new CommandAPICommand(meta); - } - /** * Forces a command to return a success value of 0 * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index 3fb95c73c8..840ebc8862 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java @@ -9,20 +9,15 @@ import org.bukkit.plugin.java.JavaPlugin; public class CommandAPICommand extends AbstractCommandAPICommand, CommandSender> implements BukkitExecutable { - - public CommandAPICommand(CommandMetaData meta) { - super(meta); - } - + /** + * Creates a new command builder + * + * @param commandName The name of the command to create + */ public CommandAPICommand(String commandName) { super(commandName); } - @Override - protected CommandAPICommand newConcreteCommandAPICommand(CommandMetaData metaData) { - return new CommandAPICommand(metaData); - } - @Override public CommandAPICommand instance() { return this; @@ -42,18 +37,18 @@ public CommandAPICommand instance() { * @return this command builder */ public CommandAPICommand withHelp(HelpTopic helpTopic) { - this.meta.helpTopic = Optional.of(helpTopic); + this.helpTopic = Optional.of(helpTopic); return instance(); } /** - * Registers the command with a given namespace + * Registers the command with a given namespace. * - * @param namespace The namespace of this command. This cannot be null or empty + * @param namespace The namespace of this command. This cannot be null or empty. * */ public void register(String namespace) { - if (CommandAPIBukkit.get().isInvalidNamespace(this.meta.commandName, namespace)) { + if (CommandAPIBukkit.get().isInvalidNamespace(this.name, namespace)) { super.register(); return; } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java index 30f10b7ad6..3f2e78b266 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java @@ -1,7 +1,6 @@ package dev.jorel.commandapi; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; @@ -76,9 +75,9 @@ protected static boolean isThisTheCommandButNamespaced(String commandName, Strin public abstract void runTasksAfterServerStart(); - public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); + public abstract void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes); - public abstract LiteralCommandNode registerCommandNode(LiteralArgumentBuilder node, String namespace); + public abstract void registerCommandNode(LiteralCommandNode node, String namespace); public abstract void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java index 4a9df1de95..fd01dec68b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java @@ -1,7 +1,11 @@ package dev.jorel.commandapi; import dev.jorel.commandapi.arguments.Argument; + +import java.util.Optional; + import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpTopic; import org.bukkit.plugin.java.JavaPlugin; public class CommandTree extends AbstractCommandTree, CommandSender> implements BukkitExecutable { @@ -18,6 +22,24 @@ public CommandTree(String commandName) { public CommandTree instance() { return this; } + + /** + * Sets the {@link HelpTopic} for this command. Using this method will override + * any declared short description, full description or usage description provided + * via the following methods: + *

    + *
  • {@link CommandAPICommand#withShortDescription(String)}
  • + *
  • {@link CommandAPICommand#withFullDescription(String)}
  • + *
  • {@link CommandAPICommand#withUsage(String...)}
  • + *
  • {@link CommandAPICommand#withHelp(String, String)}
  • + *
+ * @param helpTopic the help topic to use for this command + * @return this command builder + */ + public CommandTree withHelp(HelpTopic helpTopic) { + this.helpTopic = Optional.of(helpTopic); + return instance(); + } /** * Registers the command with a given namespace @@ -25,7 +47,7 @@ public CommandTree instance() { * @param namespace The namespace of this command. This cannot be null or empty */ public void register(String namespace) { - if (CommandAPIBukkit.get().isInvalidNamespace(this.meta.commandName, namespace)) { + if (CommandAPIBukkit.get().isInvalidNamespace(this.name, namespace)) { super.register(); return; } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java index 58dedd464f..82c715067c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java @@ -20,25 +20,23 @@ *******************************************************************************/ package dev.jorel.commandapi; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - +import dev.jorel.commandapi.arguments.Argument; +import dev.jorel.commandapi.arguments.FlattenableArgument; +import dev.jorel.commandapi.arguments.GreedyStringArgument; +import dev.jorel.commandapi.executors.CommandArguments; +import dev.jorel.commandapi.executors.NativeResultingCommandExecutor; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.command.CommandSender; import org.bukkit.entity.LivingEntity; import org.bukkit.plugin.java.JavaPlugin; -import dev.jorel.commandapi.arguments.Argument; -import dev.jorel.commandapi.arguments.GreedyStringArgument; -import dev.jorel.commandapi.executors.NativeCommandExecutor; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.function.Function; /** * 'Simple' conversion of Plugin commands @@ -128,24 +126,18 @@ private static void convertCommand(String commandName, List> argumen CommandAPI.logInfo("Converting command /" + commandName); // No arguments - new CommandAPICommand(commandName).withPermission(CommandPermission.NONE).executesNative((sender, args) -> { - Bukkit.dispatchCommand(mergeProxySender(sender), commandName); - return; - }).register(); + new CommandAPICommand(commandName).withPermission(CommandPermission.NONE) + .executesNative((sender, args) -> Bukkit.dispatchCommand(mergeProxySender(sender), commandName) ? 1 : 0).register(); // Multiple arguments - CommandAPICommand multiArgs = new CommandAPICommand(commandName).withPermission(CommandPermission.NONE) - .withArguments(arguments).executesNative((sender, args) -> { - // We know the args are a String[] because that's how converted things are - // handled in generateCommand() - CommandSender proxiedSender = mergeProxySender(sender); - Bukkit.dispatchCommand(proxiedSender, commandName + " " + String.join(" ", (String[]) args.args())); - }); - - multiArgs.setConverted(true); - multiArgs.register(); + new CommandAPICommand(commandName).withPermission(CommandPermission.NONE) + .withArguments(arguments).executesNative((sender, args) -> { + CommandSender proxiedSender = mergeProxySender(sender); + return flattenArguments(args, arguments, + flattened -> Bukkit.dispatchCommand(proxiedSender, commandName + " " + String.join(" ", flattened))); + }).register(); } - + private static String[] unpackAliases(Object aliasObj) { if (aliasObj == null) { return new String[0]; @@ -191,23 +183,23 @@ private static void convertPluginCommand(JavaPlugin plugin, String commandName, CommandAPI.logInfo("Permission for command /" + commandName + " found. Using " + permission); permissionNode = CommandPermission.fromString(permission); } - - NativeCommandExecutor executor = (sender, args) -> { + + NativeResultingCommandExecutor executor = (sender, args) -> { org.bukkit.command.Command command = plugin.getCommand(commandName); - + if (command == null) { - command = CommandAPIBukkit.get().getSimpleCommandMap() - .getCommand(commandName); + command = CommandAPIBukkit.get().getSimpleCommandMap().getCommand(commandName); } CommandSender proxiedSender = CommandAPI.getConfiguration().shouldSkipSenderProxy(plugin.getName()) ? sender.getCallee() : mergeProxySender(sender); - if (args.args() instanceof String[] argsArr) { - command.execute(proxiedSender, commandName, argsArr); + if (args.count() != 0) { + org.bukkit.command.Command finalCommand = command; + return flattenArguments(args, arguments, flattened -> finalCommand.execute(proxiedSender, commandName, flattened)); } else { - command.execute(proxiedSender, commandName, new String[0]); + return command.execute(proxiedSender, commandName, new String[0]) ? 1 : 0; } }; @@ -226,10 +218,41 @@ private static void convertPluginCommand(JavaPlugin plugin, String commandName, .withArguments(arguments) .withFullDescription(fullDescription) .executesNative(executor) - .setConverted(true) .register(); } + private static int flattenArguments(CommandArguments argumentInfo, List> commandAPIArguments, Function argumentConsumer) { + // Most arguments stay the same, just pass through the raw input as given + String[] rawArguments = argumentInfo.rawArgs(); + return flattenArguments(argumentInfo, commandAPIArguments, argumentConsumer, rawArguments, 0); + } + + private static int flattenArguments(CommandArguments argumentInfo, List> commandAPIArguments, Function argumentConsumer, + String[] rawArguments, int argumentIndex) { + if (argumentIndex > commandAPIArguments.size()) { + // Processed all the arguments, use it now + return argumentConsumer.apply(rawArguments) ? 1 : 0; + } + + Argument argument = commandAPIArguments.get(argumentIndex); + + if (argument instanceof FlattenableArgument flattenable) { + // This argument wants to be flattened into its possibilities + List possibilities = flattenable.flatten(argumentInfo.get(argumentIndex)); + int successCount = 0; + for (String item : possibilities) { + rawArguments[argumentIndex] = item; + successCount += flattenArguments(argumentInfo, commandAPIArguments, argumentConsumer, + rawArguments, argumentIndex + 1); + } + return successCount; + } else { + // No processing needed for this argument, move to the next + return flattenArguments(argumentInfo, commandAPIArguments, argumentConsumer, + rawArguments, argumentIndex + 1); + } + } + /* * https://www.jorel.dev/blog/posts/Simplifying-Bukkit-CommandSenders/ */ @@ -272,5 +295,4 @@ private static CommandSender mergeProxySender(NativeProxyCommandSender proxySend return (CommandSender) Proxy.newProxyInstance(CommandSender.class.getClassLoader(), calleeInterfaces, handler); } - } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java index c6313f5ddf..fce5f7fe18 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java @@ -1,7 +1,6 @@ package dev.jorel.commandapi; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; @@ -50,23 +49,24 @@ public void runTasksAfterServerStart() { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { // Nothing to do } @Override - public LiteralCommandNode registerCommandNode(LiteralArgumentBuilder node, String namespace) { - LiteralCommandNode commandNode = getBrigadierDispatcher.get().register(node); - LiteralCommandNode namespacedCommandNode = CommandAPIHandler.getInstance().namespaceNode(commandNode, namespace); - - // Add to registered command nodes - registeredNodes.addChild(commandNode); - registeredNodes.addChild(namespacedCommandNode); - + public void registerCommandNode(LiteralCommandNode node, String namespace) { // Namespace is not empty on Bukkit forks - getBrigadierDispatcher.get().getRoot().addChild(namespacedCommandNode); + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + LiteralCommandNode namespacedCommandNode = commandAPIHandler.namespaceNode(node, namespace); - return commandNode; + // Register nodes + RootCommandNode dispatcherRoot = getBrigadierDispatcher.get().getRoot(); + dispatcherRoot.addChild(node); + dispatcherRoot.addChild(namespacedCommandNode); + + // Track registered command nodes for reloads + registeredNodes.addChild(node); + registeredNodes.addChild(namespacedCommandNode); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index cef9833985..5230acb234 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -1,7 +1,6 @@ package dev.jorel.commandapi; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; @@ -29,9 +28,12 @@ public class SpigotCommandRegistration extends CommandRegistrationStrate private final Function, Command> wrapToVanillaCommandWrapper; private final Predicate> isBukkitCommandWrapper; - // Namespaces + // We need to fix permissions and namespaces on Bukkit + // Bukkit does a bunch of mucking about moving nodes between Brigadier CommandDispatchers and its own CommandMap, + // and these variables help us set that all straight private final Set namespacesToFix = new HashSet<>(); private RootCommandNode minecraftCommandNamespaces = new RootCommandNode<>(); + private final TreeMap permissionsToFix = new TreeMap<>(); // Reflection private final SafeVarHandle> commandMapKnownCommands; @@ -102,7 +104,7 @@ private String unpackInternalPermissionNodeString(CommandPermission perm) { return optionalPerm.get(); } else { throw new IllegalStateException("Invalid permission detected: " + perm + - "! This should never happen - if you're seeing this message, please" + + "! This should never happen - if you're seeing this message, please " + "contact the developers of the CommandAPI, we'd love to know how you managed to get this error!"); } } @@ -152,9 +154,6 @@ private void fixNamespaces() { * Makes permission checks more "Bukkit" like and less "Vanilla Minecraft" like */ private void fixPermissions() { - // Get the command map to find registered commands - final Map permissionsToFix = CommandAPIHandler.getInstance().registeredPermissions; - if (!permissionsToFix.isEmpty()) { CommandAPI.logInfo("Linking permissions to commands:"); @@ -184,7 +183,11 @@ private void fixPermissions() { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { + // Using registeredCommands.get(0) as representation for most command features. + // This is fine, because the only difference between the commands in the list is their argument strings. + RegisteredCommand commonCommandInformation = registeredCommands.get(0); + if (!CommandAPI.canRegister()) { // Usually, when registering commands during server startup, we can just put our commands into the // `net.minecraft.server.MinecraftServer#vanillaCommandDispatcher` and leave it. As the server finishes setup, @@ -198,8 +201,8 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal RootCommandNode root = getResourcesDispatcher.get().getRoot(); String name = resultantNode.getLiteral(); - String namespace = registeredCommand.namespace(); - String permNode = unpackInternalPermissionNodeString(registeredCommand.permission()); + String namespace = commonCommandInformation.namespace(); + String permNode = unpackInternalPermissionNodeString(commonCommandInformation.permission()); registerCommand(knownCommands, root, name, permNode, namespace, resultantNode); @@ -224,10 +227,24 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal } minecraftCommandNamespaces = new RootCommandNode<>(); } + } else { + CommandPermission permission = commonCommandInformation.permission(); + + // Since the VanillaCommandWrappers aren't created yet, we need to remember to + // fix those permissions once the server is enabled. Using `putIfAbsent` to + // default to the first permission associated with this command. + String commandName = commonCommandInformation.commandName().toLowerCase(); + permissionsToFix.putIfAbsent(commandName, permission); + + // Do the same for the namespaced version of the command (which is never empty on Bukkit forks) + String namespace = commonCommandInformation.namespace().toLowerCase(); + permissionsToFix.putIfAbsent(namespace + ":" + commandName, permission); } } private void registerCommand(Map knownCommands, RootCommandNode root, String name, String permNode, String namespace, LiteralCommandNode resultantNode) { + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + // Wrapping Brigadier nodes into VanillaCommandWrappers and putting them in the CommandMap usually happens // in `CraftServer#setVanillaCommands` Command command = wrapToVanillaCommandWrapper.apply(resultantNode); @@ -240,7 +257,7 @@ private void registerCommand(Map knownCommands, RootCommandNode root.addChild(resultantNode); // Handle namespace - LiteralCommandNode namespacedNode = CommandAPIHandler.getInstance().namespaceNode(resultantNode, namespace); + LiteralCommandNode namespacedNode = commandAPIHandler.namespaceNode(resultantNode, namespace); if (namespace.equals("minecraft")) { // The minecraft namespace version should be registered as a straight alias of the original command, since // the `minecraft:name` node does not exist in the Brigadier dispatcher, which is referenced by @@ -259,10 +276,10 @@ private void registerCommand(Map knownCommands, RootCommandNode } @Override - public LiteralCommandNode registerCommandNode(LiteralArgumentBuilder node, String namespace) { + public void registerCommandNode(LiteralCommandNode node, String namespace) { + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); RootCommandNode rootNode = brigadierDispatcher.getRoot(); - LiteralCommandNode builtNode = node.build(); String name = node.getLiteral(); if (namespace.equals("minecraft")) { if (namespacesToFix.contains("minecraft:" + name)) { @@ -270,7 +287,7 @@ public LiteralCommandNode registerCommandNode(LiteralArgumentBuilder registerCommandNode(LiteralArgumentBuilder commandAPIHandler = CommandAPIHandler.getInstance(); for (String namespacedCommand : namespacedCommands) { // We'll remove these commands later when fixNamespaces is called if (!namespacesToFix.add("minecraft:" + namespacedCommand)) { @@ -304,7 +320,7 @@ private void fillNamespacesToFix(String... namespacedCommands) { // We'll keep track of everything that should be `minecraft:command` in // `minecraftCommandNamespaces` and fix this later in `#fixNamespaces` // TODO: Ideally, we should be working without this cast to LiteralCommandNode. I don't know if this can fail - minecraftCommandNamespaces.addChild(CommandAPIHandler.getInstance().namespaceNode((LiteralCommandNode) currentNode, "minecraft")); + minecraftCommandNamespaces.addChild(commandAPIHandler.namespaceNode((LiteralCommandNode) currentNode, "minecraft")); } } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/EntitySelectorArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/EntitySelectorArgument.java index 3e01bc0064..3b9a918914 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/EntitySelectorArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/EntitySelectorArgument.java @@ -47,7 +47,7 @@ private EntitySelectorArgument() { * * @apiNote Returns an {@link Entity} object */ - public static class OneEntity extends Argument { + public static class OneEntity extends Argument implements FlattenableArgument { /** * An argument that represents a single entity @@ -73,7 +73,7 @@ public Entity parseArgument(CommandContext getEntityNames(Object argument) { + public List flatten(Object argument) { return List.of(((Entity) argument).getName()); } @@ -84,7 +84,7 @@ public List getEntityNames(Object argument) { * * @apiNote Returns a {@link Player} object */ - public static class OnePlayer extends Argument { + public static class OnePlayer extends Argument implements FlattenableArgument { /** * An argument that represents a single player @@ -110,7 +110,7 @@ public Player parseArgument(CommandContext getEntityNames(Object argument) { + public List flatten(Object argument) { return List.of(((Player) argument).getName()); } @@ -122,7 +122,7 @@ public List getEntityNames(Object argument) { * @apiNote Returns a {@link Collection}{@code <}{@link Entity}{@code >} object */ @SuppressWarnings("rawtypes") - public static class ManyEntities extends Argument { + public static class ManyEntities extends Argument implements FlattenableArgument { private final boolean allowEmpty; @@ -162,7 +162,7 @@ public Collection parseArgument(CommandContext getEntityNames(Object argument) { + public List flatten(Object argument) { List entityNames = new ArrayList<>(); for (Entity entity : (List) argument) { entityNames.add(entity.getName()); @@ -178,7 +178,7 @@ public List getEntityNames(Object argument) { * @apiNote Returns a {@link Collection}{@code <}{@link Player}{@code >} object */ @SuppressWarnings("rawtypes") - public static class ManyPlayers extends Argument { + public static class ManyPlayers extends Argument implements FlattenableArgument { private final boolean allowEmpty; @@ -218,7 +218,7 @@ public Collection parseArgument(CommandContext getEntityNames(Object argument) { + public List flatten(Object argument) { List playerNames = new ArrayList<>(); for (Player entity : (List) argument) { playerNames.add(entity.getName()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlattenableArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlattenableArgument.java new file mode 100644 index 0000000000..55b2051d23 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlattenableArgument.java @@ -0,0 +1,15 @@ +package dev.jorel.commandapi.arguments; + +import java.util.List; + +/** + * An interface indicating that the raw input for an argument can be flattened into a collection of Strings. + * This is intended to be used for entity selectors in Converted commands. + */ +public interface FlattenableArgument { + /** + * @param parsedInput The original CommandAPI parse result for this argument + * @return The flattened version of the argument input + */ + List flatten(Object parsedInput); +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index b08c4301e2..3b53c6e397 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -20,17 +20,21 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; +import org.bukkit.command.CommandSender; + +import java.util.List; /** * A pseudo-argument representing a single literal string * * @since 1.3 */ -public class LiteralArgument extends Argument implements Literal> { +public class LiteralArgument extends Argument implements Literal, CommandSender> { private final String literal; @@ -122,11 +126,6 @@ public static LiteralArgument literal(String nodeName, final String literal) { return new LiteralArgument(nodeName, literal); } - /** - * Returns the literal string represented by this argument - * - * @return the literal string represented by this argument - */ @Override public String getLiteral() { return literal; @@ -146,4 +145,20 @@ public String getHelpString() { public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { return literal; } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Literal interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the Literal interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { + Literal.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + } + + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { + return Literal.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index eeea5b5ec1..d89dad81d7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -20,10 +20,15 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandAPIExecutor; +import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; +import org.bukkit.command.CommandSender; import java.util.List; @@ -32,7 +37,7 @@ * * @since 4.1 */ -public class MultiLiteralArgument extends Argument implements MultiLiteral> { +public class MultiLiteralArgument extends Argument implements MultiLiteral, CommandSender> { private final String[] literals; @@ -80,10 +85,6 @@ public Class getPrimitiveType() { return String.class; } - /** - * Returns the literals that are present in this argument - * @return the literals that are present in this argument - */ @Override public String[] getLiterals() { return literals; @@ -96,6 +97,32 @@ public CommandAPIArgumentType getArgumentType() { @Override public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { - throw new IllegalStateException("Cannot parse MultiLiteralArgument"); + return cmdCtx.getArgument(key, String.class); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MultiLiteral interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the MultiLiteral interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { + MultiLiteral.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + } + + @Override + public void appendToCommandPaths(List> argumentStrings) { + MultiLiteral.super.appendToCommandPaths(argumentStrings); + } + + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { + return MultiLiteral.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + } + + @Override + public CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { + return MultiLiteral.super.linkNode(previousNode, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java index 9fe49d7c42..5473666305 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java @@ -118,7 +118,15 @@ public void setUp() { // Set up a Vanilla command vanillaResults = Mut.of(); new CommandAPICommand("test") - .withAliases("minecraft:test") + .withArguments(new StringArgument("string")) + .executes((sender, args) -> { + vanillaResults.set(args.getUnchecked(0)); + }) + .register(); + // Pretend the command exists in a namespace version + // Namespaces usually only exist in the Bukkit CommandMap, but CommandAPIBukkit can + // check for and remove namespaces, so we'll test it + new CommandAPICommand("minecraft:test") .withArguments(new StringArgument("string")) .executes((sender, args) -> { vanillaResults.set(args.getUnchecked(0)); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java index 587faa4f95..86c61932db 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java @@ -97,29 +97,15 @@ void testOnEnableRegisterAndUnregisterCommand() { }, "alias1": { "type": "literal", - "children": { - "argument": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } + "redirect": [ + "command" + ] }, "alias2": { "type": "literal", - "children": { - "argument": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } + "redirect": [ + "command" + ] } } }""", getDispatcherString()); @@ -195,35 +181,21 @@ void testOnEnableRegisterAndUnregisterCommand() { Mockito.verify(updateCommandsPlayer, Mockito.times(2)).updateCommands(); // Make sure main command was removed from tree + // Note: The redirects for alias1 and alias2 are no longer listed. This is expected behavior. + // The redirect entry is supposed to point to where the target node is located in the dispatcher. + // Since the main node "command" doesn't exist anymore, the json serializer can't generate a path + // to the target node, and so it just doesn't add anything. + // While the "command" node is no longer in the tree, the alias nodes still have a reference to it + // in their redirect modifier, so they still function perfectly fine as commands. assertEquals(""" { "type": "root", "children": { "alias1": { - "type": "literal", - "children": { - "argument": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } + "type": "literal" }, "alias2": { - "type": "literal", - "children": { - "argument": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } + "type": "literal" } } }""", getDispatcherString()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java index 16b6f7e71f..8c8759526d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java @@ -12,6 +12,7 @@ import org.bukkit.inventory.ItemStack; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.util.List; @@ -389,6 +390,11 @@ void executionTestWithMultiLiteralArgumentNodeName() { assertNoMoreResults(results); } + // TODO: This test currently fails because MultiLiteralArguments are broken + // See https://github.com/Mojang/brigadier/issues/137 + // I hope this is a Brigadier bug, because otherwise the new command build system needs to be rewritten + // Also, it just wouldn't be as cool if MultiLiteralArguments couldn't use redirects + @Disabled @Test void executionTestWithMultipleMultiLiteralArguments() { Mut results = Mut.of(); diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index 75b8d61691..ebadd97cc4 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java @@ -17,20 +17,6 @@ public CommandAPICommand(String commandName) { super(commandName); } - /** - * Creates a new Command builder - * - * @param metaData The metadata of the command to create - */ - protected CommandAPICommand(CommandMetaData metaData) { - super(metaData); - } - - @Override - protected CommandAPICommand newConcreteCommandAPICommand(CommandMetaData metaData) { - return new CommandAPICommand(metaData); - } - /** * Registers the command with a given namespace * diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index f07e074483..5aa38da077 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -6,7 +6,6 @@ import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; @@ -98,11 +97,6 @@ public void onDisable() { // Nothing to do } - @Override - public void registerPermission(String string) { - // Unsurprisingly, Velocity doesn't have a dumb permission system! - } - @Override public void unregister(String commandName, boolean unregisterNamespaces) { commandManager.unregister(commandName); @@ -210,18 +204,21 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { // Nothing to do } @Override - public LiteralCommandNode registerCommandNode(LiteralArgumentBuilder node, String namespace) { - LiteralCommandNode builtNode = getBrigadierDispatcher().register(node); + public void registerCommandNode(LiteralCommandNode node, String namespace) { + // Register the main node + getBrigadierDispatcher().getRoot().addChild(node); + + // Register the namespaced node if it is not empty if (!namespace.isEmpty()) { - getBrigadierDispatcher().getRoot().addChild(CommandAPIHandler.getInstance().namespaceNode(builtNode, namespace)); + getBrigadierDispatcher().getRoot().addChild( + CommandAPIHandler., CommandSource, CommandSource>getInstance().namespaceNode(node, namespace) + ); } - // We're done. The command already is registered - return builtNode; } @Override @@ -243,9 +240,4 @@ public Argument newConcreteMultiLiteralArgument(String nodeName, String[ public Argument newConcreteLiteralArgument(String nodeName, String literal) { return new LiteralArgument(nodeName, literal); } - - @Override - public AbstractCommandAPICommand, CommandSource> newConcreteCommandAPICommand(CommandMetaData meta) { - return new CommandAPICommand(meta); - } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index ae8e470016..e6051086bd 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -20,15 +20,19 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.velocitypowered.api.command.CommandSource; import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; +import java.util.List; + /** * A pseudo-argument representing a single literal string */ -public class LiteralArgument extends Argument implements Literal> { +public class LiteralArgument extends Argument implements Literal, CommandSource> { private final String literal; @@ -118,11 +122,6 @@ public Class getPrimitiveType() { return String.class; } - /** - * Returns the literal string represented by this argument - * - * @return the literal string represented by this argument - */ @Override public String getLiteral() { return literal; @@ -142,4 +141,20 @@ public String getHelpString() { public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { return literal; } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Literal interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the Literal interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { + super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + } + + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { + return Literal.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index ee7fca3572..0bf1fa01cb 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -20,8 +20,13 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.CommandNode; +import com.velocitypowered.api.command.CommandSource; +import dev.jorel.commandapi.CommandAPIExecutor; +import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; @@ -30,7 +35,7 @@ /** * An argument that represents multiple LiteralArguments */ -public class MultiLiteralArgument extends Argument implements MultiLiteral> { +public class MultiLiteralArgument extends Argument implements MultiLiteral, CommandSource> { private final String[] literals; @@ -78,10 +83,6 @@ public Class getPrimitiveType() { return String.class; } - /** - * Returns the literals that are present in this argument - * @return the literals that are present in this argument - */ @Override public String[] getLiterals() { return literals; @@ -94,7 +95,32 @@ public CommandAPIArgumentType getArgumentType() { @Override public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { - throw new IllegalStateException("Cannot parse MultiLiteralArgument"); + return cmdCtx.getArgument(key, String.class); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MultiLiteral interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the MultiLiteral interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { + super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + } + + @Override + public void appendToCommandPaths(List> argumentStrings) { + MultiLiteral.super.appendToCommandPaths(argumentStrings); } + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { + return MultiLiteral.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + } + + @Override + public CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { + return MultiLiteral.super.linkNode(previousNode, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + } } From a321c9f36ea987ab0f919482dfc37f0da4c636b9 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Sat, 2 Sep 2023 17:46:44 -0400 Subject: [PATCH 02/42] Disable broken test: `OptionalArgumentTests.kt#executionTestWithCommandTreeAndOptionalArgumentMethod` TODO: Figure out what to do about this --- .../test/dsltests/OptionalArgumentTests.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt index 99a562ee75..064dc82905 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt @@ -9,6 +9,7 @@ import dev.jorel.commandapi.test.TestBase import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -71,6 +72,27 @@ class OptionalArgumentTests: TestBase() { } } + // TODO: This test currently fails - The optionality of an argument in a CommandTree is currently ignored + // Note: I'm not sure if optional arguments in command trees make sense + // For example, consider this CommandTree (excuse my Java) + // new CommandTree("command") + // .then(new StringArgument("string").setOptional(true) + // .then(new LiteralArgument("a").executes((sender, args) -> { + // sender.sendMessage("You ran the A path"); + // })) + // .then(new LiteralArgument("b").executes((sender, args) -> { + // sender.sendMessage("You ran the B path"); + // })) + // ) + // .register(); + // The `string` argument is optional, which theoretically defines the following commands: + // /command a + // /command b + // /command + // The problem is: What is `/command` supposed to execute? `A path`, `B path`, something else? This works fine for + // a CommandAPICommand since there can only be one executor defined, but I don't think anything makes sense for a + // CommandTree. + @Disabled @Test fun executionTestWithCommandTreeAndOptionalArgumentMethod() { val results: Mut = Mut.of() From 96e73382e7751a9bfc5efb0f20b2b75b66880f15 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 08:58:53 -0400 Subject: [PATCH 03/42] Initial rewrite of optional Arguments Part of `dev/command-build-rewrite` Solves the problem with https://github.com/JorelAli/CommandAPI/commit/1803e4eb2b280bb5ac15258a161a23ab857c9027 where optional arguments in CommandTrees didn't work This commit implements option 3 from https://github.com/JorelAli/CommandAPI/commit/193f237f9ca3cd65c659a695383ed3b9c388a111#r126326791 Basically, Arguments can no longer be optional by themselves. Instead, they are optional if they exist in a special list. This restricts the freedom of optional arguments in CommandTrees to avoid ambiguous cases, while CommandAPICommands work essentially the same via the `withOptionalArguments` method. Changes: AbstractArgument - Removed `boolean isOptional` - Removed method `setOptional` AbstractCommandAPICommand, AbstractCommandTree, AbstractArgumentTree - Added `List optionalArguments` - Tweaked command-building logic to include `optionalArguments` list ExecutableCommand - Merged some registration logic shared between CommandAPICommand and CommandTree Kotlin DSL - `optional` boolean now chooses between calling `withArguments` or `withOptionalArgument`, instead of calling `setOptional` Tests - Added tests for making sure `RegisteredCommand` information is correct - Added tests for optional Arguments in CommandTree - Re-enabled test disabled by https://github.com/JorelAli/CommandAPI/commit/193f237f9ca3cd65c659a695383ed3b9c388a111 --- .../commandapi/AbstractArgumentTree.java | 188 ++++++- .../commandapi/AbstractCommandAPICommand.java | 231 ++++---- .../jorel/commandapi/AbstractCommandTree.java | 172 ++++-- .../jorel/commandapi/ExecutableCommand.java | 55 +- .../arguments/AbstractArgument.java | 63 ++- .../commandapi/arguments/MultiLiteral.java | 2 + .../kotlindsl/CommandAPICommandDSL.kt | 122 ++--- .../commandapi/kotlindsl/CommandTreeDSL.kt | 246 ++++----- .../kotlindsl/CommandAPICommandDSL.kt | 30 +- .../commandapi/kotlindsl/CommandTreeDSL.kt | 62 ++- .../test/dsltests/OptionalArgumentTests.kt | 28 +- ...mmandAPICommandOptionalArgumentTests.java} | 30 +- ...mmandAPICommandRegisteredCommandTests.java | 494 ++++++++++++++++++ .../CommandTreeOptionalArgumentTests.java | 233 +++++++++ .../CommandTreeRegisteredCommandTests.java | 415 +++++++++++++++ 15 files changed, 1898 insertions(+), 473 deletions(-) rename commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/{arguments/OptionalArgumentTests.java => CommandAPICommandOptionalArgumentTests.java} (92%) create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 155394e04d..953b149378 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -7,6 +7,7 @@ import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -25,8 +26,9 @@ public abstract class AbstractArgumentTree extends Executable { - final List> arguments = new ArrayList<>(); - final Argument argument; + private final Argument argument; + private List> branches = new ArrayList<>(); + private List optionalArguments = new ArrayList<>(); /** * Instantiates an {@link AbstractArgumentTree}. This can only be called if the class @@ -47,12 +49,16 @@ protected AbstractArgumentTree() { * @param argument the argument to use as the underlying argument for this * argument tree */ - protected AbstractArgumentTree(final Argument argument) { + public AbstractArgumentTree(final Argument argument) { this.argument = argument; // Copy the executor in case any executions were defined on the argument this.executor = argument.executor; } + ///////////////////// + // Builder methods // + ///////////////////// + /** * Create a child branch on this node * @@ -60,51 +66,109 @@ protected AbstractArgumentTree(final Argument argument) { * @return this tree node */ public Impl then(final AbstractArgumentTree tree) { - this.arguments.add(tree); + this.branches.add(tree); return instance(); } /** - * Builds the Brigadier {@link CommandNode} structure for this argument tree. + * Adds optional arguments onto this node. * - * @param previousNode The {@link CommandNode} to add this argument tree onto. - * @param previousArguments A List of CommandAPI arguments that came before this argument tree. - * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. - * @param The Brigadier Source object for running commands. + * @param optionalArguments A List of Arguments to add as optional arguments at this node. + * @return this command builder */ - public void buildBrigadierNode(CommandNode previousNode, List previousArguments, List previousNonLiteralArgumentNames) { - // Check preconditions - if (argument instanceof GreedyArgument && !arguments.isEmpty()) { - throw new GreedyArgumentException(previousArguments, argument, getBranchesAsList()); - } - if (!executor.hasAnyExecutors() && arguments.isEmpty()) { - throw new MissingCommandExecutorException(previousArguments, argument); - } + public Impl withOptionalArguments(List optionalArguments) { + this.optionalArguments.addAll(optionalArguments); + return instance(); + } - // Create node for this argument - CommandNode rootNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); + /** + * Adds optional arguments onto this node. + * + * @param optionalArguments The Arguments to add as optional arguments at this node. + * @return this command builder + */ + @SafeVarargs + public final Impl withOptionalArguments(Argument... optionalArguments) { + return this.withOptionalArguments(Arrays.asList(optionalArguments)); + } - // Add our branches as children to the node - for (AbstractArgumentTree child : arguments) { - // We need a new list for each branch of the tree - List newPreviousArguments = new ArrayList<>(previousArguments); - List newPreviousArgumentNames = new ArrayList<>(previousNonLiteralArgumentNames); + ///////////////////////// + // Getters and setters // + ///////////////////////// - child.buildBrigadierNode(rootNode, newPreviousArguments, newPreviousArgumentNames); - } + /** + * @return The child branches added to this tree by {@link #then(AbstractArgumentTree)}. + */ + public List> getArguments() { + return branches; + } + + /** + * Sets the child branches that this node has + * + * @param arguments A new list of branches for this node + */ + public void setArguments(List> arguments) { + this.branches = arguments; + } + + /** + * @return The optional arguments added to this tree by {@link #withOptionalArguments(List)}. + */ + public List getOptionalArguments() { + return optionalArguments; } + /** + * Sets the optional arguments that this node has + * + * @param optionalArguments A new list of optional arguments for this node + */ + public void setOptionalArguments(List optionalArguments) { + this.optionalArguments = optionalArguments; + } + + ////////////////// + // Registration // + ////////////////// + /** * @return A list of paths that represent the possible branches of this argument tree as Strings, starting with the * base argument held by this tree. */ public List> getBranchesAsStrings() { + List> argumentStrings = new ArrayList<>(); + + // Create paths for the argument at this node List> baseArgumentPaths = new ArrayList<>(); baseArgumentPaths.add(new ArrayList<>()); argument.appendToCommandPaths(baseArgumentPaths); - List> argumentStrings = new ArrayList<>(); - for (AbstractArgumentTree child : arguments) { + // Build optional argument paths, if it is executable + if (executor.hasAnyExecutors()) { + // Just the argument is a valid path + List slicePositions = new ArrayList<>(); + // Note: Assumption that all paths are the same length + // E.g. One `Argument argument : optionalArguments` won't expand to [arg1] and [arg1, arg2] + // Either ([arg1] and [arg2]) or ([arg1, part2] and [arg2, part2]) etc. + slicePositions.add(baseArgumentPaths.get(0).size()); + + // Each optional argument is a potential stopping point + for (Argument argument : optionalArguments) { + argument.appendToCommandPaths(baseArgumentPaths); + slicePositions.add(baseArgumentPaths.get(0).size()); + } + + // Return each path as sublists of the main path + for (List path : baseArgumentPaths) { + for (int slicePos : slicePositions) { + argumentStrings.add(path.subList(0, slicePos)); + } + } + } + + // Add paths for the branches + for (AbstractArgumentTree child : branches) { for (List subArgs : child.getBranchesAsStrings()) { for (List basePath : baseArgumentPaths) { List mergedPaths = new ArrayList<>(); @@ -118,12 +182,56 @@ public List> getBranchesAsStrings() { return argumentStrings; } + /** + * Builds the Brigadier {@link CommandNode} structure for this argument tree. + * + * @param previousNode The {@link CommandNode} to add this argument tree onto. + * @param previousArguments A List of CommandAPI arguments that came before this argument tree. + * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. + * @param The Brigadier Source object for running commands. + */ + public void buildBrigadierNode(CommandNode previousNode, List previousArguments, List previousNonLiteralArgumentNames) { + // Check preconditions + if (argument instanceof GreedyArgument && (!branches.isEmpty() || !optionalArguments.isEmpty())) { + // Argument is followed by at least some arguments + throw new GreedyArgumentException(previousArguments, argument, getBranchesAsList()); + } + if (!executor.hasAnyExecutors() && (branches.isEmpty() || !optionalArguments.isEmpty())) { + // If we don't have any executors then: + // No branches is bad because this path can't be run at all + // Having arguments is bad because developer intended this path to be executable with arguments + throw new MissingCommandExecutorException(previousArguments, argument); + } + + // Create node for this argument + CommandNode rootNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); + + // Add our branches as children to the node + for (AbstractArgumentTree child : branches) { + // We need a new list for each branch of the tree + List newPreviousArguments = new ArrayList<>(previousArguments); + List newPreviousArgumentNames = new ArrayList<>(previousNonLiteralArgumentNames); + + child.buildBrigadierNode(rootNode, newPreviousArguments, newPreviousArgumentNames); + } + + // Build optional argument paths + if (!optionalArguments.isEmpty()) { + previousNode = rootNode; + for (Argument argument : optionalArguments) { + // All optional arguments are executable + previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); + } + } + } + /** * @return A list of paths that represent the possible branches of this argument tree as Argument objects. */ protected List> getBranchesAsList() { List> branchesList = new ArrayList<>(); - for (AbstractArgumentTree branch : arguments) { + // Add branches + for (AbstractArgumentTree branch : branches) { for (List subBranchList : branch.getBranchesAsList()) { List newBranchList = new ArrayList<>(); newBranchList.add(branch.argument); @@ -131,6 +239,28 @@ protected List> getBranchesAsList() { branchesList.add(newBranchList); } } + // Add optional arguments + if (!optionalArguments.isEmpty()) { + List> optionalPaths = new ArrayList<>(); + List slicePositions = new ArrayList<>(); + // Note: Assumption that all paths are the same length + // E.g. One `Argument argument : optionalArguments` won't expand to [arg1] and [arg1, arg2] + // Either ([arg1] and [arg2]) or ([arg1, part2] and [arg2, part2]) etc. + + // Each optional argument is a potential stopping point + for (Argument argument : optionalArguments) { + argument.unpackCombinedArguments(optionalPaths); + slicePositions.add(optionalPaths.get(0).size()); + } + + // Return each path as sublists of the main path + for (List path : optionalPaths) { + for (int slicePos : slicePositions) { + branchesList.add(path.subList(0, slicePos)); + } + } + } + return branchesList; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 08f1a597f8..514c8f87a7 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -20,7 +20,6 @@ *******************************************************************************/ package dev.jorel.commandapi; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; @@ -50,8 +49,9 @@ public abstract class AbstractCommandAPICommand extends ExecutableCommand { - protected List arguments = new ArrayList<>(); - protected List subcommands = new ArrayList<>(); + private List requiredArguments = new ArrayList<>(); + private List optionalArguments = new ArrayList<>(); + private List subcommands = new ArrayList<>(); /** * Creates a new command builder @@ -62,15 +62,27 @@ protected AbstractCommandAPICommand(String commandName) { super(commandName); } + ///////////////////// + // Builder methods // + ///////////////////// + /** * Appends the arguments to the current command builder * * @param args A List that represents the arguments that this * command can accept * @return this command builder + * @throws OptionalArgumentException If this method is used to add required arguments after optional arguments. */ public Impl withArguments(List args) { - this.arguments.addAll(args); + if (!args.isEmpty() && !this.optionalArguments.isEmpty()) { + // Tried to add required arguments after optional arguments + List previousArguments = new ArrayList<>(); + previousArguments.addAll(this.requiredArguments); + previousArguments.addAll(this.optionalArguments); + throw new OptionalArgumentException(name, previousArguments, args.get(0)); + } + this.requiredArguments.addAll(args); return instance(); } @@ -79,45 +91,34 @@ public Impl withArguments(List args) { * * @param args Arguments that this command can accept * @return this command builder + * @throws OptionalArgumentException If this method is used to add required arguments after optional arguments. */ @SafeVarargs public final Impl withArguments(Argument... args) { - this.arguments.addAll(Arrays.asList(args)); - return instance(); + return this.withArguments(Arrays.asList(args)); } /** * Appends the optional arguments to the current command builder. - *

- * This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional * * @param args A List that represents the arguments that this * command can accept * @return this command builder */ public Impl withOptionalArguments(List args) { - for (Argument argument : args) { - argument.setOptional(true); - this.arguments.add(argument); - } + this.optionalArguments.addAll(args); return instance(); } /** * Appends the optional arguments to the current command builder. - *

- * This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional * * @param args Arguments that this command can accept * @return this command builder */ @SafeVarargs public final Impl withOptionalArguments(Argument... args) { - for (Argument argument : args) { - argument.setOptional(true); - this.arguments.add(argument); - } - return instance(); + return this.withOptionalArguments(Arrays.asList(args)); } /** @@ -131,6 +132,17 @@ public Impl withSubcommand(Impl subcommand) { return instance(); } + /** + * Adds subcommands to this command builder + * + * @param subcommands the subcommands to add as children of this command + * @return this command builder + */ + public Impl withSubcommands(List subcommands) { + this.subcommands.addAll(subcommands); + return instance(); + } + /** * Adds subcommands to this command builder * @@ -139,17 +151,20 @@ public Impl withSubcommand(Impl subcommand) { */ @SafeVarargs public final Impl withSubcommands(Impl... subcommands) { - this.subcommands.addAll(Arrays.asList(subcommands)); - return instance(); + return this.withSubcommands(Arrays.asList(subcommands)); } + ///////////////////////// + // Getters and setters // + ///////////////////////// + /** * Returns the list of arguments that this command has * * @return the list of arguments that this command has */ public List getArguments() { - return arguments; + return requiredArguments; } /** @@ -158,7 +173,32 @@ public List getArguments() { * @param args the arguments that this command has */ public void setArguments(List args) { - this.arguments = args; + this.requiredArguments = args; + } + + /** + * Returns the list of optional arguments that this command has + * + * @return the list of optional arguments that this command has + */ + public List getOptionalArguments() { + return optionalArguments; + } + + /** + * Sets the optional arguments that this command has + * + * @param args the optional arguments that this command has + */ + public void setOptionalArguments(List args) { + this.optionalArguments = args; + } + + /** + * @return True if this command has any required or optional arguments. + */ + public boolean hasAnyArguments() { + return !requiredArguments.isEmpty() || !optionalArguments.isEmpty(); } /** @@ -179,44 +219,62 @@ public void setSubcommands(List subcommands) { this.subcommands = subcommands; } + ////////////////// + // Registration // + ////////////////// + @Override public List> getArgumentsAsStrings() { // Return an empty list if we have no arguments - if (arguments.isEmpty() && subcommands.isEmpty()) { - // Note: the inner list needs to be mutable in the case that this is a subcommand/sub-subcommand... - // In that case, the parent subcommands will be built backwards inside this list - return List.of(new ArrayList<>()); + if (subcommands.isEmpty() && !hasAnyArguments()) { + return List.of(List.of()); } List> argumentStrings = new ArrayList<>(); - if (!arguments.isEmpty()) { - // Build main path - List> currentPaths = new ArrayList<>(); - currentPaths.add(new ArrayList<>()); - boolean foundOptional = arguments.get(0).isOptional(); - for (int i = 0; i < arguments.size(); i++) { - Argument argument = arguments.get(i); - argument.appendToCommandPaths(currentPaths); - - // Non-optional argument after an optional argument - // This state is invalid, so we cannot continue - boolean nextIsOptional = i == arguments.size() - 1 || arguments.get(i + 1).isOptional(); - if (foundOptional && !nextIsOptional) - throw new OptionalArgumentException(name, arguments.subList(0, i), argument); - foundOptional = nextIsOptional; - - // If this is the last argument, or the next argument is optional, then the current path should be included by itself - if (nextIsOptional) argumentStrings.addAll(currentPaths); + // Build main path, if it is executable + if (executor.hasAnyExecutors()) { + // Required arguments represent one path + List> mainPath = new ArrayList<>(); + mainPath.add(new ArrayList<>()); + for (Argument argument : requiredArguments) { + argument.appendToCommandPaths(mainPath); + } + + List slicePositions = new ArrayList<>(); + // Note: Assumption that all paths are the same length + slicePositions.add(mainPath.get(0).size()); + + // Each optional argument is a potential stopping point + for (Argument argument : optionalArguments) { + argument.appendToCommandPaths(mainPath); + slicePositions.add(mainPath.get(0).size()); + } + + // Return each path as sublists of the main path + for (List path : mainPath) { + for (int slicePos : slicePositions) { + argumentStrings.add(path.subList(0, slicePos)); + } } } - // Add subcommands + // Build subcommand paths for (Impl subCommand : subcommands) { - String subCommandArgument = subCommand.name + ":LiteralArgument"; + String[] subCommandArguments = new String[subCommand.aliases.length + 1]; + subCommandArguments[0] = subCommand.name; + System.arraycopy(subCommand.aliases, 0, subCommandArguments, 1, subCommand.aliases.length); + for (int i = 0; i < subCommandArguments.length; i++) { + subCommandArguments[i] += ":LiteralArgument"; + } + for (List subArgs : subCommand.getArgumentsAsStrings()) { - subArgs.add(0, subCommandArgument); - argumentStrings.add(subArgs); + for (String subCommandArgument : subCommandArguments) { + List newPath = new ArrayList<>(); + newPath.add(subCommandArgument); + newPath.addAll(subArgs); + argumentStrings.add(newPath); + } } } @@ -224,33 +282,26 @@ public List> getArgumentsAsStrings() { } @Override - Nodes createCommandNodes() { - CommandAPIHandler handler = CommandAPIHandler.getInstance(); - - // Check preconditions - if (!executor.hasAnyExecutors() && (subcommands.isEmpty() || !arguments.isEmpty())) { + protected void checkPreconditions() { + if (!executor.hasAnyExecutors() && (subcommands.isEmpty() || hasAnyArguments())) { // If we don't have any executors then: // No subcommands is bad because this path can't be run at all // Having arguments is bad because developer intended this path to be executable with arguments throw new MissingCommandExecutorException(name); } + } - // Create node - LiteralArgumentBuilder rootBuilder = LiteralArgumentBuilder.literal(name); - - // Add permission and requirements - rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); - - // Add our executor if this is the last node, or the next argument is optional - if ((arguments.isEmpty() || arguments.get(0).isOptional()) && executor.hasAnyExecutors()) { - rootBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); - } + @Override + protected boolean isRootExecutable() { + return executor.hasAnyExecutors() && requiredArguments.isEmpty(); + } - // Register main node - LiteralCommandNode rootNode = rootBuilder.build(); + @Override + protected void createArgumentNodes(LiteralCommandNode rootNode) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); // Create arguments - if (!arguments.isEmpty()) { + if (hasAnyArguments()) { CommandNode previousNode = rootNode; List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); @@ -265,19 +316,18 @@ Nodes createCommandNodes() { previousArguments.add(commandNames); - boolean foundOptional = arguments.get(0).isOptional(); - for (int i = 0; i < arguments.size(); i++) { - Argument argument = arguments.get(i); - - boolean nextIsOptional = i == arguments.size() - 1 || arguments.get(i + 1).isOptional(); - // Non-optional argument after an optional argument - // This state is invalid, so we cannot continue - if (foundOptional && !nextIsOptional) throw new OptionalArgumentException(previousArguments, argument); - foundOptional = nextIsOptional; - + // Add required arguments + for (int i = 0; i < requiredArguments.size(); i++) { + Argument argument = requiredArguments.get(i); previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousArgumentNames, - // If this is the last argument, or the next argument is optional, add the executor - nextIsOptional ? executor : null); + // Only the last required argument is executable + i == requiredArguments.size() - 1 ? executor : null); + } + + // Add optional arguments + for (Argument argument : optionalArguments) { + // All optional arguments are executable + previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousArgumentNames, executor); } // Check greedy argument constraint @@ -302,28 +352,5 @@ Nodes createCommandNodes() { rootNode.addChild(aliasNode); } } - - // Generate alias nodes - List> aliasNodes = new ArrayList<>(); - for (String alias : aliases) { - // Create node - LiteralArgumentBuilder aliasBuilder = LiteralArgumentBuilder.literal(alias); - - // Add permission and requirements - aliasBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); - - // Add our executor - if ((arguments.isEmpty() || arguments.get(0).isOptional()) && executor.hasAnyExecutors()) { - aliasBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); - } - - // Redirect to rootNode so all its arguments come after this node - aliasBuilder.redirect(rootNode); - - // Register alias node - aliasNodes.add(aliasBuilder.build()); - } - - return new Nodes<>(rootNode, aliasNodes); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index c1fbe660f2..e25e2fcb10 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -1,11 +1,12 @@ package dev.jorel.commandapi; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -24,7 +25,8 @@ public abstract class AbstractCommandTree extends ExecutableCommand { - private final List> arguments = new ArrayList<>(); + private List> branches = new ArrayList<>(); + private List optionalArguments = new ArrayList<>(); /** * Creates a main root node for a command tree with a given command name @@ -35,6 +37,10 @@ protected AbstractCommandTree(final String commandName) { super(commandName); } + ///////////////////// + // Builder methods // + ///////////////////// + /** * Create a child branch on the tree * @@ -42,21 +48,105 @@ protected AbstractCommandTree(final String commandName) { * @return this root node */ public Impl then(final AbstractArgumentTree tree) { - this.arguments.add(tree); + this.branches.add(tree); + return instance(); + } + + /** + * Adds optional arguments onto this node. + * + * @param optionalArguments A List of Arguments to add as optional arguments at this node. + * @return this command builder + */ + public Impl withOptionalArguments(List optionalArguments) { + this.optionalArguments.addAll(optionalArguments); return instance(); } + /** + * Adds optional arguments onto this node. + * + * @param optionalArguments The Arguments to add as optional arguments at this node. + * @return this command builder + */ + @SafeVarargs + public final Impl withOptionalArguments(Argument... optionalArguments) { + return this.withOptionalArguments(Arrays.asList(optionalArguments)); + } + + ///////////////////////// + // Getters and setters // + ///////////////////////// + + /** + * @return The child branches added to this tree by {@link #then(AbstractArgumentTree)}. + */ public List> getArguments() { - return arguments; + return branches; + } + + /** + * Sets the child branches that this command has + * + * @param arguments A new list of branches for this command + */ + public void setArguments(List> arguments) { + this.branches = arguments; + } + + /** + * @return The optional arguments added to this tree by {@link #withOptionalArguments(List)}. + */ + public List getOptionalArguments() { + return optionalArguments; + } + + /** + * Sets the optional arguments that this command has + * + * @param optionalArguments A new list of optional arguments for this command + */ + public void setOptionalArguments(List optionalArguments) { + this.optionalArguments = optionalArguments; } + ////////////////// + // Registration // + ////////////////// + @Override public List> getArgumentsAsStrings() { - if (arguments.isEmpty()) return List.of(List.of()); + // Return an empty list if we have no arguments + if (branches.isEmpty() && optionalArguments.isEmpty()) return List.of(List.of()); List> argumentStrings = new ArrayList<>(); - argumentStrings.add(new ArrayList<>()); - for (AbstractArgumentTree argument : arguments) { + + // Build optional argument paths, if it is executable + if (executor.hasAnyExecutors()) { + List> currentPaths = new ArrayList<>(); + currentPaths.add(new ArrayList<>()); + + // Just the command is a valid path + List slicePositions = new ArrayList<>(); + // Note: Assumption that all paths are the same length + slicePositions.add(0); + + // Each optional argument is a potential stopping point + for (Argument argument : optionalArguments) { + argument.appendToCommandPaths(currentPaths); + slicePositions.add(currentPaths.get(0).size()); + } + + // Return each path as sublists of the main path + for (List path : currentPaths) { + for (int slicePos : slicePositions) { + argumentStrings.add(path.subList(0, slicePos)); + } + } + } + + // Add branching paths + for (AbstractArgumentTree argument : branches) { argumentStrings.addAll(argument.getBranchesAsStrings()); } @@ -64,29 +154,24 @@ public List> getArgumentsAsStrings() { } @Override - Nodes createCommandNodes() { - CommandAPIHandler handler = CommandAPIHandler.getInstance(); - - // Check preconditions - if (!executor.hasAnyExecutors() && arguments.isEmpty()) { + protected void checkPreconditions() { + if (!executor.hasAnyExecutors() && (branches.isEmpty() || !optionalArguments.isEmpty())) { + // If we don't have any executors then: + // No branches is bad because this path can't be run at all + // Having arguments is bad because developer intended this path to be executable with arguments throw new MissingCommandExecutorException(name); } + } - // Create node - LiteralArgumentBuilder rootBuilder = LiteralArgumentBuilder.literal(name); - - // Add permission and requirements - rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); - - // Add our executor - if (executor.hasAnyExecutors()) { - rootBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); - } + @Override + protected boolean isRootExecutable() { + return executor.hasAnyExecutors(); + } - // Register main node - LiteralCommandNode rootNode = rootBuilder.build(); + @Override + protected void createArgumentNodes(LiteralCommandNode rootNode) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); - // Add our arguments as children to the node // The previous arguments include an unlisted MultiLiteral representing the command name and aliases // This doesn't affect how the command acts, but it helps represent the command path in exceptions String[] literals = new String[aliases.length + 1]; @@ -95,36 +180,27 @@ Nodes createCommandNodes() { Argument commandNames = handler.getPlatform().newConcreteMultiLiteralArgument(name, literals); commandNames.setListed(false); - for (AbstractArgumentTree argument : arguments) { - // We need new previousArguments lists for each branch + // Build branches + for (AbstractArgumentTree argument : branches) { + // We need new previousArguments lists for each branch so they don't interfere List previousArguments = new ArrayList<>(); - List previousArgumentNames = new ArrayList<>(); + List previousNonLiteralArgumentNames = new ArrayList<>(); previousArguments.add(commandNames); - argument.buildBrigadierNode(rootNode, previousArguments, previousArgumentNames); + argument.buildBrigadierNode(rootNode, previousArguments, previousNonLiteralArgumentNames); } - // Generate alias nodes - List> aliasNodes = new ArrayList<>(); - for (String alias : aliases) { - // Create node - LiteralArgumentBuilder aliasBuilder = LiteralArgumentBuilder.literal(alias); - - // Add permission and requirements - aliasBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); + // Build optional argument paths + if (!optionalArguments.isEmpty()) { + CommandNode previousNode = rootNode; + List previousArguments = new ArrayList<>(); + List previousNonLiteralArgumentNames = new ArrayList<>(); + previousArguments.add(commandNames); - // Add our executor - if (executor.hasAnyExecutors()) { - aliasBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); + for (Argument argument : optionalArguments) { + // All optional arguments are executable + previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); } - - // Redirect to rootNode so all its arguments come after this node - aliasBuilder.redirect(rootNode); - - // Register alias node - aliasNodes.add(aliasBuilder.build()); } - - return new Nodes<>(rootNode, aliasNodes); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 464d097682..3f12dabee4 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -1,9 +1,11 @@ package dev.jorel.commandapi; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; +import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; @@ -327,8 +329,57 @@ public void register(String namespace) { ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); } - abstract Nodes createCommandNodes(); - record Nodes(LiteralCommandNode rootNode, List> aliasNodes) { } + + Nodes createCommandNodes() { + checkPreconditions(); + + LiteralCommandNode rootNode = this.createCommandNodeBuilder(name).build(); + + createArgumentNodes(rootNode); + + List> aliasNodes = createAliasNodes(rootNode); + + return new Nodes<>(rootNode, aliasNodes); + } + + protected LiteralArgumentBuilder createCommandNodeBuilder(String nodeName) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Create node + LiteralArgumentBuilder rootBuilder = LiteralArgumentBuilder.literal(nodeName); + + // Add permission and requirements + rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); + + // Add the executor + if (isRootExecutable()) { + rootBuilder.executes(handler.generateBrigadierCommand(List.of(), executor)); + } + + return rootBuilder; + } + + protected List> createAliasNodes(LiteralCommandNode rootNode) { + List> aliasNodes = new ArrayList<>(); + for (String alias : aliases) { + // Create node + LiteralArgumentBuilder aliasBuilder = createCommandNodeBuilder(alias); + + // Redirect to rootNode so all its arguments come after this node + aliasBuilder.redirect(rootNode); + + // Register alias node + aliasNodes.add(aliasBuilder.build()); + } + + return aliasNodes; + } + + protected abstract void checkPreconditions(); + + protected abstract boolean isRootExecutable(); + + protected abstract void createArgumentNodes(LiteralCommandNode rootNode); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index b1c7f3ac69..91cf0a0fec 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -273,30 +273,8 @@ public Impl setListed(boolean listed) { ///////////////// // Optionality // ///////////////// - - private boolean isOptional = false; private final List combinedArguments = new ArrayList<>(); - /** - * Returns true if this argument will be optional when executing the command this argument is included in - * - * @return true if this argument will be optional when executing the command this argument is included in - */ - public boolean isOptional() { - return isOptional; - } - - /** - * Sets whether this argument will be optional when executing the command this argument is included in - * - * @param optional if true, this argument will be optional when executing the command this argument is included in - * @return this current argument - */ - public Impl setOptional(boolean optional) { - this.isOptional = optional; - return instance(); - } - /** * Returns a list of arguments linked to this argument. * @@ -316,11 +294,22 @@ public boolean hasCombinedArguments() { } /** - * Adds combined arguments to this argument. Combined arguments are used to have required arguments after optional arguments - * by ignoring they exist until they are added to the arguments array for registration. - *

- * This method also causes permissions and requirements from this argument to be copied over to the arguments you want to combine - * this argument with. Their permissions and requirements will be ignored. + * Adds combined arguments to this argument. Combined arguments are used to have required arguments after optional + * arguments. If this argument is optional and the user includes it in their command, any arguments combined with + * this argument will be required to execute the command. + * + * @param combinedArguments The arguments to combine to this argument + * @return this current argument + */ + public Impl combineWith(List combinedArguments) { + this.combinedArguments.addAll(combinedArguments); + return instance(); + } + + /** + * Adds combined arguments to this argument. Combined arguments are used to have required arguments after optional + * arguments. If this argument is optional and the user includes it in their command, any arguments combined with + * this argument will be required to execute the command. * * @param combinedArguments The arguments to combine to this argument * @return this current argument @@ -346,8 +335,9 @@ public String toString() { /** * Adds this argument to the end of the all the current possible paths given. The added entry is built as {code nodeName:argumentClass}. + * Any arguments combined with this one are also added. * - * @param argumentStrings A list of possible paths to this argument so far. + * @param argumentStrings A list of possible paths up to this argument so far. */ public void appendToCommandPaths(List> argumentStrings) { // Create paths for this argument @@ -362,6 +352,23 @@ public void appendToCommandPaths(List> argumentStrings) { } } + /** + * Adds this argument to the end of all the current possible paths given. Any arguments combined with this one are also added. + * + * @param previousArguments A list of possible paths up to this argument so far. + */ + public void unpackCombinedArguments(List> previousArguments) { + // Add this argument + for(List path : previousArguments) { + path.add((Argument) this); + } + + // Add combined arguments + for (Argument subArgument : combinedArguments) { + subArgument.unpackCombinedArguments(previousArguments); + } + } + /** * Builds the Brigadier {@link CommandNode} structure for this argument. Note that the Brigadier node structure may * contain multiple nodes, for example if {@link #combineWith(AbstractArgument[])} was called for this argument to diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index c39115b3f2..e396b83de0 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -75,6 +75,7 @@ default void appendToCommandPaths(List> argumentStrings) { Iterator literals = Arrays.asList(getLiterals()).iterator(); String firstLiteralArgumentString = literals.next() + ":LiteralArgument"; + // Copy each path for the other literals List> newPaths = new ArrayList<>(); while (literals.hasNext()) { String literalArgumentString = literals.next() + ":LiteralArgument"; @@ -85,6 +86,7 @@ default void appendToCommandPaths(List> argumentStrings) { } } + // Add first literal to the original paths for (List path : argumentStrings) { path.add(firstLiteralArgumentString); } diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index 773ac92063..6958981ba5 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -24,107 +24,109 @@ inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): inline fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command) inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command)) +inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) { optionalArgument(argument, block) } else { argument(argument, block) } + // Integer arguments -inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(IntegerArgument(nodeName, min, max).setOptional(optional).apply(block)) -inline fun CommandAPICommand.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(IntegerRangeArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(IntegerArgument(nodeName, min, max), optional, block) +inline fun CommandAPICommand.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(IntegerRangeArgument(nodeName), optional, block) // Float arguments -inline fun CommandAPICommand.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(FloatArgument(nodeName, min, max).setOptional(optional).apply(block)) -inline fun CommandAPICommand.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(FloatRangeArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(FloatArgument(nodeName, min, max), optional, block) +inline fun CommandAPICommand.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(FloatRangeArgument(nodeName), optional, block) // Double arguments -inline fun CommandAPICommand.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(DoubleArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandAPICommand.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(DoubleArgument(nodeName, min, max), optional, block) // Long arguments -inline fun CommandAPICommand.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LongArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandAPICommand.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LongArgument(nodeName, min, max), optional, block) // Boolean argument -inline fun CommandAPICommand.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(BooleanArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(BooleanArgument(nodeName), optional, block) // String arguments -inline fun CommandAPICommand.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(StringArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(TextArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(GreedyStringArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(StringArgument(nodeName), optional, block) +inline fun CommandAPICommand.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(TextArgument(nodeName), optional, block) +inline fun CommandAPICommand.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(GreedyStringArgument(nodeName), optional, block) // Positional arguments -inline fun CommandAPICommand.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LocationArgument(nodeName, locationType, centerPosition).setOptional(optional).apply(block)) -inline fun CommandAPICommand.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(Location2DArgument(nodeName, locationType, centerPosition).setOptional(optional).apply(block)) -inline fun CommandAPICommand.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(RotationArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(AxisArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LocationArgument(nodeName, locationType, centerPosition), optional, block) +inline fun CommandAPICommand.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(Location2DArgument(nodeName, locationType, centerPosition), optional, block) +inline fun CommandAPICommand.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(RotationArgument(nodeName), optional, block) +inline fun CommandAPICommand.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(AxisArgument(nodeName), optional, block) // Chat arguments -inline fun CommandAPICommand.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ChatColorArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ChatComponentArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ChatArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(AdventureChatColorArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(AdventureChatComponentArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(AdventureChatArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ChatColorArgument(nodeName), optional, block) +inline fun CommandAPICommand.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ChatComponentArgument(nodeName), optional, block) +inline fun CommandAPICommand.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ChatArgument(nodeName), optional, block) +inline fun CommandAPICommand.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(AdventureChatColorArgument(nodeName), optional, block) +inline fun CommandAPICommand.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(AdventureChatComponentArgument(nodeName), optional, block) +inline fun CommandAPICommand.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(AdventureChatArgument(nodeName), optional, block) // Entity & Player arguments -inline fun CommandAPICommand.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(EntitySelectorArgument.OneEntity(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty).setOptional(optional).apply(block)) -inline fun CommandAPICommand.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(EntitySelectorArgument.OnePlayer(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty).setOptional(optional).apply(block)) -inline fun CommandAPICommand.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(PlayerArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(OfflinePlayerArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(EntityTypeArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(EntitySelectorArgument.OneEntity(nodeName), optional, block) +inline fun CommandAPICommand.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty), optional, block) +inline fun CommandAPICommand.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(EntitySelectorArgument.OnePlayer(nodeName), optional, block) +inline fun CommandAPICommand.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty), optional, block) +inline fun CommandAPICommand.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(PlayerArgument(nodeName), optional, block) +inline fun CommandAPICommand.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(OfflinePlayerArgument(nodeName), optional, block) +inline fun CommandAPICommand.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(EntityTypeArgument(nodeName), optional, block) // Scoreboard arguments -inline fun CommandAPICommand.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ScoreHolderArgument.Single(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ScoreHolderArgument.Multiple(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ScoreboardSlotArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ObjectiveArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ObjectiveCriteriaArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(TeamArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ScoreHolderArgument.Single(nodeName), optional, block) +inline fun CommandAPICommand.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ScoreHolderArgument.Multiple(nodeName), optional, block) +inline fun CommandAPICommand.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ScoreboardSlotArgument(nodeName), optional, block) +inline fun CommandAPICommand.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ObjectiveArgument(nodeName), optional, block) +inline fun CommandAPICommand.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ObjectiveCriteriaArgument(nodeName), optional, block) +inline fun CommandAPICommand.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(TeamArgument(nodeName), optional, block) // Miscellaneous arguments -inline fun CommandAPICommand.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(AngleArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(AdvancementArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(AngleArgument(nodeName), optional, block) +inline fun CommandAPICommand.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(AdvancementArgument(nodeName), optional, block) inline fun CommandAPICommand.biomeArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = - if (useNamespacedKey) withArguments(BiomeArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else withArguments(BiomeArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(BiomeArgument.NamespacedKey(nodeName), optional, block) else addArgument(BiomeArgument(nodeName), optional, block) -inline fun CommandAPICommand.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(BlockStateArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(CommandArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(EnchantmentArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(BlockStateArgument(nodeName), optional, block) +inline fun CommandAPICommand.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(CommandArgument(nodeName), optional, block) +inline fun CommandAPICommand.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(EnchantmentArgument(nodeName), optional, block) -inline fun CommandAPICommand.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ItemStackArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LootTableArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(MathOperationArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(NamespacedKeyArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ParticleArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ItemStackArgument(nodeName), optional, block) +inline fun CommandAPICommand.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LootTableArgument(nodeName), optional, block) +inline fun CommandAPICommand.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MathOperationArgument(nodeName), optional, block) +inline fun CommandAPICommand.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(NamespacedKeyArgument(nodeName), optional, block) +inline fun CommandAPICommand.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ParticleArgument(nodeName), optional, block) inline fun CommandAPICommand.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = - if (useNamespacedKey) withArguments(PotionEffectArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else withArguments(PotionEffectArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(RecipeArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(PotionEffectArgument.NamespacedKey(nodeName), optional, block) else addArgument(PotionEffectArgument(nodeName), optional, block) +inline fun CommandAPICommand.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(RecipeArgument(nodeName), optional, block) inline fun CommandAPICommand.soundArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = - if (useNamespacedKey) withArguments(SoundArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else withArguments(SoundArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(SoundArgument.NamespacedKey(nodeName), optional, block) else addArgument(SoundArgument(nodeName), optional, block) -inline fun CommandAPICommand.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(TimeArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(UUIDArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(WorldArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(TimeArgument(nodeName), optional, block) +inline fun CommandAPICommand.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(UUIDArgument(nodeName), optional, block) +inline fun CommandAPICommand.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(WorldArgument(nodeName), optional, block) // Predicate arguments -inline fun CommandAPICommand.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(BlockPredicateArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(ItemStackPredicateArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(BlockPredicateArgument(nodeName), optional, block) +inline fun CommandAPICommand.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(ItemStackPredicateArgument(nodeName), optional, block) // NBT arguments -inline fun CommandAPICommand.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(NBTCompoundArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(NBTCompoundArgument(nodeName), optional, block) // Literal arguments -inline fun CommandAPICommand.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LiteralArgument.of(literal, literal).setOptional(optional).apply(block)) -inline fun CommandAPICommand.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LiteralArgument.of(nodeName, literal).setOptional(optional).apply(block)) +inline fun CommandAPICommand.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(literal, literal), optional, block) +inline fun CommandAPICommand.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(nodeName, literal), optional, block) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandAPICommand.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(MultiLiteralArgument(literals).setOptional(optional).apply(block)) +inline fun CommandAPICommand.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(block)) +inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) -inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(block)) +inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) // Function arguments -inline fun CommandAPICommand.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(FunctionArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(FunctionArgument(nodeName), optional, block) // Requirements @Deprecated("This method has been deprecated since version 9.1.0 as it is not needed anymore. See the documentation for more information", ReplaceWith(""), DeprecationLevel.WARNING) -inline fun CommandAPICommand.requirement(base: Argument<*>, predicate: Predicate, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(base.setOptional(optional).withRequirement(predicate).apply(block)) +inline fun CommandAPICommand.requirement(base: Argument<*>, predicate: Predicate, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(base.withRequirement(predicate), optional, block) diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index d363d7dadb..144a97baf5 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -15,214 +15,218 @@ inline fun commandTree(name: String, predicate: Predicate, tree: // CommandTree start inline fun CommandTree.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.apply(block)) -inline fun CommandTree.optionalArgument(base: Argument<*>, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.setOptional(true).setOptional(optional).apply(block)) +inline fun CommandTree.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = withOptionalArguments(base.apply(block)) + +inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) { optionalArgument(base, block) } else { argument(base, block) } // Integer arguments -inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(IntegerArgument(nodeName, min, max).setOptional(optional).apply(block)) -inline fun CommandTree.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(IntegerRangeArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerArgument(nodeName, min, max), optional, block) +inline fun CommandTree.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerRangeArgument(nodeName), optional, block) // Float arguments -inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(FloatArgument(nodeName, min, max).setOptional(optional).apply(block)) -inline fun CommandTree.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(FloatRangeArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FloatArgument(nodeName, min, max), optional, block) +inline fun CommandTree.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FloatRangeArgument(nodeName), optional, block) // Double arguments -inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(DoubleArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(DoubleArgument(nodeName, min, max), optional, block) // Long arguments -inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(LongArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LongArgument(nodeName, min, max), optional, block) // Boolean argument -inline fun CommandTree.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(BooleanArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BooleanArgument(nodeName), optional, block) // String arguments -inline fun CommandTree.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(StringArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(TextArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(GreedyStringArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(StringArgument(nodeName), optional, block) +inline fun CommandTree.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TextArgument(nodeName), optional, block) +inline fun CommandTree.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(GreedyStringArgument(nodeName), optional, block) // Positional arguments -inline fun CommandTree.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(LocationArgument(nodeName, locationType, centerPosition).setOptional(optional).apply(block)) -inline fun CommandTree.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(Location2DArgument(nodeName, locationType, centerPosition).setOptional(optional).apply(block)) -inline fun CommandTree.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(RotationArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(AxisArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LocationArgument(nodeName, locationType, centerPosition), optional, block) +inline fun CommandTree.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(Location2DArgument(nodeName, locationType, centerPosition), optional, block) +inline fun CommandTree.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(RotationArgument(nodeName), optional, block) +inline fun CommandTree.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AxisArgument(nodeName), optional, block) // Chat arguments -inline fun CommandTree.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ChatColorArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ChatComponentArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ChatArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdventureChatColorArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdventureChatComponentArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdventureChatArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ChatColorArgument(nodeName), optional, block) +inline fun CommandTree.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ChatComponentArgument(nodeName), optional, block) +inline fun CommandTree.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ChatArgument(nodeName), optional, block) +inline fun CommandTree.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdventureChatColorArgument(nodeName), optional, block) +inline fun CommandTree.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdventureChatComponentArgument(nodeName), optional, block) +inline fun CommandTree.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdventureChatArgument(nodeName), optional, block) // Entity & Player arguments -inline fun CommandTree.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.OneEntity(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty).setOptional(optional).apply(block)) -inline fun CommandTree.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.OnePlayer(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty).setOptional(optional).apply(block)) -inline fun CommandTree.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(PlayerArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(OfflinePlayerArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntityTypeArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.OneEntity(nodeName), optional, block) +inline fun CommandTree.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty), optional, block) +inline fun CommandTree.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.OnePlayer(nodeName), optional, block) +inline fun CommandTree.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty), optional, block) +inline fun CommandTree.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(PlayerArgument(nodeName), optional, block) +inline fun CommandTree.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(OfflinePlayerArgument(nodeName), optional, block) +inline fun CommandTree.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntityTypeArgument(nodeName), optional, block) // Scoreboard arguments -inline fun CommandTree.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ScoreHolderArgument.Single(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ScoreHolderArgument.Multiple(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ScoreboardSlotArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ObjectiveArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ObjectiveCriteriaArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(TeamArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ScoreHolderArgument.Single(nodeName), optional, block) +inline fun CommandTree.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ScoreHolderArgument.Multiple(nodeName), optional, block) +inline fun CommandTree.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ScoreboardSlotArgument(nodeName), optional, block) +inline fun CommandTree.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ObjectiveArgument(nodeName), optional, block) +inline fun CommandTree.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ObjectiveCriteriaArgument(nodeName), optional, block) +inline fun CommandTree.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TeamArgument(nodeName), optional, block) // Miscellaneous arguments -inline fun CommandTree.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(AngleArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdvancementArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AngleArgument(nodeName), optional, block) +inline fun CommandTree.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdvancementArgument(nodeName), optional, block) inline fun CommandTree.biomeArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = - if (useNamespacedKey) then(BiomeArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(BiomeArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(BiomeArgument.NamespacedKey(nodeName), optional, block) else addArgument(BiomeArgument(nodeName), optional, block) -inline fun CommandTree.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(BlockStateArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(CommandArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(EnchantmentArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BlockStateArgument(nodeName), optional, block) +inline fun CommandTree.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(CommandArgument(nodeName), optional, block) +inline fun CommandTree.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EnchantmentArgument(nodeName), optional, block) -inline fun CommandTree.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ItemStackArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(LootTableArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(MathOperationArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(NamespacedKeyArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ParticleArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ItemStackArgument(nodeName), optional, block) +inline fun CommandTree.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LootTableArgument(nodeName), optional, block) +inline fun CommandTree.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(MathOperationArgument(nodeName), optional, block) +inline fun CommandTree.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(NamespacedKeyArgument(nodeName), optional, block) +inline fun CommandTree.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ParticleArgument(nodeName), optional, block) inline fun CommandTree.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = - if (useNamespacedKey) then(PotionEffectArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(PotionEffectArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(RecipeArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(PotionEffectArgument.NamespacedKey(nodeName), optional, block) else addArgument(PotionEffectArgument(nodeName), optional, block) +inline fun CommandTree.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(RecipeArgument(nodeName), optional, block) inline fun CommandTree.soundArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = - if (useNamespacedKey) then(SoundArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(SoundArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(SoundArgument.NamespacedKey(nodeName), optional, block) else addArgument(SoundArgument(nodeName), optional, block) -inline fun CommandTree.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(TimeArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(UUIDArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(WorldArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TimeArgument(nodeName), optional, block) +inline fun CommandTree.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(UUIDArgument(nodeName), optional, block) +inline fun CommandTree.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(WorldArgument(nodeName), optional, block) // Predicate arguments -inline fun CommandTree.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(BlockPredicateArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(ItemStackPredicateArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BlockPredicateArgument(nodeName), optional, block) +inline fun CommandTree.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ItemStackPredicateArgument(nodeName), optional, block) // NBT arguments -inline fun CommandTree.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(NBTCompoundArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(NBTCompoundArgument(nodeName), optional, block) // Literal arguments -inline fun CommandTree.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(literal, literal).setOptional(optional).apply(block)) -inline fun CommandTree.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(nodeName, literal).setOptional(optional).apply(block)) +inline fun CommandTree.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(literal, literal), optional, block) +inline fun CommandTree.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(nodeName, literal), optional, block) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(literals).setOptional(optional).apply(block)) +inline fun CommandTree.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(block)) +inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, literals), optional, block) -inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(block)) +inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) // Function arguments -inline fun CommandTree.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(FunctionArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FunctionArgument(nodeName), optional, block) // ArgumentTree start -inline fun Argument<*>.argument(base: Argument<*>, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.setOptional(optional).apply(block)) +inline fun Argument<*>.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.apply(block)) + +inline fun Argument<*>.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = withOptionalArguments(base.apply(block)) -inline fun Argument<*>.optionalArgument(base: Argument<*>, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.setOptional(true).setOptional(optional).apply(block)) +inline fun Argument<*>.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): Argument<*> = if (optional) { optionalArgument(base, block) } else { argument(base, block) } // Integer arguments -inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(IntegerArgument(nodeName, min, max).setOptional(optional).apply(block)) -inline fun Argument<*>.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(IntegerRangeArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(IntegerArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(IntegerRangeArgument(nodeName), optional, block) // Float arguments -inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FloatArgument(nodeName, min, max).setOptional(optional).apply(block)) -inline fun Argument<*>.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FloatRangeArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FloatArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FloatRangeArgument(nodeName), optional, block) // Double arguments -inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(DoubleArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(DoubleArgument(nodeName, min, max), optional, block) // Long arguments -inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LongArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LongArgument(nodeName, min, max), optional, block) // Boolean argument -inline fun Argument<*>.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BooleanArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BooleanArgument(nodeName), optional, block) // String arguments -inline fun Argument<*>.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(StringArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TextArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(GreedyStringArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(StringArgument(nodeName), optional, block) +inline fun Argument<*>.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TextArgument(nodeName), optional, block) +inline fun Argument<*>.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(GreedyStringArgument(nodeName), optional, block) // Positional arguments -inline fun Argument<*>.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LocationArgument(nodeName, locationType, centerPosition).setOptional(optional).apply(block)) -inline fun Argument<*>.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(Location2DArgument(nodeName, locationType, centerPosition).setOptional(optional).apply(block)) -inline fun Argument<*>.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(RotationArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AxisArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LocationArgument(nodeName, locationType, centerPosition), optional, block) +inline fun Argument<*>.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(Location2DArgument(nodeName, locationType, centerPosition), optional, block) +inline fun Argument<*>.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(RotationArgument(nodeName), optional, block) +inline fun Argument<*>.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AxisArgument(nodeName), optional, block) // Chat arguments -inline fun Argument<*>.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ChatColorArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ChatComponentArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ChatArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdventureChatColorArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdventureChatComponentArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdventureChatArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ChatColorArgument(nodeName), optional, block) +inline fun Argument<*>.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ChatComponentArgument(nodeName), optional, block) +inline fun Argument<*>.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ChatArgument(nodeName), optional, block) +inline fun Argument<*>.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdventureChatColorArgument(nodeName), optional, block) +inline fun Argument<*>.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdventureChatComponentArgument(nodeName), optional, block) +inline fun Argument<*>.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdventureChatArgument(nodeName), optional, block) // Entity & Player arguments -inline fun Argument<*>.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.OneEntity(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty).setOptional(optional).apply(block)) -inline fun Argument<*>.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.OnePlayer(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty).setOptional(optional).apply(block)) -inline fun Argument<*>.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(PlayerArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(OfflinePlayerArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntityTypeArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.OneEntity(nodeName), optional, block) +inline fun Argument<*>.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty), optional, block) +inline fun Argument<*>.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.OnePlayer(nodeName), optional, block) +inline fun Argument<*>.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty), optional, block) +inline fun Argument<*>.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(PlayerArgument(nodeName), optional, block) +inline fun Argument<*>.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(OfflinePlayerArgument(nodeName), optional, block) +inline fun Argument<*>.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntityTypeArgument(nodeName), optional, block) // Scoreboard arguments -inline fun Argument<*>.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ScoreHolderArgument.Single(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ScoreHolderArgument.Multiple(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ScoreboardSlotArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ObjectiveArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ObjectiveCriteriaArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TeamArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ScoreHolderArgument.Single(nodeName), optional, block) +inline fun Argument<*>.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ScoreHolderArgument.Multiple(nodeName), optional, block) +inline fun Argument<*>.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ScoreboardSlotArgument(nodeName), optional, block) +inline fun Argument<*>.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ObjectiveArgument(nodeName), optional, block) +inline fun Argument<*>.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ObjectiveCriteriaArgument(nodeName), optional, block) +inline fun Argument<*>.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TeamArgument(nodeName), optional, block) // Miscellaneous arguments -inline fun Argument<*>.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AngleArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdvancementArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AngleArgument(nodeName), optional, block) +inline fun Argument<*>.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdvancementArgument(nodeName), optional, block) inline fun Argument<*>.biomeArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = - if (useNamespacedKey) then(BiomeArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(BiomeArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(BiomeArgument.NamespacedKey(nodeName), optional, block) else addArgument(BiomeArgument(nodeName), optional, block) -inline fun Argument<*>.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BlockStateArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(CommandArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EnchantmentArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BlockStateArgument(nodeName), optional, block) +inline fun Argument<*>.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(CommandArgument(nodeName), optional, block) +inline fun Argument<*>.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EnchantmentArgument(nodeName), optional, block) -inline fun Argument<*>.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ItemStackArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LootTableArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MathOperationArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(NamespacedKeyArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ParticleArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ItemStackArgument(nodeName), optional, block) +inline fun Argument<*>.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LootTableArgument(nodeName), optional, block) +inline fun Argument<*>.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MathOperationArgument(nodeName), optional, block) +inline fun Argument<*>.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(NamespacedKeyArgument(nodeName), optional, block) +inline fun Argument<*>.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ParticleArgument(nodeName), optional, block) inline fun Argument<*>.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = - if (useNamespacedKey) then(PotionEffectArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(PotionEffectArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(RecipeArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(PotionEffectArgument.NamespacedKey(nodeName), optional, block) else addArgument(PotionEffectArgument(nodeName), optional, block) +inline fun Argument<*>.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(RecipeArgument(nodeName), optional, block) inline fun Argument<*>.soundArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = - if (useNamespacedKey) then(SoundArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(SoundArgument(nodeName).setOptional(optional).apply(block)) + if (useNamespacedKey) addArgument(SoundArgument.NamespacedKey(nodeName), optional, block) else addArgument(SoundArgument(nodeName), optional, block) -inline fun Argument<*>.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TimeArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(UUIDArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(WorldArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TimeArgument(nodeName), optional, block) +inline fun Argument<*>.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(UUIDArgument(nodeName), optional, block) +inline fun Argument<*>.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(WorldArgument(nodeName), optional, block) // Predicate arguments -inline fun Argument<*>.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BlockPredicateArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ItemStackPredicateArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BlockPredicateArgument(nodeName), optional, block) +inline fun Argument<*>.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ItemStackPredicateArgument(nodeName), optional, block) // NBT arguments -inline fun Argument<*>.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(NBTCompoundArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(NBTCompoundArgument(nodeName), optional, block) // Literal arguments -inline fun Argument<*>.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(literal, literal).setOptional(optional).apply(block)) -inline fun Argument<*>.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(nodeName, literal).setOptional(optional).apply(block)) +inline fun Argument<*>.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(literal, literal), optional, block) +inline fun Argument<*>.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(nodeName, literal), optional, block) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(literals).setOptional(optional).apply(block)) +inline fun Argument<*>.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(block)) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(block)) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) // Function arguments -inline fun Argument<*>.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FunctionArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FunctionArgument(nodeName), optional, block) @Deprecated("This method has been deprecated since version 9.1.0 as it is not needed anymore. See the documentation for more information", ReplaceWith(""), DeprecationLevel.WARNING) inline fun CommandTree.requirement(base: Argument<*>, predicate: Predicate, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.withRequirement(predicate).apply(block)) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index fcd82a56e7..6b03c72e0f 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -22,37 +22,39 @@ inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): inline fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command) inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command)) +inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) { optionalArgument(argument, block) } else { argument(argument, block) } + // Integer arguments -inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(IntegerArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(IntegerArgument(nodeName, min, max), optional, block) // Float arguments -inline fun CommandAPICommand.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(FloatArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandAPICommand.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(FloatArgument(nodeName, min, max), optional, block) // Double arguments -inline fun CommandAPICommand.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(DoubleArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandAPICommand.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(DoubleArgument(nodeName, min, max), optional, block) // Long arguments -inline fun CommandAPICommand.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LongArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandAPICommand.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LongArgument(nodeName, min, max), optional, block) // Boolean argument -inline fun CommandAPICommand.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(BooleanArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(BooleanArgument(nodeName), optional, block) // String arguments -inline fun CommandAPICommand.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(StringArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(TextArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandAPICommand.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(GreedyStringArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandAPICommand.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(StringArgument(nodeName), optional, block) +inline fun CommandAPICommand.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(TextArgument(nodeName), optional, block) +inline fun CommandAPICommand.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(GreedyStringArgument(nodeName), optional, block) // Literal arguments -inline fun CommandAPICommand.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LiteralArgument.of(literal, literal).setOptional(optional).apply(block)) -inline fun CommandAPICommand.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(LiteralArgument.of(nodeName, literal).setOptional(optional).apply(block)) +inline fun CommandAPICommand.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(literal, literal), optional, block) +inline fun CommandAPICommand.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(nodeName, literal), optional, block) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandAPICommand.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(MultiLiteralArgument(literals).setOptional(optional).apply(block)) +inline fun CommandAPICommand.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(block)) +inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) -inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(block)) +inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) // Requirements @Deprecated("This method has been deprecated since version 9.1.0 as it is not needed anymore. See the documentation for more information", ReplaceWith(""), DeprecationLevel.WARNING) -inline fun CommandAPICommand.requirement(base: Argument<*>, predicate: Predicate, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(base.setOptional(optional).withRequirement(predicate).apply(block)) +inline fun CommandAPICommand.requirement(base: Argument<*>, predicate: Predicate, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(base.withRequirement(predicate), optional, block) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index 607db19244..4c1541a331 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -13,75 +13,79 @@ inline fun commandTree(name: String, predicate: Predicate, tree: // CommandTree start inline fun CommandTree.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.apply(block)) -inline fun CommandTree.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.setOptional(true).apply(block)) +inline fun CommandTree.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = withOptionalArguments(base.apply(block)) + +inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) { optionalArgument(base, block) } else { argument(base, block) } // Integer arguments -inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(IntegerArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerArgument(nodeName, min, max), optional, block) // Float arguments -inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(FloatArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FloatArgument(nodeName, min, max), optional, block) // Double arguments -inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(DoubleArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(DoubleArgument(nodeName, min, max), optional, block) // Long arguments -inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(LongArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LongArgument(nodeName, min, max), optional, block) // Boolean argument -inline fun CommandTree.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(BooleanArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BooleanArgument(nodeName), optional, block) // String arguments -inline fun CommandTree.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(StringArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(TextArgument(nodeName).setOptional(optional).apply(block)) -inline fun CommandTree.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = then(GreedyStringArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(StringArgument(nodeName), optional, block) +inline fun CommandTree.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TextArgument(nodeName), optional, block) +inline fun CommandTree.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(GreedyStringArgument(nodeName), optional, block) // Literal arguments -inline fun CommandTree.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(literal, literal).setOptional(optional).apply(block)) -inline fun CommandTree.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(nodeName, literal).setOptional(optional).apply(block)) +inline fun CommandTree.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(literal, literal), optional, block) +inline fun CommandTree.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(nodeName, literal), optional, block) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(literals).setOptional(optional).apply(block)) +inline fun CommandTree.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(block)) +inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, literals), optional, block) -inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(block)) +inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) // ArgumentTree start -inline fun Argument<*>.argument(base: Argument<*>, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.setOptional(optional).apply(block)) +inline fun Argument<*>.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.apply(block)) + +inline fun Argument<*>.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = withOptionalArguments(base.apply(block)) -inline fun Argument<*>.optionalArgument(base: Argument<*>, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.setOptional(true).setOptional(optional).apply(block)) +inline fun Argument<*>.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): Argument<*> = if (optional) { optionalArgument(base, block) } else { argument(base, block) } // Integer arguments -inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(IntegerArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(IntegerArgument(nodeName, min, max), optional, block) // Float arguments -inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FloatArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FloatArgument(nodeName, min, max), optional, block) // Double arguments -inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(DoubleArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(DoubleArgument(nodeName, min, max), optional, block) // Long arguments -inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LongArgument(nodeName, min, max).setOptional(optional).apply(block)) +inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LongArgument(nodeName, min, max), optional, block) // Boolean argument -inline fun Argument<*>.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BooleanArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BooleanArgument(nodeName), optional, block) // String arguments -inline fun Argument<*>.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(StringArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TextArgument(nodeName).setOptional(optional).apply(block)) -inline fun Argument<*>.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(GreedyStringArgument(nodeName).setOptional(optional).apply(block)) +inline fun Argument<*>.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(StringArgument(nodeName), optional, block) +inline fun Argument<*>.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TextArgument(nodeName), optional, block) +inline fun Argument<*>.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(GreedyStringArgument(nodeName), optional, block) // Literal arguments -inline fun Argument<*>.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(literal, literal).setOptional(optional).apply(block)) -inline fun Argument<*>.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(nodeName, literal).setOptional(optional).apply(block)) +inline fun Argument<*>.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(literal, literal), optional, block) +inline fun Argument<*>.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(nodeName, literal), optional, block) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(literals).setOptional(optional).apply(block)) +inline fun Argument<*>.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(block)) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(block)) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0 as it is not needed anymore. See the documentation for more information", ReplaceWith(""), DeprecationLevel.WARNING) inline fun CommandTree.requirement(base: Argument<*>, predicate: Predicate, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.withRequirement(predicate).apply(block)) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt index 064dc82905..7d8af435da 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt @@ -72,27 +72,6 @@ class OptionalArgumentTests: TestBase() { } } - // TODO: This test currently fails - The optionality of an argument in a CommandTree is currently ignored - // Note: I'm not sure if optional arguments in command trees make sense - // For example, consider this CommandTree (excuse my Java) - // new CommandTree("command") - // .then(new StringArgument("string").setOptional(true) - // .then(new LiteralArgument("a").executes((sender, args) -> { - // sender.sendMessage("You ran the A path"); - // })) - // .then(new LiteralArgument("b").executes((sender, args) -> { - // sender.sendMessage("You ran the B path"); - // })) - // ) - // .register(); - // The `string` argument is optional, which theoretically defines the following commands: - // /command a - // /command b - // /command - // The problem is: What is `/command` supposed to execute? `A path`, `B path`, something else? This works fine for - // a CommandAPICommand since there can only be one executor defined, but I don't think anything makes sense for a - // CommandTree. - @Disabled @Test fun executionTestWithCommandTreeAndOptionalArgumentMethod() { val results: Mut = Mut.of() @@ -102,10 +81,9 @@ class OptionalArgumentTests: TestBase() { // because everything can be achieved by using the normal CommandTree DSL syntax and // the argument() method commandTree("test") { - stringArgument("value", optional = true) { - playerExecutor { player, args -> - results.set(args.getOptional("value").orElse("DefaultValue") as String) - } + stringArgument("value", optional = true) + playerExecutor { player, args -> + results.set(args.getOptional("value").orElse("DefaultValue") as String) } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/OptionalArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandOptionalArgumentTests.java similarity index 92% rename from commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/OptionalArgumentTests.java rename to commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandOptionalArgumentTests.java index a418b2502e..94141e06c0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/OptionalArgumentTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandOptionalArgumentTests.java @@ -1,8 +1,4 @@ -package dev.jorel.commandapi.test.arguments; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; +package dev.jorel.commandapi.test; import dev.jorel.commandapi.arguments.DoubleArgument; import dev.jorel.commandapi.arguments.IntegerArgument; @@ -14,13 +10,13 @@ import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.arguments.StringArgument; import dev.jorel.commandapi.exceptions.OptionalArgumentException; -import dev.jorel.commandapi.test.Mut; -import dev.jorel.commandapi.test.TestBase; + +import static org.junit.jupiter.api.Assertions.*; /** - * Tests for optional arguments + * Tests for using optional arguments in CommandAPICommands */ -class OptionalArgumentTests extends TestBase { +class CommandAPICommandOptionalArgumentTests extends TestBase { /********* * Setup * @@ -111,18 +107,22 @@ void testTwoOptionalArguments() { @Test() public void testOptionalArgumentException() { - Mut type = Mut.of(); - // An optional argument followed by a required argument should throw // an OptionalArgumentException assertThrows(OptionalArgumentException.class, () -> { new CommandAPICommand("test") .withOptionalArguments(new StringArgument("string1")) .withArguments(new StringArgument("string2")) - .executesPlayer((player, args) -> { - type.set((String) args.get("string1")); - type.set((String) args.get("string2")); - }) + .executesPlayer(P_EXEC) + .register(); + }); + + // No need to worry: since we didn't actually add any arguments in `withArguments`, this is fine + assertDoesNotThrow(() -> { + new CommandAPICommand("test") + .withOptionalArguments(new StringArgument("string")) + .withArguments() + .executesPlayer(P_EXEC) .register(); }); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java new file mode 100644 index 0000000000..99b86296e0 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java @@ -0,0 +1,494 @@ +package dev.jorel.commandapi.test; + +import dev.jorel.commandapi.CommandAPI; +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.arguments.IntegerArgument; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.MultiLiteralArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests for making sure the {@link RegisteredCommand} information is correct when registering {@link CommandAPICommand}s + */ +class CommandAPICommandRegisteredCommandTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + private RegisteredCommand registeredCommandNoHelpOrPermission(String name, List argsAsStr, String... aliases) { + return new RegisteredCommand(name, argsAsStr, + Optional.empty(), Optional.empty(), Optional.empty(), + aliases, + CommandPermission.NONE + ); + } + + private void assertCreatedRegisteredCommands(RegisteredCommand... commands) { + List expectedCommands = Arrays.asList(commands); + List actualCommands = CommandAPI.getRegisteredCommands(); + + if (expectedCommands.size() != actualCommands.size()) { + StringBuilder builder = new StringBuilder(); + builder.append("Expected ").append(expectedCommands.size()).append(" command(s), found ").append(actualCommands.size()).append(" command(s)."); + + builder.append("\nExpected: "); + addRegisteredCommandList(builder, expectedCommands); + builder.append("\nActual: "); + addRegisteredCommandList(builder, actualCommands); + + fail(builder.toString()); + } + + for (int i = 0; i < expectedCommands.size(); i++) { + RegisteredCommand expectedCommand = expectedCommands.get(i); + RegisteredCommand actualCommand = actualCommands.get(i); + + if (!Objects.equals(expectedCommand, actualCommand)) { + StringBuilder builder = new StringBuilder(); + builder.append("Command #").append(i + 1).append(" differed. Expected:\n"); + builder.append(expectedCommand); + builder.append("\nActual:\n"); + builder.append(actualCommand); + + builder.append("\nExpected list: "); + addRegisteredCommandList(builder, expectedCommands); + builder.append("\nActual list: "); + addRegisteredCommandList(builder, actualCommands); + + fail(builder.toString()); + } + } + } + + private void addRegisteredCommandList(StringBuilder builder, List commands) { + if (commands.isEmpty()) { + builder.append("[]"); + return; + } + + builder.append("[\n"); + for (RegisteredCommand command : commands) { + builder.append("\t"); + builder.append(command); + builder.append("\n"); + } + builder.append("]"); + } + + /********* + * Tests * + *********/ + + @Test + void testRegister() { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of())); + } + + @Test + void testRegisterHelpInformation() { + new CommandAPICommand("command") + .withHelp("short description", "full description") + .withUsage( + "usage 1", + "usage 2", + "usage 3" + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + new RegisteredCommand( + "command", List.of(), + Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + new String[0], CommandPermission.NONE + ) + ); + } + + @Test + void testRegisterOpPermission() { + new CommandAPICommand("command") + .withPermission(CommandPermission.OP) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + new RegisteredCommand( + "command", List.of(), + Optional.empty(), Optional.empty(), Optional.empty(), + new String[0], CommandPermission.OP + ) + ); + } + + @Test + void testRegisterStringPermission() { + new CommandAPICommand("command") + .withPermission("permission") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + new RegisteredCommand( + "command", List.of(), + Optional.empty(), Optional.empty(), Optional.empty(), + new String[0], CommandPermission.fromString("permission") + ) + ); + } + + @Test + void testRegisterOneAlias() { + new CommandAPICommand("command") + .withAliases("alias1") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1")); + } + + @Test + void testRegisterTwoAliases() { + new CommandAPICommand("command") + .withAliases("alias1", "alias2") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1", "alias2")); + } + + @Test + void testRegisterOneArgument() { + new CommandAPICommand("command") + .withArguments(new StringArgument("string")) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument"))); + } + + @Test + void testRegisterTwoArguments() { + new CommandAPICommand("command") + .withArguments( + new StringArgument("string"), + new IntegerArgument("integer") + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument", "integer:IntegerArgument"))); + } + + @Test + void testRegisterMultiLiteralArguments() { + new CommandAPICommand("command") + .withArguments( + new MultiLiteralArgument("literal1", "a", "b", "c"), + new MultiLiteralArgument("literal2", "d", "e", "f") + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "e:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "e:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "e:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "f:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "f:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "f:LiteralArgument")) + ); + } + + @Test + void testRegisterOneOptionalArgument() { + new CommandAPICommand("command") + .withOptionalArguments(new StringArgument("string")) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")) + ); + } + + @Test + void testRegisterTwoOptionalArguments() { + new CommandAPICommand("command") + .withOptionalArguments( + new StringArgument("string"), + new IntegerArgument("integer") + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")), + registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument", "integer:IntegerArgument")) + ); + } + + @Test + void testRegisterCombinedOptionalArguments() { + new CommandAPICommand("command") + .withOptionalArguments( + new LiteralArgument("1").combineWith(new LiteralArgument("2")), + new LiteralArgument("3").combineWith(new LiteralArgument("4")) + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument")) + ); + } + + @Test + void testRegisterCombinedRequiredAndOptionalArguments() { + new CommandAPICommand("command") + .withArguments( + new LiteralArgument("1").combineWith(new LiteralArgument("2")), + new LiteralArgument("3").combineWith(new LiteralArgument("4")) + ) + .withOptionalArguments( + new LiteralArgument("5").combineWith(new LiteralArgument("6")), + new LiteralArgument("7").combineWith(new LiteralArgument("8")) + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of( + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" + )), + registeredCommandNoHelpOrPermission("command", List.of( + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument" + )), + registeredCommandNoHelpOrPermission("command", List.of( + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" + )) + ); + } + + ////////////////////////////////////// + // SUBCOMMANDS // + // The same as commands, but deeper // + ////////////////////////////////////// + + @Test + void testRegisterOneSubcommand() { + new CommandAPICommand("command") + .withSubcommand( + new CommandAPICommand("subcommand") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument"))); + } + + @Test + void testRegisterTwoSubcommands() { + new CommandAPICommand("command") + .withSubcommands( + new CommandAPICommand("subcommand1") + .executesPlayer(P_EXEC), + new CommandAPICommand("subcommand2") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument")) + ); + } + + @Test + void testRegisterOneSubcommandAndBaseExecutable() { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .withSubcommand( + new CommandAPICommand("subcommand") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument")) + ); + } + + @Test + void testRegisterSubcommandWithAliases() { + new CommandAPICommand("command") + .withSubcommand( + new CommandAPICommand("subcommand") + .withAliases("alias1", "alias2") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument")) + ); + } + + @Test + void testRegisterSubcommandsWithArguments() { + new CommandAPICommand("command") + .withSubcommands( + new CommandAPICommand("subcommand1") + .withArguments( + new StringArgument("string1"), + new IntegerArgument("integer1") + ) + .executesPlayer(P_EXEC), + new CommandAPICommand("subcommand2") + .withArguments( + new StringArgument("string2"), + new IntegerArgument("integer2") + ) + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "string1:StringArgument", "integer1:IntegerArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "string2:StringArgument", "integer2:IntegerArgument")) + ); + } + + @Test + void testRegisterSubcommandWithAliasesAndMultiLiteralArgument() { + new CommandAPICommand("command") + .withSubcommand( + new CommandAPICommand("subcommand") + .withAliases("alias1", "alias2") + .withArguments( + new MultiLiteralArgument("literal1", "a", "b"), + new MultiLiteralArgument("literal2", "c", "d") + ) + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "a:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "a:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "a:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "b:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "b:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "b:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "a:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "a:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "a:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "b:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "b:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "b:LiteralArgument", "d:LiteralArgument")) + ); + } + + @Test + void testRegisterSubcommandsWithOptionalArguments() { + new CommandAPICommand("command") + .withSubcommand( + new CommandAPICommand("subcommand1") + .withOptionalArguments( + new LiteralArgument("a"), + new LiteralArgument("b") + ) + .executesPlayer(P_EXEC) + ) + .withSubcommand( + new CommandAPICommand("subcommand2") + .withOptionalArguments( + new LiteralArgument("c"), + new LiteralArgument("d") + ) + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument", "b:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument", "d:LiteralArgument")) + ); + } + + @Test + void testRegisterSubcommandsWithCombinedRequiredAndOptionalArguments() { + new CommandAPICommand("command") + .withSubcommand( + new CommandAPICommand("subcommand1") + .withArguments( + new LiteralArgument("1a").combineWith(new LiteralArgument("1b")) + ) + .withOptionalArguments( + new LiteralArgument("1c").combineWith(new LiteralArgument("1d")) + ) + .executesPlayer(P_EXEC) + ) + .withSubcommand( + new CommandAPICommand("subcommand2") + .withArguments( + new LiteralArgument("2a").combineWith(new LiteralArgument("2b")) + ) + .withOptionalArguments( + new LiteralArgument("2c").combineWith(new LiteralArgument("2d")) + ) + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument", "1c:LiteralArgument", "1d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument", "2c:LiteralArgument", "2d:LiteralArgument")) + ); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java new file mode 100644 index 0000000000..141e7496d5 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java @@ -0,0 +1,233 @@ +package dev.jorel.commandapi.test; + +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.executors.PlayerCommandExecutor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests for using optional arguments in CommandTrees + */ +class CommandTreeOptionalArgumentTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @Test + void testNormalArgument() { + Mut results = Mut.of(); + + new CommandTree("test") + .then(new StringArgument("string").executesPlayer((player, args) -> { + results.set(args.getUnchecked("string")); + })) + .register(); + + PlayerMock player = server.addPlayer(); + + assertCommandFailsWith(player, "test", "Unknown or incomplete command, see below for error at position 4: test<--[HERE]"); + + assertStoresResult(player, "test hello", results, "hello"); + assertStoresResult(player, "test world", results, "world"); + + assertNoMoreResults(results); + } + + @Test + void testOptionalArgument() { + Mut results = Mut.of(); + + new CommandTree("test") + .withOptionalArguments(new StringArgument("string")) + .executesPlayer((player, args) -> { + results.set(args.getUnchecked("string")); + }) + .register(); + + PlayerMock player = server.addPlayer(); + + assertStoresResult(player, "test", results, null); + + assertStoresResult(player, "test hello", results, "hello"); + assertStoresResult(player, "test world", results, "world"); + + assertNoMoreResults(results); + } + + @Test + void testTwoOptionalArguments() { + Mut arg1 = Mut.of(); + Mut arg2 = Mut.of(); + + new CommandTree("test") + .withOptionalArguments( + new StringArgument("string1"), + new StringArgument("string2") + ) + .executesPlayer((player, args) -> { + arg1.set(args.getUnchecked("string1")); + arg2.set(args.getUnchecked("string2")); + }) + .register(); + + PlayerMock player = server.addPlayer(); + + // "/test" should return null, null + assertStoresResult(player, "test", arg1, null); + assertNull(arg2.get()); + + // "/test hello" should return "hello", null + assertStoresResult(player, "test hello", arg1, "hello"); + assertNull(arg2.get()); + + // "/test hello world" should return "hello", "world" + assertStoresResult(player, "test hello world", arg1, "hello"); + assertEquals("world", arg2.get()); + + assertNoMoreResults(arg1); + assertNoMoreResults(arg2); + } + + @Test + void testCombinedOptionalArguments() { + Mut results = Mut.of(); + + new CommandTree("test") + .withOptionalArguments( + new LiteralArgument("1").combineWith(new LiteralArgument("2"), new LiteralArgument("3")), + new LiteralArgument("4"), + new LiteralArgument("5").combineWith(new LiteralArgument("6")) + ) + .executesPlayer((player, args) -> { + results.set(args.fullInput()); + }) + .register(); + + PlayerMock player = server.addPlayer(); + + assertStoresResult(player, "test", results, "/test"); + + assertCommandFailsWith(player, "test 1", "Unknown or incomplete command, see below for error at position 6: test 1<--[HERE]"); + assertCommandFailsWith(player, "test 1 2", "Unknown or incomplete command, see below for error at position 8: test 1 2<--[HERE]"); + assertStoresResult(player, "test 1 2 3", results, "/test 1 2 3"); + + assertStoresResult(player, "test 1 2 3 4", results, "/test 1 2 3 4"); + + assertCommandFailsWith(player, "test 1 2 3 4 5", "Unknown or incomplete command, see below for error at position 14: ... 1 2 3 4 5<--[HERE]"); + assertStoresResult(player, "test 1 2 3 4 5 6", results, "/test 1 2 3 4 5 6"); + + assertNoMoreResults(results); + } + + @Test + void testBranchesWithOptionalArguments() { + Mut executedPath = Mut.of(); + Mut results = Mut.of(); + + Function executor = path -> (player, args) -> { + executedPath.set(path); + results.set(args.fullInput()); + }; + + new CommandTree("test") + .then(new LiteralArgument("1") + .then(new LiteralArgument("1") + .withOptionalArguments(new LiteralArgument("optional")) + .executesPlayer(executor.apply("1 1")) + ) + .then(new LiteralArgument("2") + .withOptionalArguments(new LiteralArgument("optional")) + .executesPlayer(executor.apply("1 2")) + ) + .withOptionalArguments(new LiteralArgument("optional")) + .executesPlayer(executor.apply("1")) + ) + .then(new LiteralArgument("2") + .then(new LiteralArgument("1") + .withOptionalArguments(new LiteralArgument("optional")) + .executesPlayer(executor.apply("2 1")) + ) + .then(new LiteralArgument("2") + .withOptionalArguments(new LiteralArgument("optional")) + .executesPlayer(executor.apply("2 2")) + ) + .withOptionalArguments(new LiteralArgument("optional")) + .executesPlayer(executor.apply("2")) + ) + .withOptionalArguments(new LiteralArgument("optional")) + .executesPlayer(executor.apply("")) + .register(); + + PlayerMock player = server.addPlayer(); + + // Base command path + assertStoresResult(player, "test", executedPath, ""); + assertEquals("/test", results.get()); + assertStoresResult(player, "test optional", executedPath, ""); + assertEquals("/test optional", results.get()); + + // "1" path + assertStoresResult(player, "test 1", executedPath, "1"); + assertEquals("/test 1", results.get()); + assertStoresResult(player, "test 1 optional", executedPath, "1"); + assertEquals("/test 1 optional", results.get()); + + // "1 1" path + assertStoresResult(player, "test 1 1", executedPath, "1 1"); + assertEquals("/test 1 1", results.get()); + assertStoresResult(player, "test 1 1 optional", executedPath, "1 1"); + assertEquals("/test 1 1 optional", results.get()); + + // "1 2" path + assertStoresResult(player, "test 1 2", executedPath, "1 2"); + assertEquals("/test 1 2", results.get()); + assertStoresResult(player, "test 1 2 optional", executedPath, "1 2"); + assertEquals("/test 1 2 optional", results.get()); + + // "2" path + assertStoresResult(player, "test 1", executedPath, "1"); + assertEquals("/test 1", results.get()); + assertStoresResult(player, "test 1 optional", executedPath, "1"); + assertEquals("/test 1 optional", results.get()); + + // "2 1" path + assertStoresResult(player, "test 2 1", executedPath, "2 1"); + assertEquals("/test 2 1", results.get()); + assertStoresResult(player, "test 2 1 optional", executedPath, "2 1"); + assertEquals("/test 2 1 optional", results.get()); + + // "2 2" path + assertStoresResult(player, "test 2 2", executedPath, "2 2"); + assertEquals("/test 2 2", results.get()); + assertStoresResult(player, "test 2 2 optional", executedPath, "2 2"); + assertEquals("/test 2 2 optional", results.get()); + + + assertNoMoreResults(executedPath); + assertNoMoreResults(results); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java new file mode 100644 index 0000000000..e4e53062e4 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java @@ -0,0 +1,415 @@ +package dev.jorel.commandapi.test; + +import dev.jorel.commandapi.CommandAPI; +import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.arguments.IntegerArgument; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.MultiLiteralArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests for making sure the {@link RegisteredCommand} information is correct when registering {@link CommandTree}s + */ +class CommandTreeRegisteredCommandTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + private RegisteredCommand registeredCommandNoHelpOrPermission(String name, List argsAsStr, String... aliases) { + return new RegisteredCommand(name, argsAsStr, + Optional.empty(), Optional.empty(), Optional.empty(), + aliases, + CommandPermission.NONE + ); + } + + private void assertCreatedRegisteredCommands(RegisteredCommand... commands) { + List expectedCommands = Arrays.asList(commands); + List actualCommands = CommandAPI.getRegisteredCommands(); + + if (expectedCommands.size() != actualCommands.size()) { + StringBuilder builder = new StringBuilder(); + builder.append("Expected ").append(expectedCommands.size()).append(" command(s), found ").append(actualCommands.size()).append(" command(s)."); + + builder.append("\nExpected: "); + addRegisteredCommandList(builder, expectedCommands); + builder.append("\nActual: "); + addRegisteredCommandList(builder, actualCommands); + + fail(builder.toString()); + } + + for (int i = 0; i < expectedCommands.size(); i++) { + RegisteredCommand expectedCommand = expectedCommands.get(i); + RegisteredCommand actualCommand = actualCommands.get(i); + + if (!Objects.equals(expectedCommand, actualCommand)) { + StringBuilder builder = new StringBuilder(); + builder.append("Command #").append(i + 1).append(" differed. Expected:\n"); + builder.append(expectedCommand); + builder.append("\nActual:\n"); + builder.append(actualCommand); + + builder.append("\nExpected list: "); + addRegisteredCommandList(builder, expectedCommands); + builder.append("\nActual list: "); + addRegisteredCommandList(builder, actualCommands); + + fail(builder.toString()); + } + } + } + + private void addRegisteredCommandList(StringBuilder builder, List commands) { + if (commands.isEmpty()) { + builder.append("[]"); + return; + } + + builder.append("[\n"); + for (RegisteredCommand command : commands) { + builder.append("\t"); + builder.append(command); + builder.append("\n"); + } + builder.append("]"); + } + + /********* + * Tests * + *********/ + + @Test + void testRegister() { + new CommandTree("command") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of())); + } + + @Test + void testRegisterHelpInformation() { + new CommandTree("command") + .withHelp("short description", "full description") + .withUsage( + "usage 1", + "usage 2", + "usage 3" + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + new RegisteredCommand( + "command", List.of(), + Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + new String[0], CommandPermission.NONE + ) + ); + } + + @Test + void testRegisterOpPermission() { + new CommandTree("command") + .withPermission(CommandPermission.OP) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + new RegisteredCommand( + "command", List.of(), + Optional.empty(), Optional.empty(), Optional.empty(), + new String[0], CommandPermission.OP + ) + ); + } + + @Test + void testRegisterStringPermission() { + new CommandTree("command") + .withPermission("permission") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + new RegisteredCommand( + "command", List.of(), + Optional.empty(), Optional.empty(), Optional.empty(), + new String[0], CommandPermission.fromString("permission") + ) + ); + } + + @Test + void testRegisterOneAlias() { + new CommandTree("command") + .withAliases("alias1") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1")); + } + + @Test + void testRegisterTwoAliases() { + new CommandTree("command") + .withAliases("alias1", "alias2") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1", "alias2")); + } + + @Test + void testRegisterOneBranch() { + new CommandTree("command") + .then(new StringArgument("string").executesPlayer(P_EXEC)) + .register(); + + assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument"))); + } + + @Test + void testRegisterTwoBranches() { + new CommandTree("command") + .then(new StringArgument("string").executesPlayer(P_EXEC)) + .then(new IntegerArgument("integer").executesPlayer(P_EXEC)) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")), + registeredCommandNoHelpOrPermission("command", List.of("integer:IntegerArgument")) + ); + } + + @Test + void testRegisterMultiLiteralArguments() { + new CommandTree("command") + .then( + new MultiLiteralArgument("literal1", "a", "b", "c") + .then(new MultiLiteralArgument("literal2", "d", "e", "f").executesPlayer(P_EXEC)) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "e:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "e:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "e:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "f:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "f:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "f:LiteralArgument")) + ); + } + + @Test + void testRegisterOneOptionalArgument() { + new CommandTree("command") + .withOptionalArguments(new StringArgument("string")) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")) + ); + } + + @Test + void testRegisterTwoOptionalArguments() { + new CommandTree("command") + .withOptionalArguments( + new StringArgument("string"), + new IntegerArgument("integer") + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")), + registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument", "integer:IntegerArgument")) + ); + } + + @Test + void testRegisterCombinedOptionalArguments() { + new CommandTree("command") + .withOptionalArguments( + new LiteralArgument("1").combineWith(new LiteralArgument("2")), + new LiteralArgument("3").combineWith(new LiteralArgument("4")) + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument")) + ); + } + + @Test + void testRegisterCombinedRequiredAndOptionalArguments() { + new CommandTree("command") + .then( + new LiteralArgument("1").combineWith(new LiteralArgument("2")) + .then( + new LiteralArgument("3").combineWith(new LiteralArgument("4")) + .withOptionalArguments( + new LiteralArgument("5").combineWith(new LiteralArgument("6")), + new LiteralArgument("7").combineWith(new LiteralArgument("8")) + ).executesPlayer(P_EXEC) + ) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of( + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" + )), + registeredCommandNoHelpOrPermission("command", List.of( + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument" + )), + registeredCommandNoHelpOrPermission("command", List.of( + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" + )) + ); + } + + ////////////////////////////////////// + // SUBTREES // + // The same as commands, but deeper // + ////////////////////////////////////// + + @Test + void testRegisterOneBranchAndBaseExecutable() { + new CommandTree("command") + .executesPlayer(P_EXEC) + .then( + new LiteralArgument("subcommand") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of()), + registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument")) + ); + } + + @Test + void testRegisterBranchesWithBranches() { + new CommandTree("command") + .then( + new LiteralArgument("subcommand1") + .then(new StringArgument("string1").executesPlayer(P_EXEC)) + .then(new IntegerArgument("integer1").executesPlayer(P_EXEC)) + ) + .then( + new LiteralArgument("subcommand2") + .then(new StringArgument("string2").executesPlayer(P_EXEC)) + .then(new IntegerArgument("integer2").executesPlayer(P_EXEC)) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "string1:StringArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "integer1:IntegerArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "string2:StringArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "integer2:IntegerArgument")) + ); + } + + @Test + void testRegisterBranchesWithOptionalArguments() { + new CommandTree("command") + .then( + new LiteralArgument("subcommand1") + .withOptionalArguments( + new LiteralArgument("a"), + new LiteralArgument("b") + ) + .executesPlayer(P_EXEC) + ) + .then( + new LiteralArgument("subcommand2") + .withOptionalArguments( + new LiteralArgument("c"), + new LiteralArgument("d") + ) + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument", "b:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument", "d:LiteralArgument")) + ); + } + + @Test + void testRegisterBranchesWithCombinedRequiredAndOptionalArguments() { + new CommandTree("command") + .then( + new LiteralArgument("subcommand1") + .then( + new LiteralArgument("1a").combineWith(new LiteralArgument("1b")) + .withOptionalArguments( + new LiteralArgument("1c").combineWith(new LiteralArgument("1d")) + ) + .executesPlayer(P_EXEC) + ) + ) + .then( + new LiteralArgument("subcommand2") + .then( + new LiteralArgument("2a").combineWith(new LiteralArgument("2b")) + .withOptionalArguments( + new LiteralArgument("2c").combineWith(new LiteralArgument("2d")) + ) + .executesPlayer(P_EXEC) + ) + ) + .register(); + + assertCreatedRegisteredCommands( + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument", "1c:LiteralArgument", "1d:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument")), + registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument", "2c:LiteralArgument", "2d:LiteralArgument")) + ); + } +} From 0c9bea6268c4a630064b973a258a7f466cf116f8 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 09:50:31 -0400 Subject: [PATCH 04/42] Remove { } from KotlinDSL inline if-else's https://github.com/JorelAli/CommandAPI/commit/48cdcf0b21337068150ccd0819ef6846a8852fac#r129980259 --- .../dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt | 2 +- .../kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt | 2 +- .../dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt | 2 +- .../kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index 6958981ba5..5fcbedf49c 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -24,7 +24,7 @@ inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): inline fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command) inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command)) -inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) { optionalArgument(argument, block) } else { argument(argument, block) } +inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) optionalArgument(argument, block) else argument(argument, block) // Integer arguments inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(IntegerArgument(nodeName, min, max), optional, block) diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index 144a97baf5..4484697ba9 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -17,7 +17,7 @@ inline fun CommandTree.argument(base: Argument<*>, block: Argument<*>.() -> Unit inline fun CommandTree.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = withOptionalArguments(base.apply(block)) -inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) { optionalArgument(base, block) } else { argument(base, block) } +inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) optionalArgument(base, block) else argument(base, block) // Integer arguments inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerArgument(nodeName, min, max), optional, block) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index 6b03c72e0f..1446996703 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -22,7 +22,7 @@ inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): inline fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command) inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command)) -inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) { optionalArgument(argument, block) } else { argument(argument, block) } +inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) optionalArgument(argument, block) else argument(argument, block) // Integer arguments inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(IntegerArgument(nodeName, min, max), optional, block) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index 4c1541a331..fdabe8c9fe 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -15,7 +15,7 @@ inline fun CommandTree.argument(base: Argument<*>, block: Argument<*>.() -> Unit inline fun CommandTree.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = withOptionalArguments(base.apply(block)) -inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) { optionalArgument(base, block) } else { argument(base, block) } +inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) optionalArgument(base, block) else argument(base, block) // Integer arguments inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerArgument(nodeName, min, max), optional, block) @@ -54,7 +54,7 @@ inline fun Argument<*>.argument(base: Argument<*>, block: Argument<*>.() -> Unit inline fun Argument<*>.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = withOptionalArguments(base.apply(block)) -inline fun Argument<*>.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): Argument<*> = if (optional) { optionalArgument(base, block) } else { argument(base, block) } +inline fun Argument<*>.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): Argument<*> = if (optional) optionalArgument(base, block) else argument(base, block) // Integer arguments inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(IntegerArgument(nodeName, min, max), optional, block) From 365580a8d4486395688b16102e17cc1a4a746918 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 11:24:56 -0400 Subject: [PATCH 05/42] Fix usage of `setOptional` in ExamplesKotlinDSL --- .../dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt index 39e6dd5d7d..ad6958ce71 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt @@ -1138,7 +1138,7 @@ commandAPICommand("sayhi") { /* ANCHOR: optionalArguments3 */ commandAPICommand("rate") { - argument(StringArgument("topic").setOptional(true).combineWith(IntegerArgument("rating", 0, 10))) + optionalArgument(StringArgument("topic").combineWith(IntegerArgument("rating", 0, 10))) playerArgument("target", optional = true) anyExecutor { sender, args -> val topic: String? = args["topic"] as String? From 5c41d50de5f8f5f43942596d2d80e07aa6860d3e Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:39:51 -0400 Subject: [PATCH 06/42] Remove optional arguments from CommandTrees --- .../commandapi/AbstractArgumentTree.java | 118 +------- .../jorel/commandapi/AbstractCommandTree.java | 99 +------ .../commandapi/kotlindsl/CommandTreeDSL.kt | 252 +++++++++--------- .../commandapi/kotlindsl/CommandTreeDSL.kt | 60 ++--- .../test/dsltests/OptionalArgumentTests.kt | 29 -- .../CommandTreeOptionalArgumentTests.java | 233 ---------------- .../CommandTreeRegisteredCommandTests.java | 105 ++------ ...tTests.java => OptionalArgumentTests.java} | 2 +- 8 files changed, 190 insertions(+), 708 deletions(-) delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java rename commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/{CommandAPICommandOptionalArgumentTests.java => OptionalArgumentTests.java} (99%) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 953b149378..ba1dbc7aab 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -7,7 +7,6 @@ import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -27,8 +26,7 @@ public abstract class AbstractArgumentTree extends Executable { private final Argument argument; - private List> branches = new ArrayList<>(); - private List optionalArguments = new ArrayList<>(); + private List> arguments = new ArrayList<>(); /** * Instantiates an {@link AbstractArgumentTree}. This can only be called if the class @@ -66,32 +64,10 @@ public AbstractArgumentTree(final Argument argument) { * @return this tree node */ public Impl then(final AbstractArgumentTree tree) { - this.branches.add(tree); + this.arguments.add(tree); return instance(); } - /** - * Adds optional arguments onto this node. - * - * @param optionalArguments A List of Arguments to add as optional arguments at this node. - * @return this command builder - */ - public Impl withOptionalArguments(List optionalArguments) { - this.optionalArguments.addAll(optionalArguments); - return instance(); - } - - /** - * Adds optional arguments onto this node. - * - * @param optionalArguments The Arguments to add as optional arguments at this node. - * @return this command builder - */ - @SafeVarargs - public final Impl withOptionalArguments(Argument... optionalArguments) { - return this.withOptionalArguments(Arrays.asList(optionalArguments)); - } - ///////////////////////// // Getters and setters // ///////////////////////// @@ -100,7 +76,7 @@ public final Impl withOptionalArguments(Argument... optionalArguments) { * @return The child branches added to this tree by {@link #then(AbstractArgumentTree)}. */ public List> getArguments() { - return branches; + return arguments; } /** @@ -109,23 +85,7 @@ public List> getArguments() { * @param arguments A new list of branches for this node */ public void setArguments(List> arguments) { - this.branches = arguments; - } - - /** - * @return The optional arguments added to this tree by {@link #withOptionalArguments(List)}. - */ - public List getOptionalArguments() { - return optionalArguments; - } - - /** - * Sets the optional arguments that this node has - * - * @param optionalArguments A new list of optional arguments for this node - */ - public void setOptionalArguments(List optionalArguments) { - this.optionalArguments = optionalArguments; + this.arguments = arguments; } ////////////////// @@ -144,31 +104,11 @@ public List> getBranchesAsStrings() { baseArgumentPaths.add(new ArrayList<>()); argument.appendToCommandPaths(baseArgumentPaths); - // Build optional argument paths, if it is executable - if (executor.hasAnyExecutors()) { - // Just the argument is a valid path - List slicePositions = new ArrayList<>(); - // Note: Assumption that all paths are the same length - // E.g. One `Argument argument : optionalArguments` won't expand to [arg1] and [arg1, arg2] - // Either ([arg1] and [arg2]) or ([arg1, part2] and [arg2, part2]) etc. - slicePositions.add(baseArgumentPaths.get(0).size()); - - // Each optional argument is a potential stopping point - for (Argument argument : optionalArguments) { - argument.appendToCommandPaths(baseArgumentPaths); - slicePositions.add(baseArgumentPaths.get(0).size()); - } - - // Return each path as sublists of the main path - for (List path : baseArgumentPaths) { - for (int slicePos : slicePositions) { - argumentStrings.add(path.subList(0, slicePos)); - } - } - } + // If this node is executable, add it as a valid path + if (this.executor.hasAnyExecutors()) argumentStrings.addAll(baseArgumentPaths); // Add paths for the branches - for (AbstractArgumentTree child : branches) { + for (AbstractArgumentTree child : arguments) { for (List subArgs : child.getBranchesAsStrings()) { for (List basePath : baseArgumentPaths) { List mergedPaths = new ArrayList<>(); @@ -192,14 +132,12 @@ public List> getBranchesAsStrings() { */ public void buildBrigadierNode(CommandNode previousNode, List previousArguments, List previousNonLiteralArgumentNames) { // Check preconditions - if (argument instanceof GreedyArgument && (!branches.isEmpty() || !optionalArguments.isEmpty())) { + if (argument instanceof GreedyArgument && !arguments.isEmpty()) { // Argument is followed by at least some arguments throw new GreedyArgumentException(previousArguments, argument, getBranchesAsList()); } - if (!executor.hasAnyExecutors() && (branches.isEmpty() || !optionalArguments.isEmpty())) { - // If we don't have any executors then: - // No branches is bad because this path can't be run at all - // Having arguments is bad because developer intended this path to be executable with arguments + if (!executor.hasAnyExecutors() && arguments.isEmpty()) { + // If we don't have any executors then no branches is bad because this path can't be run at all throw new MissingCommandExecutorException(previousArguments, argument); } @@ -207,22 +145,13 @@ public void buildBrigadierNode(CommandNode previousNode, List rootNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); // Add our branches as children to the node - for (AbstractArgumentTree child : branches) { + for (AbstractArgumentTree child : arguments) { // We need a new list for each branch of the tree List newPreviousArguments = new ArrayList<>(previousArguments); List newPreviousArgumentNames = new ArrayList<>(previousNonLiteralArgumentNames); child.buildBrigadierNode(rootNode, newPreviousArguments, newPreviousArgumentNames); } - - // Build optional argument paths - if (!optionalArguments.isEmpty()) { - previousNode = rootNode; - for (Argument argument : optionalArguments) { - // All optional arguments are executable - previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); - } - } } /** @@ -230,8 +159,8 @@ public void buildBrigadierNode(CommandNode previousNode, List> getBranchesAsList() { List> branchesList = new ArrayList<>(); - // Add branches - for (AbstractArgumentTree branch : branches) { + + for (AbstractArgumentTree branch : arguments) { for (List subBranchList : branch.getBranchesAsList()) { List newBranchList = new ArrayList<>(); newBranchList.add(branch.argument); @@ -239,27 +168,6 @@ protected List> getBranchesAsList() { branchesList.add(newBranchList); } } - // Add optional arguments - if (!optionalArguments.isEmpty()) { - List> optionalPaths = new ArrayList<>(); - List slicePositions = new ArrayList<>(); - // Note: Assumption that all paths are the same length - // E.g. One `Argument argument : optionalArguments` won't expand to [arg1] and [arg1, arg2] - // Either ([arg1] and [arg2]) or ([arg1, part2] and [arg2, part2]) etc. - - // Each optional argument is a potential stopping point - for (Argument argument : optionalArguments) { - argument.unpackCombinedArguments(optionalPaths); - slicePositions.add(optionalPaths.get(0).size()); - } - - // Return each path as sublists of the main path - for (List path : optionalPaths) { - for (int slicePos : slicePositions) { - branchesList.add(path.subList(0, slicePos)); - } - } - } return branchesList; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index e25e2fcb10..c8ee1bc701 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -1,12 +1,10 @@ package dev.jorel.commandapi; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -25,8 +23,7 @@ public abstract class AbstractCommandTree extends ExecutableCommand { - private List> branches = new ArrayList<>(); - private List optionalArguments = new ArrayList<>(); + private List> arguments = new ArrayList<>(); /** * Creates a main root node for a command tree with a given command name @@ -48,32 +45,10 @@ protected AbstractCommandTree(final String commandName) { * @return this root node */ public Impl then(final AbstractArgumentTree tree) { - this.branches.add(tree); + this.arguments.add(tree); return instance(); } - /** - * Adds optional arguments onto this node. - * - * @param optionalArguments A List of Arguments to add as optional arguments at this node. - * @return this command builder - */ - public Impl withOptionalArguments(List optionalArguments) { - this.optionalArguments.addAll(optionalArguments); - return instance(); - } - - /** - * Adds optional arguments onto this node. - * - * @param optionalArguments The Arguments to add as optional arguments at this node. - * @return this command builder - */ - @SafeVarargs - public final Impl withOptionalArguments(Argument... optionalArguments) { - return this.withOptionalArguments(Arrays.asList(optionalArguments)); - } - ///////////////////////// // Getters and setters // ///////////////////////// @@ -82,7 +57,7 @@ public final Impl withOptionalArguments(Argument... optionalArguments) { * @return The child branches added to this tree by {@link #then(AbstractArgumentTree)}. */ public List> getArguments() { - return branches; + return arguments; } /** @@ -91,23 +66,7 @@ public List> getArguments() { * @param arguments A new list of branches for this command */ public void setArguments(List> arguments) { - this.branches = arguments; - } - - /** - * @return The optional arguments added to this tree by {@link #withOptionalArguments(List)}. - */ - public List getOptionalArguments() { - return optionalArguments; - } - - /** - * Sets the optional arguments that this command has - * - * @param optionalArguments A new list of optional arguments for this command - */ - public void setOptionalArguments(List optionalArguments) { - this.optionalArguments = optionalArguments; + this.arguments = arguments; } ////////////////// @@ -117,36 +76,15 @@ public void setOptionalArguments(List optionalArguments) { @Override public List> getArgumentsAsStrings() { // Return an empty list if we have no arguments - if (branches.isEmpty() && optionalArguments.isEmpty()) return List.of(List.of()); + if (arguments.isEmpty()) return List.of(List.of()); List> argumentStrings = new ArrayList<>(); - // Build optional argument paths, if it is executable - if (executor.hasAnyExecutors()) { - List> currentPaths = new ArrayList<>(); - currentPaths.add(new ArrayList<>()); - - // Just the command is a valid path - List slicePositions = new ArrayList<>(); - // Note: Assumption that all paths are the same length - slicePositions.add(0); - - // Each optional argument is a potential stopping point - for (Argument argument : optionalArguments) { - argument.appendToCommandPaths(currentPaths); - slicePositions.add(currentPaths.get(0).size()); - } - - // Return each path as sublists of the main path - for (List path : currentPaths) { - for (int slicePos : slicePositions) { - argumentStrings.add(path.subList(0, slicePos)); - } - } - } + // If this node is executable, no arguments is a valid path + if (this.executor.hasAnyExecutors()) argumentStrings.add(List.of()); // Add branching paths - for (AbstractArgumentTree argument : branches) { + for (AbstractArgumentTree argument : arguments) { argumentStrings.addAll(argument.getBranchesAsStrings()); } @@ -155,10 +93,8 @@ public List> getArgumentsAsStrings() { @Override protected void checkPreconditions() { - if (!executor.hasAnyExecutors() && (branches.isEmpty() || !optionalArguments.isEmpty())) { - // If we don't have any executors then: - // No branches is bad because this path can't be run at all - // Having arguments is bad because developer intended this path to be executable with arguments + if (!executor.hasAnyExecutors() && arguments.isEmpty()) { + // If we don't have any executors then no branches is bad because this path can't be run at all throw new MissingCommandExecutorException(name); } } @@ -181,7 +117,7 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) commandNames.setListed(false); // Build branches - for (AbstractArgumentTree argument : branches) { + for (AbstractArgumentTree argument : arguments) { // We need new previousArguments lists for each branch so they don't interfere List previousArguments = new ArrayList<>(); List previousNonLiteralArgumentNames = new ArrayList<>(); @@ -189,18 +125,5 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) argument.buildBrigadierNode(rootNode, previousArguments, previousNonLiteralArgumentNames); } - - // Build optional argument paths - if (!optionalArguments.isEmpty()) { - CommandNode previousNode = rootNode; - List previousArguments = new ArrayList<>(); - List previousNonLiteralArgumentNames = new ArrayList<>(); - previousArguments.add(commandNames); - - for (Argument argument : optionalArguments) { - // All optional arguments are executable - previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); - } - } } } diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index 4484697ba9..07b06ed38e 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -15,218 +15,210 @@ inline fun commandTree(name: String, predicate: Predicate, tree: // CommandTree start inline fun CommandTree.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.apply(block)) -inline fun CommandTree.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = withOptionalArguments(base.apply(block)) - -inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) optionalArgument(base, block) else argument(base, block) - // Integer arguments -inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerArgument(nodeName, min, max), optional, block) -inline fun CommandTree.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerRangeArgument(nodeName), optional, block) +inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(IntegerArgument(nodeName, min, max).apply(block)) +inline fun CommandTree.integerRangeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(IntegerRangeArgument(nodeName).apply(block)) // Float arguments -inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FloatArgument(nodeName, min, max), optional, block) -inline fun CommandTree.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FloatRangeArgument(nodeName), optional, block) +inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(FloatArgument(nodeName, min, max).apply(block)) +inline fun CommandTree.floatRangeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(FloatRangeArgument(nodeName).apply(block)) // Double arguments -inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(DoubleArgument(nodeName, min, max), optional, block) +inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(DoubleArgument(nodeName, min, max).apply(block)) // Long arguments -inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LongArgument(nodeName, min, max), optional, block) +inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(LongArgument(nodeName, min, max).apply(block)) // Boolean argument -inline fun CommandTree.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BooleanArgument(nodeName), optional, block) +inline fun CommandTree.booleanArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(BooleanArgument(nodeName).apply(block)) // String arguments -inline fun CommandTree.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(StringArgument(nodeName), optional, block) -inline fun CommandTree.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TextArgument(nodeName), optional, block) -inline fun CommandTree.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(GreedyStringArgument(nodeName), optional, block) +inline fun CommandTree.stringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(StringArgument(nodeName).apply(block)) +inline fun CommandTree.textArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(TextArgument(nodeName).apply(block)) +inline fun CommandTree.greedyStringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(GreedyStringArgument(nodeName).apply(block)) // Positional arguments -inline fun CommandTree.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LocationArgument(nodeName, locationType, centerPosition), optional, block) -inline fun CommandTree.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(Location2DArgument(nodeName, locationType, centerPosition), optional, block) -inline fun CommandTree.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(RotationArgument(nodeName), optional, block) -inline fun CommandTree.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AxisArgument(nodeName), optional, block) +inline fun CommandTree.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, block: Argument<*>.() -> Unit = {}): CommandTree = then(LocationArgument(nodeName, locationType, centerPosition).apply(block)) +inline fun CommandTree.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, block: Argument<*>.() -> Unit = {}): CommandTree = then(Location2DArgument(nodeName, locationType, centerPosition).apply(block)) +inline fun CommandTree.rotationArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(RotationArgument(nodeName).apply(block)) +inline fun CommandTree.axisArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(AxisArgument(nodeName).apply(block)) // Chat arguments -inline fun CommandTree.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ChatColorArgument(nodeName), optional, block) -inline fun CommandTree.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ChatComponentArgument(nodeName), optional, block) -inline fun CommandTree.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ChatArgument(nodeName), optional, block) -inline fun CommandTree.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdventureChatColorArgument(nodeName), optional, block) -inline fun CommandTree.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdventureChatComponentArgument(nodeName), optional, block) -inline fun CommandTree.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdventureChatArgument(nodeName), optional, block) +inline fun CommandTree.chatColorArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ChatColorArgument(nodeName).apply(block)) +inline fun CommandTree.chatComponentArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ChatComponentArgument(nodeName).apply(block)) +inline fun CommandTree.chatArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ChatArgument(nodeName).apply(block)) +inline fun CommandTree.adventureChatColorArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdventureChatColorArgument(nodeName).apply(block)) +inline fun CommandTree.adventureChatComponentArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdventureChatComponentArgument(nodeName).apply(block)) +inline fun CommandTree.adventureChatArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdventureChatArgument(nodeName).apply(block)) // Entity & Player arguments -inline fun CommandTree.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.OneEntity(nodeName), optional, block) -inline fun CommandTree.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty), optional, block) -inline fun CommandTree.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.OnePlayer(nodeName), optional, block) -inline fun CommandTree.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty), optional, block) -inline fun CommandTree.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(PlayerArgument(nodeName), optional, block) -inline fun CommandTree.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(OfflinePlayerArgument(nodeName), optional, block) -inline fun CommandTree.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EntityTypeArgument(nodeName), optional, block) +inline fun CommandTree.entitySelectorArgumentOneEntity(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.OneEntity(nodeName).apply(block)) +inline fun CommandTree.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty).apply(block)) +inline fun CommandTree.entitySelectorArgumentOnePlayer(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.OnePlayer(nodeName).apply(block)) +inline fun CommandTree.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty).apply(block)) +inline fun CommandTree.playerArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(PlayerArgument(nodeName).apply(block)) +inline fun CommandTree.offlinePlayerArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(OfflinePlayerArgument(nodeName).apply(block)) +inline fun CommandTree.entityTypeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(EntityTypeArgument(nodeName).apply(block)) // Scoreboard arguments -inline fun CommandTree.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ScoreHolderArgument.Single(nodeName), optional, block) -inline fun CommandTree.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ScoreHolderArgument.Multiple(nodeName), optional, block) -inline fun CommandTree.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ScoreboardSlotArgument(nodeName), optional, block) -inline fun CommandTree.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ObjectiveArgument(nodeName), optional, block) -inline fun CommandTree.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ObjectiveCriteriaArgument(nodeName), optional, block) -inline fun CommandTree.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TeamArgument(nodeName), optional, block) +inline fun CommandTree.scoreHolderArgumentSingle(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ScoreHolderArgument.Single(nodeName).apply(block)) +inline fun CommandTree.scoreHolderArgumentMultiple(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ScoreHolderArgument.Multiple(nodeName).apply(block)) +inline fun CommandTree.scoreboardSlotArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ScoreboardSlotArgument(nodeName).apply(block)) +inline fun CommandTree.objectiveArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ObjectiveArgument(nodeName).apply(block)) +inline fun CommandTree.objectiveCriteriaArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ObjectiveCriteriaArgument(nodeName).apply(block)) +inline fun CommandTree.teamArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(TeamArgument(nodeName).apply(block)) // Miscellaneous arguments -inline fun CommandTree.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AngleArgument(nodeName), optional, block) -inline fun CommandTree.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(AdvancementArgument(nodeName), optional, block) +inline fun CommandTree.angleArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(AngleArgument(nodeName).apply(block)) +inline fun CommandTree.advancementArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(AdvancementArgument(nodeName).apply(block)) -inline fun CommandTree.biomeArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = - if (useNamespacedKey) addArgument(BiomeArgument.NamespacedKey(nodeName), optional, block) else addArgument(BiomeArgument(nodeName), optional, block) +inline fun CommandTree.biomeArgument(nodeName: String, useNamespacedKey: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = + if (useNamespacedKey) then(BiomeArgument.NamespacedKey(nodeName).apply(block)) else then(BiomeArgument(nodeName).apply(block)) -inline fun CommandTree.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BlockStateArgument(nodeName), optional, block) -inline fun CommandTree.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(CommandArgument(nodeName), optional, block) -inline fun CommandTree.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(EnchantmentArgument(nodeName), optional, block) +inline fun CommandTree.blockStateArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(BlockStateArgument(nodeName).apply(block)) +inline fun CommandTree.commandArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(CommandArgument(nodeName).apply(block)) +inline fun CommandTree.enchantmentArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(EnchantmentArgument(nodeName).apply(block)) -inline fun CommandTree.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ItemStackArgument(nodeName), optional, block) -inline fun CommandTree.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LootTableArgument(nodeName), optional, block) -inline fun CommandTree.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(MathOperationArgument(nodeName), optional, block) -inline fun CommandTree.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(NamespacedKeyArgument(nodeName), optional, block) -inline fun CommandTree.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ParticleArgument(nodeName), optional, block) +inline fun CommandTree.itemStackArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ItemStackArgument(nodeName).apply(block)) +inline fun CommandTree.lootTableArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(LootTableArgument(nodeName).apply(block)) +inline fun CommandTree.mathOperationArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(MathOperationArgument(nodeName).apply(block)) +inline fun CommandTree.namespacedKeyArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(NamespacedKeyArgument(nodeName).apply(block)) +inline fun CommandTree.particleArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ParticleArgument(nodeName).apply(block)) inline fun CommandTree.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = - if (useNamespacedKey) addArgument(PotionEffectArgument.NamespacedKey(nodeName), optional, block) else addArgument(PotionEffectArgument(nodeName), optional, block) -inline fun CommandTree.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(RecipeArgument(nodeName), optional, block) + if (useNamespacedKey) then(PotionEffectArgument.NamespacedKey(nodeName).apply(block)) else then(PotionEffectArgument(nodeName).apply(block)) +inline fun CommandTree.recipeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(RecipeArgument(nodeName).apply(block)) -inline fun CommandTree.soundArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = - if (useNamespacedKey) addArgument(SoundArgument.NamespacedKey(nodeName), optional, block) else addArgument(SoundArgument(nodeName), optional, block) +inline fun CommandTree.soundArgument(nodeName: String, useNamespacedKey: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = + if (useNamespacedKey) then(SoundArgument.NamespacedKey(nodeName).apply(block)) else then(SoundArgument(nodeName).apply(block)) -inline fun CommandTree.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TimeArgument(nodeName), optional, block) -inline fun CommandTree.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(UUIDArgument(nodeName), optional, block) -inline fun CommandTree.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(WorldArgument(nodeName), optional, block) +inline fun CommandTree.timeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(TimeArgument(nodeName).apply(block)) +inline fun CommandTree.uuidArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(UUIDArgument(nodeName).apply(block)) +inline fun CommandTree.worldArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(WorldArgument(nodeName).apply(block)) // Predicate arguments -inline fun CommandTree.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BlockPredicateArgument(nodeName), optional, block) -inline fun CommandTree.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(ItemStackPredicateArgument(nodeName), optional, block) +inline fun CommandTree.blockPredicateArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(BlockPredicateArgument(nodeName).apply(block)) +inline fun CommandTree.itemStackPredicateArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ItemStackPredicateArgument(nodeName).apply(block)) // NBT arguments -inline fun CommandTree.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(NBTCompoundArgument(nodeName), optional, block) +inline fun CommandTree.nbtCompoundArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(NBTCompoundArgument(nodeName).apply(block)) // Literal arguments -inline fun CommandTree.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(literal, literal), optional, block) -inline fun CommandTree.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(nodeName, literal), optional, block) +inline fun CommandTree.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(literal, literal).apply(block)) +inline fun CommandTree.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(nodeName, literal).apply(block)) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(literals), optional, block) +inline fun CommandTree.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, literals), optional, block) +inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, literals).apply(block)) -inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) +inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, *literals).apply(block)) // Function arguments -inline fun CommandTree.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FunctionArgument(nodeName), optional, block) +inline fun CommandTree.functionArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(FunctionArgument(nodeName).apply(block)) // ArgumentTree start inline fun Argument<*>.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.apply(block)) -inline fun Argument<*>.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = withOptionalArguments(base.apply(block)) - -inline fun Argument<*>.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): Argument<*> = if (optional) { optionalArgument(base, block) } else { argument(base, block) } - // Integer arguments -inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(IntegerArgument(nodeName, min, max), optional, block) -inline fun Argument<*>.integerRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(IntegerRangeArgument(nodeName), optional, block) +inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(IntegerArgument(nodeName, min, max).apply(block)) +inline fun Argument<*>.integerRangeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(IntegerRangeArgument(nodeName).apply(block)) // Float arguments -inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FloatArgument(nodeName, min, max), optional, block) -inline fun Argument<*>.floatRangeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FloatRangeArgument(nodeName), optional, block) +inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FloatArgument(nodeName, min, max).apply(block)) +inline fun Argument<*>.floatRangeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FloatRangeArgument(nodeName).apply(block)) // Double arguments -inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(DoubleArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(DoubleArgument(nodeName, min, max).apply(block)) // Long arguments -inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LongArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LongArgument(nodeName, min, max).apply(block)) // Boolean argument -inline fun Argument<*>.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BooleanArgument(nodeName), optional, block) +inline fun Argument<*>.booleanArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BooleanArgument(nodeName).apply(block)) // String arguments -inline fun Argument<*>.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(StringArgument(nodeName), optional, block) -inline fun Argument<*>.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TextArgument(nodeName), optional, block) -inline fun Argument<*>.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(GreedyStringArgument(nodeName), optional, block) +inline fun Argument<*>.stringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(StringArgument(nodeName).apply(block)) +inline fun Argument<*>.textArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TextArgument(nodeName).apply(block)) +inline fun Argument<*>.greedyStringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(GreedyStringArgument(nodeName).apply(block)) // Positional arguments -inline fun Argument<*>.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LocationArgument(nodeName, locationType, centerPosition), optional, block) -inline fun Argument<*>.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(Location2DArgument(nodeName, locationType, centerPosition), optional, block) -inline fun Argument<*>.rotationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(RotationArgument(nodeName), optional, block) -inline fun Argument<*>.axisArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AxisArgument(nodeName), optional, block) +inline fun Argument<*>.locationArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LocationArgument(nodeName, locationType, centerPosition).apply(block)) +inline fun Argument<*>.location2DArgument(nodeName: String, locationType: LocationType = LocationType.PRECISE_POSITION, centerPosition: Boolean = true, block: Argument<*>.() -> Unit = {}): Argument<*> = then(Location2DArgument(nodeName, locationType, centerPosition).apply(block)) +inline fun Argument<*>.rotationArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(RotationArgument(nodeName).apply(block)) +inline fun Argument<*>.axisArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AxisArgument(nodeName).apply(block)) // Chat arguments -inline fun Argument<*>.chatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ChatColorArgument(nodeName), optional, block) -inline fun Argument<*>.chatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ChatComponentArgument(nodeName), optional, block) -inline fun Argument<*>.chatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ChatArgument(nodeName), optional, block) -inline fun Argument<*>.adventureChatColorArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdventureChatColorArgument(nodeName), optional, block) -inline fun Argument<*>.adventureChatComponentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdventureChatComponentArgument(nodeName), optional, block) -inline fun Argument<*>.adventureChatArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdventureChatArgument(nodeName), optional, block) +inline fun Argument<*>.chatColorArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ChatColorArgument(nodeName).apply(block)) +inline fun Argument<*>.chatComponentArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ChatComponentArgument(nodeName).apply(block)) +inline fun Argument<*>.chatArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ChatArgument(nodeName).apply(block)) +inline fun Argument<*>.adventureChatColorArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdventureChatColorArgument(nodeName).apply(block)) +inline fun Argument<*>.adventureChatComponentArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdventureChatComponentArgument(nodeName).apply(block)) +inline fun Argument<*>.adventureChatArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdventureChatArgument(nodeName).apply(block)) // Entity & Player arguments -inline fun Argument<*>.entitySelectorArgumentOneEntity(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.OneEntity(nodeName), optional, block) -inline fun Argument<*>.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty), optional, block) -inline fun Argument<*>.entitySelectorArgumentOnePlayer(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.OnePlayer(nodeName), optional, block) -inline fun Argument<*>.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty), optional, block) -inline fun Argument<*>.playerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(PlayerArgument(nodeName), optional, block) -inline fun Argument<*>.offlinePlayerArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(OfflinePlayerArgument(nodeName), optional, block) -inline fun Argument<*>.entityTypeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EntityTypeArgument(nodeName), optional, block) +inline fun Argument<*>.entitySelectorArgumentOneEntity(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.OneEntity(nodeName).apply(block)) +inline fun Argument<*>.entitySelectorArgumentManyEntities(nodeName: String, allowEmpty: Boolean = true, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.ManyEntities(nodeName, allowEmpty).apply(block)) +inline fun Argument<*>.entitySelectorArgumentOnePlayer(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.OnePlayer(nodeName).apply(block)) +inline fun Argument<*>.entitySelectorArgumentManyPlayers(nodeName: String, allowEmpty: Boolean = true, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntitySelectorArgument.ManyPlayers(nodeName, allowEmpty).apply(block)) +inline fun Argument<*>.playerArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(PlayerArgument(nodeName).apply(block)) +inline fun Argument<*>.offlinePlayerArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(OfflinePlayerArgument(nodeName).apply(block)) +inline fun Argument<*>.entityTypeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EntityTypeArgument(nodeName).apply(block)) // Scoreboard arguments -inline fun Argument<*>.scoreHolderArgumentSingle(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ScoreHolderArgument.Single(nodeName), optional, block) -inline fun Argument<*>.scoreHolderArgumentMultiple(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ScoreHolderArgument.Multiple(nodeName), optional, block) -inline fun Argument<*>.scoreboardSlotArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ScoreboardSlotArgument(nodeName), optional, block) -inline fun Argument<*>.objectiveArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ObjectiveArgument(nodeName), optional, block) -inline fun Argument<*>.objectiveCriteriaArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ObjectiveCriteriaArgument(nodeName), optional, block) -inline fun Argument<*>.teamArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TeamArgument(nodeName), optional, block) +inline fun Argument<*>.scoreHolderArgumentSingle(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ScoreHolderArgument.Single(nodeName).apply(block)) +inline fun Argument<*>.scoreHolderArgumentMultiple(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ScoreHolderArgument.Multiple(nodeName).apply(block)) +inline fun Argument<*>.scoreboardSlotArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ScoreboardSlotArgument(nodeName).apply(block)) +inline fun Argument<*>.objectiveArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ObjectiveArgument(nodeName).apply(block)) +inline fun Argument<*>.objectiveCriteriaArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ObjectiveCriteriaArgument(nodeName).apply(block)) +inline fun Argument<*>.teamArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TeamArgument(nodeName).apply(block)) // Miscellaneous arguments -inline fun Argument<*>.angleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AngleArgument(nodeName), optional, block) -inline fun Argument<*>.advancementArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(AdvancementArgument(nodeName), optional, block) +inline fun Argument<*>.angleArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AngleArgument(nodeName).apply(block)) +inline fun Argument<*>.advancementArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(AdvancementArgument(nodeName).apply(block)) -inline fun Argument<*>.biomeArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = - if (useNamespacedKey) addArgument(BiomeArgument.NamespacedKey(nodeName), optional, block) else addArgument(BiomeArgument(nodeName), optional, block) +inline fun Argument<*>.biomeArgument(nodeName: String, useNamespacedKey: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = + if (useNamespacedKey) then(BiomeArgument.NamespacedKey(nodeName).apply(block)) else then(BiomeArgument(nodeName).apply(block)) -inline fun Argument<*>.blockStateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BlockStateArgument(nodeName), optional, block) -inline fun Argument<*>.commandArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(CommandArgument(nodeName), optional, block) -inline fun Argument<*>.enchantmentArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(EnchantmentArgument(nodeName), optional, block) +inline fun Argument<*>.blockStateArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BlockStateArgument(nodeName).apply(block)) +inline fun Argument<*>.commandArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(CommandArgument(nodeName).apply(block)) +inline fun Argument<*>.enchantmentArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(EnchantmentArgument(nodeName).apply(block)) -inline fun Argument<*>.itemStackArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ItemStackArgument(nodeName), optional, block) -inline fun Argument<*>.lootTableArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LootTableArgument(nodeName), optional, block) -inline fun Argument<*>.mathOperationArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MathOperationArgument(nodeName), optional, block) -inline fun Argument<*>.namespacedKeyArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(NamespacedKeyArgument(nodeName), optional, block) -inline fun Argument<*>.particleArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ParticleArgument(nodeName), optional, block) +inline fun Argument<*>.itemStackArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ItemStackArgument(nodeName).apply(block)) +inline fun Argument<*>.lootTableArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LootTableArgument(nodeName).apply(block)) +inline fun Argument<*>.mathOperationArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MathOperationArgument(nodeName).apply(block)) +inline fun Argument<*>.namespacedKeyArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(NamespacedKeyArgument(nodeName).apply(block)) +inline fun Argument<*>.particleArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ParticleArgument(nodeName).apply(block)) inline fun Argument<*>.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = - if (useNamespacedKey) addArgument(PotionEffectArgument.NamespacedKey(nodeName), optional, block) else addArgument(PotionEffectArgument(nodeName), optional, block) -inline fun Argument<*>.recipeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(RecipeArgument(nodeName), optional, block) + if (useNamespacedKey) then(PotionEffectArgument.NamespacedKey(nodeName).apply(block)) else then(PotionEffectArgument(nodeName).apply(block)) +inline fun Argument<*>.recipeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(RecipeArgument(nodeName).apply(block)) -inline fun Argument<*>.soundArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = - if (useNamespacedKey) addArgument(SoundArgument.NamespacedKey(nodeName), optional, block) else addArgument(SoundArgument(nodeName), optional, block) +inline fun Argument<*>.soundArgument(nodeName: String, useNamespacedKey: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = + if (useNamespacedKey) then(SoundArgument.NamespacedKey(nodeName).apply(block)) else then(SoundArgument(nodeName).apply(block)) -inline fun Argument<*>.timeArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TimeArgument(nodeName), optional, block) -inline fun Argument<*>.uuidArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(UUIDArgument(nodeName), optional, block) -inline fun Argument<*>.worldArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(WorldArgument(nodeName), optional, block) +inline fun Argument<*>.timeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TimeArgument(nodeName).apply(block)) +inline fun Argument<*>.uuidArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(UUIDArgument(nodeName).apply(block)) +inline fun Argument<*>.worldArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(WorldArgument(nodeName).apply(block)) // Predicate arguments -inline fun Argument<*>.blockPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BlockPredicateArgument(nodeName), optional, block) -inline fun Argument<*>.itemStackPredicateArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(ItemStackPredicateArgument(nodeName), optional, block) +inline fun Argument<*>.blockPredicateArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BlockPredicateArgument(nodeName).apply(block)) +inline fun Argument<*>.itemStackPredicateArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ItemStackPredicateArgument(nodeName).apply(block)) // NBT arguments -inline fun Argument<*>.nbtCompoundArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(NBTCompoundArgument(nodeName), optional, block) +inline fun Argument<*>.nbtCompoundArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(NBTCompoundArgument(nodeName).apply(block)) // Literal arguments -inline fun Argument<*>.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(literal, literal), optional, block) -inline fun Argument<*>.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(nodeName, literal), optional, block) +inline fun Argument<*>.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(literal, literal).apply(block)) +inline fun Argument<*>.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(nodeName, literal).apply(block)) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(literals), optional, block) +inline fun Argument<*>.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, literals).apply(block)) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, *literals).apply(block)) // Function arguments -inline fun Argument<*>.functionArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FunctionArgument(nodeName), optional, block) +inline fun Argument<*>.functionArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FunctionArgument(nodeName).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0 as it is not needed anymore. See the documentation for more information", ReplaceWith(""), DeprecationLevel.WARNING) inline fun CommandTree.requirement(base: Argument<*>, predicate: Predicate, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.withRequirement(predicate).apply(block)) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index fdabe8c9fe..9e8d1a895e 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -13,79 +13,71 @@ inline fun commandTree(name: String, predicate: Predicate, tree: // CommandTree start inline fun CommandTree.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.apply(block)) -inline fun CommandTree.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandTree = withOptionalArguments(base.apply(block)) - -inline fun CommandTree.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandTree = if(optional) optionalArgument(base, block) else argument(base, block) - // Integer arguments -inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(IntegerArgument(nodeName, min, max), optional, block) +inline fun CommandTree.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(IntegerArgument(nodeName, min, max).apply(block)) // Float arguments -inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(FloatArgument(nodeName, min, max), optional, block) +inline fun CommandTree.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(FloatArgument(nodeName, min, max).apply(block)) // Double arguments -inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(DoubleArgument(nodeName, min, max), optional, block) +inline fun CommandTree.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(DoubleArgument(nodeName, min, max).apply(block)) // Long arguments -inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(LongArgument(nodeName, min, max), optional, block) +inline fun CommandTree.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, block: Argument<*>.() -> Unit = {}): CommandTree = then(LongArgument(nodeName, min, max).apply(block)) // Boolean argument -inline fun CommandTree.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(BooleanArgument(nodeName), optional, block) +inline fun CommandTree.booleanArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(BooleanArgument(nodeName).apply(block)) // String arguments -inline fun CommandTree.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(StringArgument(nodeName), optional, block) -inline fun CommandTree.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(TextArgument(nodeName), optional, block) -inline fun CommandTree.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = addArgument(GreedyStringArgument(nodeName), optional, block) +inline fun CommandTree.stringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(StringArgument(nodeName).apply(block)) +inline fun CommandTree.textArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(TextArgument(nodeName).apply(block)) +inline fun CommandTree.greedyStringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(GreedyStringArgument(nodeName).apply(block)) // Literal arguments -inline fun CommandTree.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(literal, literal), optional, block) -inline fun CommandTree.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(LiteralArgument.of(nodeName, literal), optional, block) +inline fun CommandTree.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(literal, literal).apply(block)) +inline fun CommandTree.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(nodeName, literal).apply(block)) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(literals), optional, block) +inline fun CommandTree.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, literals), optional, block) +inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, literals).apply(block)) -inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree= addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) +inline fun CommandTree.multiLiteralArgument(nodeName: String, vararg literals: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, *literals).apply(block)) // ArgumentTree start inline fun Argument<*>.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.apply(block)) -inline fun Argument<*>.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = withOptionalArguments(base.apply(block)) - -inline fun Argument<*>.addArgument(base: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): Argument<*> = if (optional) optionalArgument(base, block) else argument(base, block) - // Integer arguments -inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(IntegerArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(IntegerArgument(nodeName, min, max).apply(block)) // Float arguments -inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(FloatArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.floatArgument(nodeName: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(FloatArgument(nodeName, min, max).apply(block)) // Double arguments -inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(DoubleArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.doubleArgument(nodeName: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(DoubleArgument(nodeName, min, max).apply(block)) // Long arguments -inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LongArgument(nodeName, min, max), optional, block) +inline fun Argument<*>.longArgument(nodeName: String, min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LongArgument(nodeName, min, max).apply(block)) // Boolean argument -inline fun Argument<*>.booleanArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(BooleanArgument(nodeName), optional, block) +inline fun Argument<*>.booleanArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(BooleanArgument(nodeName).apply(block)) // String arguments -inline fun Argument<*>.stringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(StringArgument(nodeName), optional, block) -inline fun Argument<*>.textArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(TextArgument(nodeName), optional, block) -inline fun Argument<*>.greedyStringArgument(nodeName: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(GreedyStringArgument(nodeName), optional, block) +inline fun Argument<*>.stringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(StringArgument(nodeName).apply(block)) +inline fun Argument<*>.textArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(TextArgument(nodeName).apply(block)) +inline fun Argument<*>.greedyStringArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(GreedyStringArgument(nodeName).apply(block)) // Literal arguments -inline fun Argument<*>.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(literal, literal), optional, block) -inline fun Argument<*>.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(LiteralArgument.of(nodeName, literal), optional, block) +inline fun Argument<*>.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(literal, literal).apply(block)) +inline fun Argument<*>.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(nodeName, literal).apply(block)) @Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(literals), optional, block) +inline fun Argument<*>.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, literals).apply(block)) -inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = addArgument(MultiLiteralArgument(nodeName, *literals), optional, block) +inline fun Argument<*>.multiLiteralArgument(nodeName: String, vararg literals: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, *literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0 as it is not needed anymore. See the documentation for more information", ReplaceWith(""), DeprecationLevel.WARNING) inline fun CommandTree.requirement(base: Argument<*>, predicate: Predicate, block: Argument<*>.() -> Unit = {}): CommandTree = then(base.withRequirement(predicate).apply(block)) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt index 7d8af435da..c61a0c0e7f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/OptionalArgumentTests.kt @@ -9,7 +9,6 @@ import dev.jorel.commandapi.test.TestBase import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -72,34 +71,6 @@ class OptionalArgumentTests: TestBase() { } } - @Test - fun executionTestWithCommandTreeAndOptionalArgumentMethod() { - val results: Mut = Mut.of() - - // TODO: Come up with a more complicated test for optional arguments in a CommandTree - // I was unsure about the optionalArgument() method for the CommandTree DSL anyway - // because everything can be achieved by using the normal CommandTree DSL syntax and - // the argument() method - commandTree("test") { - stringArgument("value", optional = true) - playerExecutor { player, args -> - results.set(args.getOptional("value").orElse("DefaultValue") as String) - } - } - - val player: PlayerMock = server.addPlayer() - - // /test - server.dispatchCommand(player, "test") - assertEquals("DefaultValue", results.get()) - - // /test hello - server.dispatchCommand(player, "test hello") - assertEquals("hello", results.get()) - - assertNoMoreResults(results) - } - @Test fun executionTestWithOptionalValue() { val results: Mut = Mut.of() diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java deleted file mode 100644 index 141e7496d5..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeOptionalArgumentTests.java +++ /dev/null @@ -1,233 +0,0 @@ -package dev.jorel.commandapi.test; - -import be.seeseemelk.mockbukkit.entity.PlayerMock; -import dev.jorel.commandapi.CommandTree; -import dev.jorel.commandapi.arguments.LiteralArgument; -import dev.jorel.commandapi.arguments.StringArgument; -import dev.jorel.commandapi.executors.PlayerCommandExecutor; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; - -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -/** - * Tests for using optional arguments in CommandTrees - */ -class CommandTreeOptionalArgumentTests extends TestBase { - - /********* - * Setup * - *********/ - - @BeforeEach - public void setUp() { - super.setUp(); - } - - @AfterEach - public void tearDown() { - super.tearDown(); - } - - /********* - * Tests * - *********/ - - @Test - void testNormalArgument() { - Mut results = Mut.of(); - - new CommandTree("test") - .then(new StringArgument("string").executesPlayer((player, args) -> { - results.set(args.getUnchecked("string")); - })) - .register(); - - PlayerMock player = server.addPlayer(); - - assertCommandFailsWith(player, "test", "Unknown or incomplete command, see below for error at position 4: test<--[HERE]"); - - assertStoresResult(player, "test hello", results, "hello"); - assertStoresResult(player, "test world", results, "world"); - - assertNoMoreResults(results); - } - - @Test - void testOptionalArgument() { - Mut results = Mut.of(); - - new CommandTree("test") - .withOptionalArguments(new StringArgument("string")) - .executesPlayer((player, args) -> { - results.set(args.getUnchecked("string")); - }) - .register(); - - PlayerMock player = server.addPlayer(); - - assertStoresResult(player, "test", results, null); - - assertStoresResult(player, "test hello", results, "hello"); - assertStoresResult(player, "test world", results, "world"); - - assertNoMoreResults(results); - } - - @Test - void testTwoOptionalArguments() { - Mut arg1 = Mut.of(); - Mut arg2 = Mut.of(); - - new CommandTree("test") - .withOptionalArguments( - new StringArgument("string1"), - new StringArgument("string2") - ) - .executesPlayer((player, args) -> { - arg1.set(args.getUnchecked("string1")); - arg2.set(args.getUnchecked("string2")); - }) - .register(); - - PlayerMock player = server.addPlayer(); - - // "/test" should return null, null - assertStoresResult(player, "test", arg1, null); - assertNull(arg2.get()); - - // "/test hello" should return "hello", null - assertStoresResult(player, "test hello", arg1, "hello"); - assertNull(arg2.get()); - - // "/test hello world" should return "hello", "world" - assertStoresResult(player, "test hello world", arg1, "hello"); - assertEquals("world", arg2.get()); - - assertNoMoreResults(arg1); - assertNoMoreResults(arg2); - } - - @Test - void testCombinedOptionalArguments() { - Mut results = Mut.of(); - - new CommandTree("test") - .withOptionalArguments( - new LiteralArgument("1").combineWith(new LiteralArgument("2"), new LiteralArgument("3")), - new LiteralArgument("4"), - new LiteralArgument("5").combineWith(new LiteralArgument("6")) - ) - .executesPlayer((player, args) -> { - results.set(args.fullInput()); - }) - .register(); - - PlayerMock player = server.addPlayer(); - - assertStoresResult(player, "test", results, "/test"); - - assertCommandFailsWith(player, "test 1", "Unknown or incomplete command, see below for error at position 6: test 1<--[HERE]"); - assertCommandFailsWith(player, "test 1 2", "Unknown or incomplete command, see below for error at position 8: test 1 2<--[HERE]"); - assertStoresResult(player, "test 1 2 3", results, "/test 1 2 3"); - - assertStoresResult(player, "test 1 2 3 4", results, "/test 1 2 3 4"); - - assertCommandFailsWith(player, "test 1 2 3 4 5", "Unknown or incomplete command, see below for error at position 14: ... 1 2 3 4 5<--[HERE]"); - assertStoresResult(player, "test 1 2 3 4 5 6", results, "/test 1 2 3 4 5 6"); - - assertNoMoreResults(results); - } - - @Test - void testBranchesWithOptionalArguments() { - Mut executedPath = Mut.of(); - Mut results = Mut.of(); - - Function executor = path -> (player, args) -> { - executedPath.set(path); - results.set(args.fullInput()); - }; - - new CommandTree("test") - .then(new LiteralArgument("1") - .then(new LiteralArgument("1") - .withOptionalArguments(new LiteralArgument("optional")) - .executesPlayer(executor.apply("1 1")) - ) - .then(new LiteralArgument("2") - .withOptionalArguments(new LiteralArgument("optional")) - .executesPlayer(executor.apply("1 2")) - ) - .withOptionalArguments(new LiteralArgument("optional")) - .executesPlayer(executor.apply("1")) - ) - .then(new LiteralArgument("2") - .then(new LiteralArgument("1") - .withOptionalArguments(new LiteralArgument("optional")) - .executesPlayer(executor.apply("2 1")) - ) - .then(new LiteralArgument("2") - .withOptionalArguments(new LiteralArgument("optional")) - .executesPlayer(executor.apply("2 2")) - ) - .withOptionalArguments(new LiteralArgument("optional")) - .executesPlayer(executor.apply("2")) - ) - .withOptionalArguments(new LiteralArgument("optional")) - .executesPlayer(executor.apply("")) - .register(); - - PlayerMock player = server.addPlayer(); - - // Base command path - assertStoresResult(player, "test", executedPath, ""); - assertEquals("/test", results.get()); - assertStoresResult(player, "test optional", executedPath, ""); - assertEquals("/test optional", results.get()); - - // "1" path - assertStoresResult(player, "test 1", executedPath, "1"); - assertEquals("/test 1", results.get()); - assertStoresResult(player, "test 1 optional", executedPath, "1"); - assertEquals("/test 1 optional", results.get()); - - // "1 1" path - assertStoresResult(player, "test 1 1", executedPath, "1 1"); - assertEquals("/test 1 1", results.get()); - assertStoresResult(player, "test 1 1 optional", executedPath, "1 1"); - assertEquals("/test 1 1 optional", results.get()); - - // "1 2" path - assertStoresResult(player, "test 1 2", executedPath, "1 2"); - assertEquals("/test 1 2", results.get()); - assertStoresResult(player, "test 1 2 optional", executedPath, "1 2"); - assertEquals("/test 1 2 optional", results.get()); - - // "2" path - assertStoresResult(player, "test 1", executedPath, "1"); - assertEquals("/test 1", results.get()); - assertStoresResult(player, "test 1 optional", executedPath, "1"); - assertEquals("/test 1 optional", results.get()); - - // "2 1" path - assertStoresResult(player, "test 2 1", executedPath, "2 1"); - assertEquals("/test 2 1", results.get()); - assertStoresResult(player, "test 2 1 optional", executedPath, "2 1"); - assertEquals("/test 2 1 optional", results.get()); - - // "2 2" path - assertStoresResult(player, "test 2 2", executedPath, "2 2"); - assertEquals("/test 2 2", results.get()); - assertStoresResult(player, "test 2 2 optional", executedPath, "2 2"); - assertEquals("/test 2 2 optional", results.get()); - - - assertNoMoreResults(executedPath); - assertNoMoreResults(results); - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java index e4e53062e4..f190b6b928 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java @@ -229,63 +229,21 @@ void testRegisterMultiLiteralArguments() { } @Test - void testRegisterOneOptionalArgument() { - new CommandTree("command") - .withOptionalArguments(new StringArgument("string")) - .executesPlayer(P_EXEC) - .register(); - - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")) - ); - } - - @Test - void testRegisterTwoOptionalArguments() { - new CommandTree("command") - .withOptionalArguments( - new StringArgument("string"), - new IntegerArgument("integer") - ) - .executesPlayer(P_EXEC) - .register(); - - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")), - registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument", "integer:IntegerArgument")) - ); - } - - @Test - void testRegisterCombinedOptionalArguments() { - new CommandTree("command") - .withOptionalArguments( - new LiteralArgument("1").combineWith(new LiteralArgument("2")), - new LiteralArgument("3").combineWith(new LiteralArgument("4")) - ) - .executesPlayer(P_EXEC) - .register(); - - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument")) - ); - } - - @Test - void testRegisterCombinedRequiredAndOptionalArguments() { + void testRegisterCombinedArguments() { new CommandTree("command") .then( new LiteralArgument("1").combineWith(new LiteralArgument("2")) .then( new LiteralArgument("3").combineWith(new LiteralArgument("4")) - .withOptionalArguments( - new LiteralArgument("5").combineWith(new LiteralArgument("6")), - new LiteralArgument("7").combineWith(new LiteralArgument("8")) - ).executesPlayer(P_EXEC) + .executesPlayer(P_EXEC) + .then( + new LiteralArgument("5").combineWith(new LiteralArgument("6")) + .executesPlayer(P_EXEC) + .then( + new LiteralArgument("7").combineWith(new LiteralArgument("8")) + .executesPlayer(P_EXEC) + ) + ) ) ) .register(); @@ -350,57 +308,28 @@ void testRegisterBranchesWithBranches() { } @Test - void testRegisterBranchesWithOptionalArguments() { - new CommandTree("command") - .then( - new LiteralArgument("subcommand1") - .withOptionalArguments( - new LiteralArgument("a"), - new LiteralArgument("b") - ) - .executesPlayer(P_EXEC) - ) - .then( - new LiteralArgument("subcommand2") - .withOptionalArguments( - new LiteralArgument("c"), - new LiteralArgument("d") - ) - .executesPlayer(P_EXEC) - ) - .register(); - - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument", "b:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument", "d:LiteralArgument")) - ); - } - - @Test - void testRegisterBranchesWithCombinedRequiredAndOptionalArguments() { + void testRegisterBranchesWithCombinedArguments() { new CommandTree("command") .then( new LiteralArgument("subcommand1") .then( new LiteralArgument("1a").combineWith(new LiteralArgument("1b")) - .withOptionalArguments( + .executesPlayer(P_EXEC) + .then( new LiteralArgument("1c").combineWith(new LiteralArgument("1d")) + .executesPlayer(P_EXEC) ) - .executesPlayer(P_EXEC) ) ) .then( new LiteralArgument("subcommand2") .then( new LiteralArgument("2a").combineWith(new LiteralArgument("2b")) - .withOptionalArguments( + .executesPlayer(P_EXEC) + .then( new LiteralArgument("2c").combineWith(new LiteralArgument("2d")) + .executesPlayer(P_EXEC) ) - .executesPlayer(P_EXEC) ) ) .register(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandOptionalArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java similarity index 99% rename from commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandOptionalArgumentTests.java rename to commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java index 94141e06c0..010165441b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandOptionalArgumentTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java @@ -16,7 +16,7 @@ /** * Tests for using optional arguments in CommandAPICommands */ -class CommandAPICommandOptionalArgumentTests extends TestBase { +class OptionalArgumentTests extends TestBase { /********* * Setup * From 9bcbb2a71d7066ebaf0d958baaadde2913155ea2 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 14:39:35 -0400 Subject: [PATCH 07/42] Update optional Argument use in example CommandTree --- .../examples/kotlin/ExamplesKotlinDSL.kt | 24 +++++++++++-------- docssrc/src/kotlindsl.md | 4 +++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt index ad6958ce71..dc5e65a634 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt @@ -2,6 +2,7 @@ package dev.jorel.commandapi.examples.kotlin import de.tr7zw.changeme.nbtapi.NBTContainer import dev.jorel.commandapi.arguments.* +import dev.jorel.commandapi.executors.CommandArguments import dev.jorel.commandapi.kotlindsl.* import dev.jorel.commandapi.wrappers.* import dev.jorel.commandapi.wrappers.Rotation @@ -1041,19 +1042,22 @@ commandAPICommand("commandRequirement") { /* ANCHOR_END: kotlindsl6 */ /* ANCHOR: kotlindsl7 */ +fun giveOptionalAmount(player: Player, args: CommandArguments) { + // This command will let you execute: + // "/optionalArgument give minecraft:stick" + // "/optionalArgument give minecraft:stick 5" + val itemStack: ItemStack = args["item"] as ItemStack + val amount: Int = args.getOptional("amount").orElse(1) as Int + itemStack.amount = amount + player.inventory.addItem(itemStack) +} + commandTree("optionalArgument") { literalArgument("give") { itemStackArgument("item") { - integerArgument("amount", optional = true) { - playerExecutor { player, args -> - // This command will let you execute: - // "/optionalArgument give minecraft:stick" - // "/optionalArgument give minecraft:stick 5" - val itemStack: ItemStack = args["item"] as ItemStack - val amount: Int = args.getOptional("amount").orElse(1) as Int - itemStack.amount = amount - player.inventory.addItem(itemStack) - } + playerExecutor(::giveOptionalAmount) + integerArgument("amount") { + playerExecutor(::giveOptionalAmount) } } } diff --git a/docssrc/src/kotlindsl.md b/docssrc/src/kotlindsl.md index 64d607f467..62be19c8e5 100644 --- a/docssrc/src/kotlindsl.md +++ b/docssrc/src/kotlindsl.md @@ -145,7 +145,9 @@ We want to create a `/give` command with the following syntax: /optionalArgument give ``` -To declare an argument as optional you need to set the `optional` value to `true`: +When using a CommandTree, you can do this by using the same executor in two places. + +When using a CommandAPICommand, you can create an optional Argument by setting the `optional` value to `true`:

From 84b38878123b7c8ef92250ade29fa91caf7e4e79 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 14:56:08 -0400 Subject: [PATCH 08/42] Give default value for `block` in DSL's `CommandAPICommand.addArgument` method --- .../dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt | 2 +- .../dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index 5fcbedf49c..bc84ce7d0e 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -24,7 +24,7 @@ inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): inline fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command) inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command)) -inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) optionalArgument(argument, block) else argument(argument, block) +inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit = {}): CommandAPICommand = if (optional) optionalArgument(argument, block) else argument(argument, block) // Integer arguments inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(IntegerArgument(nodeName, min, max), optional, block) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index 1446996703..bd7865b87c 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -22,7 +22,7 @@ inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): inline fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command) inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command)) -inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit): CommandAPICommand = if (optional) optionalArgument(argument, block) else argument(argument, block) +inline fun CommandAPICommand.addArgument(argument: Argument<*>, optional: Boolean, block: Argument<*>.() -> Unit = {}): CommandAPICommand = if (optional) optionalArgument(argument, block) else argument(argument, block) // Integer arguments inline fun CommandAPICommand.integerArgument(nodeName: String, min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(IntegerArgument(nodeName, min, max), optional, block) From 89570478b0edc4c2f029b660d7bfd4c3cfa91dff Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:34:39 -0400 Subject: [PATCH 09/42] Address some SonarCloud code smells Whoops, these actually are some silly mistakes I missed :P, thanks SonarCloud --- .../main/java/dev/jorel/commandapi/AbstractArgumentTree.java | 2 +- .../java/dev/jorel/commandapi/arguments/LiteralArgument.java | 2 +- .../dev/jorel/commandapi/arguments/MultiLiteralArgument.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index ba1dbc7aab..3e61ba9e0e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -47,7 +47,7 @@ protected AbstractArgumentTree() { * @param argument the argument to use as the underlying argument for this * argument tree */ - public AbstractArgumentTree(final Argument argument) { + protected AbstractArgumentTree(final Argument argument) { this.argument = argument; // Copy the executor in case any executions were defined on the argument this.executor = argument.executor; diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index e6051086bd..ab0d032b83 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -150,7 +150,7 @@ public String parseArgument(CommandContext cmdCtx, String key, @Override public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { - super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + Literal.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); } @Override diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index 0bf1fa01cb..d8b8424086 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -106,7 +106,7 @@ public String parseArgument(CommandContext cmdCtx, String key, @Override public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { - super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + MultiLiteral.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); } @Override From e176f2aaa38ff022a65e9ceb1822c5d9afa9909d Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:00:55 -0400 Subject: [PATCH 10/42] Remove unused `AbstractArgument#unpackCombinedArguments` --- .../commandapi/arguments/AbstractArgument.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 91cf0a0fec..da9f91e2e7 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -352,23 +352,6 @@ public void appendToCommandPaths(List> argumentStrings) { } } - /** - * Adds this argument to the end of all the current possible paths given. Any arguments combined with this one are also added. - * - * @param previousArguments A list of possible paths up to this argument so far. - */ - public void unpackCombinedArguments(List> previousArguments) { - // Add this argument - for(List path : previousArguments) { - path.add((Argument) this); - } - - // Add combined arguments - for (Argument subArgument : combinedArguments) { - subArgument.unpackCombinedArguments(previousArguments); - } - } - /** * Builds the Brigadier {@link CommandNode} structure for this argument. Note that the Brigadier node structure may * contain multiple nodes, for example if {@link #combineWith(AbstractArgument[])} was called for this argument to From 100c8bb44447f105695e039ee77aaf6a66521406 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:41:05 -0400 Subject: [PATCH 11/42] Update tests added to `dev/command-build-rewrite` from `dev/dev` See https://github.com/JorelAli/CommandAPI/compare/9727e1208031...e742c689bfa1 --- .../commandapi/AbstractArgumentTree.java | 2 + .../exceptions/GreedyArgumentException.java | 2 +- .../test/CommandRegistrationTests.java | 440 +----------------- .../arguments/ArgumentMultiLiteralTests.java | 255 ++++------ 4 files changed, 122 insertions(+), 577 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 3e61ba9e0e..7701d2be29 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -158,6 +158,8 @@ public void buildBrigadierNode(CommandNode previousNode, List> getBranchesAsList() { + if (arguments.isEmpty()) return List.of(List.of()); + List> branchesList = new ArrayList<>(); for (AbstractArgumentTree branch : arguments) { diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java index 3300a1cbac..82aa4576cf 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java @@ -45,7 +45,7 @@ public class GreedyArgumentException extends CommandRegistrationException { List previousArguments, Argument argument, List> followingBranches) { StringBuilder builder = new StringBuilder(); - builder.append("A greedy argument must be declared at the end of a command. Going down the "); + builder.append("A greedy argument can only be declared at the end of a command. Going down the "); addArgumentList(builder, previousArguments); builder.append(" branch, found "); addArgument(builder, argument); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java index c995cb105d..b9126146b4 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java @@ -1,22 +1,20 @@ package dev.jorel.commandapi.test; import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandTree; import dev.jorel.commandapi.arguments.GreedyStringArgument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; import dev.jorel.commandapi.exceptions.GreedyArgumentException; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests for the semantics of registering commands @@ -59,7 +57,7 @@ void testCommandAPICommandGreedyArgumentException() { assertThrowsWithMessage( GreedyArgumentException.class, - "Only one GreedyStringArgument or ChatArgument can be declared, at the end of a List. Found arguments: arg1 arg2 ", + "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found arg1 followed by [arg2]", invalidGreedyCommand::register ); } @@ -90,7 +88,7 @@ void testCommandTreeGreedyArgumentException() { assertThrowsWithMessage( GreedyArgumentException.class, - "Only one GreedyStringArgument or ChatArgument can be declared, at the end of a List. Found arguments: arg1 arg2 ", + "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found arg1 followed by [arg2]", invalidGreedyCommand::register ); } @@ -100,7 +98,7 @@ void testCommandAPICommandInvalidCommandNameException() { assertThrowsWithMessage( InvalidCommandNameException.class, "Invalid command with name 'null' cannot be registered!", - () -> new CommandAPICommand((String) null) + () -> new CommandAPICommand(null) ); assertThrowsWithMessage( @@ -145,7 +143,7 @@ void testCommandAPICommandMissingCommandExecutorException() { assertThrowsWithMessage( MissingCommandExecutorException.class, - "/test does not declare any executors or executable subcommands!", + "The command path test is not executable!", commandWithNoExecutors::register ); @@ -155,7 +153,7 @@ void testCommandAPICommandMissingCommandExecutorException() { assertThrowsWithMessage( MissingCommandExecutorException.class, - "/test does not declare any executors or executable subcommands!", + "The command path sub is not executable!", commandWithNoRunnableSubcommands::register ); @@ -174,10 +172,6 @@ void testCommandAPICommandMissingCommandExecutorException() { assertDoesNotThrow(() -> commandWithEventuallyRunnableSubcommand.register()); } - // TODO: This test does not succeed - // Calling `register` on CommandTree without any `executes` defined simply does not create any commands - // These cases should throw MissingCommandExecutorExceptions - @Disabled @Test void testCommandTreeMissingCommandExecutorException() { // This command has no executor, should complain because this isn't runnable @@ -185,7 +179,7 @@ void testCommandTreeMissingCommandExecutorException() { assertThrowsWithMessage( MissingCommandExecutorException.class, - "", + "The command path test is not executable!", commandWithNoExecutors::register ); @@ -195,7 +189,7 @@ void testCommandTreeMissingCommandExecutorException() { assertThrowsWithMessage( MissingCommandExecutorException.class, - "", + "The command path [test] ending with sub is not executable!", commandWithNoRunnableSubcommands::register ); @@ -222,16 +216,13 @@ void testCommandTreeMissingCommandExecutorException() { assertThrowsWithMessage( MissingCommandExecutorException.class, - "", + "The command path [test notExecutable1] ending with sub is not executable!", commandTreeWithSomeNotExecutablePaths::register ); } @Test void testCommandAPICommandDuplicateNodeNameException() { - // Make sure dispatcher is cleared from any previous tests - CommandAPIHandler.getInstance().writeDispatcherToFile(); - // This command is not okay because it has duplicate names for Arguments 1 and 3 CommandAPICommand commandWithDuplicateArgumentNames = new CommandAPICommand("test") .withArguments( @@ -241,12 +232,11 @@ void testCommandAPICommandDuplicateNodeNameException() { ) .executesPlayer(P_EXEC); - commandWithDuplicateArgumentNames.register(); - // No commands in tree - assertEquals(""" - { - "type": "root" - }""", getDispatcherString()); + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for non-literal arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicate node name", + commandWithDuplicateArgumentNames::register + ); // This command is okay because LiteralArguments are exempt from the duplicate name rule CommandAPICommand commandWithDuplicateLiteralArgumentNames = new CommandAPICommand("test") @@ -257,35 +247,7 @@ void testCommandAPICommandDuplicateNodeNameException() { ) .executesPlayer(P_EXEC); - commandWithDuplicateLiteralArgumentNames.register(); - // Command added to tree - assertEquals(""" - { - "type": "root", - "children": { - "test": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "children": { - "bob": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "executable": true - } - } - } - } - } - } - } - } - }""", - getDispatcherString() - ); + assertDoesNotThrow(commandWithDuplicateLiteralArgumentNames::register); // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule CommandAPICommand commandWithDuplicateMultiLiteralArgumentNames = new CommandAPICommand("test") @@ -296,104 +258,11 @@ void testCommandAPICommandDuplicateNodeNameException() { ) .executesPlayer(P_EXEC); - commandWithDuplicateMultiLiteralArgumentNames.register(); - // Command added to tree - assertEquals(""" - { - "type": "root", - "children": { - "test": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "children": { - "bob": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - } - } - } - } - }""", - getDispatcherString() - ); + assertDoesNotThrow(commandWithDuplicateMultiLiteralArgumentNames::register); } @Test void testCommandTreeDuplicateNodeNameException() { - // Make sure dispatcher is cleared from any previous tests - CommandAPIHandler.getInstance().writeDispatcherToFile(); - // This command is not okay because it has duplicate names for Arguments 1 and 3 CommandTree commandWithDuplicateArgumentNames = new CommandTree("test") .then( @@ -405,12 +274,11 @@ void testCommandTreeDuplicateNodeNameException() { ) ); - commandWithDuplicateArgumentNames.register(); - // No commands in tree - assertEquals(""" - { - "type": "root" - }""", getDispatcherString()); + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for non-literal arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicate node name", + commandWithDuplicateArgumentNames::register + ); // This command is okay because LiteralArguments are exempt from the duplicate name rule CommandTree commandWithDuplicateLiteralArgumentNames = new CommandTree("test") @@ -423,35 +291,7 @@ void testCommandTreeDuplicateNodeNameException() { ) ); - commandWithDuplicateLiteralArgumentNames.register(); - // Command added to tree - assertEquals(""" - { - "type": "root", - "children": { - "test": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "children": { - "bob": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "executable": true - } - } - } - } - } - } - } - } - }""", - getDispatcherString() - ); + assertDoesNotThrow(commandWithDuplicateLiteralArgumentNames::register); // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule CommandTree commandWithDuplicateMultiLiteralArgumentNames = new CommandTree("test") @@ -464,97 +304,7 @@ void testCommandTreeDuplicateNodeNameException() { ) ); - commandWithDuplicateMultiLiteralArgumentNames.register(); - // Command added to tree - assertEquals(""" - { - "type": "root", - "children": { - "test": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "children": { - "bob": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - } - } - } - } - }""", - getDispatcherString() - ); + assertDoesNotThrow(commandWithDuplicateMultiLiteralArgumentNames::register); // This command is okay because the duplicate names are on different paths CommandTree commandWithDuplicateNamesSeparated = new CommandTree("test") @@ -563,148 +313,6 @@ void testCommandTreeDuplicateNodeNameException() { .then(new LiteralArgument("path3").then(new StringArgument("alice").executesPlayer(P_EXEC))) .then(new LiteralArgument("path4").then(new StringArgument("alice").executesPlayer(P_EXEC))); - commandWithDuplicateNamesSeparated.register(); - // Command added to tree - assertEquals(""" - { - "type": "root", - "children": { - "test": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "children": { - "bob": { - "type": "literal", - "children": { - "alice": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - }, - "path1": { - "type": "literal", - "children": { - "alice": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } - }, - "path2": { - "type": "literal", - "children": { - "alice": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } - }, - "path3": { - "type": "literal", - "children": { - "alice": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } - }, - "path4": { - "type": "literal", - "children": { - "alice": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } - } - } - } - } - }""", - getDispatcherString() - ); + assertDoesNotThrow(commandWithDuplicateNamesSeparated::register); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java index 8c8759526d..5e65abed4e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java @@ -83,6 +83,7 @@ void commandBuildingTestWithMultiLiteralArgument() { .register(); // Make sure all the commands were set up in the tree correctly + // Redirects make the command structure more compact assertEquals(""" { "type": "root", @@ -90,52 +91,42 @@ void commandBuildingTestWithMultiLiteralArgument() { "command1": { "type": "literal", "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } - } - }, "option2": { "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } - } + "redirect": [ + "command1", + "option1" + ] }, "option3": { + "type": "literal", + "redirect": [ + "command1", + "option1" + ] + }, + "option1": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, "option2": { "type": "literal", - "executable": true + "executable": true, + "redirect": [ + "command1", + "option1", + "option1" + ] }, "option3": { + "type": "literal", + "executable": true, + "redirect": [ + "command1", + "option1", + "option1" + ] + }, + "option1": { "type": "literal", "executable": true } @@ -146,52 +137,42 @@ void commandBuildingTestWithMultiLiteralArgument() { "command2": { "type": "literal", "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } - } - }, "option2": { "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } - } + "redirect": [ + "command2", + "option1" + ] }, "option3": { + "type": "literal", + "redirect": [ + "command2", + "option1" + ] + }, + "option1": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, "option2": { "type": "literal", - "executable": true + "executable": true, + "redirect": [ + "command2", + "option1", + "option1" + ] }, "option3": { + "type": "literal", + "executable": true, + "redirect": [ + "command2", + "option1", + "option1" + ] + }, + "option1": { "type": "literal", "executable": true } @@ -202,61 +183,38 @@ void commandBuildingTestWithMultiLiteralArgument() { "command3": { "type": "literal", "children": { + "option2": { + "type": "literal", + "redirect": [ + "command3", + "option1" + ] + }, "option1": { "type": "literal", "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, "option2": { "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option2": { - "type": "literal", - "children": { + "redirect": [ + "command3", + "option1", + "option1" + ] + }, "option1": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, "option2": { "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true + "executable": true, + "redirect": [ + "command3", + "option1", + "option1", + "option1" + ] }, - "option2": { + "option1": { "type": "literal", "executable": true } @@ -269,61 +227,38 @@ void commandBuildingTestWithMultiLiteralArgument() { "command4": { "type": "literal", "children": { + "option2": { + "type": "literal", + "redirect": [ + "command4", + "option1" + ] + }, "option1": { "type": "literal", "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, "option2": { "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } - } - }, - "option2": { - "type": "literal", - "children": { + "redirect": [ + "command4", + "option1", + "option1" + ] + }, "option1": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, "option2": { "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true + "executable": true, + "redirect": [ + "command4", + "option1", + "option1", + "option1" + ] }, - "option2": { + "option1": { "type": "literal", "executable": true } From bd0cbdb1d5c667838c98db60fe5aff6e95cbefca Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sun, 15 Oct 2023 08:42:44 -0400 Subject: [PATCH 12/42] Expand command-building test coverage --- ...mmandAPICommandRegisteredCommandTests.java | 8 +- .../test/CommandRegistrationTests.java | 25 ++++-- .../CommandTreeRegisteredCommandTests.java | 8 +- .../test/OptionalArgumentTests.java | 85 +++++++++++++++---- .../dev/jorel/commandapi/test/TestBase.java | 19 +++-- 5 files changed, 108 insertions(+), 37 deletions(-) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java index 99b86296e0..fbe4ea7b49 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java @@ -42,7 +42,7 @@ private RegisteredCommand registeredCommandNoHelpOrPermission(String name, List< return new RegisteredCommand(name, argsAsStr, Optional.empty(), Optional.empty(), Optional.empty(), aliases, - CommandPermission.NONE + CommandPermission.NONE, "minecraft" ); } @@ -127,7 +127,7 @@ void testRegisterHelpInformation() { new RegisteredCommand( "command", List.of(), Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), - new String[0], CommandPermission.NONE + new String[0], CommandPermission.NONE, "minecraft" ) ); } @@ -143,7 +143,7 @@ void testRegisterOpPermission() { new RegisteredCommand( "command", List.of(), Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.OP + new String[0], CommandPermission.OP, "minecraft" ) ); } @@ -159,7 +159,7 @@ void testRegisterStringPermission() { new RegisteredCommand( "command", List.of(), Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.fromString("permission") + new String[0], CommandPermission.fromString("permission"), "minecraft" ) ); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java index b9126146b4..33f8fb0ea7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java @@ -170,6 +170,21 @@ void testCommandAPICommandMissingCommandExecutorException() { ); assertDoesNotThrow(() -> commandWithEventuallyRunnableSubcommand.register()); + + // This command is not okay, because the presence of arguments on + // the root indicate the root path was meant to be executed + CommandAPICommand commandWithNotExecutableRootArgument = new CommandAPICommand("test") + .withArguments(new StringArgument("arg1")) + .withSubcommand( + new CommandAPICommand("sub") + .executesPlayer(P_EXEC) + ); + + assertThrowsWithMessage( + MissingCommandExecutorException.class, + "The command path test is not executable!", + commandWithNotExecutableRootArgument::register + ); } @Test @@ -247,7 +262,7 @@ void testCommandAPICommandDuplicateNodeNameException() { ) .executesPlayer(P_EXEC); - assertDoesNotThrow(commandWithDuplicateLiteralArgumentNames::register); + assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule CommandAPICommand commandWithDuplicateMultiLiteralArgumentNames = new CommandAPICommand("test") @@ -258,7 +273,7 @@ void testCommandAPICommandDuplicateNodeNameException() { ) .executesPlayer(P_EXEC); - assertDoesNotThrow(commandWithDuplicateMultiLiteralArgumentNames::register); + assertDoesNotThrow(() -> commandWithDuplicateMultiLiteralArgumentNames.register()); } @Test @@ -291,7 +306,7 @@ void testCommandTreeDuplicateNodeNameException() { ) ); - assertDoesNotThrow(commandWithDuplicateLiteralArgumentNames::register); + assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule CommandTree commandWithDuplicateMultiLiteralArgumentNames = new CommandTree("test") @@ -304,7 +319,7 @@ void testCommandTreeDuplicateNodeNameException() { ) ); - assertDoesNotThrow(commandWithDuplicateMultiLiteralArgumentNames::register); + assertDoesNotThrow(() -> commandWithDuplicateMultiLiteralArgumentNames.register()); // This command is okay because the duplicate names are on different paths CommandTree commandWithDuplicateNamesSeparated = new CommandTree("test") @@ -313,6 +328,6 @@ void testCommandTreeDuplicateNodeNameException() { .then(new LiteralArgument("path3").then(new StringArgument("alice").executesPlayer(P_EXEC))) .then(new LiteralArgument("path4").then(new StringArgument("alice").executesPlayer(P_EXEC))); - assertDoesNotThrow(commandWithDuplicateNamesSeparated::register); + assertDoesNotThrow(() -> commandWithDuplicateNamesSeparated.register()); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java index f190b6b928..1c3b5764b0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java @@ -42,7 +42,7 @@ private RegisteredCommand registeredCommandNoHelpOrPermission(String name, List< return new RegisteredCommand(name, argsAsStr, Optional.empty(), Optional.empty(), Optional.empty(), aliases, - CommandPermission.NONE + CommandPermission.NONE, "minecraft" ); } @@ -127,7 +127,7 @@ void testRegisterHelpInformation() { new RegisteredCommand( "command", List.of(), Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), - new String[0], CommandPermission.NONE + new String[0], CommandPermission.NONE, "minecraft" ) ); } @@ -143,7 +143,7 @@ void testRegisterOpPermission() { new RegisteredCommand( "command", List.of(), Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.OP + new String[0], CommandPermission.OP, "minecraft" ) ); } @@ -159,7 +159,7 @@ void testRegisterStringPermission() { new RegisteredCommand( "command", List.of(), Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.fromString("permission") + new String[0], CommandPermission.fromString("permission"), "minecraft" ) ); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java index 010165441b..972e799c9e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java @@ -1,16 +1,16 @@ package dev.jorel.commandapi.test; +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.arguments.DoubleArgument; import dev.jorel.commandapi.arguments.IntegerArgument; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.exceptions.OptionalArgumentException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import be.seeseemelk.mockbukkit.entity.PlayerMock; -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.arguments.StringArgument; -import dev.jorel.commandapi.exceptions.OptionalArgumentException; - import static org.junit.jupiter.api.Assertions.*; /** @@ -109,32 +109,36 @@ void testTwoOptionalArguments() { public void testOptionalArgumentException() { // An optional argument followed by a required argument should throw // an OptionalArgumentException - assertThrows(OptionalArgumentException.class, () -> { - new CommandAPICommand("test") + assertThrowsWithMessage( + OptionalArgumentException.class, + "Uncombined required arguments following optional arguments are not allowed! Going down the [test string1] branch, found a required argument string2 after an optional argument", + () -> new CommandAPICommand("test") .withOptionalArguments(new StringArgument("string1")) .withArguments(new StringArgument("string2")) .executesPlayer(P_EXEC) - .register(); - }); + .register() + ); // No need to worry: since we didn't actually add any arguments in `withArguments`, this is fine - assertDoesNotThrow(() -> { - new CommandAPICommand("test") + assertDoesNotThrow( + () -> new CommandAPICommand("test") .withOptionalArguments(new StringArgument("string")) .withArguments() .executesPlayer(P_EXEC) - .register(); - }); + .register() + ); // A required argument following a required argument that is linked // to an optional argument should throw an OptionalArgumentException - assertThrows(OptionalArgumentException.class, () -> { - new CommandAPICommand("test") + assertThrowsWithMessage( + OptionalArgumentException.class, + "Uncombined required arguments following optional arguments are not allowed! Going down the [test string1] branch, found a required argument string3 after an optional argument", + () -> new CommandAPICommand("test") .withOptionalArguments(new StringArgument("string1").combineWith(new StringArgument("string2"))) .withArguments(new StringArgument("string3")) .executesPlayer(P_EXEC) - .register(); - }); + .register() + ); } @Test @@ -254,4 +258,51 @@ void testCombinedOptionalArguments() { assertNoMoreResults(results); } + @Test + void testDoubleCombinedRequiredAndOptionalArguments() { + Mut results = Mut.of(); + + new CommandAPICommand("test") + .withArguments( + new LiteralArgument("1").setListed(true).combineWith( + new LiteralArgument("2").setListed(true), + new LiteralArgument("3").setListed(true) + ) + ) + .withOptionalArguments( + new LiteralArgument("4").setListed(true).combineWith( + new LiteralArgument("5").setListed(true), + new LiteralArgument("6").setListed(true) + ), + new LiteralArgument("7").setListed(true).combineWith( + new LiteralArgument("8").setListed(true), + new LiteralArgument("9").setListed(true) + ) + ) + .executesPlayer(info -> { + results.set(info.args().args()); + }) + .register(); + + PlayerMock player = server.addPlayer(); + + // Required arguments + assertCommandFailsWith(player, "test 1", "Unknown or incomplete command, see below for error at position 6: test 1<--[HERE]"); + assertCommandFailsWith(player, "test 1 2", "Unknown or incomplete command, see below for error at position 8: test 1 2<--[HERE]"); + + assertStoresArrayResult(player, "test 1 2 3", results, "1", "2", "3"); + + // First optional argument + assertCommandFailsWith(player, "test 1 2 3 4", "Unknown or incomplete command, see below for error at position 12: ...st 1 2 3 4<--[HERE]"); + assertCommandFailsWith(player, "test 1 2 3 4 5", "Unknown or incomplete command, see below for error at position 14: ... 1 2 3 4 5<--[HERE]"); + + assertStoresArrayResult(player, "test 1 2 3 4 5 6", results, "1", "2", "3", "4", "5", "6"); + + // Second optional argument + assertCommandFailsWith(player, "test 1 2 3 4 5 6 7", "Unknown or incomplete command, see below for error at position 18: ... 3 4 5 6 7<--[HERE]"); + assertCommandFailsWith(player, "test 1 2 3 4 5 6 7 8", "Unknown or incomplete command, see below for error at position 20: ... 4 5 6 7 8<--[HERE]"); + + assertStoresArrayResult(player, "test 1 2 3 4 5 6 7 8 9", results, "1", "2", "3", "4", "5", "6", "7", "8", "9"); + + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java index 7bb3280ea1..71b22d7ba8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java @@ -1,12 +1,5 @@ package dev.jorel.commandapi.test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -44,6 +37,8 @@ import dev.jorel.commandapi.SafeVarHandle; import dev.jorel.commandapi.executors.PlayerCommandExecutor; +import static org.junit.jupiter.api.Assertions.*; + public abstract class TestBase { public CommandAPIServerMock server; @@ -124,6 +119,16 @@ public void assertStoresResult(CommandSender sender, String command, Mut ); } + @SafeVarargs + public final void assertStoresArrayResult(CommandSender sender, String command, Mut queue, T... expected) { + assertDoesNotThrow(() -> assertTrue( + server.dispatchThrowableCommand(sender, command), + "Expected command dispatch to return true, but it gave false")); + assertArrayEquals(expected, + assertDoesNotThrow(queue::get, "Expected to find <" + Arrays.toString(expected) + "> in queue, but nothing was present") + ); + } + @Deprecated public void assertInvalidSyntax(CommandSender sender, String command) { // XXX: Bogus code, do not use. Use assertCommandFailsWith instead. From 963a5cf2b64f6d45ee9447b94a5aa6d8a34ac023 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sun, 15 Oct 2023 13:22:22 -0400 Subject: [PATCH 13/42] Extract logic for generating the SuggestionsProvider for an Argument into CommandAPIHandler --- .../jorel/commandapi/CommandAPIHandler.java | 63 +++++++++++++++++-- .../arguments/AbstractArgument.java | 37 +---------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index 73d535d6ed..08b6f879f9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -39,17 +39,17 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.tree.LiteralCommandNode; -import dev.jorel.commandapi.arguments.AbstractArgument; -import dev.jorel.commandapi.arguments.ArgumentSuggestions; -import dev.jorel.commandapi.arguments.PreviewInfo; -import dev.jorel.commandapi.arguments.Previewable; +import dev.jorel.commandapi.arguments.*; import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.executors.CommandArguments; import dev.jorel.commandapi.executors.ExecutionInfo; import dev.jorel.commandapi.preprocessor.RequireField; import dev.jorel.commandapi.wrappers.PreviewableFunction; +import java.util.concurrent.CompletableFuture; + /** * The "brains" behind the CommandAPI. * Handles command registration @@ -301,6 +301,61 @@ public CommandArguments args() { }; } + /** + * Generates a Brigadier {@link SuggestionProvider} using the given CommandAPI objects. + * + * @param previousArguments A list of Arguments that came before the argument using these suggestions. These arguments + * will be available in the {@link SuggestionInfo} when providing suggestions. + * @param argument The argument to give suggestions for. + * @return A Brigadier SuggestionProvider object that generates suggestions for the given argument with the previous + * arguments as input, or null if there are no suggestions for the given argument. + */ + public SuggestionProvider generateBrigadierSuggestions(List previousArguments, Argument argument) { + // Overriding suggestions take precedence + Optional> overriddenSuggestions = argument.getOverriddenSuggestions(); + if (overriddenSuggestions.isPresent()) { + return generateBrigadierSuggestions(previousArguments, overriddenSuggestions.get()); + } + + // Included suggestions add on to whatever "default" suggestions exist + Optional> includedSuggestions = argument.getIncludedSuggestions(); + if (includedSuggestions.isPresent()) { + // Insert additional defined suggestions + SuggestionProvider defaultSuggestions; + if (argument instanceof CustomProvidedArgument cPA) { + defaultSuggestions = platform.getSuggestionProvider(cPA.getSuggestionProvider()); + } else { + defaultSuggestions = argument.getRawType()::listSuggestions; + } + + SuggestionProvider suggestionsToAdd = generateBrigadierSuggestions(previousArguments, includedSuggestions.get()); + + return (cmdCtx, builder) -> { + // Heavily inspired by CommandDispatcher#getCompletionSuggestions, with combining + // multiple CompletableFuture into one. + CompletableFuture defaultSuggestionsFuture = defaultSuggestions.getSuggestions(cmdCtx, builder); + CompletableFuture includedSuggestionsFuture = suggestionsToAdd.getSuggestions(cmdCtx, builder); + + CompletableFuture result = new CompletableFuture<>(); + CompletableFuture.allOf(defaultSuggestionsFuture, includedSuggestionsFuture).thenRun(() -> { + List suggestions = new ArrayList<>(); + suggestions.add(defaultSuggestionsFuture.join()); + suggestions.add(includedSuggestionsFuture.join()); + result.complete(Suggestions.merge(cmdCtx.getInput(), suggestions)); + }); + return result; + }; + } + + // Custom provided arguments + if (argument instanceof CustomProvidedArgument cPA) { + return platform.getSuggestionProvider(cPA.getSuggestionProvider()); + } + + // Calling `RequiredArgumentBuilder.suggests(null)` makes it so no custom suggestions are given, so this makes sense + return null; + } + /** * Generates a Brigadier {@link SuggestionProvider} using the given CommandAPI objects. * diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index da9f91e2e7..c1bb4a616a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -25,8 +25,6 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.AbstractArgumentTree; import dev.jorel.commandapi.CommandAPIExecutor; @@ -40,7 +38,6 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; /** @@ -429,39 +426,7 @@ public void checkPreconditions(List previousArguments, List pr RequiredArgumentBuilder rootBuilder = RequiredArgumentBuilder.argument(nodeName, rawType); // Add suggestions - if (replaceSuggestions != null) { - // Overridden suggestions take precedence - rootBuilder.suggests(handler.generateBrigadierSuggestions(previousArguments, replaceSuggestions)); - } else if (includedSuggestions != null) { - // Insert additional defined suggestions - SuggestionProvider defaultSuggestions; - if (this instanceof CustomProvidedArgument cPA) { - defaultSuggestions = handler.getPlatform().getSuggestionProvider(cPA.getSuggestionProvider()); - } else { - defaultSuggestions = rawType::listSuggestions; - } - - SuggestionProvider includedSuggestions = handler.generateBrigadierSuggestions(previousArguments, this.includedSuggestions); - - rootBuilder.suggests((cmdCtx, builder) -> { - // Heavily inspired by CommandDispatcher#getCompletionSuggestions, with combining - // multiple CompletableFuture into one. - CompletableFuture defaultSuggestionsFuture = defaultSuggestions.getSuggestions(cmdCtx, builder); - CompletableFuture includedSuggestionsFuture = includedSuggestions.getSuggestions(cmdCtx, builder); - - CompletableFuture result = new CompletableFuture<>(); - CompletableFuture.allOf(defaultSuggestionsFuture, includedSuggestionsFuture).thenRun(() -> { - List suggestions = new ArrayList<>(); - suggestions.add(defaultSuggestionsFuture.join()); - suggestions.add(includedSuggestionsFuture.join()); - result.complete(Suggestions.merge(cmdCtx.getInput(), suggestions)); - }); - return result; - }); - } else if (this instanceof CustomProvidedArgument cPA) { - // Handle arguments with built-in suggestion providers - rootBuilder.suggests(handler.getPlatform().getSuggestionProvider(cPA.getSuggestionProvider())); - } + rootBuilder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this)); // Add argument to previousArgument lists // Note: this argument should not be in the previous arguments list when doing suggestions, From d6e8a5dd3b64baf90bede1bd7eb1592ea46aa43b Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Sun, 24 Dec 2023 17:58:36 -0500 Subject: [PATCH 14/42] Fix MultiLiteralArguments - give nodes multiple parents instead of using redirects The Brigadier CommandDispatcher CommandNode structure does not need to be a tree. This means one node can have multiple parents. In this case, the node after a MultiLiteralArgument has parent node for each literal in the MultiLiteral. This works around the issue documented in https://github.com/Mojang/brigadier/issues/137, where arguments disappear from the CommandContext used when executing an argument when the command encounters a redirect. This way, we can remember which literal was chosen no matter which branch was taken, yet still converge on the same path. --- .../commandapi/AbstractArgumentTree.java | 11 +- .../commandapi/AbstractCommandAPICommand.java | 6 +- .../jorel/commandapi/AbstractCommandTree.java | 2 +- .../arguments/AbstractArgument.java | 50 ++-- .../commandapi/arguments/MultiLiteral.java | 37 ++- .../arguments/MultiLiteralArgument.java | 4 +- .../arguments/ArgumentMultiLiteralTests.java | 261 +++++++++++------- .../arguments/MultiLiteralArgument.java | 4 +- 8 files changed, 226 insertions(+), 149 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 7701d2be29..994cbe8a3e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -125,12 +125,15 @@ public List> getBranchesAsStrings() { /** * Builds the Brigadier {@link CommandNode} structure for this argument tree. * - * @param previousNode The {@link CommandNode} to add this argument tree onto. + * @param previousNodes A List of {@link CommandNode}s to add this argument onto. * @param previousArguments A List of CommandAPI arguments that came before this argument tree. * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. * @param The Brigadier Source object for running commands. */ - public void buildBrigadierNode(CommandNode previousNode, List previousArguments, List previousNonLiteralArgumentNames) { + public void buildBrigadierNode( + List> previousNodes, + List previousArguments, List previousNonLiteralArgumentNames + ) { // Check preconditions if (argument instanceof GreedyArgument && !arguments.isEmpty()) { // Argument is followed by at least some arguments @@ -142,7 +145,7 @@ public void buildBrigadierNode(CommandNode previousNode, List rootNode = argument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, executor); + previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousNonLiteralArgumentNames, executor); // Add our branches as children to the node for (AbstractArgumentTree child : arguments) { @@ -150,7 +153,7 @@ public void buildBrigadierNode(CommandNode previousNode, List newPreviousArguments = new ArrayList<>(previousArguments); List newPreviousArgumentNames = new ArrayList<>(previousNonLiteralArgumentNames); - child.buildBrigadierNode(rootNode, newPreviousArguments, newPreviousArgumentNames); + child.buildBrigadierNode(previousNodes, newPreviousArguments, newPreviousArgumentNames); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 514c8f87a7..94655d8763 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -302,7 +302,7 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) // Create arguments if (hasAnyArguments()) { - CommandNode previousNode = rootNode; + List> previousNodes = List.of(rootNode); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); @@ -319,7 +319,7 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) // Add required arguments for (int i = 0; i < requiredArguments.size(); i++) { Argument argument = requiredArguments.get(i); - previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousArgumentNames, + previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, // Only the last required argument is executable i == requiredArguments.size() - 1 ? executor : null); } @@ -327,7 +327,7 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) // Add optional arguments for (Argument argument : optionalArguments) { // All optional arguments are executable - previousNode = argument.addArgumentNodes(previousNode, previousArguments, previousArgumentNames, executor); + previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, executor); } // Check greedy argument constraint diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index c8ee1bc701..82a8c1c657 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -123,7 +123,7 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) List previousNonLiteralArgumentNames = new ArrayList<>(); previousArguments.add(commandNames); - argument.buildBrigadierNode(rootNode, previousArguments, previousNonLiteralArgumentNames); + argument.buildBrigadierNode(List.of(rootNode), previousArguments, previousNonLiteralArgumentNames); } } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index c1bb4a616a..4fd96be549 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -360,20 +360,23 @@ public void appendToCommandPaths(List> argumentStrings) { *
  • {@link #checkPreconditions(List, List)}
  • *
  • {@link #createArgumentBuilder(List, List)}
  • *
  • {@link #finishBuildingNode(ArgumentBuilder, List, CommandAPIExecutor)}
  • - *
  • {@link #linkNode(CommandNode, CommandNode, List, List, CommandAPIExecutor)}
  • + *
  • {@link #linkNode(List, CommandNode, List, List, CommandAPIExecutor)}
  • * * - * @param previousNode The {@link CommandNode} to add this argument onto. + * @param previousNodes A List of {@link CommandNode}s to add this argument onto. * @param previousArguments A List of CommandAPI arguments that came before this argument. * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. * @param terminalExecutor The {@link CommandAPIExecutor} to apply at the end of the node structure. * This parameter can be null to indicate that this argument should not be * executable. * @param The Brigadier Source object for running commands. - * @return The last node in the Brigadier node structure for this argument. + * @return The list of last nodes in the Brigadier node structure for this argument. */ - public CommandNode addArgumentNodes(CommandNode previousNode, List previousArguments, List previousNonLiteralArgumentNames, - CommandAPIExecutor> terminalExecutor) { + public List> addArgumentNodes( + List> previousNodes, + List previousArguments, List previousNonLiteralArgumentNames, + CommandAPIExecutor> terminalExecutor + ) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); // Check preconditions @@ -391,10 +394,10 @@ public CommandNode addArgumentNodes(CommandNode previou CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalExecutor); // Link node to previous - previousNode = linkNode(previousNode, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + previousNodes = linkNode(previousNodes, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); - // Return last node - return previousNode; + // Return last nodes + return previousNodes; } /** @@ -464,7 +467,7 @@ public CommandNode finishBuildingNode(ArgumentBuilder CommandNode finishBuildingNode(ArgumentBuilder The Brigadier Source object for running commands. - * @return The last node in the Brigadier {@link CommandNode} structure representing this Argument. Note that this - * is not necessarily the {@code rootNode} for this argument, since the Brigadier node structure may contain multiple - * nodes. This might happen if {@link #combineWith(AbstractArgument[])} was called for this argument to merge it with - * other arguments. + * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing this Argument. Note that + * this is not necessarily the {@code rootNode} for this argument, since the Brigadier node structure may contain multiple + * nodes in a chain. This might happen if {@link #combineWith(AbstractArgument[])} was called for this argument to merge + * it with other arguments. */ - public CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List previousArguments, List previousNonLiteralArgumentNames, - CommandAPIExecutor> terminalExecutor) { - // Add rootNode to the previous - previousNode.addChild(rootNode); + public List> linkNode( + List> previousNodes, CommandNode rootNode, + List previousArguments, List previousNonLiteralArgumentNames, + CommandAPIExecutor> terminalExecutor + ) { + // Add rootNode to the previous nodes + for(CommandNode previousNode : previousNodes) { + previousNode.addChild(rootNode); + } // Add combined arguments - previousNode = rootNode; + previousNodes = List.of(rootNode); for (int i = 0; i < combinedArguments.size(); i++) { Argument subArgument = combinedArguments.get(i); - previousNode = subArgument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, + previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousNonLiteralArgumentNames, // Only apply the `terminalExecutor` to the last argument i == combinedArguments.size() - 1 ? terminalExecutor : null); } - // Return last node - return previousNode; + // Return last nodes + return previousNodes; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index e396b83de0..12d9ab764b 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -112,13 +112,23 @@ default void appendToCommandPaths(List> argumentStrings) { } /** - * Overrides {@link AbstractArgument#linkNode(CommandNode, CommandNode, List, List, CommandAPIExecutor)}. + * Overrides {@link AbstractArgument#linkNode(List, CommandNode, List, List, CommandAPIExecutor)}. *

    * Normally, Arguments are only represented by a single node, and so only need to link one node. However, a MultiLiteral * represents multiple literal node paths, which also need to be generated and inserted into the node structure. */ - default CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List previousArguments, List previousNonLiteralArgumentNames, - CommandAPIExecutor> terminalExecutor) { + default List> linkNode( + List> previousNodes, CommandNode rootNode, + List previousArguments, List previousNonLiteralArgumentNames, + CommandAPIExecutor> terminalExecutor + ) { + List> newNodes = new ArrayList<>(); + // Add root node to previous + for(CommandNode previousNode : previousNodes) { + previousNode.addChild(rootNode); + } + newNodes.add(rootNode); + // Generate nodes for other literals Iterator literals = Arrays.asList(getLiterals()).iterator(); literals.next(); // Skip first literal; that was handled by `#createArgumentBuilder` @@ -126,30 +136,27 @@ default CommandNode linkNode(CommandNode previousNode, // Create node MultiLiteralArgumentBuilder literalBuilder = MultiLiteralArgumentBuilder.multiLiteral(getNodeName(), literals.next()); - // Redirect to root node so all its arguments come after this - literalBuilder.redirect(rootNode); - // Finish building node CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalExecutor); - // Link node to previous - previousNode.addChild(literalNode); + // Add node to previous + for(CommandNode previousNode : previousNodes) { + previousNode.addChild(literalNode); + } + newNodes.add(literalNode); } - // Add root node to previous - previousNode.addChild(rootNode); - // Add combined arguments - previousNode = rootNode; + previousNodes = newNodes; List combinedArguments = getCombinedArguments(); for (int i = 0; i < combinedArguments.size(); i++) { Argument subArgument = combinedArguments.get(i); - previousNode = subArgument.addArgumentNodes(previousNode, previousArguments, previousNonLiteralArgumentNames, + previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousNonLiteralArgumentNames, // Only apply the `terminalExecutor` to the last argument i == combinedArguments.size() - 1 ? terminalExecutor : null); } - // Return last node - return previousNode; + // Return last nodes + return previousNodes; } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index d89dad81d7..526e3ece84 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -122,7 +122,7 @@ public void appendToCommandPaths(List> argumentStrings) { } @Override - public CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { - return MultiLiteral.super.linkNode(previousNode, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { + return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java index 5e65abed4e..16b6f7e71f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java @@ -12,7 +12,6 @@ import org.bukkit.inventory.ItemStack; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.util.List; @@ -83,7 +82,6 @@ void commandBuildingTestWithMultiLiteralArgument() { .register(); // Make sure all the commands were set up in the tree correctly - // Redirects make the command structure more compact assertEquals(""" { "type": "root", @@ -91,44 +89,54 @@ void commandBuildingTestWithMultiLiteralArgument() { "command1": { "type": "literal", "children": { - "option2": { - "type": "literal", - "redirect": [ - "command1", - "option1" - ] - }, - "option3": { - "type": "literal", - "redirect": [ - "command1", - "option1" - ] - }, "option1": { "type": "literal", "children": { + "option1": { + "type": "literal", + "executable": true + }, "option2": { "type": "literal", - "executable": true, - "redirect": [ - "command1", - "option1", - "option1" - ] + "executable": true }, "option3": { "type": "literal", - "executable": true, - "redirect": [ - "command1", - "option1", - "option1" - ] + "executable": true + } + } + }, + "option2": { + "type": "literal", + "children": { + "option1": { + "type": "literal", + "executable": true + }, + "option2": { + "type": "literal", + "executable": true }, + "option3": { + "type": "literal", + "executable": true + } + } + }, + "option3": { + "type": "literal", + "children": { "option1": { "type": "literal", "executable": true + }, + "option2": { + "type": "literal", + "executable": true + }, + "option3": { + "type": "literal", + "executable": true } } } @@ -137,44 +145,54 @@ void commandBuildingTestWithMultiLiteralArgument() { "command2": { "type": "literal", "children": { - "option2": { - "type": "literal", - "redirect": [ - "command2", - "option1" - ] - }, - "option3": { - "type": "literal", - "redirect": [ - "command2", - "option1" - ] - }, "option1": { "type": "literal", "children": { + "option1": { + "type": "literal", + "executable": true + }, "option2": { "type": "literal", - "executable": true, - "redirect": [ - "command2", - "option1", - "option1" - ] + "executable": true }, "option3": { "type": "literal", - "executable": true, - "redirect": [ - "command2", - "option1", - "option1" - ] + "executable": true + } + } + }, + "option2": { + "type": "literal", + "children": { + "option1": { + "type": "literal", + "executable": true }, + "option2": { + "type": "literal", + "executable": true + }, + "option3": { + "type": "literal", + "executable": true + } + } + }, + "option3": { + "type": "literal", + "children": { "option1": { "type": "literal", "executable": true + }, + "option2": { + "type": "literal", + "executable": true + }, + "option3": { + "type": "literal", + "executable": true } } } @@ -183,40 +201,63 @@ void commandBuildingTestWithMultiLiteralArgument() { "command3": { "type": "literal", "children": { - "option2": { - "type": "literal", - "redirect": [ - "command3", - "option1" - ] - }, "option1": { "type": "literal", "children": { - "option2": { + "option1": { "type": "literal", - "redirect": [ - "command3", - "option1", - "option1" - ] + "children": { + "option1": { + "type": "literal", + "executable": true + }, + "option2": { + "type": "literal", + "executable": true + } + } }, - "option1": { + "option2": { "type": "literal", "children": { + "option1": { + "type": "literal", + "executable": true + }, "option2": { "type": "literal", - "executable": true, - "redirect": [ - "command3", - "option1", - "option1", - "option1" - ] + "executable": true + } + } + } + } + }, + "option2": { + "type": "literal", + "children": { + "option1": { + "type": "literal", + "children": { + "option1": { + "type": "literal", + "executable": true }, + "option2": { + "type": "literal", + "executable": true + } + } + }, + "option2": { + "type": "literal", + "children": { "option1": { "type": "literal", "executable": true + }, + "option2": { + "type": "literal", + "executable": true } } } @@ -227,40 +268,63 @@ void commandBuildingTestWithMultiLiteralArgument() { "command4": { "type": "literal", "children": { - "option2": { - "type": "literal", - "redirect": [ - "command4", - "option1" - ] - }, "option1": { "type": "literal", "children": { - "option2": { + "option1": { "type": "literal", - "redirect": [ - "command4", - "option1", - "option1" - ] + "children": { + "option1": { + "type": "literal", + "executable": true + }, + "option2": { + "type": "literal", + "executable": true + } + } }, - "option1": { + "option2": { "type": "literal", "children": { + "option1": { + "type": "literal", + "executable": true + }, "option2": { "type": "literal", - "executable": true, - "redirect": [ - "command4", - "option1", - "option1", - "option1" - ] + "executable": true + } + } + } + } + }, + "option2": { + "type": "literal", + "children": { + "option1": { + "type": "literal", + "children": { + "option1": { + "type": "literal", + "executable": true }, + "option2": { + "type": "literal", + "executable": true + } + } + }, + "option2": { + "type": "literal", + "children": { "option1": { "type": "literal", "executable": true + }, + "option2": { + "type": "literal", + "executable": true } } } @@ -325,11 +389,6 @@ void executionTestWithMultiLiteralArgumentNodeName() { assertNoMoreResults(results); } - // TODO: This test currently fails because MultiLiteralArguments are broken - // See https://github.com/Mojang/brigadier/issues/137 - // I hope this is a Brigadier bug, because otherwise the new command build system needs to be rewritten - // Also, it just wouldn't be as cool if MultiLiteralArguments couldn't use redirects - @Disabled @Test void executionTestWithMultipleMultiLiteralArguments() { Mut results = Mut.of(); diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index d8b8424086..0b60541092 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -120,7 +120,7 @@ public void appendToCommandPaths(List> argumentStrings) { } @Override - public CommandNode linkNode(CommandNode previousNode, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { - return MultiLiteral.super.linkNode(previousNode, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { + return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); } } From f464df173ffebe4d133e2f4c8e201f069b71ef8e Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:56:11 -0500 Subject: [PATCH 15/42] Initial implementation of `FlagsArgument` (and other changes) Changes: - Implemented `FlagsArgument` (https://github.com/JorelAli/CommandAPI/issues/483) - Moved var handles for `CommandNode` `children`,`literals`, and `arguments` to `CommandAPIHandler` - Added `FlagsArgumentCommon` FlagsArgumentRootNode` and `FlagsArgumentEndingNode` to handle the special node structure and parsing required - Updated `CustomArgument` - All `AbstractArgument` builder methods are delegated to the base argument - Replaced `CommandAPIExecutor` parameter of `AbstractArgument#addArgumentNodes` to a `Function` to allow objects that hold arguments to better control how those arguments are executed - Added package `dev.jorel.commandapi.commandnodes` for class that extend `CommandNode` and related classes - Tweaked some exceptions - `GreedyArgumentException` - Changed because the `FlagsArgument` is sometimes greedy - only greedy iff it has no terminal branches - Greedy arguments are now detected when `AbstractArgument#addArgumentNodes` returns an empty list - Tweaked the exception message - `DuplicateNodeNameException` - Changed because literal arguments can conflict with other nodes if they are listed - Now thrown when two listed arguments have the same node name - Added `UnnamedArgumentCommandNode` to make sure unlisted arguments do not conflict - Renamed `MultiLiteralCommandNode` to `NamedLiteralCommandNode` for use by listed `Literal` arguments - Tweaked the exception message TODO: - Clean up code - Add tests - Remove test commands in CommandAPIMain - Add javadocs and documentation - Hope https://github.com/Mojang/brigadier/pull/144 is resolved, otherwise be annoyed :( --- .../commandapi/AbstractArgumentTree.java | 43 +-- .../commandapi/AbstractCommandAPICommand.java | 23 +- .../jorel/commandapi/AbstractCommandTree.java | 4 +- .../jorel/commandapi/CommandAPIHandler.java | 86 +++++- .../arguments/AbstractArgument.java | 158 ++++++----- .../arguments/CommandAPIArgumentType.java | 5 + .../arguments/FlagsArgumentCommon.java | 255 ++++++++++++++++++ .../jorel/commandapi/arguments/Literal.java | 41 ++- .../commandapi/arguments/MultiLiteral.java | 55 ++-- .../commandnodes/FlagsArgumentEndingNode.java | 159 +++++++++++ .../commandnodes/FlagsArgumentRootNode.java | 77 ++++++ .../NamedLiteralArgumentBuilder.java} | 26 +- .../NamedLiteralCommandNode.java} | 28 +- .../UnnamedArgumentCommandNode.java | 60 +++++ .../UnnamedRequiredArgumentBuilder.java | 65 +++++ .../DuplicateNodeNameException.java | 8 +- .../exceptions/GreedyArgumentException.java | 26 +- .../CommandRegistrationStrategy.java | 29 +- .../commandapi/arguments/CustomArgument.java | 117 +++++++- .../commandapi/arguments/FlagsArgument.java | 80 ++++++ .../commandapi/arguments/LiteralArgument.java | 9 +- .../arguments/MultiLiteralArgument.java | 17 +- .../dev/jorel/commandapi/CommandAPIMain.java | 111 +++++++- .../test/CommandRegistrationTests.java | 72 +++-- .../commandapi/arguments/LiteralArgument.java | 9 +- .../arguments/MultiLiteralArgument.java | 17 +- 26 files changed, 1279 insertions(+), 301 deletions(-) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java rename commandapi-core/src/main/java/dev/jorel/commandapi/{arguments/MultiLiteralArgumentBuilder.java => commandnodes/NamedLiteralArgumentBuilder.java} (54%) rename commandapi-core/src/main/java/dev/jorel/commandapi/{arguments/MultiLiteralCommandNode.java => commandnodes/NamedLiteralCommandNode.java} (66%) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedRequiredArgumentBuilder.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 994cbe8a3e..0b32375f0d 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -2,8 +2,6 @@ import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; -import dev.jorel.commandapi.arguments.GreedyArgument; -import dev.jorel.commandapi.exceptions.GreedyArgumentException; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; @@ -125,55 +123,34 @@ public List> getBranchesAsStrings() { /** * Builds the Brigadier {@link CommandNode} structure for this argument tree. * - * @param previousNodes A List of {@link CommandNode}s to add this argument onto. - * @param previousArguments A List of CommandAPI arguments that came before this argument tree. - * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. - * @param The Brigadier Source object for running commands. + * @param previousNodes A List of {@link CommandNode}s to add this argument onto. + * @param previousArguments A List of CommandAPI arguments that came before this argument tree. + * @param previousArgumentNames A List of Strings containing the node names that came before this argument. + * @param The Brigadier Source object for running commands. */ public void buildBrigadierNode( List> previousNodes, - List previousArguments, List previousNonLiteralArgumentNames + List previousArguments, List previousArgumentNames ) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + // Check preconditions - if (argument instanceof GreedyArgument && !arguments.isEmpty()) { - // Argument is followed by at least some arguments - throw new GreedyArgumentException(previousArguments, argument, getBranchesAsList()); - } if (!executor.hasAnyExecutors() && arguments.isEmpty()) { // If we don't have any executors then no branches is bad because this path can't be run at all throw new MissingCommandExecutorException(previousArguments, argument); } // Create node for this argument - previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousNonLiteralArgumentNames, executor); + previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, + executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null); // Add our branches as children to the node for (AbstractArgumentTree child : arguments) { // We need a new list for each branch of the tree List newPreviousArguments = new ArrayList<>(previousArguments); - List newPreviousArgumentNames = new ArrayList<>(previousNonLiteralArgumentNames); + List newPreviousArgumentNames = new ArrayList<>(previousArgumentNames); child.buildBrigadierNode(previousNodes, newPreviousArguments, newPreviousArgumentNames); } } - - /** - * @return A list of paths that represent the possible branches of this argument tree as Argument objects. - */ - protected List> getBranchesAsList() { - if (arguments.isEmpty()) return List.of(List.of()); - - List> branchesList = new ArrayList<>(); - - for (AbstractArgumentTree branch : arguments) { - for (List subBranchList : branch.getBranchesAsList()) { - List newBranchList = new ArrayList<>(); - newBranchList.add(branch.argument); - newBranchList.addAll(subBranchList); - branchesList.add(newBranchList); - } - } - - return branchesList; - } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 94655d8763..90639f85cc 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -20,17 +20,17 @@ *******************************************************************************/ package dev.jorel.commandapi; +import com.mojang.brigadier.Command; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; -import dev.jorel.commandapi.arguments.GreedyArgument; -import dev.jorel.commandapi.exceptions.GreedyArgumentException; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import dev.jorel.commandapi.exceptions.OptionalArgumentException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Function; /** * A builder used to create commands to be registered by the CommandAPI. @@ -317,30 +317,19 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) previousArguments.add(commandNames); // Add required arguments + Function, Command> executorCreator = executor.hasAnyExecutors() ? + args -> handler.generateBrigadierCommand(args, executor) : null; for (int i = 0; i < requiredArguments.size(); i++) { Argument argument = requiredArguments.get(i); previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, // Only the last required argument is executable - i == requiredArguments.size() - 1 ? executor : null); + i == requiredArguments.size() - 1 ? executorCreator : null); } // Add optional arguments for (Argument argument : optionalArguments) { // All optional arguments are executable - previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, executor); - } - - // Check greedy argument constraint - // We need to check it down here so that all the combined arguments are properly considered after unpacking - for (int i = 0; i < previousArguments.size() - 1 /* Minus one since we don't need to check last argument */; i++) { - Argument argument = previousArguments.get(i); - if (argument instanceof GreedyArgument) { - throw new GreedyArgumentException( - previousArguments.subList(0, i), // Arguments before this - argument, - List.of(previousArguments.subList(i + 1, previousArguments.size())) // Arguments after this - ); - } + previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, executorCreator); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index 82a8c1c657..add75565e2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -120,10 +120,10 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) for (AbstractArgumentTree argument : arguments) { // We need new previousArguments lists for each branch so they don't interfere List previousArguments = new ArrayList<>(); - List previousNonLiteralArgumentNames = new ArrayList<>(); + List previousArgumentNames = new ArrayList<>(); previousArguments.add(commandNames); - argument.buildBrigadierNode(List.of(rootNode), previousArguments, previousNonLiteralArgumentNames); + argument.buildBrigadierNode(List.of(rootNode), previousArguments, previousArgumentNames); } } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index 08b6f879f9..cbc8b14b31 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -40,6 +40,7 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.*; import dev.jorel.commandapi.commandsenders.AbstractCommandSender; @@ -59,24 +60,35 @@ * @param The class for running Brigadier commands */ @RequireField(in = CommandContext.class, name = "arguments", ofType = Map.class) +@RequireField(in = CommandNode.class, name = "children", ofType = Map.class) +@RequireField(in = CommandNode.class, name = "literals", ofType = Map.class) +@RequireField(in = CommandNode.class, name = "arguments", ofType = Map.class) public class CommandAPIHandler /// @endcond , CommandSender, Source> { + // TODO: Need to ensure this can be safely "disposed of" when we're done (e.g. on reloads). + // I hiiiiiiighly doubt we're storing class caches of classes that can be unloaded at runtime, + // but this IS a generic class caching system and we don't want derpy memory leaks + private static final Map FIELDS; + private static final SafeVarHandle, Map>> commandContextArguments; + // VarHandle seems incapable of setting final fields, so we have to use Field here + private static final Field commandNodeChildren; + private static final Field commandNodeLiterals; + private static final Field commandNodeArguments; - // Compute all var handles all in one go so we don't do this during main server - // runtime + // Compute all var handles all in one go so we don't do this during main server runtime static { + FIELDS = new HashMap<>(); + commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", "arguments", Map.class); + commandNodeChildren = CommandAPIHandler.getField(CommandNode.class, "children"); + commandNodeLiterals = CommandAPIHandler.getField(CommandNode.class, "literals"); + commandNodeArguments = CommandAPIHandler.getField(CommandNode.class, "arguments"); } - // TODO: Need to ensure this can be safely "disposed of" when we're done (e.g. on reloads). - // I hiiiiiiighly doubt we're storing class caches of classes that can be unloaded at runtime, - // but this IS a generic class caching system and we don't want derpy memory leaks - private static final Map FIELDS = new HashMap<>(); - final CommandAPIPlatform platform; final List registeredCommands; // Keep track of what has been registered for type checking final Map, Previewable> previewableArguments; // Arguments with previewable chat @@ -141,7 +153,7 @@ public CommandAPIPlatform getPlatform() { // SECTION: Creating commands // //////////////////////////////// - void registerCommand(ExecutableCommand command, String namespace) { + public void registerCommand(ExecutableCommand command, String namespace) { platform.preCommandRegistration(command.getName()); List registeredCommandInformation = RegisteredCommand.fromExecutableCommand(command, namespace); @@ -431,6 +443,10 @@ public Predicate generateBrigadierRequirements(CommandPermission permiss }; } + //////////////////////////////// + // SECTION: Brigadier Helpers // + //////////////////////////////// + public void writeDispatcherToFile() { File file = CommandAPI.getConfiguration().getDispatcherFile(); if (file != null) { @@ -452,7 +468,7 @@ public void writeDispatcherToFile() { } } - LiteralCommandNode namespaceNode(LiteralCommandNode original, String namespace) { + public LiteralCommandNode namespaceNode(LiteralCommandNode original, String namespace) { // Adapted from a section of `CraftServer#syncCommands` LiteralCommandNode clone = new LiteralCommandNode<>( namespace + ":" + original.getLiteral(), @@ -469,6 +485,54 @@ LiteralCommandNode namespaceNode(LiteralCommandNode original, St return clone; } + public static Map> getCommandNodeChildren(CommandNode target) { + try { + return (Map>) commandNodeChildren.get(target); + } catch (IllegalAccessException e) { + throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); + } + } + + public static void setCommandNodeChildren(CommandNode target, Map> children) { + try { + commandNodeChildren.set(target, children); + } catch (IllegalAccessException e) { + throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); + } + } + + public static Map> getCommandNodeLiterals(CommandNode target) { + try { + return (Map>) commandNodeLiterals.get(target); + } catch (IllegalAccessException e) { + throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); + } + } + + public static void setCommandNodeLiterals(CommandNode target, Map> literals) { + try { + commandNodeLiterals.set(target, literals); + } catch (IllegalAccessException e) { + throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); + } + } + + public static Map> getCommandNodeArguments(CommandNode target) { + try { + return (Map>) commandNodeArguments.get(target); + } catch (IllegalAccessException e) { + throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); + } + } + + public static void setCommandNodeArguments(CommandNode target, Map> arguments) { + try { + commandNodeArguments.set(target, arguments); + } catch (IllegalAccessException e) { + throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); + } + } + //////////////////////////////// // SECTION: Parsing arguments // //////////////////////////////// @@ -522,7 +586,7 @@ public static String getRawArgumentInput(CommandContext * @throws CommandSyntaxException If an argument is improperly formatted and cannot be parsed */ - CommandArguments argsToCommandArgs(CommandContext cmdCtx, List args) throws CommandSyntaxException { + public CommandArguments argsToCommandArgs(CommandContext cmdCtx, List args) throws CommandSyntaxException { // Array for arguments for executor List argList = new ArrayList<>(); @@ -564,7 +628,7 @@ CommandArguments argsToCommandArgs(CommandContext cmdCtx, List * @return the Argument's corresponding object * @throws CommandSyntaxException when the input for the argument isn't formatted correctly */ - Object parseArgument(CommandContext cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException { + public Object parseArgument(CommandContext cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException { if (value.isListed()) { return value.parseArgument(cmdCtx, key, previousArgs); } else { diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 4fd96be549..6e11041f23 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -20,6 +20,7 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; @@ -27,17 +28,18 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.AbstractArgumentTree; -import dev.jorel.commandapi.CommandAPIExecutor; import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandPermission; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; +import dev.jorel.commandapi.commandnodes.UnnamedRequiredArgumentBuilder; import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; +import dev.jorel.commandapi.exceptions.GreedyArgumentException; import dev.jorel.commandapi.executors.CommandArguments; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -172,7 +174,7 @@ public Impl replaceSuggestions(ArgumentSuggestions suggestions) { * @return a function that provides suggestions, or Optional.empty() if there * are no overridden suggestions. */ - public final Optional> getOverriddenSuggestions() { + public Optional> getOverriddenSuggestions() { return Optional.ofNullable(replaceSuggestions); } @@ -188,7 +190,7 @@ public final Optional> getOverriddenSuggestio * @param permission the permission required to execute this command * @return this current argument */ - public final Impl withPermission(CommandPermission permission) { + public Impl withPermission(CommandPermission permission) { this.permission = permission; return instance(); } @@ -199,7 +201,7 @@ public final Impl withPermission(CommandPermission permission) { * @param permission the permission required to execute this command * @return this current argument */ - public final Impl withPermission(String permission) { + public Impl withPermission(String permission) { this.permission = CommandPermission.fromString(permission); return instance(); } @@ -209,7 +211,7 @@ public final Impl withPermission(String permission) { * * @return the permission required to run this command */ - public final CommandPermission getArgumentPermission() { + public CommandPermission getArgumentPermission() { return permission; } @@ -224,7 +226,7 @@ public final CommandPermission getArgumentPermission() { * * @return the requirements required to run this command */ - public final Predicate getRequirements() { + public Predicate getRequirements() { return this.requirements; } @@ -236,7 +238,7 @@ public final Predicate getRequirements() { * @param requirement the predicate that must be satisfied to use this argument * @return this current argument */ - public final Impl withRequirement(Predicate requirement) { + public Impl withRequirement(Predicate requirement) { this.requirements = this.requirements.and(requirement); return instance(); } @@ -313,8 +315,7 @@ public Impl combineWith(List combinedArguments) { */ @SafeVarargs public final Impl combineWith(Argument... combinedArguments) { - this.combinedArguments.addAll(Arrays.asList(combinedArguments)); - return instance(); + return this.combineWith(Arrays.asList(combinedArguments)); } ////////////////////// @@ -357,30 +358,31 @@ public void appendToCommandPaths(List> argumentStrings) { * This process is broken up into 4 other methods for the convenience of defining special node structures for specific * arguments. Those methods are: *
      - *
    • {@link #checkPreconditions(List, List)}
    • + *
    • {@link #checkPreconditions(List, List, List)}
    • *
    • {@link #createArgumentBuilder(List, List)}
    • - *
    • {@link #finishBuildingNode(ArgumentBuilder, List, CommandAPIExecutor)}
    • - *
    • {@link #linkNode(List, CommandNode, List, List, CommandAPIExecutor)}
    • + *
    • {@link #finishBuildingNode(ArgumentBuilder, List, Function)}
    • + *
    • {@link #linkNode(List, CommandNode, List, List, Function)}
    • *
    * - * @param previousNodes A List of {@link CommandNode}s to add this argument onto. - * @param previousArguments A List of CommandAPI arguments that came before this argument. - * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. - * @param terminalExecutor The {@link CommandAPIExecutor} to apply at the end of the node structure. - * This parameter can be null to indicate that this argument should not be - * executable. - * @param The Brigadier Source object for running commands. + * @param previousNodes A List of {@link CommandNode}s to add this argument onto. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousArgumentNames A List of Strings containing the node names that came before this argument. + * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an + * appropriate Brigadier {@link Command} which should be applied at the end of + * the node structure. This parameter can be null to indicate that this argument + * should not be executable. + * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier node structure for this argument. */ public List> addArgumentNodes( List> previousNodes, - List previousArguments, List previousNonLiteralArgumentNames, - CommandAPIExecutor> terminalExecutor + List previousArguments, List previousArgumentNames, + Function, Command> terminalExecutorCreator ) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); // Check preconditions - checkPreconditions(previousArguments, previousNonLiteralArgumentNames); + checkPreconditions(previousNodes, previousArguments, previousArgumentNames); // Handle previewable argument if (this instanceof Previewable) { @@ -388,54 +390,72 @@ public List> addArgumentNodes( } // Create node - ArgumentBuilder rootBuilder = createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + ArgumentBuilder rootBuilder = createArgumentBuilder(previousArguments, previousArgumentNames); // Finish building node - CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalExecutor); + CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalExecutorCreator); // Link node to previous - previousNodes = linkNode(previousNodes, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + previousNodes = linkNode(previousNodes, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); // Return last nodes return previousNodes; } /** - * Checks for any conditions that mean this argument cannot be build properly. + * Checks for any conditions that mean this argument cannot be built properly. * - * @param previousArguments A List of CommandAPI arguments that came before this argument. - * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. + * @param previousNodes A List of {@link CommandNode}s to add this argument onto. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousArgumentNames A List of Strings containing the node names that came before this argument. */ - public void checkPreconditions(List previousArguments, List previousNonLiteralArgumentNames) { - if (previousNonLiteralArgumentNames.contains(nodeName)) { + public void checkPreconditions( + List> previousNodes, List previousArguments, List previousArgumentNames + ) { + if(previousNodes.isEmpty()) { + throw new GreedyArgumentException(previousArguments, (Argument) this); + } + if (isListed && previousArgumentNames.contains(nodeName)) { throw new DuplicateNodeNameException(previousArguments, (Argument) this); } } /** * Creates a Brigadier {@link ArgumentBuilder} representing this argument. Note: calling this method will also add - * this argument and its name to the end of the given {@code previousArguments} and {@code previousNonLiteralArgumentNames} + * this argument and its name to the end of the given {@code previousArguments} and {@code previousArgumentNames} * lists. * - * @param previousArguments A List of CommandAPI arguments that came before this argument. - * @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument. - * @param The Brigadier Source object for running commands. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousArgumentNames A List of Strings containing the node names that came before this argument. + * @param The Brigadier Source object for running commands. * @return The {@link ArgumentBuilder} for this argument. */ - public ArgumentBuilder createArgumentBuilder(List previousArguments, List previousNonLiteralArgumentNames) { + public ArgumentBuilder createArgumentBuilder(List previousArguments, List previousArgumentNames) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); - // Create node - RequiredArgumentBuilder rootBuilder = RequiredArgumentBuilder.argument(nodeName, rawType); - - // Add suggestions - rootBuilder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this)); + // Create node and add suggestions + // Note: I would like to combine these two `build.suggests(...)` calls, but they are actually two unrelated + // methods since UnnamedRequiredArgumentBuilder does not extend RequiredArgumentBuilder (see + // UnnamedRequiredArgumentBuilder for why). If UnnamedRequiredArgumentBuilder *does* extend + // RequiredArgumentBuilder, please simplify this if statement, like what Literal#createArgumentBuilder does. + ArgumentBuilder rootBuilder; + if(isListed) { + RequiredArgumentBuilder builder = RequiredArgumentBuilder.argument(nodeName, rawType); + builder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this)); + + rootBuilder = builder; + } else { + UnnamedRequiredArgumentBuilder builder = UnnamedRequiredArgumentBuilder.unnamedArgument(nodeName, rawType); + builder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this)); + + rootBuilder = builder; + } // Add argument to previousArgument lists // Note: this argument should not be in the previous arguments list when doing suggestions, // since this argument is not going to be present in the cmdCtx while being suggested previousArguments.add((Argument) this); - previousNonLiteralArgumentNames.add(nodeName); + if(isListed) previousArgumentNames.add(nodeName); return rootBuilder; } @@ -443,22 +463,24 @@ public void checkPreconditions(List previousArguments, List pr /** * Finishes building the Brigadier {@link ArgumentBuilder} representing this argument. * - * @param rootBuilder The {@link ArgumentBuilder} to finish building. - * @param previousArguments A List of CommandAPI arguments that came before this argument. - * @param terminalExecutor The {@link CommandAPIExecutor} to apply at the end of the node structure. This parameter - * can be null to indicate that this argument should not be executable. - * @param The Brigadier Source object for running commands. + * @param rootBuilder The {@link ArgumentBuilder} to finish building. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an + * appropriate Brigadier {@link Command} which should be applied at the end + * of the node structure. This parameter can be null to indicate that this + * argument should not be executable. + * @param The Brigadier Source object for running commands. * @return The {@link CommandNode} representing this argument created by building the given {@link ArgumentBuilder}. */ - public CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, CommandAPIExecutor> terminalExecutor) { + public CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, Function, Command> terminalExecutorCreator) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); // Add permission and requirements rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); // Add the given executor if we are the last node - if (combinedArguments.isEmpty() && terminalExecutor != null && terminalExecutor.hasAnyExecutors()) { - rootBuilder.executes(handler.generateBrigadierCommand(previousArguments, terminalExecutor)); + if (combinedArguments.isEmpty() && terminalExecutorCreator != null) { + rootBuilder.executes(terminalExecutorCreator.apply(previousArguments)); } return rootBuilder.build(); @@ -467,23 +489,24 @@ public CommandNode finishBuildingNode(ArgumentBuilder The Brigadier Source object for running commands. - * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing this Argument. Note that - * this is not necessarily the {@code rootNode} for this argument, since the Brigadier node structure may contain multiple - * nodes in a chain. This might happen if {@link #combineWith(AbstractArgument[])} was called for this argument to merge - * it with other arguments. + * @param previousNodes A List of {@link CommandNode}s to add this argument onto. + * @param rootNode The {@link CommandNode} representing this argument. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousArgumentNames A List of Strings containing the node names that came before this argument. + * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an + * appropriate Brigadier {@link Command} which should be applied at the end + * of the node structure. This parameter can be null to indicate that this + * argument should not be executable. + * @param The Brigadier Source object for running commands. + * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing this Argument. Note + * that this is not necessarily the {@code rootNode} for this argument, since the Brigadier node structure may + * contain multiple nodes in a chain. This might happen if {@link #combineWith(AbstractArgument[])} was called + * for this argument to merge it with other arguments. */ public List> linkNode( List> previousNodes, CommandNode rootNode, - List previousArguments, List previousNonLiteralArgumentNames, - CommandAPIExecutor> terminalExecutor + List previousArguments, List previousArgumentNames, + Function, Command> terminalExecutorCreator ) { // Add rootNode to the previous nodes for(CommandNode previousNode : previousNodes) { @@ -491,12 +514,13 @@ public List> linkNode( } // Add combined arguments - previousNodes = List.of(rootNode); + // A GreedyArgument cannot have arguments after it + previousNodes = this instanceof GreedyArgument ? List.of() : List.of(rootNode); for (int i = 0; i < combinedArguments.size(); i++) { Argument subArgument = combinedArguments.get(i); - previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousNonLiteralArgumentNames, + previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, // Only apply the `terminalExecutor` to the last argument - i == combinedArguments.size() - 1 ? terminalExecutor : null); + i == combinedArguments.size() - 1 ? terminalExecutorCreator : null); } // Return last nodes diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java index 0c66d6e22f..b665e82926 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java @@ -123,6 +123,11 @@ public enum CommandAPIArgumentType { */ ENVIRONMENT("api:environment"), + /** + * The FlagsArgument + */ + FLAGS_ARGUMENT, + /** * The FloatRangeArgument */ diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java new file mode 100644 index 0000000000..d155427d6b --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -0,0 +1,255 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.commandnodes.FlagsArgumentEndingNode; +import dev.jorel.commandapi.commandnodes.FlagsArgumentRootNode; +import dev.jorel.commandapi.executors.CommandArguments; + +import java.util.*; +import java.util.function.Function; + +public interface FlagsArgumentCommon +/// @endcond +, Argument +/// @cond DOX +extends AbstractArgument +/// @endcond +, CommandSender> { + // Setup information + Impl loopingBranch(Argument... arguments); + + List> getLoopingBranches(); + + Impl terminalBranch(Argument... arguments); + + List> getTerminalBranches(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // LINKED METHODS // + // These automatically link to methods in AbstractArgument (make sure they have the same signature) // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Links to {@link AbstractArgument#getNodeName()}. + */ + String getNodeName(); + + /** + * Links to {@link AbstractArgument#getCombinedArguments()}. + */ + List getCombinedArguments(); + + /** + * Links to {@link AbstractArgument#checkPreconditions(List, List, List)}. + */ + void checkPreconditions( + List> previousNodes, List previousArguments, List previousArgumentNames + ); + + /** + * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, Function)}. + */ + CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, Function, Command> terminalExecutorCreator); + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDING METHODS // + // A FlagsArgument has special logic that should override the implementations in AbstractArgument // + ///////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Overrides {@link AbstractArgument#parseArgument(CommandContext, String, CommandArguments)} + */ + default List parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + List> parseInformations = cmdCtx.getArgument(key, List.class); + List results = new ArrayList<>(parseInformations.size()); + + for (ParseInformation parseInformation : parseInformations) { + results.add(handler.argsToCommandArgs(parseInformation.context(), parseInformation.arguments())); + } + + return results; + } + + record ParseInformation + /// @endcond + , CommandSender, Source> + (CommandContext context, List arguments) { + } + + /** + * Overrides {@link AbstractArgument#addArgumentNodes(List, List, List, Function)}. + *

    + * A FlagsArgument works completely differently from a typical argument, so we need to completely + * override the usual logic. + */ + default List> addArgumentNodes( + List> previousNodes, + List previousArguments, List previousArgumentNames, + Function, Command> terminalExecutorCreator + ) { + // Typical preconditions still apply + checkPreconditions(previousNodes, previousArguments, previousArgumentNames); + + // Add this argument to the lists + String nodeName = getNodeName(); + previousArguments.add((Argument) this); + previousArgumentNames.add(nodeName); + + // Create root node, add to previous + LiteralArgumentBuilder rootBuilder = LiteralArgumentBuilder.literal(nodeName); + finishBuildingNode(rootBuilder, previousArguments, null); + FlagsArgumentRootNode rootNode = new FlagsArgumentRootNode<>(rootBuilder.build()); + + for(CommandNode previousNode : previousNodes) { + previousNode.addChild(rootNode); + } + + // Setup looping branches + boolean loopingBranchesExecutable = getCombinedArguments().isEmpty() && getTerminalBranches().isEmpty(); + for(List loopingBranch : getLoopingBranches()) { + // Make clones of our lists to treat this branch independently + List branchPreviousArguments = new ArrayList<>(previousArguments); + List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); + + RootCommandNode branchRoot = new RootCommandNode<>(); + List> branchPreviousNodes = List.of(branchRoot); + + int lastIndex = loopingBranch.size() - 1; + for(int i = 0; i < loopingBranch.size(); i++) { + Argument argument = loopingBranch.get(i); + branchPreviousNodes = argument.addArgumentNodes( + branchPreviousNodes, branchPreviousArguments, branchPreviousArgumentNames, + // Only apply the `terminalExecutor` to the last argument + (loopingBranchesExecutable && i == lastIndex) ? terminalExecutorCreator : null + ); + } + + // Find second-to-last nodes so their children can be modified + // Unfortunately, we can't get this from the previous loop since arguments may unpack to multiple layers + Collection> currentNodes = branchRoot.getChildren(); + Collection> lastNodes = List.of(branchRoot); + Collection> secondLastNodes = null; + while (!currentNodes.isEmpty()) { + secondLastNodes = lastNodes; + lastNodes = currentNodes; + currentNodes = new HashSet<>(); + + for (CommandNode node : lastNodes) { + currentNodes.addAll(node.getChildren()); + } + } + + // Make final nodes loop back to the rootNode + rootNode.makeChildrenLoopBack(secondLastNodes, branchPreviousArguments); + + // Copy branch nodes directly to the root node (branchRoot's maps may have been intentionally de-synced) + CommandAPIHandler.getCommandNodeChildren(rootNode).putAll(CommandAPIHandler.getCommandNodeChildren(branchRoot)); + CommandAPIHandler.getCommandNodeLiterals(rootNode).putAll(CommandAPIHandler.getCommandNodeLiterals(branchRoot)); + CommandAPIHandler.getCommandNodeArguments(rootNode).putAll(CommandAPIHandler.getCommandNodeArguments(branchRoot)); + } + + // Setup terminal branches + boolean terminalBranchesExecutable = getCombinedArguments().isEmpty(); + List> newNodes = new ArrayList<>(); + for(List terminalBranch : getTerminalBranches()) { + // Make clones of our lists to treat this branch independently + List branchPreviousArguments = new ArrayList<>(previousArguments); + List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); + + RootCommandNode branchRoot = new RootCommandNode<>(); + List> branchPreviousNodes = List.of(branchRoot); + + int lastIndex = terminalBranch.size() - 1; + for(int i = 0; i < terminalBranch.size(); i++) { + Argument argument = terminalBranch.get(i); + branchPreviousNodes = argument.addArgumentNodes( + branchPreviousNodes, branchPreviousArguments, branchPreviousArgumentNames, + // Only apply the `terminalExecutor` to the last argument + (terminalBranchesExecutable && i == lastIndex) ? terminalExecutorCreator : null + ); + } + + // Find second-to-last nodes so their children can be modified + // Unfortunately, we can't get this from the previous loop since arguments may unpack to multiple layers + Collection> currentNodes = branchRoot.getChildren(); + Collection> lastNodes = List.of(branchRoot); + Collection> secondLastNodes = null; + while (!currentNodes.isEmpty()) { + secondLastNodes = lastNodes; + lastNodes = currentNodes; + currentNodes = new HashSet<>(); + + for (CommandNode node : lastNodes) { + currentNodes.addAll(node.getChildren()); + } + } + + // Wrap terminating nodes to extract flag values + for(CommandNode node : secondLastNodes) { + // Nodes in the `children` and `arguments`/`literals` maps need to be wrapped and substituted + Map> children = CommandAPIHandler.getCommandNodeChildren(node); + for (CommandNode child : node.getChildren()) { + CommandNode finalWrappedNode; + if (child instanceof LiteralCommandNode literalNode) { + LiteralCommandNode wrappedNode = + FlagsArgumentEndingNode.wrapNode(literalNode, nodeName, branchPreviousArguments); + + CommandAPIHandler.getCommandNodeLiterals(node).put(literalNode.getName(), wrappedNode); + finalWrappedNode = wrappedNode; + } else if(child instanceof ArgumentCommandNode argumentNode) { + ArgumentCommandNode wrappedNode = + FlagsArgumentEndingNode.wrapNode(argumentNode, nodeName, branchPreviousArguments); + + CommandAPIHandler.getCommandNodeArguments(node).put(argumentNode.getName(), wrappedNode); + finalWrappedNode = wrappedNode; + } else { + throw new IllegalArgumentException("Node must be an argument or literal. Given " + child + " with type " + child.getClass().getName()); + } + children.put(child.getName(), finalWrappedNode); + + // These wrapped nodes should always have the same children as the node they are wrapping, + // so let's share map instances + CommandAPIHandler.setCommandNodeChildren(finalWrappedNode, CommandAPIHandler.getCommandNodeChildren(child)); + CommandAPIHandler.setCommandNodeLiterals(finalWrappedNode, CommandAPIHandler.getCommandNodeLiterals(child)); + CommandAPIHandler.setCommandNodeArguments(finalWrappedNode, CommandAPIHandler.getCommandNodeArguments(child)); + } + } + + // These nodes will be our final nodes + newNodes.addAll(branchPreviousNodes); + + // Copy branch nodes directly to the root node (branchRoot's maps may have been intentionally de-synced) + CommandAPIHandler.getCommandNodeChildren(rootNode).putAll(CommandAPIHandler.getCommandNodeChildren(branchRoot)); + CommandAPIHandler.getCommandNodeLiterals(rootNode).putAll(CommandAPIHandler.getCommandNodeLiterals(branchRoot)); + CommandAPIHandler.getCommandNodeArguments(rootNode).putAll(CommandAPIHandler.getCommandNodeArguments(branchRoot)); + } + + // Add combined arguments + previousNodes = newNodes; + List combinedArguments = getCombinedArguments(); + for (int i = 0; i < combinedArguments.size(); i++) { + Argument subArgument = combinedArguments.get(i); + previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, + // Only apply the `terminalExecutor` to the last argument + i == combinedArguments.size() - 1 ? terminalExecutorCreator : null); + } + + // Return last nodes + return previousNodes; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java index 5afb6a4f8b..f91dc86661 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.List; @@ -22,31 +23,43 @@ public interface Literal - * Normally, Arguments cannot be built if their node name is found in {@code previousNonLiteralArgumentNames} list. - * However, Literals do not have this problem, so we want to skip that check. + * Links to {@link AbstractArgument#getNodeName()}. */ - default void checkPreconditions(List previousArguments, List previousNonLiteralArgumentNames) { + String getNodeName(); - } + /** + * Links to {@link AbstractArgument#isListed()}. + */ + boolean isListed(); + + /////////////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDING METHODS // + // A Literal has special logic that should override the implementations in AbstractArgument // + /////////////////////////////////////////////////////////////////////////////////////////////// /** * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. *

    * Normally, Arguments will use Brigadier's RequiredArgumentBuilder. However, Literals use LiteralArgumentBuilders. - * Arguments also usually add their name to the {@code previousNonLiteralArgumentNames} list, but literal node names - * do not conflict with required argument node names. + * Arguments also usually add their name to the {@code previousArgumentNames} list here, but Literals only do + * this if they are listed. */ - default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousNonLiteralArgumentNames) { + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousArgumentNames) { + String nodeName = getNodeName(); + String literal = getLiteral(); + previousArguments.add((Argument) this); + if(isListed()) previousArgumentNames.add(nodeName); - return LiteralArgumentBuilder.literal(getLiteral()); + // If we are listed, use a NamedLiteralArgumentBuilder to put our literal into the CommandContext + return isListed() ? + NamedLiteralArgumentBuilder.namedLiteral(nodeName, literal) : + LiteralArgumentBuilder.literal(literal); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index 12d9ab764b..ff2724a95d 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -1,14 +1,16 @@ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; -import dev.jorel.commandapi.CommandAPIExecutor; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; +import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.function.Function; /** * An interface representing arguments with multiple literal string definitions @@ -39,31 +41,26 @@ public interface MultiLiteral getCombinedArguments(); /** - * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, CommandAPIExecutor)}. + * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, Function)}. */ - CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, CommandAPIExecutor> terminalExecutor); + CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, Function, Command> terminalExecutorCreator); //////////////////////////////////////////////////////////////////////////////////////////////////// // OVERRIDING METHODS // // A MultiLiteral has special logic that should override the implementations in AbstractArgument // //////////////////////////////////////////////////////////////////////////////////////////////////// - /** - * Overrides {@link AbstractArgument#checkPreconditions(List, List)}. - *

    - * Normally, Arguments cannot be built if their node name is found in {@code previousNonLiteralArgumentNames} list. - * However, LiteralArguments do not have this problem, so we want to skip that check. - */ - default void checkPreconditions(List previousArguments, List previousNonLiteralArgumentNames) { - - } - /** * Overrides {@link AbstractArgument#appendToCommandPaths(List)}. *

    @@ -102,25 +99,29 @@ default void appendToCommandPaths(List> argumentStrings) { * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. *

    * Normally, Arguments will use Brigadier's RequiredArgumentBuilder. However, MultiLiterals use LiteralArgumentBuilders. - * Arguments also usually add their name to the {@code previousNonLiteralArgumentNames} list, but literal node names - * do not conflict with required argument node names. */ - default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousNonLiteralArgumentNames) { + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousArgumentNames) { + String nodeName = getNodeName(); + String literal = getLiterals()[0]; + previousArguments.add((Argument) this); + if(isListed()) previousArgumentNames.add(nodeName); - return MultiLiteralArgumentBuilder.multiLiteral(getNodeName(), getLiterals()[0]); + return isListed() ? + NamedLiteralArgumentBuilder.namedLiteral(nodeName, literal) : + LiteralArgumentBuilder.literal(literal); } /** - * Overrides {@link AbstractArgument#linkNode(List, CommandNode, List, List, CommandAPIExecutor)}. + * Overrides {@link AbstractArgument#linkNode(List, CommandNode, List, List, Function)}. *

    * Normally, Arguments are only represented by a single node, and so only need to link one node. However, a MultiLiteral * represents multiple literal node paths, which also need to be generated and inserted into the node structure. */ default List> linkNode( List> previousNodes, CommandNode rootNode, - List previousArguments, List previousNonLiteralArgumentNames, - CommandAPIExecutor> terminalExecutor + List previousArguments, List previousArgumentNames, + Function, Command> terminalExecutorCreator ) { List> newNodes = new ArrayList<>(); // Add root node to previous @@ -130,14 +131,18 @@ default List> linkNode( newNodes.add(rootNode); // Generate nodes for other literals + String nodeName = getNodeName(); + boolean isListed = isListed(); Iterator literals = Arrays.asList(getLiterals()).iterator(); literals.next(); // Skip first literal; that was handled by `#createArgumentBuilder` while (literals.hasNext()) { // Create node - MultiLiteralArgumentBuilder literalBuilder = MultiLiteralArgumentBuilder.multiLiteral(getNodeName(), literals.next()); + LiteralArgumentBuilder literalBuilder = isListed ? + NamedLiteralArgumentBuilder.namedLiteral(nodeName, literals.next()) : + LiteralArgumentBuilder.literal(literals.next()); // Finish building node - CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalExecutor); + CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalExecutorCreator); // Add node to previous for(CommandNode previousNode : previousNodes) { @@ -151,9 +156,9 @@ default List> linkNode( List combinedArguments = getCombinedArguments(); for (int i = 0; i < combinedArguments.size(); i++) { Argument subArgument = combinedArguments.get(i); - previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousNonLiteralArgumentNames, + previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, // Only apply the `terminalExecutor` to the last argument - i == combinedArguments.size() - 1 ? terminalExecutor : null); + i == combinedArguments.size() - 1 ? terminalExecutorCreator : null); } // Return last nodes diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java new file mode 100644 index 0000000000..fce3fe4e94 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java @@ -0,0 +1,159 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedArgument; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.FlagsArgumentCommon; + +import java.util.ArrayList; +import java.util.List; + +public interface FlagsArgumentEndingNode +/// @endcond +, CommandSender, Source> { + static , CommandSender, Source> + LiteralCommandNode wrapNode( + LiteralCommandNode literalNode, String flagsArgumentName, List previousArguments + ) { + return new LiteralNode<>(literalNode, flagsArgumentName, previousArguments); + } + + static , CommandSender, Source> + ArgumentCommandNode wrapNode( + ArgumentCommandNode argumentNode, String flagsArgumentName, List previousArguments + ) { + return new ArgumentNode<>(argumentNode, flagsArgumentName, previousArguments); + } + + String getFlagsArgumentName(); + + List getPreviousArguments(); + + default void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + // Correctly set parsed node value + List> nodes = contextBuilder.getNodes(); + int lastNodeIndex = nodes.size() - 1; + ParsedCommandNode currentNode = nodes.get(lastNodeIndex); + nodes.set(lastNodeIndex, new ParsedCommandNode<>((CommandNode) this, currentNode.getRange())); + + // Extract previous flags + String name = getFlagsArgumentName(); + ParsedArgument>> currentValue = + (ParsedArgument>>) + contextBuilder.getArguments().get(name); + List> currentInformation = currentValue.getResult(); + + // Add new flags + // We do need to copy the information list and the contextBuilder + // Otherwise, parsing the argument result references itself, and you get a stack overflow + List> newInformation = new ArrayList<>(currentInformation); + newInformation.add(new FlagsArgumentCommon.ParseInformation<>( + contextBuilder.copy().build(reader.getRead()), getPreviousArguments() + )); + + // Add new flags back + ParsedArgument>> newValue = + new ParsedArgument<>(currentValue.getRange().getStart(), reader.getCursor(), newInformation); + contextBuilder.withArgument(name, newValue); + } + + class LiteralNode + /// @endcond + , CommandSender, Source> + extends LiteralCommandNode implements FlagsArgumentEndingNode { + private final LiteralCommandNode literalNode; + private final String flagsArgumentName; + private final List previousArguments; + + public LiteralNode( + LiteralCommandNode literalNode, String flagsArgumentName, List previousArguments + ) { + super( + literalNode.getName(), literalNode.getCommand(), literalNode.getRequirement(), + literalNode.getRedirect(), literalNode.getRedirectModifier(), literalNode.isFork() + ); + + this.literalNode = literalNode; + this.flagsArgumentName = flagsArgumentName; + this.previousArguments = previousArguments; + } + + @Override + public String getFlagsArgumentName() { + return flagsArgumentName; + } + + @Override + public List getPreviousArguments() { + return previousArguments; + } + + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + literalNode.parse(reader, contextBuilder); + FlagsArgumentEndingNode.super.parse(reader, contextBuilder); + } + + @Override + public String toString() { + return ""; + } + } + + class ArgumentNode + /// @endcond + , CommandSender, Source, T> + extends ArgumentCommandNode implements FlagsArgumentEndingNode { + private final ArgumentCommandNode argumentNode; + private final String flagsArgumentName; + private final List previousArguments; + + public ArgumentNode( + ArgumentCommandNode argumentNode, String flagsArgumentName, List previousArguments + ) { + super( + argumentNode.getName(), argumentNode.getType(), + argumentNode.getCommand(), argumentNode.getRequirement(), + argumentNode.getRedirect(), argumentNode.getRedirectModifier(), argumentNode.isFork(), + argumentNode.getCustomSuggestions() + ); + + this.argumentNode = argumentNode; + this.flagsArgumentName = flagsArgumentName; + this.previousArguments = previousArguments; + } + + @Override + public String getFlagsArgumentName() { + return flagsArgumentName; + } + + @Override + public List getPreviousArguments() { + return previousArguments; + } + + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + argumentNode.parse(reader, contextBuilder); + FlagsArgumentEndingNode.super.parse(reader, contextBuilder); + } + + @Override + public String toString() { + return ""; + } + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java new file mode 100644 index 0000000000..1a83ff91a4 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java @@ -0,0 +1,77 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedArgument; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.arguments.AbstractArgument; + +import java.util.*; + +public class FlagsArgumentRootNode +/// @endcond +, CommandSender, Source> extends LiteralCommandNode { + public FlagsArgumentRootNode(LiteralCommandNode literalNode) { + super( + literalNode.getName(), literalNode.getCommand(), literalNode.getRequirement(), + literalNode.getRedirect(), literalNode.getRedirectModifier(), literalNode.isFork() + ); + } + + // Handle setting up the loop back + public void makeChildrenLoopBack(Collection> nodes, List previousArguments) { + for (CommandNode node : nodes) { + // Nodes in the `children` map should redirect to this node, so the client doesn't see a child cycle + // Nodes in the `arguments`/`literals` maps should have all children of this node as children + // and store all the flag values given + Map> children = CommandAPIHandler.getCommandNodeChildren(node); + for (CommandNode child : node.getChildren()) { + // Clone the node, redirect it here, then put the clone into the children map + children.put(child.getName(), child.createBuilder().redirect(this).build()); + + // Wrap nodes in the `arguments`/`literals` map to extract the flag values on loop + CommandNode finalWrappedNode; + if (child instanceof LiteralCommandNode literalNode) { + LiteralCommandNode wrappedNode = + FlagsArgumentEndingNode.wrapNode(literalNode, getName(), previousArguments); + + CommandAPIHandler.getCommandNodeLiterals(node).put(literalNode.getName(), wrappedNode); + finalWrappedNode = wrappedNode; + } else if(child instanceof ArgumentCommandNode argumentNode) { + ArgumentCommandNode wrappedNode = + FlagsArgumentEndingNode.wrapNode(argumentNode, getName(), previousArguments); + + CommandAPIHandler.getCommandNodeArguments(node).put(argumentNode.getName(), wrappedNode); + finalWrappedNode = wrappedNode; + } else { + throw new IllegalArgumentException("Node must be an argument or literal. Given " + child + " with type " + child.getClass().getName()); + } + + // These wrapped nodes should always have our children as their children, so let's share map instances + CommandAPIHandler.setCommandNodeChildren(finalWrappedNode, CommandAPIHandler.getCommandNodeChildren(this)); + CommandAPIHandler.setCommandNodeLiterals(finalWrappedNode, CommandAPIHandler.getCommandNodeLiterals(this)); + CommandAPIHandler.setCommandNodeArguments(finalWrappedNode, CommandAPIHandler.getCommandNodeArguments(this)); + } + } + } + + // Set up the list of information output by this argument + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + int start = reader.getCursor(); + super.parse(reader, contextBuilder); + + contextBuilder.withArgument(getName(), new ParsedArgument<>(start, reader.getCursor(), new ArrayList<>())); + } + + @Override + public String toString() { + return ""; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralArgumentBuilder.java similarity index 54% rename from commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgumentBuilder.java rename to commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralArgumentBuilder.java index 533965d84a..6317d3639b 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgumentBuilder.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralArgumentBuilder.java @@ -1,38 +1,40 @@ -package dev.jorel.commandapi.arguments; +package dev.jorel.commandapi.commandnodes; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.arguments.Literal; +import dev.jorel.commandapi.arguments.MultiLiteral; /** - * A special type of {@link LiteralArgumentBuilder} for {@link MultiLiteral}. Compared to the {@link LiteralArgumentBuilder}, - * this class also has a {code nodeName} and builds a {@link MultiLiteralCommandNode}. + * A special type of {@link LiteralArgumentBuilder} for listed {@link Literal}s and {@link MultiLiteral}s. Compared to + * the {@link LiteralArgumentBuilder}, this class also has a {code nodeName} and builds a {@link NamedLiteralCommandNode}. * * @param The Brigadier Source object for running commands. */ -public class MultiLiteralArgumentBuilder extends LiteralArgumentBuilder { +public class NamedLiteralArgumentBuilder extends LiteralArgumentBuilder { private final String nodeName; /** - * Creates a new MultiLiteralArgumentBuilder with the given nodeName and literal. + * Creates a new {@link NamedLiteralCommandNode} with the given nodeName and literal. * * @param nodeName the string that identifies the parsed command node in the CommandContext. * @param literal the literal that identifies the built command node in the CommandDispatcher */ - protected MultiLiteralArgumentBuilder(String nodeName, String literal) { + protected NamedLiteralArgumentBuilder(String nodeName, String literal) { super(literal); this.nodeName = nodeName; } /** - * A factory method to create a new builder for a {@link MultiLiteralCommandNode}. + * A factory method to create a new builder for a {@link NamedLiteralCommandNode}. * * @param nodeName the string that identifies the parsed command node in the CommandContext. * @param literal the literal that identifies the built command node in the CommandDispatcher * @param The Brigadier Source object for running commands. - * @return the created MultiLiteralArgumentBuilder. + * @return the constructed {@link NamedLiteralArgumentBuilder}. */ - public static MultiLiteralArgumentBuilder multiLiteral(String nodeName, String literal) { - return new MultiLiteralArgumentBuilder<>(nodeName, literal); + public static NamedLiteralArgumentBuilder namedLiteral(String nodeName, String literal) { + return new NamedLiteralArgumentBuilder<>(nodeName, literal); } /** @@ -43,8 +45,8 @@ public String getNodeName() { } @Override - public MultiLiteralCommandNode build() { - MultiLiteralCommandNode result = new MultiLiteralCommandNode<>(getNodeName(), getLiteral(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork()); + public NamedLiteralCommandNode build() { + NamedLiteralCommandNode result = new NamedLiteralCommandNode<>(getNodeName(), getLiteral(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork()); for (CommandNode argument : getArguments()) { result.addChild(argument); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java similarity index 66% rename from commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralCommandNode.java rename to commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java index 7a72432220..fbb210dad3 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java @@ -1,4 +1,4 @@ -package dev.jorel.commandapi.arguments; +package dev.jorel.commandapi.commandnodes; import com.mojang.brigadier.Command; import com.mojang.brigadier.RedirectModifier; @@ -8,20 +8,22 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.jorel.commandapi.arguments.Literal; +import dev.jorel.commandapi.arguments.MultiLiteral; import java.util.function.Predicate; /** - * A special type of {@link LiteralCommandNode} for {@link MultiLiteral}. Compared to the {@link LiteralCommandNode}, - * this class also has a {@code nodeName}. When this node is parsed, it will add its literal value as an argument in - * the CommandContext, allowing a MultiLiteralArgument to know which literal was selected. + * A special type of {@link LiteralCommandNode} for listed {@link Literal}s and {@link MultiLiteral}s. Compared to + * the {@link LiteralCommandNode}, this class also has a {@code nodeName}. When this node is parsed, it will add its + * literal value as an argument in the CommandContext, like a required argument. * * @param The Brigadier Source object for running commands. */ -public class MultiLiteralCommandNode extends LiteralCommandNode { +public class NamedLiteralCommandNode extends LiteralCommandNode { private final String nodeName; - public MultiLiteralCommandNode(String nodeName, String literal, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks) { + public NamedLiteralCommandNode(String nodeName, String literal, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks) { super(literal, command, requirement, redirect, modifier, forks); this.nodeName = nodeName; } @@ -33,8 +35,8 @@ public String getNodeName() { return nodeName; } - // A MultiLiteralCommandNode is mostly identical to a LiteralCommandNode - // The only difference is that when a MultiLiteral is parsed, it adds its literal as an argument based on the nodeName + // A NamedLiteralCommandNode is mostly identical to a LiteralCommandNode + // The only difference is that when a NamedLiteral is parsed, it adds its literal as an argument based on the nodeName @Override public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { int start = reader.getCursor(); @@ -45,12 +47,10 @@ public void parse(StringReader reader, CommandContextBuilder contextBuil // Typical LiteralCommandNode methods, adding in the nodeName parameter // Mostly copied and inspired by the implementations for these methods in LiteralCommandNode - @Override public boolean equals(Object obj) { if (this == obj) return true; - if (!(obj instanceof MultiLiteralCommandNode other)) return false; - + if (!(obj instanceof NamedLiteralCommandNode other)) return false; if (!nodeName.equals(other.nodeName)) return false; return super.equals(obj); @@ -64,8 +64,8 @@ public int hashCode() { } @Override - public MultiLiteralArgumentBuilder createBuilder() { - MultiLiteralArgumentBuilder builder = MultiLiteralArgumentBuilder.multiLiteral(this.nodeName, getLiteral()); + public NamedLiteralArgumentBuilder createBuilder() { + NamedLiteralArgumentBuilder builder = NamedLiteralArgumentBuilder.namedLiteral(this.nodeName, getLiteral()); builder.requires(getRequirement()); builder.forward(getRedirect(), getRedirectModifier(), isFork()); @@ -77,6 +77,6 @@ public MultiLiteralArgumentBuilder createBuilder() { @Override public String toString() { - return ""; + return ""; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java new file mode 100644 index 0000000000..ae2cf6105b --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java @@ -0,0 +1,60 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.RedirectModifier; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; + +import java.util.function.Predicate; + +/** + * A special type of {@link ArgumentCommandNode} for unlisted Arguments. Compared to the {@link ArgumentCommandNode}, + * when this node is parsed, it will not add its value as an argument in the CommandContext, like a literal argument. + * + * @param The Brigadier Source object for running commands. + * @param The type returned when this argument is parsed. + */ +public class UnnamedArgumentCommandNode extends ArgumentCommandNode { + public UnnamedArgumentCommandNode(String name, ArgumentType type, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions) { + super(name, type, command, requirement, redirect, modifier, forks, customSuggestions); + } + + // A UnnamedArgumentCommandNode is mostly identical to a ArgumentCommandNode + // The only difference is that when a UnnamedArgument is parsed, it does not add its result to the CommandContext + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + final int start = reader.getCursor(); + getType().parse(reader); + contextBuilder.withNode(this, new StringRange(start, reader.getCursor())); + } + + // Typical ArgumentCommandNode methods, but make it our classes + // Mostly copied and inspired by the implementations for these methods in ArgumentCommandNode + + // TODO: Um, this currently doesn't work since UnnamedRequiredArgumentBuilder does not extend RequiredArgumentBuilder + // See UnnamedRequiredArgumentBuilder for why + // I hope no one tries to use this method! +// @Override +// public UnnamedRequiredArgumentBuilder createBuilder() { +// UnnamedRequiredArgumentBuilder builder = UnnamedRequiredArgumentBuilder.unnamedArgument(getName(), getType()); +// +// builder.requires(getRequirement()); +// builder.forward(getRedirect(), getRedirectModifier(), isFork()); +// builder.suggests(getCustomSuggestions()); +// if (getCommand() != null) { +// builder.executes(getCommand()); +// } +// return builder; +// } + + @Override + public String toString() { + return ""; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedRequiredArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedRequiredArgumentBuilder.java new file mode 100644 index 0000000000..2ef98aa06a --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedRequiredArgumentBuilder.java @@ -0,0 +1,65 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.CommandNode; + +/** + * A special type of {@link RequiredArgumentBuilder} for unlisted Arguments. Compared to the + * {@link RequiredArgumentBuilder}, this class builds a {@link UnnamedArgumentCommandNode} + * + * @param The Brigadier Source object for running commands. + * @param The type returned when this argument is parsed. + */ +// We can't actually extend RequiredArgumentBuilder since its only constructor is private :( +// See https://github.com/Mojang/brigadier/pull/144 +public class UnnamedRequiredArgumentBuilder extends ArgumentBuilder> { + // Everything here is copied from RequiredArgumentBuilder, which is why it would be nice to extend that directly + private final String name; + private final ArgumentType type; + private SuggestionProvider suggestionsProvider = null; + + private UnnamedRequiredArgumentBuilder(final String name, final ArgumentType type) { + this.name = name; + this.type = type; + } + + public static UnnamedRequiredArgumentBuilder unnamedArgument(final String name, final ArgumentType type) { + return new UnnamedRequiredArgumentBuilder<>(name, type); + } + + public UnnamedRequiredArgumentBuilder suggests(final SuggestionProvider provider) { + this.suggestionsProvider = provider; + return getThis(); + } + + public SuggestionProvider getSuggestionsProvider() { + return suggestionsProvider; + } + + @Override + protected UnnamedRequiredArgumentBuilder getThis() { + return this; + } + + public ArgumentType getType() { + return type; + } + + public String getName() { + return name; + } + + public UnnamedArgumentCommandNode build() { + final UnnamedArgumentCommandNode result = new UnnamedArgumentCommandNode<>(getName(), getType(), + getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider()); + + for (final CommandNode argument : getArguments()) { + result.addChild(argument); + } + + return result; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java index 5351aa5c94..f185858b19 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/DuplicateNodeNameException.java @@ -5,8 +5,8 @@ import java.util.List; /** - * An exception caused when two arguments conflict due to sharing the same node name. Note that literal nodes are allowed - * to share node names with other literals and arguments. + * An exception caused when two arguments conflict due to sharing the same node name. Note that unlisted arguments are + * allowed to share node names with other arguments. */ public class DuplicateNodeNameException extends CommandRegistrationException { /** @@ -22,11 +22,11 @@ public class DuplicateNodeNameException extends CommandRegistrationException { private static > String buildMessage(List previousArguments, Argument argument) { StringBuilder builder = new StringBuilder(); - builder.append("Duplicate node names for non-literal arguments are not allowed! Going down the "); + builder.append("Duplicate node names for listed arguments are not allowed! Going down the "); addArgumentList(builder, previousArguments); builder.append(" branch, found "); addArgument(builder, argument); - builder.append(", which had a duplicate node name"); + builder.append(", which had a duplicated node name"); return builder.toString(); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java index 82aa4576cf..9891e064e6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/GreedyArgumentException.java @@ -31,32 +31,26 @@ public class GreedyArgumentException extends CommandRegistrationException { /** * Creates a GreedyArgumentException * - * @param previousArguments The arguments that came before the greedy argument - * @param argument The greedy argument that is in an invalid spot - * @param followingBranches The branches following the greedy argument that weren't supposed to be there + * @param previousArguments The arguments that came before and including the greedy argument + * @param argument The argument that invalidly came after a greedy argument * @param The Argument class being used */ public > GreedyArgumentException( - List previousArguments, Argument argument, List> followingBranches) { - super(buildMessage(previousArguments, argument, followingBranches)); + List previousArguments, Argument argument) { + super(buildMessage(previousArguments, argument)); } private static > String buildMessage( - List previousArguments, Argument argument, List> followingBranches) { + List previousArguments, Argument argument) { StringBuilder builder = new StringBuilder(); + int greedyArgumentIndex = previousArguments.size() - 1; builder.append("A greedy argument can only be declared at the end of a command. Going down the "); - addArgumentList(builder, previousArguments); - builder.append(" branch, found "); - addArgument(builder, argument); + addArgumentList(builder, previousArguments.subList(0, greedyArgumentIndex)); + builder.append(" branch, found the greedy argument "); + addArgument(builder, previousArguments.get(greedyArgumentIndex)); builder.append(" followed by "); - - for (List branch : followingBranches) { - addArgumentList(builder, branch); - builder.append(" and "); - } - - builder.setLength(builder.length() - 5); + addArgument(builder, argument); return builder.toString(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java index 3f2e78b266..477d8a24b7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java @@ -1,40 +1,23 @@ package dev.jorel.commandapi; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; -import dev.jorel.commandapi.preprocessor.RequireField; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Predicate; -@RequireField(in = CommandNode.class, name = "children", ofType = Map.class) -@RequireField(in = CommandNode.class, name = "literals", ofType = Map.class) -@RequireField(in = CommandNode.class, name = "arguments", ofType = Map.class) public abstract class CommandRegistrationStrategy { - // Reflection - // I'd like to make the Maps here `Map>`, but these static fields cannot use the type - // parameter Source. We still need to cast to that signature for map, so Map is raw. - static final SafeVarHandle, Map> commandNodeChildren; - private static final SafeVarHandle, Map> commandNodeLiterals; - private static final SafeVarHandle, Map> commandNodeArguments; - - // Compute all var handles all in one go so we don't do this during main server runtime - static { - commandNodeChildren = SafeVarHandle.ofOrNull(CommandNode.class, "children", "children", Map.class); - commandNodeLiterals = SafeVarHandle.ofOrNull(CommandNode.class, "literals", "literals", Map.class); - commandNodeArguments = SafeVarHandle.ofOrNull(CommandNode.class, "arguments", "arguments", Map.class); - } - // Utility methods protected void removeBrigadierCommands(RootCommandNode root, String commandName, boolean unregisterNamespaces, Predicate> extraCheck) { - Map> children = (Map>) commandNodeChildren.get(root); - Map> literals = (Map>) commandNodeLiterals.get(root); - Map> arguments = (Map>) commandNodeArguments.get(root); + Map> children = CommandAPIHandler.getCommandNodeChildren(root); + Map> literals = CommandAPIHandler.getCommandNodeLiterals(root); + Map> arguments = CommandAPIHandler.getCommandNodeArguments(root); removeCommandFromMapIfCheckPasses(children, commandName, extraCheck); removeCommandFromMapIfCheckPasses(literals, commandName, extraCheck); @@ -49,7 +32,7 @@ protected void removeBrigadierCommands(RootCommandNode root, String comm } } - protected static void removeCommandNamespace(Map map, String commandName, Predicate extraCheck) { + protected static void removeCommandNamespace(Map map, String commandName, Predicate extraCheck) { for (String key : new HashSet<>(map.keySet())) { if (!isThisTheCommandButNamespaced(commandName, key)) continue; @@ -57,7 +40,7 @@ protected static void removeCommandNamespace(Map map, String comm } } - protected static void removeCommandFromMapIfCheckPasses(Map map, String key, Predicate extraCheck) { + protected static void removeCommandFromMapIfCheckPasses(Map map, String key, Predicate extraCheck) { T element = map.get(key); if (element == null) return; if (extraCheck.test(element)) map.remove(key); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java index 330d82117d..ca0663f7b0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java @@ -20,22 +20,28 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import java.io.Serializable; - -import org.bukkit.command.CommandSender; - +import com.mojang.brigadier.Command; import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.Message; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; - +import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.BukkitTooltip; import dev.jorel.commandapi.CommandAPIBukkit; import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.executors.CommandArguments; import net.kyori.adventure.text.Component; import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.command.CommandSender; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; /** * An argument that represents any custom object @@ -115,6 +121,107 @@ public T parseArgument(CommandContext c } } + // All `AbstractArgument` builder method calls should be handled by the base argument + @Override + public Argument includeSuggestions(ArgumentSuggestions suggestions) { + base.includeSuggestions(suggestions); + return this; + } + + @Override + public Optional> getIncludedSuggestions() { + return base.getIncludedSuggestions(); + } + + @Override + public Argument replaceSuggestions(ArgumentSuggestions suggestions) { + base.replaceSuggestions(suggestions); + return this; + } + + @Override + public Optional> getOverriddenSuggestions() { + return base.getOverriddenSuggestions(); + } + + @Override + public Argument withPermission(CommandPermission permission) { + base.withPermission(permission); + return this; + } + + @Override + public Argument withPermission(String permission) { + base.withPermission(permission); + return this; + } + + @Override + public CommandPermission getArgumentPermission() { + return base.getArgumentPermission(); + } + + @Override + public Predicate getRequirements() { + return base.getRequirements(); + } + + @Override + public Argument withRequirement(Predicate requirement) { + base.withRequirement(requirement); + return this; + } + + @Override + public boolean isListed() { + return base.isListed(); + } + + @Override + public Argument setListed(boolean listed) { + base.setListed(listed); + return this; + } + + @Override + public List> getCombinedArguments() { + return base.getCombinedArguments(); + } + + @Override + public boolean hasCombinedArguments() { + return base.hasCombinedArguments(); + } + + @Override + public Argument combineWith(List> combinedArguments) { + base.combineWith(combinedArguments); + return this; + } + + @Override + public List> addArgumentNodes( + List> previousNodes, + List> previousArguments, List previousArgumentNames, + Function>, Command> terminalExecutorCreator + ) { + // Node structure is determined by the base argument + previousNodes = base.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, + // Replace the base argument with this argument when executing the command + terminalExecutorCreator == null ? null : + args -> { + List> newArgs = new ArrayList<>(args); + newArgs.set(args.indexOf(base), this); + return terminalExecutorCreator.apply(newArgs); + } + ); + + // Replace the base argument with this argument when executing the command + previousArguments.set(previousArguments.indexOf(base), this); + + return previousNodes; + } + /** * MessageBuilder is used to create error messages for invalid argument inputs */ diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java new file mode 100644 index 0000000000..5642becf89 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java @@ -0,0 +1,80 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.executors.CommandArguments; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * @apiNote Yields a {@code List<}{@link CommandArguments}{@code >} + */ +@SuppressWarnings("rawtypes") +public class FlagsArgument extends Argument implements FlagsArgumentCommon, CommandSender> { + // Setup information + private final List>> loopingBranches = new ArrayList<>(); + private final List>> terminalBranches = new ArrayList<>(); + + /** + * Constructs a {@link FlagsArgument}. + * + * @param nodeName the node name for this argument + */ + public FlagsArgument(String nodeName) { + super(nodeName, null); + } + + @Override + public FlagsArgument loopingBranch(Argument... arguments) { + loopingBranches.add(List.of(arguments)); + return this; + } + + @Override + public List>> getLoopingBranches() { + return loopingBranches; + } + + @Override + public FlagsArgument terminalBranch(Argument... arguments) { + terminalBranches.add(List.of(arguments)); + return this; + } + + @Override + public List>> getTerminalBranches() { + return terminalBranches; + } + + // Normal Argument stuff + @Override + public Class getPrimitiveType() { + return List.class; + } + + @Override + public CommandAPIArgumentType getArgumentType() { + return CommandAPIArgumentType.FLAGS_ARGUMENT; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // FlagsArgumentCommon interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the FlagsArgumentCommon interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public List parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { + return FlagsArgumentCommon.super.parseArgument(cmdCtx, key, previousArgs); + } + + @Override + public List> addArgumentNodes(List> previousNodes, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return FlagsArgumentCommon.super.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index 3b53c6e397..1740f54b2f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -153,12 +153,7 @@ public String parseArgument(CommandContext cmdCtx, String key, //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override - public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { - Literal.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); - } - - @Override - public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { - return Literal.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return Literal.super.createArgumentBuilder(previousArguments, previousArgumentNames); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index 526e3ece84..42f0b93caf 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -20,17 +20,17 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.CommandNode; -import dev.jorel.commandapi.CommandAPIExecutor; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; import org.bukkit.command.CommandSender; import java.util.List; +import java.util.function.Function; /** * An argument that represents multiple LiteralArguments @@ -106,23 +106,18 @@ public String parseArgument(CommandContext cmdCtx, String key, // method by default. However, we want to use the implementations found in the MultiLiteral interface. // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { - MultiLiteral.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); - } - @Override public void appendToCommandPaths(List> argumentStrings) { MultiLiteral.super.appendToCommandPaths(argumentStrings); } @Override - public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { - return MultiLiteral.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return MultiLiteral.super.createArgumentBuilder(previousArguments, previousArgumentNames); } @Override - public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { - return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index cb5a44eb15..c03047b1cc 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -21,11 +21,12 @@ package dev.jorel.commandapi; import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; +import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.executors.CommandArguments; +import dev.jorel.commandapi.wrappers.IntegerRange; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.InvalidPluginException; @@ -132,5 +133,109 @@ private JavaPlugin getAndValidatePlugin(String pluginName) { @Override public void onEnable() { CommandAPI.onEnable(); + + // examples from https://github.com/JorelAli/CommandAPI/issues/483 + new CommandAPICommand("singleFlag") + .withArguments( + new FlagsArgument("filters") + .loopingBranch( + new LiteralArgument("filter", "sort").setListed(true), + new MultiLiteralArgument("sortType", "furthest", "nearest", "random") + ) + .loopingBranch( + new LiteralArgument("filter", "limit").setListed(true), + new IntegerArgument("limitAmount", 0) + ) + .loopingBranch( + new LiteralArgument("filter", "distance").setListed(true), + new IntegerRangeArgument("distanceRange") + ) + ) + .executes(info -> { + for (CommandArguments branch : info.args().>getUnchecked("filters")) { + String filterType = branch.getUnchecked("filter"); + info.sender().sendMessage(switch (filterType) { + case "sort" -> "Sort " + branch.getUnchecked("sortType"); + case "limit" -> "Limit " + branch.getUnchecked("limitAmount"); + case "distance" -> "Distance " + branch.getUnchecked("distanceRange"); + default -> "Unknown branch " + filterType; + }); + } + }) + .register(); + + new CommandAPICommand("nestedFlags") + .withArguments( + new FlagsArgument("execute") + .loopingBranch( + new LiteralArgument("subcommand", "as").setListed(true), + new EntitySelectorArgument.ManyEntities("targets") + ) + .loopingBranch( + new FlagsArgument("if") + .terminalBranch( + new LiteralArgument("ifType", "block").setListed(true), + new BlockPredicateArgument("predicate") + ) + .terminalBranch( + new LiteralArgument("ifType", "entity").setListed(true), + new EntitySelectorArgument.ManyEntities("predicate") + ) + ) + .terminalBranch( + new LiteralArgument("run") + ), + new CommandArgument("command") + ) + .executes(info -> { + info.sender().sendMessage(info.args().argsMap().toString()); + }) + .register(); + + // example from NextdoorPsycho + new CommandAPICommand("colorCommand") + .withArguments( + createColorArgument("color") + ) + .executes(info -> { + info.sender().sendMessage(info.args().getUnchecked("color").toString()); + }) + .register(); } + + private static Argument createColorArgument(String nodeName) { + return new CustomArgument<>( + new FlagsArgument(nodeName) + .loopingBranch( + // A DynamicMultiLiteral would be perfect here :P + // https://github.com/JorelAli/CommandAPI/issues/513 + // At least, this is how I imagine it would work + new StringArgument("channel").replaceSuggestions(ArgumentSuggestions.strings(info -> { + Set channelsLeft = new HashSet<>(Set.of("-r", "-g", "-b")); + for(CommandArguments previousChannels : info.previousArgs().>getUnchecked(nodeName)) { + // Yes, you can reference previous versions of yourself + channelsLeft.remove(previousChannels.getUnchecked("channel")); + } + return channelsLeft.toArray(String[]::new); + })), + new IntegerArgument("value", 0, 255) + ), + info -> { + int red = 0, green = 0, blue = 0; + for (CommandArguments channels : (List) info.currentInput()) { + int value = channels.getUnchecked("value"); + String channel = channels.getUnchecked("channel"); + switch (channel) { + case "-r" -> red = value; + case "-g" -> green = value; + case "-b" -> blue = value; + default -> throw CustomArgument.CustomArgumentException.fromString("Unknown channel \"" + channel + "\""); + } + } + return new Color(red, green, blue); + } + ); + } + + private record Color(int red, int green, int blue) {} } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java index 33f8fb0ea7..943e2a3f56 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java @@ -4,7 +4,6 @@ import dev.jorel.commandapi.CommandTree; import dev.jorel.commandapi.arguments.GreedyStringArgument; import dev.jorel.commandapi.arguments.LiteralArgument; -import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; import dev.jorel.commandapi.exceptions.GreedyArgumentException; @@ -57,7 +56,7 @@ void testCommandAPICommandGreedyArgumentException() { assertThrowsWithMessage( GreedyArgumentException.class, - "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found arg1 followed by [arg2]", + "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found the greedy argument arg1 followed by arg2", invalidGreedyCommand::register ); } @@ -88,7 +87,7 @@ void testCommandTreeGreedyArgumentException() { assertThrowsWithMessage( GreedyArgumentException.class, - "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found arg1 followed by [arg2]", + "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found the greedy argument arg1 followed by arg2", invalidGreedyCommand::register ); } @@ -249,11 +248,23 @@ void testCommandAPICommandDuplicateNodeNameException() { assertThrowsWithMessage( DuplicateNodeNameException.class, - "Duplicate node names for non-literal arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicate node name", + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", commandWithDuplicateArgumentNames::register ); - // This command is okay because LiteralArguments are exempt from the duplicate name rule + // This command is okay because unlisted arguments do not cause conflict + CommandAPICommand commandWithDuplicateUnlistedArgumentNames = new CommandAPICommand("test") + .withArguments( + new StringArgument("alice").setListed(false), + new StringArgument("bob"), + new StringArgument("alice"), + new StringArgument("bob").setListed(false) + ) + .executesPlayer(P_EXEC); + + assertDoesNotThrow(() -> commandWithDuplicateUnlistedArgumentNames.register()); + + // This command is okay because LiteralArguments are unlisted by default CommandAPICommand commandWithDuplicateLiteralArgumentNames = new CommandAPICommand("test") .withArguments( new LiteralArgument("alice"), @@ -264,16 +275,20 @@ void testCommandAPICommandDuplicateNodeNameException() { assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); - // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule - CommandAPICommand commandWithDuplicateMultiLiteralArgumentNames = new CommandAPICommand("test") + // However, listed LiteralArguments do conflict + CommandAPICommand commandWithDuplicateListedLiteralArgumentNames = new CommandAPICommand("test") .withArguments( - new MultiLiteralArgument("alice", "option1", "option2"), - new MultiLiteralArgument("bob", "option1", "option2"), - new MultiLiteralArgument("alice", "option1", "option2") + new LiteralArgument("alice").setListed(true), + new LiteralArgument("bob").setListed(true), + new LiteralArgument("alice").setListed(true) ) .executesPlayer(P_EXEC); - assertDoesNotThrow(() -> commandWithDuplicateMultiLiteralArgumentNames.register()); + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", + commandWithDuplicateListedLiteralArgumentNames::register + ); } @Test @@ -291,11 +306,26 @@ void testCommandTreeDuplicateNodeNameException() { assertThrowsWithMessage( DuplicateNodeNameException.class, - "Duplicate node names for non-literal arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicate node name", + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", commandWithDuplicateArgumentNames::register ); - // This command is okay because LiteralArguments are exempt from the duplicate name rule + // This command is okay because unlisted arguments do not cause conflict + CommandTree commandWithDuplicateUnlistedArgumentNames = new CommandTree("test") + .then( + new StringArgument("alice").setListed(false).then( + new StringArgument("bob").then( + new StringArgument("alice").then( + new StringArgument("bob").setListed(false) + .executesPlayer(P_EXEC) + ) + ) + ) + ); + + assertDoesNotThrow(() -> commandWithDuplicateUnlistedArgumentNames.register()); + + // This command is okay because LiteralArguments are unlisted by default CommandTree commandWithDuplicateLiteralArgumentNames = new CommandTree("test") .then( new LiteralArgument("alice").then( @@ -308,18 +338,22 @@ void testCommandTreeDuplicateNodeNameException() { assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); - // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule - CommandTree commandWithDuplicateMultiLiteralArgumentNames = new CommandTree("test") + // However, listed LiteralArguments do conflict + CommandTree commandWithDuplicateListedLiteralArgumentNames = new CommandTree("test") .then( - new MultiLiteralArgument("alice", "option1", "option2").then( - new MultiLiteralArgument("bob", "option1", "option2").then( - new MultiLiteralArgument("alice", "option1", "option2") + new LiteralArgument("alice").setListed(true).then( + new LiteralArgument("bob").setListed(true).then( + new LiteralArgument("alice").setListed(true) .executesPlayer(P_EXEC) ) ) ); - assertDoesNotThrow(() -> commandWithDuplicateMultiLiteralArgumentNames.register()); + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", + commandWithDuplicateListedLiteralArgumentNames::register + ); // This command is okay because the duplicate names are on different paths CommandTree commandWithDuplicateNamesSeparated = new CommandTree("test") diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index ab0d032b83..4cc2bd8cfa 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -149,12 +149,7 @@ public String parseArgument(CommandContext cmdCtx, String key, //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override - public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { - Literal.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); - } - - @Override - public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { - return Literal.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return Literal.super.createArgumentBuilder(previousArguments, previousArgumentNames); } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index 0b60541092..10ba0e5c25 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -20,17 +20,17 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.CommandNode; import com.velocitypowered.api.command.CommandSource; -import dev.jorel.commandapi.CommandAPIExecutor; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; import java.util.List; +import java.util.function.Function; /** * An argument that represents multiple LiteralArguments @@ -104,23 +104,18 @@ public String parseArgument(CommandContext cmdCtx, String key, // method by default. However, we want to use the implementations found in the MultiLiteral interface. // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void checkPreconditions(List> previousArguments, List previousNonLiteralArgumentNames) { - MultiLiteral.super.checkPreconditions(previousArguments, previousNonLiteralArgumentNames); - } - @Override public void appendToCommandPaths(List> argumentStrings) { MultiLiteral.super.appendToCommandPaths(argumentStrings); } @Override - public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousNonLiteralArgumentNames) { - return MultiLiteral.super.createArgumentBuilder(previousArguments, previousNonLiteralArgumentNames); + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return MultiLiteral.super.createArgumentBuilder(previousArguments, previousArgumentNames); } @Override - public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousNonLiteralArgumentNames, CommandAPIExecutor> terminalExecutor) { - return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousNonLiteralArgumentNames, terminalExecutor); + public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } From f36c43a5b5d99d2d044d7ef4257f11a5fd340615 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:02:23 -0500 Subject: [PATCH 16/42] Fix `CommandNamespaceTests` I messed this up when rebasing changes from #509 into `dev/command-build-rewrite` --- .../dev/jorel/commandapi/SpigotCommandRegistration.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index 5230acb234..3bf6ffc8b4 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -239,6 +239,13 @@ public void postCommandRegistration(List registeredCommands, // Do the same for the namespaced version of the command (which is never empty on Bukkit forks) String namespace = commonCommandInformation.namespace().toLowerCase(); permissionsToFix.putIfAbsent(namespace + ":" + commandName, permission); + + // Do the same for the aliases + for (String alias : commonCommandInformation.aliases()) { + alias = alias.toLowerCase(); + permissionsToFix.putIfAbsent(alias, permission); + permissionsToFix.putIfAbsent(namespace + ":" + alias, permission); + } } } From 91e10233b9300fd0d25d18d474a9c3645e3a640d Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:37:31 -0400 Subject: [PATCH 17/42] Update `dev/command-build-rewrite` after rebasing #509 and #526 --- .../jorel/commandapi/CommandAPIHandler.java | 17 ++++++++++ .../jorel/commandapi/CommandAPIPlatform.java | 10 ++++++ .../jorel/commandapi/ExecutableCommand.java | 19 +++++------ .../jorel/commandapi/CommandAPIBukkit.java | 34 +++++++++++-------- .../jorel/commandapi/CommandAPICommand.java | 11 +++--- .../dev/jorel/commandapi/CommandTree.java | 10 +++--- .../jorel/commandapi/CommandAPICommand.java | 10 ++---- .../jorel/commandapi/CommandAPIVelocity.java | 15 ++++++++ .../dev/jorel/commandapi/CommandTree.java | 10 ++---- 9 files changed, 84 insertions(+), 52 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index cbc8b14b31..58319db3b0 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -153,9 +153,25 @@ public CommandAPIPlatform getPlatform() { // SECTION: Creating commands // //////////////////////////////// + /** + * Registers a command with a given namespace. This is intended to be called by {@link ExecutableCommand#register(String)} + * + * @param command The command to register. + * @param namespace The namespace of this command. This cannot be null, and each platform may impose additional requirements. + * See {@link CommandAPIPlatform#validateNamespace(ExecutableCommand, String)}. + * @throws NullPointerException if the namespace is null. + */ public void registerCommand(ExecutableCommand command, String namespace) { + // Validate parameters + if (namespace == null) { + throw new NullPointerException("Parameter 'namespace' was null when registering command /" + command.getName() + "!"); + } + namespace = platform.validateNamespace(command, namespace); + + // Do plaform-specific pre-registration tasks platform.preCommandRegistration(command.getName()); + // Record the commands that are registered List registeredCommandInformation = RegisteredCommand.fromExecutableCommand(command, namespace); registeredCommands.addAll(registeredCommandInformation); @@ -191,6 +207,7 @@ public void registerCommand(ExecutableCommand command, String // partial) command registration. Generate the dispatcher file! writeDispatcherToFile(); + // Do platform-specific post-registration tasks platform.postCommandRegistration(registeredCommandInformation, resultantNode, aliasNodes); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index 44846542e5..0709a5a026 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -83,6 +83,16 @@ public interface CommandAPIPlatform getSuggestionProvider(SuggestionProviders suggestionProvider); + /** + * Ensures the given String is a valid command namespace on this platform. If the namespace + * is not valid, this method will return a String that should be used instead. + * + * @param command The command being registered with the given namespace. + * @param namespace The String that wants to be used as a namespace. + * @return The String that should be used as the namespace. If the given String is a valid namespace, it will be returned. + */ + public abstract String validateNamespace(ExecutableCommand command, String namespace); + /** * Stuff to run before a command is generated. For Bukkit, this involves checking * if a command was declared in the plugin.yml when it isn't supposed to be. diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 3f12dabee4..46417462ef 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -1,14 +1,15 @@ package dev.jorel.commandapi; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; + import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - /** * This is a base class for {@link AbstractCommandAPICommand} and {@link AbstractCommandTree} command definitions * @@ -317,15 +318,13 @@ public void register() { } /** - * Registers the command with a given namespace. + * Registers the command with the given namespace. * - * @param namespace The namespace of this command. This cannot be null - * @throws NullPointerException if the namespace is null + * @param namespace The namespace for this command. This cannot be null, and each platform may impose additional requirements. + * See {@link CommandAPIPlatform#validateNamespace(ExecutableCommand, String)}. + * @throws NullPointerException if the namespace is null. */ public void register(String namespace) { - if (namespace == null) { - throw new NullPointerException("Parameter 'namespace' was null when registering command /" + this.name + "!"); - } ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index dd1539c9a0..eaf4dbc878 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -423,6 +423,25 @@ public BukkitCommandSender wrapCommandSender(CommandSen @Unimplemented(because = REQUIRES_MINECRAFT_SERVER) public abstract SuggestionProvider getSuggestionProvider(SuggestionProviders suggestionProvider); + /** + * {@inheritDoc} + * On Bukkit, namespaces must not be empty, and can only contain 0-9, a-z, underscores, periods, and hyphens. + */ + @Override + public String validateNamespace(ExecutableCommand command, String namespace) { + if (namespace.isEmpty()) { + CommandAPI.logNormal("Registering command '" + command.getName() + "' using the default namespace because an empty namespace was given!"); + return config.getNamespace(); + } + if (!CommandAPIHandler.NAMESPACE_PATTERN.matcher(namespace).matches()) { + CommandAPI.logNormal("Registering comand '" + command.getName() + "' using the default namespace because an invalid namespace (" + namespace + ") was given. Only 0-9, a-z, underscores, periods and hyphens are allowed!"); + return config.getNamespace(); + } + + // Namespace is good, return it + return namespace; + } + @Override public void preCommandRegistration(String commandName) { // Warn if the command we're registering already exists in this plugin's @@ -641,19 +660,4 @@ protected void registerBukkitRecipesSafely(Iterator recipes) { } } } - - boolean isInvalidNamespace(String commandName, String namespace) { - if (namespace == null) { - throw new NullPointerException("Parameter 'namespace' was null when registering command /" + commandName + "!"); - } - if (namespace.isEmpty()) { - CommandAPI.logNormal("Registering command '" + commandName + "' using the default namespace because an empty namespace was given!"); - return true; - } - if (!CommandAPIHandler.NAMESPACE_PATTERN.matcher(namespace).matches()) { - CommandAPI.logNormal("Registering comand '" + commandName + "' using the default namespace because an invalid namespace (" + namespace + ") was given. Only 0-9, a-z, underscores, periods and hyphens are allowed!"); - return true; - } - return false; - } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index 840ebc8862..3a8cd57426 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java @@ -42,16 +42,13 @@ public CommandAPICommand withHelp(HelpTopic helpTopic) { } /** - * Registers the command with a given namespace. - * - * @param namespace The namespace of this command. This cannot be null or empty. + * Registers this command with the given namespace. * + * @param namespace The namespace for this command. This cannot be null or empty, and can only contain 0-9, a-z, underscores, periods, and hyphens. + * @throws NullPointerException if the namespace is null. */ + @Override public void register(String namespace) { - if (CommandAPIBukkit.get().isInvalidNamespace(this.name, namespace)) { - super.register(); - return; - } super.register(namespace); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java index fd01dec68b..5cd69a3789 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java @@ -42,15 +42,13 @@ public CommandTree withHelp(HelpTopic helpTopic) { } /** - * Registers the command with a given namespace + * Registers this command with the given namespace. * - * @param namespace The namespace of this command. This cannot be null or empty + * @param namespace The namespace for this command. This cannot be null or empty, and can only contain 0-9, a-z, underscores, periods, and hyphens. + * @throws NullPointerException if the namespace is null. */ + @Override public void register(String namespace) { - if (CommandAPIBukkit.get().isInvalidNamespace(this.name, namespace)) { - super.register(); - return; - } super.register(namespace); } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index ebadd97cc4..f9ba928614 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java @@ -20,14 +20,10 @@ public CommandAPICommand(String commandName) { /** * Registers the command with a given namespace * - * @param namespace The namespace of this command. This cannot be null or empty - * + * @param namespace The namespace of this command. This cannot be null and can only contain 0-9, a-z, underscores, periods, and hyphens. */ + @Override public void register(String namespace) { - if (!namespace.isEmpty() && !CommandAPIHandler.NAMESPACE_PATTERN.matcher(namespace).matches()) { - super.register(); - return; - } super.register(namespace); } @@ -38,7 +34,7 @@ public void register(String namespace) { */ public void register(Object plugin) { if (plugin == null) { - throw new NullPointerException("Parameter 'plugin' was null while trying to register command /" + meta.commandName + "!"); + throw new NullPointerException("Parameter 'plugin' was null while trying to register command /" + this.getName() + "!"); } ProxyServer server = CommandAPIVelocity.getConfiguration().getServer(); Optional pluginContainerOptional = server.getPluginManager().fromInstance(plugin); diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index 5aa38da077..2f7deee7c6 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -198,6 +198,21 @@ public SuggestionProvider getSuggestionProvider(SuggestionProvide return (context, builder) -> Suggestions.empty(); } + /** + * {@inheritDoc} + * On Velocity, namespaces may be empty, but can only contain 0-9, a-z, underscores, periods, and hyphens. + */ + @Override + public String validateNamespace(ExecutableCommand command, String namespace) { + if (!CommandAPIHandler.NAMESPACE_PATTERN.matcher(namespace).matches()) { + CommandAPI.logNormal("Registering comand '" + command.getName() + "' using the default namespace because an invalid namespace (" + namespace + ") was given. Only 0-9, a-z, underscores, periods and hyphens are allowed!"); + return config.getNamespace(); + } + + // Namespace is good, return it + return namespace; + } + @Override public void preCommandRegistration(String commandName) { // Nothing to do diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandTree.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandTree.java index 4f82c7b2db..d802999cf4 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandTree.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandTree.java @@ -20,14 +20,10 @@ public CommandTree(String commandName) { /** * Registers the command with a given namespace * - * @param namespace The namespace of this command. This cannot be null or empty - * + * @param namespace The namespace of this command. This cannot be null and can only contain 0-9, a-z, underscores, periods, and hyphens. */ + @Override public void register(String namespace) { - if (!namespace.isEmpty() && !CommandAPIHandler.NAMESPACE_PATTERN.matcher(namespace).matches()) { - super.register(); - return; - } super.register(namespace); } @@ -38,7 +34,7 @@ public void register(String namespace) { */ public void register(Object plugin) { if (plugin == null) { - throw new NullPointerException("Parameter 'plugin' was null while trying to register command /" + meta.commandName + "!"); + throw new NullPointerException("Parameter 'plugin' was null while trying to register command /" + this.getName() + "!"); } ProxyServer server = CommandAPIVelocity.getConfiguration().getServer(); Optional pluginContainerOptional = server.getPluginManager().fromInstance(plugin); From aa5947cce4af7d0d713a8ae2c2aaa4cfc2bedd41 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:28:33 -0400 Subject: [PATCH 18/42] Re-implement command conflict detection --- .../jorel/commandapi/CommandAPIHandler.java | 119 ++++++------------ .../exceptions/CommandConflictException.java | 32 +++++ .../CommandRegistrationException.java | 6 +- 3 files changed, 76 insertions(+), 81 deletions(-) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index 58319db3b0..eea07642a8 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -44,6 +44,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.*; import dev.jorel.commandapi.commandsenders.AbstractCommandSender; +import dev.jorel.commandapi.exceptions.CommandConflictException; import dev.jorel.commandapi.executors.CommandArguments; import dev.jorel.commandapi.executors.ExecutionInfo; import dev.jorel.commandapi.preprocessor.RequireField; @@ -184,6 +185,9 @@ public void registerCommand(ExecutableCommand command, String LiteralCommandNode resultantNode = nodes.rootNode(); List> aliasNodes = nodes.aliasNodes(); + // Handle command conflicts + ensureNoCommandConflict(resultantNode); + // Register rootNode platform.registerCommandNode(resultantNode, namespace); @@ -211,84 +215,43 @@ public void registerCommand(ExecutableCommand command, String platform.postCommandRegistration(registeredCommandInformation, resultantNode, aliasNodes); } -// // Builds a command then registers it -// // TODO: Move into new register method -// void register(CommandMetaData meta, final Argument[] args, -// CommandAPIExecutor> executor, boolean converted) { -// -// // TODO: precondition, might need it if a step that depends on it is used -// // Although, kinda hard to get the same syntax -// // Create the human-readable command syntax of arguments -// final String humanReadableCommandArgSyntax; -// { -// StringBuilder builder = new StringBuilder(); -// for (Argument arg : args) { -// builder.append(arg.toString()).append(" "); -// } -// humanReadableCommandArgSyntax = builder.toString().trim(); -// } -// -// // TODO: Unclear if this step is necessary -// // Handle command conflicts -// boolean hasRegisteredCommand = false; -// for (int i = 0, size = registeredCommands.size(); i < size && !hasRegisteredCommand; i++) { -// hasRegisteredCommand |= registeredCommands.get(i).commandName().equals(commandName); -// } -// -// if (hasRegisteredCommand && hasCommandConflict(commandName, args, humanReadableCommandArgSyntax)) { -// return; -// } -// } -// -// // Prevent nodes of the same name but with different types: -// // allow /race invite player -// // disallow /race invite player -// // Return true if conflict was present, otherwise return false -// private boolean hasCommandConflict(String commandName, Argument[] args, String argumentsAsString) { -// // TODO: This code only checks if the last argument of two commands have the same name -// // I'm not sure this actually prevents any relevant problems -// List regArgs = new ArrayList<>(); -// for (RegisteredCommand rCommand : registeredCommands) { -// if (rCommand.commandName().equals(commandName)) { -// for (String str : rCommand.argsAsStr()) { -// regArgs.add(str.split(":")); -// } -// // We just find the first entry that causes a conflict. If this -// // were some industry-level code, we would probably generate a -// // list of all commands first, then check for command conflicts -// // all in one go so we can display EVERY command conflict for -// // all commands, but this works perfectly and isn't important. -// break; -// } -// } -// for (int i = 0; i < args.length; i++) { -// // Avoid IAOOBEs and ensure all node names are the same -// if ((regArgs.size() == i && regArgs.size() < args.length) || (!regArgs.get(i)[0].equals(args[i].getNodeName()))) { -// break; -// } -// // This only applies to the last argument -// if (i == args.length - 1 && !regArgs.get(i)[1].equals(args[i].getClass().getSimpleName())) { -// // Command it conflicts with -// StringBuilder builder2 = new StringBuilder(); -// for (String[] arg : regArgs) { -// builder2.append(arg[0]).append("<").append(arg[1]).append("> "); -// } -// -// // TODO: Error message isn't very clear as to what a command conflict is -// CommandAPI.logError(""" -// Failed to register command: -// -// %s %s -// -// Because it conflicts with this previously registered command: -// -// %s %s -// """.formatted(commandName, argumentsAsString, commandName, builder2.toString())); -// return true; -// } -// } -// return false; -// } + /** + * When a command is registered, all its nodes get merged into the existing nodes in the Brigadier CommandDispatcher. + * Brigadier's default behavior is to + * override executors. This method will throw an exception if this overriding is going to happen so we can avoid + * messing up previously registered commands. + * + * @param nodeToRegister The {@link CommandNode} that is going to be regsitered. + * @throws CommandConflictException if registering the given node would cause an executor set up by a previous command to be overridden. + */ + public void ensureNoCommandConflict(CommandNode nodeToRegister) { + ensureNoCommandConflict(nodeToRegister, platform.getBrigadierDispatcher().getRoot(), List.of()); + } + + private void ensureNoCommandConflict(CommandNode nodeToRegister, CommandNode targetLocation, List pathSoFar) { + CommandNode mergeTarget = targetLocation.getChild(nodeToRegister.getName()); + + if (mergeTarget == null) return; // The `nodeToRegister` does not already exist, no conflict possible + + // Add node to path + List path = new ArrayList<>(); + path.addAll(pathSoFar); + path.add(nodeToRegister.getName()); + + if (nodeToRegister.getCommand() != null && mergeTarget.getCommand() != null) { + // We just find the first entry that causes a conflict. If this + // were some industry-level code, we would probably generate a + // list of all commands first, then check for command conflicts + // all in one go so we can display EVERY command conflict for + // all commands, but this works perfectly and isn't important. + throw new CommandConflictException(path); + } + + // Ensure children do not conflict + for (CommandNode child : nodeToRegister.getChildren()) { + ensureNoCommandConflict(child, mergeTarget, path); + } + } /** * Generates a Brigadier {@link Command} using the given CommandAPI objects. diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java new file mode 100644 index 0000000000..94e1513932 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java @@ -0,0 +1,32 @@ +package dev.jorel.commandapi.exceptions; + +import java.util.List; + +/** + * An exception caused when a command path that is already executable is registered again. + */ +public class CommandConflictException extends CommandRegistrationException { + /** + * Creates a new CommandConflictException. + * + * @param path The path of Brigadier node names that was re-registered. + */ + public CommandConflictException(List path) { + super(buildMessage(path)); + } + + private static String buildMessage(List path) { + StringBuilder builder = new StringBuilder(); + + builder.append("The command path \"/"); + + for (String node : path) { + builder.append(node).append(" "); + } + builder.setCharAt(builder.length()-1, '\"'); + + builder.append(" could not be regsitered because it conflicts with a previously registered command."); + + return builder.toString(); + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java index df6cb672aa..694a2e2ef9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandRegistrationException.java @@ -7,14 +7,14 @@ /** * An exception that happens while registering a command */ -public class CommandRegistrationException extends RuntimeException { - public CommandRegistrationException(String message) { +public abstract class CommandRegistrationException extends RuntimeException { + protected CommandRegistrationException(String message) { super(message); } protected static > void addArgumentList(StringBuilder builder, List arguments) { builder.append("["); - for (AbstractArgument argument : arguments) { + for (Argument argument : arguments) { addArgument(builder, argument); builder.append(" "); } From a862694bb236286ded7b98ef9ed0f1e344342e7e Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Wed, 3 Apr 2024 21:16:51 -0400 Subject: [PATCH 19/42] Add tests for `CommandConflictException` Also fix typo in exception message --- .../exceptions/CommandConflictException.java | 2 +- .../test/CommandRegistrationTests.java | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java index 94e1513932..eac8dfc43a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java @@ -25,7 +25,7 @@ private static String buildMessage(List path) { } builder.setCharAt(builder.length()-1, '\"'); - builder.append(" could not be regsitered because it conflicts with a previously registered command."); + builder.append(" could not be registered because it conflicts with a previously registered command."); return builder.toString(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java index 943e2a3f56..b04ac5c9b1 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java @@ -2,19 +2,28 @@ import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.arguments.DoubleArgument; +import dev.jorel.commandapi.arguments.FloatArgument; import dev.jorel.commandapi.arguments.GreedyStringArgument; +import dev.jorel.commandapi.arguments.IntegerArgument; import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.LongArgument; import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.exceptions.CommandConflictException; import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; import dev.jorel.commandapi.exceptions.GreedyArgumentException; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; +import dev.jorel.commandapi.executors.PlayerExecutionInfo; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.bukkit.entity.Player; + /** * Tests for the semantics of registering commands */ @@ -364,4 +373,107 @@ void testCommandTreeDuplicateNodeNameException() { assertDoesNotThrow(() -> commandWithDuplicateNamesSeparated.register()); } + + @Test + void testCommandConflictException() { + Mut results = Mut.of(); + Player player = server.addPlayer(); + + // The executor we register first should not be overwritten and should always run + PlayerExecutionInfo firstExecutor = info -> {results.set("first");}; + PlayerExecutionInfo secondExecutor = info -> {results.set("second");}; + + // No arguments + new CommandAPICommand("noArguments") + .executesPlayer(firstExecutor) + .register(); + + CommandAPICommand noArguments = new CommandAPICommand("noArguments") + .executesPlayer(secondExecutor); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/noArguments\" could not be registered because it conflicts with a previously registered command.", + noArguments::register + ); + assertStoresResult(player, "noArguments", results, "first"); + + // Arguments + new CommandAPICommand("arguments") + .withArguments( + new LiteralArgument("literal"), + new StringArgument("string") + ) + .executesPlayer(firstExecutor) + .register(); + + CommandAPICommand arguments = new CommandAPICommand("arguments") + .withArguments( + new LiteralArgument("literal"), + new StringArgument("string") + ) + .executesPlayer(secondExecutor); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/arguments literal string\" could not be registered because it conflicts with a previously registered command.", + arguments::register + ); + assertStoresResult(player, "arguments literal string", results, "first"); + + // Different argument types + new CommandAPICommand("argumentTypes") + .withArguments( + new IntegerArgument("alice"), + new FloatArgument("bob") + ) + .executesPlayer(firstExecutor) + .register(); + + CommandAPICommand argumentTypes = new CommandAPICommand("argumentTypes") + .withArguments( + new LongArgument("alice"), + new DoubleArgument("bob") + ) + .executesPlayer(secondExecutor); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/argumentTypes alice bob\" could not be registered because it conflicts with a previously registered command.", + argumentTypes::register + ); + assertStoresResult(player, "argumentTypes 0 0", results, "first"); + + // Different branches + new CommandTree("branches") + .then( + new LiteralArgument("alice") + .executesPlayer(firstExecutor) + ) + .then( + new LiteralArgument("bob") + .executesPlayer(firstExecutor) + ) + .register(); + + CommandTree branches = new CommandTree("branches") + .then( + new LiteralArgument("bob") + .executesPlayer(secondExecutor) + ) + .then( + new LiteralArgument("alice") + .executesPlayer(secondExecutor) + ); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/branches bob\" could not be registered because it conflicts with a previously registered command.", + branches::register + ); + assertStoresResult(player, "branches alice", results, "first"); + assertStoresResult(player, "branches bob", results, "first"); + + assertNoMoreResults(results); + } } From 5219af23e3bb5e0cfa85633ec150e492525d4fc2 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Thu, 4 Apr 2024 09:34:40 -0400 Subject: [PATCH 20/42] Extract common node stacking logic --- .../commandapi/AbstractCommandAPICommand.java | 10 +- .../arguments/AbstractArgument.java | 37 ++- .../arguments/FlagsArgumentCommon.java | 226 ++++++++---------- .../commandapi/arguments/MultiLiteral.java | 11 +- .../commandnodes/FlagsArgumentRootNode.java | 7 +- 5 files changed, 142 insertions(+), 149 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 90639f85cc..f9df1d633c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -316,15 +316,11 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) previousArguments.add(commandNames); - // Add required arguments Function, Command> executorCreator = executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null; - for (int i = 0; i < requiredArguments.size(); i++) { - Argument argument = requiredArguments.get(i); - previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, - // Only the last required argument is executable - i == requiredArguments.size() - 1 ? executorCreator : null); - } + // Stack required arguments + // Only the last required argument is executable + previousNodes = AbstractArgument.stackArguments(requiredArguments, previousNodes, previousArguments, previousArgumentNames, executorCreator); // Add optional arguments for (Argument argument : optionalArguments) { diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 6e11041f23..fcbc6c68a2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -513,17 +513,42 @@ public List> linkNode( previousNode.addChild(rootNode); } - // Add combined arguments // A GreedyArgument cannot have arguments after it previousNodes = this instanceof GreedyArgument ? List.of() : List.of(rootNode); - for (int i = 0; i < combinedArguments.size(); i++) { - Argument subArgument = combinedArguments.get(i); + // Stack on combined arguments + previousNodes = stackArguments(combinedArguments, previousNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); + + // Return last nodes + return previousNodes; + } + + /** + * Builds a linear Brigadier {@link CommandNode} structure using + * CommandAPI arguments. Only the last argument will be executable. + * + * @param argumentsToStack A List of CommandAPI arguments to put in sequence + * @param previousNodes A List of {@link CommandNode}s that start the stack. + * @param previousArguments A List of CommandAPI arguments that came before the {@code argumentsToStack}. + * @param previousArgumentNames A List of Strings containing the node names that came before the {@code argumentsToStack}. + * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an + * appropriate Brigadier {@link Command} which should be applied to the last + * stacked argument of the node structure. This parameter can be null to indicate + * that the argument stack should not be executable. + * @param The Brigadier Source object for running commands. + * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing the built argument stack. + */ + public static , Source> List> stackArguments( + List argumentsToStack, List> previousNodes, + List previousArguments, List previousArgumentNames, + Function, Command> terminalExecutorCreator + ) { + int lastIndex = argumentsToStack.size() - 1; + for (int i = 0; i < argumentsToStack.size(); i++) { + Argument subArgument = argumentsToStack.get(i); previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, // Only apply the `terminalExecutor` to the last argument - i == combinedArguments.size() - 1 ? terminalExecutorCreator : null); + i == lastIndex ? terminalExecutorCreator : null); } - - // Return last nodes return previousNodes; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java index d155427d6b..78d01c9464 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -15,6 +15,7 @@ import dev.jorel.commandapi.executors.CommandArguments; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.Function; public interface FlagsArgumentCommon void checkPreconditions( // A FlagsArgument has special logic that should override the implementations in AbstractArgument // ///////////////////////////////////////////////////////////////////////////////////////////////////// + record ParseInformation + /// @endcond + , CommandSender, Source> + (CommandContext context, List arguments) { + } + /** * Overrides {@link AbstractArgument#parseArgument(CommandContext, String, CommandArguments)} */ @@ -83,14 +92,6 @@ default List parseArgument(CommandContext cmd return results; } - record ParseInformation - /// @endcond - , CommandSender, Source> - (CommandContext context, List arguments) { - } - /** * Overrides {@link AbstractArgument#addArgumentNodes(List, List, List, Function)}. *

    @@ -121,135 +122,110 @@ default List> addArgumentNodes( // Setup looping branches boolean loopingBranchesExecutable = getCombinedArguments().isEmpty() && getTerminalBranches().isEmpty(); - for(List loopingBranch : getLoopingBranches()) { - // Make clones of our lists to treat this branch independently - List branchPreviousArguments = new ArrayList<>(previousArguments); - List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); - - RootCommandNode branchRoot = new RootCommandNode<>(); - List> branchPreviousNodes = List.of(branchRoot); - - int lastIndex = loopingBranch.size() - 1; - for(int i = 0; i < loopingBranch.size(); i++) { - Argument argument = loopingBranch.get(i); - branchPreviousNodes = argument.addArgumentNodes( - branchPreviousNodes, branchPreviousArguments, branchPreviousArgumentNames, - // Only apply the `terminalExecutor` to the last argument - (loopingBranchesExecutable && i == lastIndex) ? terminalExecutorCreator : null - ); - } + Function, Command> loopingBranchExecutor = loopingBranchesExecutable ? terminalExecutorCreator : null; - // Find second-to-last nodes so their children can be modified - // Unfortunately, we can't get this from the previous loop since arguments may unpack to multiple layers - Collection> currentNodes = branchRoot.getChildren(); - Collection> lastNodes = List.of(branchRoot); - Collection> secondLastNodes = null; - while (!currentNodes.isEmpty()) { - secondLastNodes = lastNodes; - lastNodes = currentNodes; - currentNodes = new HashSet<>(); - - for (CommandNode node : lastNodes) { - currentNodes.addAll(node.getChildren()); - } - } - - // Make final nodes loop back to the rootNode - rootNode.makeChildrenLoopBack(secondLastNodes, branchPreviousArguments); - - // Copy branch nodes directly to the root node (branchRoot's maps may have been intentionally de-synced) - CommandAPIHandler.getCommandNodeChildren(rootNode).putAll(CommandAPIHandler.getCommandNodeChildren(branchRoot)); - CommandAPIHandler.getCommandNodeLiterals(rootNode).putAll(CommandAPIHandler.getCommandNodeLiterals(branchRoot)); - CommandAPIHandler.getCommandNodeArguments(rootNode).putAll(CommandAPIHandler.getCommandNodeArguments(branchRoot)); + for(List loopingBranch : getLoopingBranches()) { + setupBranch(loopingBranch, rootNode, previousArguments, previousArgumentNames, loopingBranchExecutor, rootNode::makeChildrenLoopBack); } // Setup terminal branches boolean terminalBranchesExecutable = getCombinedArguments().isEmpty(); - List> newNodes = new ArrayList<>(); + Function, Command> terminalBranchExecutor = terminalBranchesExecutable ? terminalExecutorCreator : null; + + // The last nodes here will be our final nodes + previousNodes = new ArrayList<>(); for(List terminalBranch : getTerminalBranches()) { - // Make clones of our lists to treat this branch independently - List branchPreviousArguments = new ArrayList<>(previousArguments); - List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); - - RootCommandNode branchRoot = new RootCommandNode<>(); - List> branchPreviousNodes = List.of(branchRoot); - - int lastIndex = terminalBranch.size() - 1; - for(int i = 0; i < terminalBranch.size(); i++) { - Argument argument = terminalBranch.get(i); - branchPreviousNodes = argument.addArgumentNodes( - branchPreviousNodes, branchPreviousArguments, branchPreviousArgumentNames, - // Only apply the `terminalExecutor` to the last argument - (terminalBranchesExecutable && i == lastIndex) ? terminalExecutorCreator : null - ); - } + previousNodes.addAll( + setupBranch(terminalBranch, rootNode, previousArguments, previousArgumentNames, terminalBranchExecutor, this::wrapTerminalBranchNodes) + ); + } - // Find second-to-last nodes so their children can be modified - // Unfortunately, we can't get this from the previous loop since arguments may unpack to multiple layers - Collection> currentNodes = branchRoot.getChildren(); - Collection> lastNodes = List.of(branchRoot); - Collection> secondLastNodes = null; - while (!currentNodes.isEmpty()) { - secondLastNodes = lastNodes; - lastNodes = currentNodes; - currentNodes = new HashSet<>(); - - for (CommandNode node : lastNodes) { - currentNodes.addAll(node.getChildren()); - } - } + // Stack on combined arguments + previousNodes = AbstractArgument.stackArguments(getCombinedArguments(), previousNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); - // Wrap terminating nodes to extract flag values - for(CommandNode node : secondLastNodes) { - // Nodes in the `children` and `arguments`/`literals` maps need to be wrapped and substituted - Map> children = CommandAPIHandler.getCommandNodeChildren(node); - for (CommandNode child : node.getChildren()) { - CommandNode finalWrappedNode; - if (child instanceof LiteralCommandNode literalNode) { - LiteralCommandNode wrappedNode = - FlagsArgumentEndingNode.wrapNode(literalNode, nodeName, branchPreviousArguments); - - CommandAPIHandler.getCommandNodeLiterals(node).put(literalNode.getName(), wrappedNode); - finalWrappedNode = wrappedNode; - } else if(child instanceof ArgumentCommandNode argumentNode) { - ArgumentCommandNode wrappedNode = - FlagsArgumentEndingNode.wrapNode(argumentNode, nodeName, branchPreviousArguments); - - CommandAPIHandler.getCommandNodeArguments(node).put(argumentNode.getName(), wrappedNode); - finalWrappedNode = wrappedNode; - } else { - throw new IllegalArgumentException("Node must be an argument or literal. Given " + child + " with type " + child.getClass().getName()); - } - children.put(child.getName(), finalWrappedNode); - - // These wrapped nodes should always have the same children as the node they are wrapping, - // so let's share map instances - CommandAPIHandler.setCommandNodeChildren(finalWrappedNode, CommandAPIHandler.getCommandNodeChildren(child)); - CommandAPIHandler.setCommandNodeLiterals(finalWrappedNode, CommandAPIHandler.getCommandNodeLiterals(child)); - CommandAPIHandler.setCommandNodeArguments(finalWrappedNode, CommandAPIHandler.getCommandNodeArguments(child)); - } + // Return last nodes + return previousNodes; + } + + private static , Source> List> setupBranch( + List branchArguments, CommandNode rootNode, + List previousArguments, List previousArgumentNames, + Function, Command> terminalExecutorCreator, + BiConsumer>, List> secondLastNodeProcessor + ) { + // Make clones of our lists to treat this branch independently + List branchPreviousArguments = new ArrayList<>(previousArguments); + List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); + + RootCommandNode branchRoot = new RootCommandNode<>(); + List> branchPreviousNodes = List.of(branchRoot); + + // Stack branch nodes + branchPreviousNodes = AbstractArgument.stackArguments(branchArguments, branchPreviousNodes, branchPreviousArguments, branchPreviousArgumentNames, terminalExecutorCreator); + + // Find second-to-last nodes so their children can be modified + // Unfortunately, we can't get this while stacking since arguments may (and may not) unpack to multiple layers + Collection> currentNodes = branchRoot.getChildren(); + Collection> lastNodes = List.of(branchRoot); + Collection> secondLastNodes = null; + while (!currentNodes.isEmpty()) { + secondLastNodes = lastNodes; + lastNodes = currentNodes; + currentNodes = new HashSet<>(); + + for (CommandNode node : lastNodes) { + currentNodes.addAll(node.getChildren()); } + } - // These nodes will be our final nodes - newNodes.addAll(branchPreviousNodes); + // Modify the children of the secondLastNodes + secondLastNodeProcessor.accept(secondLastNodes, branchPreviousArguments); - // Copy branch nodes directly to the root node (branchRoot's maps may have been intentionally de-synced) - CommandAPIHandler.getCommandNodeChildren(rootNode).putAll(CommandAPIHandler.getCommandNodeChildren(branchRoot)); - CommandAPIHandler.getCommandNodeLiterals(rootNode).putAll(CommandAPIHandler.getCommandNodeLiterals(branchRoot)); - CommandAPIHandler.getCommandNodeArguments(rootNode).putAll(CommandAPIHandler.getCommandNodeArguments(branchRoot)); - } + // Copy branch nodes directly to the root node (branchRoot's maps may have been intentionally de-synced) + CommandAPIHandler.getCommandNodeChildren(rootNode).putAll(CommandAPIHandler.getCommandNodeChildren(branchRoot)); + CommandAPIHandler.getCommandNodeLiterals(rootNode).putAll(CommandAPIHandler.getCommandNodeLiterals(branchRoot)); + CommandAPIHandler.getCommandNodeArguments(rootNode).putAll(CommandAPIHandler.getCommandNodeArguments(branchRoot)); - // Add combined arguments - previousNodes = newNodes; - List combinedArguments = getCombinedArguments(); - for (int i = 0; i < combinedArguments.size(); i++) { - Argument subArgument = combinedArguments.get(i); - previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, - // Only apply the `terminalExecutor` to the last argument - i == combinedArguments.size() - 1 ? terminalExecutorCreator : null); - } + // Return the last nodes in the tree + return branchPreviousNodes; + } - // Return last nodes - return previousNodes; + private void wrapTerminalBranchNodes( + Collection> secondLastNodes, List branchPreviousArguments + ) { + String nodeName = getNodeName(); + + // Wrap terminating nodes to extract flag values + for(CommandNode node : secondLastNodes) { + // Nodes in the `children` and `arguments`/`literals` maps need to be wrapped and substituted + Map> children = CommandAPIHandler.getCommandNodeChildren(node); + Map> literals = CommandAPIHandler.getCommandNodeLiterals(node); + Map> arguments = CommandAPIHandler.getCommandNodeArguments(node); + + for (CommandNode child : node.getChildren()) { + CommandNode finalWrappedNode; + if (child instanceof LiteralCommandNode literalNode) { + LiteralCommandNode wrappedNode = + FlagsArgumentEndingNode.wrapNode(literalNode, nodeName, branchPreviousArguments); + + literals.put(literalNode.getName(), wrappedNode); + finalWrappedNode = wrappedNode; + } else if(child instanceof ArgumentCommandNode argumentNode) { + ArgumentCommandNode wrappedNode = + FlagsArgumentEndingNode.wrapNode(argumentNode, nodeName, branchPreviousArguments); + + arguments.put(argumentNode.getName(), wrappedNode); + finalWrappedNode = wrappedNode; + } else { + throw new IllegalArgumentException("Node must be an argument or literal. Given " + child + " with type " + child.getClass().getName()); + } + children.put(child.getName(), finalWrappedNode); + + // These wrapped nodes should always have the same children as the node they are wrapping, so let's share map instances + CommandAPIHandler.setCommandNodeChildren(finalWrappedNode, CommandAPIHandler.getCommandNodeChildren(child)); + CommandAPIHandler.setCommandNodeLiterals(finalWrappedNode, CommandAPIHandler.getCommandNodeLiterals(child)); + CommandAPIHandler.setCommandNodeArguments(finalWrappedNode, CommandAPIHandler.getCommandNodeArguments(child)); + } + } } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index ff2724a95d..ab0ea3cbd5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -151,15 +151,8 @@ default List> linkNode( newNodes.add(literalNode); } - // Add combined arguments - previousNodes = newNodes; - List combinedArguments = getCombinedArguments(); - for (int i = 0; i < combinedArguments.size(); i++) { - Argument subArgument = combinedArguments.get(i); - previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, - // Only apply the `terminalExecutor` to the last argument - i == combinedArguments.size() - 1 ? terminalExecutorCreator : null); - } + // Stack on combined arguments + previousNodes = AbstractArgument.stackArguments(getCombinedArguments(), newNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); // Return last nodes return previousNodes; diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java index 1a83ff91a4..c03bdb7d14 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java @@ -31,6 +31,9 @@ public void makeChildrenLoopBack(Collection> nodes, List> children = CommandAPIHandler.getCommandNodeChildren(node); + Map> literals = CommandAPIHandler.getCommandNodeLiterals(node); + Map> arguments = CommandAPIHandler.getCommandNodeArguments(node); + for (CommandNode child : node.getChildren()) { // Clone the node, redirect it here, then put the clone into the children map children.put(child.getName(), child.createBuilder().redirect(this).build()); @@ -41,13 +44,13 @@ public void makeChildrenLoopBack(Collection> nodes, List wrappedNode = FlagsArgumentEndingNode.wrapNode(literalNode, getName(), previousArguments); - CommandAPIHandler.getCommandNodeLiterals(node).put(literalNode.getName(), wrappedNode); + literals.put(literalNode.getName(), wrappedNode); finalWrappedNode = wrappedNode; } else if(child instanceof ArgumentCommandNode argumentNode) { ArgumentCommandNode wrappedNode = FlagsArgumentEndingNode.wrapNode(argumentNode, getName(), previousArguments); - CommandAPIHandler.getCommandNodeArguments(node).put(argumentNode.getName(), wrappedNode); + arguments.put(argumentNode.getName(), wrappedNode); finalWrappedNode = wrappedNode; } else { throw new IllegalArgumentException("Node must be an argument or literal. Given " + child + " with type " + child.getClass().getName()); From 7b24440b0e7a82316d6406fadb4d08d6bb4526f7 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:38:48 -0400 Subject: [PATCH 21/42] Add tests for `FlagsArgument` --- .../arguments/FlagsArgumentCommon.java | 2 +- .../dev/jorel/commandapi/CommandAPIMain.java | 111 +--- .../test/arguments/ArgumentFlagsTests.java | 617 ++++++++++++++++++ 3 files changed, 621 insertions(+), 109 deletions(-) create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java index 78d01c9464..28ee81c000 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -121,7 +121,7 @@ default List> addArgumentNodes( } // Setup looping branches - boolean loopingBranchesExecutable = getCombinedArguments().isEmpty() && getTerminalBranches().isEmpty(); + boolean loopingBranchesExecutable = getTerminalBranches().isEmpty(); Function, Command> loopingBranchExecutor = loopingBranchesExecutable ? terminalExecutorCreator : null; for(List loopingBranch : getLoopingBranches()) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index c03047b1cc..cb5a44eb15 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -21,12 +21,11 @@ package dev.jorel.commandapi; import java.io.File; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import dev.jorel.commandapi.arguments.*; -import dev.jorel.commandapi.executors.CommandArguments; -import dev.jorel.commandapi.wrappers.IntegerRange; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.InvalidPluginException; @@ -133,109 +132,5 @@ private JavaPlugin getAndValidatePlugin(String pluginName) { @Override public void onEnable() { CommandAPI.onEnable(); - - // examples from https://github.com/JorelAli/CommandAPI/issues/483 - new CommandAPICommand("singleFlag") - .withArguments( - new FlagsArgument("filters") - .loopingBranch( - new LiteralArgument("filter", "sort").setListed(true), - new MultiLiteralArgument("sortType", "furthest", "nearest", "random") - ) - .loopingBranch( - new LiteralArgument("filter", "limit").setListed(true), - new IntegerArgument("limitAmount", 0) - ) - .loopingBranch( - new LiteralArgument("filter", "distance").setListed(true), - new IntegerRangeArgument("distanceRange") - ) - ) - .executes(info -> { - for (CommandArguments branch : info.args().>getUnchecked("filters")) { - String filterType = branch.getUnchecked("filter"); - info.sender().sendMessage(switch (filterType) { - case "sort" -> "Sort " + branch.getUnchecked("sortType"); - case "limit" -> "Limit " + branch.getUnchecked("limitAmount"); - case "distance" -> "Distance " + branch.getUnchecked("distanceRange"); - default -> "Unknown branch " + filterType; - }); - } - }) - .register(); - - new CommandAPICommand("nestedFlags") - .withArguments( - new FlagsArgument("execute") - .loopingBranch( - new LiteralArgument("subcommand", "as").setListed(true), - new EntitySelectorArgument.ManyEntities("targets") - ) - .loopingBranch( - new FlagsArgument("if") - .terminalBranch( - new LiteralArgument("ifType", "block").setListed(true), - new BlockPredicateArgument("predicate") - ) - .terminalBranch( - new LiteralArgument("ifType", "entity").setListed(true), - new EntitySelectorArgument.ManyEntities("predicate") - ) - ) - .terminalBranch( - new LiteralArgument("run") - ), - new CommandArgument("command") - ) - .executes(info -> { - info.sender().sendMessage(info.args().argsMap().toString()); - }) - .register(); - - // example from NextdoorPsycho - new CommandAPICommand("colorCommand") - .withArguments( - createColorArgument("color") - ) - .executes(info -> { - info.sender().sendMessage(info.args().getUnchecked("color").toString()); - }) - .register(); } - - private static Argument createColorArgument(String nodeName) { - return new CustomArgument<>( - new FlagsArgument(nodeName) - .loopingBranch( - // A DynamicMultiLiteral would be perfect here :P - // https://github.com/JorelAli/CommandAPI/issues/513 - // At least, this is how I imagine it would work - new StringArgument("channel").replaceSuggestions(ArgumentSuggestions.strings(info -> { - Set channelsLeft = new HashSet<>(Set.of("-r", "-g", "-b")); - for(CommandArguments previousChannels : info.previousArgs().>getUnchecked(nodeName)) { - // Yes, you can reference previous versions of yourself - channelsLeft.remove(previousChannels.getUnchecked("channel")); - } - return channelsLeft.toArray(String[]::new); - })), - new IntegerArgument("value", 0, 255) - ), - info -> { - int red = 0, green = 0, blue = 0; - for (CommandArguments channels : (List) info.currentInput()) { - int value = channels.getUnchecked("value"); - String channel = channels.getUnchecked("channel"); - switch (channel) { - case "-r" -> red = value; - case "-g" -> green = value; - case "-b" -> blue = value; - default -> throw CustomArgument.CustomArgumentException.fromString("Unknown channel \"" + channel + "\""); - } - } - return new Color(red, green, blue); - } - ); - } - - private record Color(int red, int green, int blue) {} } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java new file mode 100644 index 0000000000..94f702a5bb --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java @@ -0,0 +1,617 @@ +package dev.jorel.commandapi.test.arguments; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.bukkit.Color; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.arguments.ArgumentSuggestions; +import dev.jorel.commandapi.arguments.CustomArgument; +import dev.jorel.commandapi.arguments.FlagsArgument; +import dev.jorel.commandapi.arguments.IntegerArgument; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.MultiLiteralArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.exceptions.GreedyArgumentException; +import dev.jorel.commandapi.executors.CommandArguments; +import dev.jorel.commandapi.test.Mut; +import dev.jorel.commandapi.test.TestBase; + +/** + * Tests for the {@link FlagsArgument} + */ +class ArgumentFlagsTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + @Test + void commandBuildingTestWithLoopingAndTerminalBranchesAndFollowingArgument() { + new CommandAPICommand("test") + .withArguments( + new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("loop1"), new StringArgument("value")) + .loopingBranch(new LiteralArgument("loop2")) + .terminalBranch(new LiteralArgument("end1"), new IntegerArgument("value")) + .terminalBranch(new LiteralArgument("end2")), + new StringArgument("argument") + ) + .executesPlayer(P_EXEC) + .register(); + + // When commands are sent to the player, looping uses redirects + assertEquals(""" + { + "type": "root", + "children": { + "test": { + "type": "literal", + "children": { + "flags": { + "type": "literal", + "children": { + "loop1": { + "type": "literal", + "children": { + "value": { + "type": "argument", + "parser": "brigadier:string", + "properties": { + "type": "word" + }, + "redirect": [ + "test", + "flags" + ] + } + } + }, + "loop2": { + "type": "literal", + "redirect": [ + "test", + "flags" + ] + }, + "end1": { + "type": "literal", + "children": { + "value": { + "type": "argument", + "parser": "brigadier:integer", + "children": { + "argument": { + "type": "argument", + "parser": "brigadier:string", + "properties": { + "type": "word" + }, + "executable": true + } + } + } + } + }, + "end2": { + "type": "literal", + "children": { + "argument": { + "type": "argument", + "parser": "brigadier:string", + "properties": { + "type": "word" + }, + "executable": true + } + } + } + } + } + } + } + } + }""", + getDispatcherString() + ); + } + + @Test + void executionTestLoopingBranches() { + Mut> results = Mut.of(); + Player sender = server.addPlayer(); + + new CommandAPICommand("test") + .withArguments( + new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("choice", "loop1").setListed(true), new StringArgument("value")) + .loopingBranch(new LiteralArgument("choice", "loop2").setListed(true), new IntegerArgument("value")) + .loopingBranch(new MultiLiteralArgument("choice", "loop3")) + .loopingBranch(new MultiLiteralArgument("choice", "loop4", "loop5")) + ) + .executes(info -> { + List flags = info.args().getUnchecked("flags"); + + List objects = new ArrayList<>(); + for (CommandArguments branches : flags) { + objects.add(branches.get("choice")); + objects.add(branches.get("value")); + } + + results.set(objects); + }) + .register(); + + // Execute branch by itself + assertStoresResult(sender, "test flags loop1 alice", results, List.of("loop1", "alice")); + assertStoresResult(sender, "test flags loop2 0", results, List.of("loop2", 0)); + // Arrays.asList is necessary for null elements + assertStoresResult(sender, "test flags loop3", results, Arrays.asList("loop3", null)); + assertStoresResult(sender, "test flags loop4", results, Arrays.asList("loop4", null)); + assertStoresResult(sender, "test flags loop5", results, Arrays.asList("loop5", null)); + + // Go back to self + assertStoresResult(sender, "test flags loop1 alice loop1 bob", results, List.of("loop1", "alice", "loop1", "bob")); + assertStoresResult(sender, "test flags loop2 0 loop2 10", results, List.of("loop2", 0, "loop2", 10)); + + // Go to other branches + assertStoresResult(sender, "test flags loop1 bob loop2 0", results, List.of("loop1", "bob", "loop2", 0)); + assertStoresResult(sender, "test flags loop2 10 loop1 alice", results, List.of("loop2", 10, "loop1", "alice")); + + assertStoresResult(sender, "test flags loop3 loop4 loop5 loop3", results, Arrays.asList("loop3", null, "loop4", null, "loop5", null, "loop3", null)); + + // Incomplete branch fails + assertCommandFailsWith( + sender, + "test flags", + "Unknown or incomplete command, see below for error at position 10: test flags<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags loop1 alice loop1", + "Unknown or incomplete command, see below for error at position 28: ...lice loop1<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags loop2 0 loop2", + "Unknown or incomplete command, see below for error at position 24: ...p2 0 loop2<--[HERE]" + ); + + // Argument parse failure + assertCommandFailsWith( + sender, + "test flags lo", + "Incorrect argument for command at position 11: ...est flags <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags loop2 0 loop2 NaN", + "Expected integer at position 25: ...2 0 loop2 <--[HERE]" + ); + + assertNoMoreResults(results); + } + + @Test + void exceptionTestLoopingBranchesWithFollowingArgument() { + // One branch + CommandAPICommand oneBranch = new CommandAPICommand("oneBranch") + .withArguments( + new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("branch1")), + new StringArgument("argument") + ) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + GreedyArgumentException.class, + "A greedy argument can only be declared at the end of a command. " + + "Going down the [oneBranch] branch, found the greedy argument flags followed by argument", + oneBranch::register + ); + + // Two branches + CommandAPICommand twoBranches = new CommandAPICommand("twoBranches") + .withArguments( + new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("branch1")) + .loopingBranch(new LiteralArgument("branch2")), + new StringArgument("argument") + ) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + GreedyArgumentException.class, + "A greedy argument can only be declared at the end of a command. " + + "Going down the [twoBranches] branch, found the greedy argument flags followed by argument", + twoBranches::register + ); + + // Deep branch + CommandAPICommand deepBranch = new CommandAPICommand("deepBranch") + .withArguments( + new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("branch1"), new StringArgument("branchArgument")), + new StringArgument("argument") + ) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + GreedyArgumentException.class, + "A greedy argument can only be declared at the end of a command. " + + "Going down the [deepBranch] branch, found the greedy argument flags followed by argument", + deepBranch::register + ); + + // Combined argument + CommandAPICommand combinedArgument = new CommandAPICommand("combinedArgument") + .withArguments( + new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("branch1")) + .combineWith(new StringArgument("argument")) + ) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + GreedyArgumentException.class, + "A greedy argument can only be declared at the end of a command. Going down the [combinedArgument] branch, found the greedy argument flags followed by argument", + combinedArgument::register + ); + } + + @Test + void executionTestTerminalBranches() { + Mut> results = Mut.of(); + Player sender = server.addPlayer(); + + new CommandAPICommand("test") + .withArguments( + new FlagsArgument("flags") + .terminalBranch(new LiteralArgument("choice", "end1").setListed(true), new StringArgument("value")) + .terminalBranch(new LiteralArgument("choice", "end2").setListed(true), new IntegerArgument("value")) + .terminalBranch(new MultiLiteralArgument("choice", "end3")) + .terminalBranch(new MultiLiteralArgument("choice", "end4", "end5")) + ) + .executes(info -> { + List flags = info.args().getUnchecked("flags"); + + List objects = new ArrayList<>(); + for (CommandArguments branches : flags) { + objects.add(branches.get("choice")); + objects.add(branches.get("value")); + } + + results.set(objects); + }) + .register(); + + // Execute branch by itself + assertStoresResult(sender, "test flags end1 alice", results, List.of("end1", "alice")); + assertStoresResult(sender, "test flags end2 0", results, List.of("end2", 0)); + // Arrays.asList is necessary for null elements + assertStoresResult(sender, "test flags end3", results, Arrays.asList("end3", null)); + assertStoresResult(sender, "test flags end4", results, Arrays.asList("end4", null)); + assertStoresResult(sender, "test flags end5", results, Arrays.asList("end5", null)); + + // Branches do not go back to themselves + assertCommandFailsWith( + sender, + "test flags end1 alice end1 bob", + "Incorrect argument for command at position 22: ...nd1 alice <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end2 0 end2 10", + "Incorrect argument for command at position 18: ...gs end2 0 <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end3 end3", + "Incorrect argument for command at position 16: ...lags end3 <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end4 end4", + "Incorrect argument for command at position 16: ...lags end4 <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end5 end5", + "Incorrect argument for command at position 16: ...lags end5 <--[HERE]" + ); + + // Branches do not go back to other branches + assertCommandFailsWith( + sender, + "test flags end1 alice end3", + "Incorrect argument for command at position 22: ...nd1 alice <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end2 0 end1 alice", + "Incorrect argument for command at position 18: ...gs end2 0 <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end4 end5", + "Incorrect argument for command at position 16: ...lags end4 <--[HERE]" + ); + + // Incomplete branch fails + assertCommandFailsWith( + sender, + "test flags", + "Unknown or incomplete command, see below for error at position 10: test flags<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end1", + "Unknown or incomplete command, see below for error at position 15: ...flags end1<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end2", + "Unknown or incomplete command, see below for error at position 15: ...flags end2<--[HERE]" + ); + + // Argument parse failure + assertCommandFailsWith( + sender, + "test flags en", + "Incorrect argument for command at position 11: ...est flags <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end2 NaN", + "Expected integer at position 16: ...lags end2 <--[HERE]" + ); + + assertNoMoreResults(results); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void executionTestTerminalBranchesWithFollowingArgument(boolean combineArgument) { + Mut> results = Mut.of(); + Player sender = server.addPlayer(); + + // Arguments + FlagsArgument flags = new FlagsArgument("flags") + .terminalBranch(new LiteralArgument("string"), new StringArgument("value")) + .terminalBranch(new LiteralArgument("int"), new IntegerArgument("value")); + StringArgument following = new StringArgument("argument"); + + // Command + CommandAPICommand test = new CommandAPICommand("test"); + + test.withArguments(flags); + + if (combineArgument) { + flags.combineWith(following); + } else { + test.withArguments(following); + } + + test.executes(info -> { + CommandArguments args = info.args(); + + List branches = args.getUnchecked("flags"); + assertEquals(1, branches.size()); + CommandArguments branch = branches.get(0); + + results.set(List.of(branch.get("value"), args.get("argument"))); + }); + test.register(); + + // Branches converge on same argument + assertStoresResult(sender, "test flags string alice argument", results, List.of("alice", "argument")); + assertStoresResult(sender, "test flags int 10 argument", results, List.of(10, "argument")); + + // Incomplete branches fail + assertCommandFailsWith( + sender, + "test flags", + "Unknown or incomplete command, see below for error at position 10: test flags<--[HERE]" + ); + + assertCommandFailsWith( + sender, + "test flags string", + "Unknown or incomplete command, see below for error at position 17: ...ags string<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags int", + "Unknown or incomplete command, see below for error at position 14: ... flags int<--[HERE]" + ); + + assertCommandFailsWith( + sender, + "test flags string alice", + "Unknown or incomplete command, see below for error at position 23: ...ring alice<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags int 10", + "Unknown or incomplete command, see below for error at position 17: ...ags int 10<--[HERE]" + ); + + assertNoMoreResults(results); + } + + @Test + void executionTestLoopingAndTerminalBranches() { + Mut> results = Mut.of(); + Player sender = server.addPlayer(); + + new CommandAPICommand("test") + .withArguments( + new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("choice", "loop1").setListed(true), new StringArgument("value")) + .loopingBranch(new LiteralArgument("choice", "loop2").setListed(true), new IntegerArgument("value")) + .terminalBranch(new LiteralArgument("choice", "end1").setListed(true), new StringArgument("value")) + .terminalBranch(new LiteralArgument("choice", "end2").setListed(true), new IntegerArgument("value")) + ) + .executes(info -> { + List flags = info.args().getUnchecked("flags"); + + List objects = new ArrayList<>(); + for (CommandArguments branches : flags) { + objects.add(branches.get("choice")); + objects.add(branches.get("value")); + } + + results.set(objects); + }) + .register(); + + // One-branch options + assertCommandFailsWith( + sender, + "test flags loop1 alice", + "Unknown or incomplete command, see below for error at position 22: ...oop1 alice<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags loop2 0", + "Unknown or incomplete command, see below for error at position 18: ...gs loop2 0<--[HERE]" + ); + assertStoresResult(sender, "test flags end1 alice", results, List.of("end1", "alice")); + assertStoresResult(sender, "test flags end2 0", results, List.of("end2", 0)); + + // Two-branch options + assertCommandFailsWith( + sender, + "test flags loop1 alice loop2 0", + "Unknown or incomplete command, see below for error at position 30: ...ce loop2 0<--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags loop2 10 loop1 bob", + "Unknown or incomplete command, see below for error at position 29: ... loop1 bob<--[HERE]" + ); + assertStoresResult(sender, "test flags loop1 alice end2 10", results, List.of("loop1", "alice", "end2", 10)); + assertStoresResult(sender, "test flags loop2 0 end1 bob", results, List.of("loop2", 0, "end1", "bob")); + assertCommandFailsWith( + sender, + "test flags end1 alice loop1 bob", + "Incorrect argument for command at position 22: ...nd1 alice <--[HERE]" + ); + assertCommandFailsWith( + sender, + "test flags end2 0 loop2 10", + "Incorrect argument for command at position 18: ...gs end2 0 <--[HERE]" + ); + + // Three-branch options + assertStoresResult(sender, "test flags loop1 alice loop2 10 end1 bob", results, List.of("loop1", "alice", "loop2", 10, "end1", "bob")); + assertStoresResult(sender, "test flags loop2 0 loop1 bob end2 10", results, List.of("loop2", 0, "loop1", "bob", "end2", 10)); + + assertNoMoreResults(results); + } + + private record Color(int red, int green, int blue) {} + + @Test + void suggestionsAndExecutionTestWithColorArgument() { + Mut results = Mut.of(); + Player sender = server.addPlayer(); + + new CommandAPICommand("test") + .withArguments( + new CustomArgument<>( + new FlagsArgument("color") + .loopingBranch( + // A DynamicMultiLiteral would be perfect here :P + // https://github.com/JorelAli/CommandAPI/issues/513 + // At least, this is how I imagine it would work + new StringArgument("channel").replaceSuggestions(ArgumentSuggestions.strings(info -> { + Set channelsLeft = new HashSet<>(Set.of("-r", "-g", "-b")); + for(CommandArguments previousChannels : info.previousArgs().>getUnchecked("color")) { + // Yes, you can reference previous versions of yourself + channelsLeft.remove(previousChannels.getUnchecked("channel")); + } + return channelsLeft.toArray(String[]::new); + })), + new IntegerArgument("value", 0, 255) + ), + info -> { + int red = 0, green = 0, blue = 0; + for (CommandArguments channels : (List) info.currentInput()) { + int value = channels.getUnchecked("value"); + + String channel = channels.getUnchecked("channel"); + switch (channel) { + case "-r" -> red = value; + case "-g" -> green = value; + case "-b" -> blue = value; + default -> throw CustomArgument.CustomArgumentException.fromString("Unknown channel \"" + channel + "\""); + } + } + return new Color(red, green, blue); + } + ) + ) + .executes(info -> { + results.set(info.args().getUnchecked("color")); + }) + .register(); + + // Initial suggestions + assertCommandSuggests(sender, "test color ", "-b", "-g", "-r"); + + // First iteration + assertCommandSuggests(sender, "test color -r 50 ", "-b", "-g"); + assertCommandSuggests(sender, "test color -g 50 ", "-b", "-r"); + assertCommandSuggests(sender, "test color -b 50 ", "-g", "-r"); + + // Third iteration + assertCommandSuggests(sender, "test color -r 50 -g 50 ", "-b"); + assertCommandSuggests(sender, "test color -g 50 -b 50 ", "-r"); + assertCommandSuggests(sender, "test color -b 50 -r 50 ", "-g"); + + // Test execution + assertStoresResult(sender, "test color -r 50", results, new Color(50, 0, 0)); + assertStoresResult(sender, "test color -g 100 -b 255", results, new Color(0, 100, 255)); + assertStoresResult(sender, "test color -g 10 -r 100 -g 200 -b 5", results, new Color(100, 200, 5)); + + // Test parse errors + assertCommandFailsWith( + sender, + "test color -f 50", + "Unknown channel \"-f\"" + ); + assertCommandFailsWith( + sender, + "test color -g 300", + "Integer must not be more than 255, found 300 at position 14: ... color -g <--[HERE]" + ); + + assertNoMoreResults(results); + } +} From 4565eb93b28fc837d742441ff5eb95619f198b68 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 29 Apr 2024 18:31:58 -0400 Subject: [PATCH 22/42] Rewrite `RegisteredCommand` - Reordered constructor arguments to more closely match the order in `ExecutableCommand` - This branch is now definitely backward incompatible (though not majorly) - Replaced `List argsAsStr` with `Node rootNode` - Added `RegisteredCommand.Node` class - Has an `argsAsStr` method as a replacement for the parameter of `RegisteredCommand` - Simplified representation of the Brigadier's `CommandNode` structure for the command - Removed `AbstractArgument#getHelpString` and `RegisteredCommand#arguments` (Added by https://github.com/JorelAli/CommandAPI/pull/537) - The `RegisteredCommand.Node` structure is used to generate usage - `Literal` and `MultiLiteral` have separate logic for creating thier `RegisteredCommand.Node, wherein they define a different help string - TODO: https://github.com/JorelAli/CommandAPI/pull/537 didn't have any tests, so I'm only guessing this new implementation works the same. In general, add more tests for usage generation. - Removed `ExecutableCommand#getArgumentsAsStrings` and `AbstractArgument#appendToCommandPaths` - Added `AbstractArgument.NodeInformation` to help pass around enough information to generate Brigadier nodes and `RegisteredCommand.Node`s in one `AbstractArgument` tree traversal. - Modified the signatures of a few methods to facilitate this, including overriding methods - TODO: This broke `Previewable` arguments, so I'll have to tweak those - Changed `CommandAPIHandler#registeredCommands` from an `ArrayList` to a `LinkedHashMap` - Instead of creating one `RegisteredCommand` object for each command path, `RegisteredCommand`s are merged when they share thier command name and namespace - One call to `ExecutableCommand#register` creates one `RegisteredCommand` object (if it has a namespace, an unnamespaced copy is created too) - `CommandAPIPlatform#postCommandRegistration` was reverted to original signature - NOTE: `CommandAPI#getRegisteredCommands` still returns a `List` - Updated `CommandAPICommandRegisteredCommandTests` and `CommandTreeRegisteredCommandTests` - Added `RegisteredCommandTestBase` to handle common utility methods --- .../commandapi/AbstractArgumentTree.java | 60 +-- .../commandapi/AbstractCommandAPICommand.java | 101 ++-- .../jorel/commandapi/AbstractCommandTree.java | 31 +- .../java/dev/jorel/commandapi/CommandAPI.java | 2 +- .../jorel/commandapi/CommandAPIHandler.java | 55 ++- .../jorel/commandapi/CommandAPIPlatform.java | 8 +- .../jorel/commandapi/ExecutableCommand.java | 23 +- .../jorel/commandapi/RegisteredCommand.java | 195 ++++++-- .../arguments/AbstractArgument.java | 100 ++-- .../arguments/FlagsArgumentCommon.java | 46 +- .../jorel/commandapi/arguments/Literal.java | 43 ++ .../commandapi/arguments/MultiLiteral.java | 69 +-- .../jorel/commandapi/CommandAPIBukkit.java | 85 ++-- .../CommandRegistrationStrategy.java | 2 +- .../commandapi/PaperCommandRegistration.java | 2 +- .../commandapi/SpigotCommandRegistration.java | 18 +- .../commandapi/arguments/CustomArgument.java | 9 +- .../commandapi/arguments/FlagsArgument.java | 5 +- .../commandapi/arguments/LiteralArgument.java | 14 +- .../arguments/MultiLiteralArgument.java | 9 +- ...mmandAPICommandRegisteredCommandTests.java | 464 ++++++++++++------ .../CommandTreeRegisteredCommandTests.java | 366 +++++++++----- .../test/RegisteredCommandTestBase.java | 164 +++++++ .../jorel/commandapi/CommandAPIVelocity.java | 2 +- .../commandapi/arguments/LiteralArgument.java | 14 +- .../arguments/MultiLiteralArgument.java | 9 +- 26 files changed, 1208 insertions(+), 688 deletions(-) create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 0b32375f0d..acaa7bb0af 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; @@ -89,47 +90,16 @@ public void setArguments(List> ////////////////// // Registration // ////////////////// - - /** - * @return A list of paths that represent the possible branches of this argument tree as Strings, starting with the - * base argument held by this tree. - */ - public List> getBranchesAsStrings() { - List> argumentStrings = new ArrayList<>(); - - // Create paths for the argument at this node - List> baseArgumentPaths = new ArrayList<>(); - baseArgumentPaths.add(new ArrayList<>()); - argument.appendToCommandPaths(baseArgumentPaths); - - // If this node is executable, add it as a valid path - if (this.executor.hasAnyExecutors()) argumentStrings.addAll(baseArgumentPaths); - - // Add paths for the branches - for (AbstractArgumentTree child : arguments) { - for (List subArgs : child.getBranchesAsStrings()) { - for (List basePath : baseArgumentPaths) { - List mergedPaths = new ArrayList<>(); - mergedPaths.addAll(basePath); - mergedPaths.addAll(subArgs); - argumentStrings.add(mergedPaths); - } - } - } - - return argumentStrings; - } - /** * Builds the Brigadier {@link CommandNode} structure for this argument tree. * - * @param previousNodes A List of {@link CommandNode}s to add this argument onto. - * @param previousArguments A List of CommandAPI arguments that came before this argument tree. - * @param previousArgumentNames A List of Strings containing the node names that came before this argument. - * @param The Brigadier Source object for running commands. + * @param previousNodeInformation The {@link NodeInformation} of the argument this argument is being added to. + * @param previousArguments A List of CommandAPI arguments that came before this argument tree. + * @param previousArgumentNames A List of Strings containing the node names that came before this argument. + * @param The Brigadier Source object for running commands. */ public void buildBrigadierNode( - List> previousNodes, + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames ) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); @@ -141,16 +111,28 @@ public void buildBrigadierNode( } // Create node for this argument - previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, + previousNodeInformation = argument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null); + // Collect children into our own list + List childrenNodeInformation = new ArrayList<>(); + // Add our branches as children to the node for (AbstractArgumentTree child : arguments) { - // We need a new list for each branch of the tree + // Collect children into our own list + NodeInformation newPreviousNodeInformation = new NodeInformation<>( + previousNodeInformation.lastCommandNodes(), + children -> childrenNodeInformation.addAll(children) + ); + + // We need a new list so each branch acts independently List newPreviousArguments = new ArrayList<>(previousArguments); List newPreviousArgumentNames = new ArrayList<>(previousArgumentNames); - child.buildBrigadierNode(previousNodes, newPreviousArguments, newPreviousArgumentNames); + child.buildBrigadierNode(newPreviousNodeInformation, newPreviousArguments, newPreviousArgumentNames); } + + // Create registered nodes now that all children are generated + previousNodeInformation.childrenConsumer().createNodeWithChildren(childrenNodeInformation); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index f9df1d633c..3fede09760 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -21,9 +21,9 @@ package dev.jorel.commandapi; import com.mojang.brigadier.Command; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import dev.jorel.commandapi.exceptions.OptionalArgumentException; @@ -222,65 +222,6 @@ public void setSubcommands(List subcommands) { ////////////////// // Registration // ////////////////// - - @Override - public List> getArgumentsAsStrings() { - // Return an empty list if we have no arguments - if (subcommands.isEmpty() && !hasAnyArguments()) { - return List.of(List.of()); - } - - List> argumentStrings = new ArrayList<>(); - - // Build main path, if it is executable - if (executor.hasAnyExecutors()) { - // Required arguments represent one path - List> mainPath = new ArrayList<>(); - mainPath.add(new ArrayList<>()); - for (Argument argument : requiredArguments) { - argument.appendToCommandPaths(mainPath); - } - - List slicePositions = new ArrayList<>(); - // Note: Assumption that all paths are the same length - slicePositions.add(mainPath.get(0).size()); - - // Each optional argument is a potential stopping point - for (Argument argument : optionalArguments) { - argument.appendToCommandPaths(mainPath); - slicePositions.add(mainPath.get(0).size()); - } - - // Return each path as sublists of the main path - for (List path : mainPath) { - for (int slicePos : slicePositions) { - argumentStrings.add(path.subList(0, slicePos)); - } - } - } - - // Build subcommand paths - for (Impl subCommand : subcommands) { - String[] subCommandArguments = new String[subCommand.aliases.length + 1]; - subCommandArguments[0] = subCommand.name; - System.arraycopy(subCommand.aliases, 0, subCommandArguments, 1, subCommand.aliases.length); - for (int i = 0; i < subCommandArguments.length; i++) { - subCommandArguments[i] += ":LiteralArgument"; - } - - for (List subArgs : subCommand.getArgumentsAsStrings()) { - for (String subCommandArgument : subCommandArguments) { - List newPath = new ArrayList<>(); - newPath.add(subCommandArgument); - newPath.addAll(subArgs); - argumentStrings.add(newPath); - } - } - } - - return argumentStrings; - } - @Override protected void checkPreconditions() { if (!executor.hasAnyExecutors() && (subcommands.isEmpty() || hasAnyArguments())) { @@ -297,12 +238,14 @@ protected boolean isRootExecutable() { } @Override - protected void createArgumentNodes(LiteralCommandNode rootNode) { + protected List createArgumentNodes(LiteralCommandNode rootNode) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); + List childrenNodes = new ArrayList<>(); + // Create arguments if (hasAnyArguments()) { - List> previousNodes = List.of(rootNode); + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); @@ -316,26 +259,50 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) previousArguments.add(commandNames); - Function, Command> executorCreator = executor.hasAnyExecutors() ? - args -> handler.generateBrigadierCommand(args, executor) : null; + // Note that `executor#hasAnyExecutors` must be true here + // If not, then `checkPreconditions` would have thrown a `MissingCommandExecutorException` + Function, Command> executorCreator = args -> handler.generateBrigadierCommand(args, executor); + // Stack required arguments // Only the last required argument is executable - previousNodes = AbstractArgument.stackArguments(requiredArguments, previousNodes, previousArguments, previousArgumentNames, executorCreator); + previousNodeInformation = AbstractArgument.stackArguments(requiredArguments, previousNodeInformation, previousArguments, previousArgumentNames, executorCreator); // Add optional arguments for (Argument argument : optionalArguments) { // All optional arguments are executable - previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, executorCreator); + previousNodeInformation = argument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, executorCreator); } + + // Create registered nodes now that all children are generated + previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of()); } // Add subcommands for (Impl subCommand : subcommands) { - Nodes nodes = subCommand.createCommandNodes(); + CommandInformation nodes = subCommand.createCommandInformation(""); + + // Add root node rootNode.addChild(nodes.rootNode()); + + RegisteredCommand.Node rootNodeInformation = nodes.command().rootNode(); + childrenNodes.add(rootNodeInformation); + + // Add aliases for (LiteralCommandNode aliasNode : nodes.aliasNodes()) { rootNode.addChild(aliasNode); + + // Create node information for the alias + String aliasName = aliasNode.getLiteral(); + childrenNodes.add( + new RegisteredCommand.Node( + aliasName, rootNodeInformation.className(), aliasName, + rootNodeInformation.executable(), rootNodeInformation.children() + ) + ); } } + + // Return node information + return childrenNodes; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index add75565e2..c2520e39c1 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; @@ -72,25 +73,6 @@ public void setArguments(List> ////////////////// // Registration // ////////////////// - - @Override - public List> getArgumentsAsStrings() { - // Return an empty list if we have no arguments - if (arguments.isEmpty()) return List.of(List.of()); - - List> argumentStrings = new ArrayList<>(); - - // If this node is executable, no arguments is a valid path - if (this.executor.hasAnyExecutors()) argumentStrings.add(List.of()); - - // Add branching paths - for (AbstractArgumentTree argument : arguments) { - argumentStrings.addAll(argument.getBranchesAsStrings()); - } - - return argumentStrings; - } - @Override protected void checkPreconditions() { if (!executor.hasAnyExecutors() && arguments.isEmpty()) { @@ -105,9 +87,11 @@ protected boolean isRootExecutable() { } @Override - protected void createArgumentNodes(LiteralCommandNode rootNode) { + protected List createArgumentNodes(LiteralCommandNode rootNode) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); + List childrenNodes = new ArrayList<>(); + // The previous arguments include an unlisted MultiLiteral representing the command name and aliases // This doesn't affect how the command acts, but it helps represent the command path in exceptions String[] literals = new String[aliases.length + 1]; @@ -119,11 +103,16 @@ protected void createArgumentNodes(LiteralCommandNode rootNode) // Build branches for (AbstractArgumentTree argument : arguments) { // We need new previousArguments lists for each branch so they don't interfere + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); + previousArguments.add(commandNames); - argument.buildBrigadierNode(List.of(rootNode), previousArguments, previousArgumentNames); + argument.buildBrigadierNode(previousNodeInformation, previousArguments, previousArgumentNames); } + + // Return children + return childrenNodes; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java index 867c6ed307..83716f36a2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java @@ -308,6 +308,6 @@ public static void registerCommand(Class commandClass) { * registered by the CommandAPI so far. The returned list is immutable. */ public static List getRegisteredCommands() { - return Collections.unmodifiableList(CommandAPIHandler.getInstance().registeredCommands); + return Collections.unmodifiableList(new ArrayList<>(CommandAPIHandler.getInstance().registeredCommands.values())); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index eea07642a8..8bd4179e5f 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -91,7 +92,7 @@ public class CommandAPIHandler platform; - final List registeredCommands; // Keep track of what has been registered for type checking + final Map registeredCommands; // Keep track of what has been registered for type checking final Map, Previewable> previewableArguments; // Arguments with previewable chat static final Pattern NAMESPACE_PATTERN = Pattern.compile("[0-9a-z_.-]+"); @@ -103,7 +104,7 @@ public class CommandAPIHandler platform) { this.platform = platform; - this.registeredCommands = new ArrayList<>(); + this.registeredCommands = new LinkedHashMap<>(); // This should be a LinkedHashMap to preserve insertion order this.previewableArguments = new HashMap<>(); CommandAPIHandler.instance = this; @@ -172,18 +173,17 @@ public void registerCommand(ExecutableCommand command, String // Do plaform-specific pre-registration tasks platform.preCommandRegistration(command.getName()); - // Record the commands that are registered - List registeredCommandInformation = RegisteredCommand.fromExecutableCommand(command, namespace); - registeredCommands.addAll(registeredCommandInformation); + // Generate command information + ExecutableCommand.CommandInformation commandInformation = command.createCommandInformation(namespace); - for (RegisteredCommand singleCommand : registeredCommandInformation) { - CommandAPI.logInfo("Registering command /" + command.getName() + " " + String.join(" ", singleCommand.argsAsStr())); - } + LiteralCommandNode resultantNode = commandInformation.rootNode(); + List> aliasNodes = commandInformation.aliasNodes(); + RegisteredCommand registeredCommand = commandInformation.command(); - // Create command nodes - ExecutableCommand.Nodes nodes = command.createCommandNodes(); - LiteralCommandNode resultantNode = nodes.rootNode(); - List> aliasNodes = nodes.aliasNodes(); + // Log the commands being registered + for (List argsAsStr : registeredCommand.rootNode().argsAsStr()) { + CommandAPI.logInfo("Registering command /" + String.join(" ", argsAsStr)); + } // Handle command conflicts ensureNoCommandConflict(resultantNode); @@ -211,8 +211,21 @@ public void registerCommand(ExecutableCommand command, String // partial) command registration. Generate the dispatcher file! writeDispatcherToFile(); + // Merge RegisteredCommand into map + BiFunction mergeRegisteredCommands = (key, value) -> value == null ? registeredCommand : value.mergeCommandInformation(registeredCommand); + + if (registeredCommand.namespace().isEmpty()) { + registeredCommands.compute(registeredCommand.commandName(), mergeRegisteredCommands); + } else { + registeredCommands.compute(registeredCommand.commandName(), (key, value) -> value == null ? + registeredCommand.copyWithEmptyNamespace() : + value.mergeCommandInformation(registeredCommand) + ); + registeredCommands.compute(registeredCommand.namespace() + ":" + registeredCommand.commandName(), mergeRegisteredCommands); + } + // Do platform-specific post-registration tasks - platform.postCommandRegistration(registeredCommandInformation, resultantNode, aliasNodes); + platform.postCommandRegistration(registeredCommand, resultantNode, aliasNodes); } /** @@ -635,12 +648,18 @@ public void addPreviewableArgument(List previousArguments, Argument pr // Generate all paths to the argument List> paths = new ArrayList<>(); paths.add(new ArrayList<>()); - for (Argument argument : previousArguments) { - argument.appendToCommandPaths(paths); - } - previewableArgument.appendToCommandPaths(paths); - // Insert paths to out map + // TODO: Fix this, the `appendToCommandPaths` method was removed + // A smarter way to get this information should exist + // It probably makes sense to make a custom CommandNode for PreviewableArgument + if(true) throw new IllegalStateException("TODO: Fix this method"); + + // for (Argument argument : previousArguments) { + // argument.appendToCommandPaths(paths); + // } + // previewableArgument.appendToCommandPaths(paths); + + // Insert paths to our map for (List path : paths) { previewableArguments.put(path, previewable); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index 0709a5a026..e7134e3cf5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -104,11 +104,11 @@ public interface CommandAPIPlatform registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes); + public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); /** * Builds and registers a Brigadier command node. diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 46417462ef..34a62566af 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.function.Predicate; import com.mojang.brigadier.builder.LiteralArgumentBuilder; @@ -298,9 +299,6 @@ public Object getHelpTopic() { ////////////////// // Registration // ////////////////// - - public abstract List> getArgumentsAsStrings(); - /** * Overrides a command. Effectively the same as unregistering the command using * CommandAPI.unregister() and then registering the command using .register() @@ -328,19 +326,28 @@ public void register(String namespace) { ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); } - record Nodes(LiteralCommandNode rootNode, List> aliasNodes) { + protected static record CommandInformation(LiteralCommandNode rootNode, List> aliasNodes, RegisteredCommand command) { } - Nodes createCommandNodes() { + protected CommandInformation createCommandInformation(String namespace) { checkPreconditions(); + // Create rootNode LiteralCommandNode rootNode = this.createCommandNodeBuilder(name).build(); - createArgumentNodes(rootNode); + List children = createArgumentNodes(rootNode); + // Create aliaseNodes List> aliasNodes = createAliasNodes(rootNode); - return new Nodes<>(rootNode, aliasNodes); + // Create command information + RegisteredCommand command = new RegisteredCommand( + name, aliases, namespace, permission, + Optional.ofNullable(shortDescription), Optional.ofNullable(fullDescription), Optional.ofNullable(usageDescription), Optional.ofNullable(helpTopic), + new RegisteredCommand.Node(name, getClass().getSimpleName(), name, isRootExecutable(), children) + ); + + return new CommandInformation<>(rootNode, aliasNodes, command); } protected LiteralArgumentBuilder createCommandNodeBuilder(String nodeName) { @@ -380,5 +387,5 @@ protected List> createAliasNodes(LiteralComm protected abstract boolean isRootExecutable(); - protected abstract void createArgumentNodes(LiteralCommandNode rootNode); + protected abstract List createArgumentNodes(LiteralCommandNode rootNode); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java index 2aa91dcb60..0243d52599 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java @@ -1,19 +1,25 @@ package dev.jorel.commandapi; -import dev.jorel.commandapi.arguments.AbstractArgument; - import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; /** - * Class to store a registered command which has its command name and a list of - * arguments as a string. The arguments are expected to be of the form - * {@code node_name:class_name}, for example {@code value:IntegerArgument}. This - * class also contains the information required to construct a meaningful help - * topic for a command + * Class to store a registered command which has its command name and a tree representing its arguments. + * This class also contains the information required to construct a meaningful help topic for a command. + * + * @param commandName The name of this command, without any leading {@code /} characters + * @param aliases A {@link String}{@code []} of aliases for this command + * @param namespace The namespace for this command + * @param permission The {@link CommandPermission} required to run this command + * @param shortDescription An {@link Optional} containing this command's help's short description + * @param fullDescription An {@link Optional} containing this command's help's full description + * @param fullDescription An {@link Optional} containing this command's help's usage + * @param rootNode The root {@link Node} in the tree structure that holds the arguments of this command */ public record RegisteredCommand( @@ -23,34 +29,34 @@ public record RegisteredCommand( String commandName, /** - * @return The list of node names and argument class simple names in the form - * {@code node_name:class_name}, for example - * {@code value:}{@link IntegerArgument} + * @return A {@link String}{@code []} of aliases for this command + */ + String[] aliases, + + /** + * @return The namespace for this command */ - List argsAsStr, + String namespace, /** - * @return An unmodifiable list of arguments for this command + * @return The {@link CommandPermission} required to run this command */ - List> arguments, + CommandPermission permission, /** - * @return An {@link Optional} containing this command's help's short - * descriptions + * @return An {@link Optional} containing this command's help's short description */ Optional shortDescription, /** - * @return An {@link Optional} containing this command's help's full - * descriptions + * @return An {@link Optional} containing this command's help's full description */ Optional fullDescription, /** - * @return An {@link Optional} containing this command's help's - * usage + * @return An {@link Optional} containing this command's help's usage */ - Optional usageDescription, + Optional usageDescription, // TODO: Bukkit specific fields probably should not be in platform agnostic classes // Either make HelpTopic platform agnostic or move this field into bukkit-core @@ -60,46 +66,125 @@ public record RegisteredCommand( Optional helpTopic, /** - * @return a {@link String}{@code []} of aliases for this command + * @return The root {@link Node} in the tree structure that holds the arguments of this command */ - String[] aliases, + Node rootNode) { /** - * @return The {@link CommandPermission} required to run this command + * Class to store information about each argument in a command's tree. + * + * @param nodeName The name of this argument node + * @param className The name of the CommandAPI object that this node represents + * @param helpString The string that should be used to represent this node when automatically generating help usage. + * @param executable True if this node can be executed, and false otherwise + * @param children A {@link List} of nodes that are children to this node */ - CommandPermission permission, + public static record Node( + + /** + * @return The name of this argument node + */ + String nodeName, + + /** + * @return The name of the CommandAPI object that this node represents + */ + String className, + + /** + * @return The string that should be used to represent this node when automatically generating help usage. + */ + String helpString, + + /** + * @return True if this node can be executed, and false otherwise + */ + boolean executable, + + /** + * @return A {@link List} of nodes that are children to this node + */ + List children) { + + /** + * @return A {@link List} of each executable path starting at this node. Each path is represented as a {@link List} of + * strings, where each string represents a node with the form {@code node_name:class_name}, for example {@code value:IntegerArgument}. + */ + public List> argsAsStr() { + List> paths = new ArrayList<>(); + + // Add this node + String nodeString = nodeName + ":" + className; + if (executable) { + // This should be an ArrayList so parent nodes can insert themselves at the start + paths.add(new ArrayList<>(List.of(nodeString))); + } + + // Add children nodes + for (Node node : children) { + List> subPaths = node.argsAsStr(); + + for (List subPath : subPaths) { + subPath.add(0, nodeString); + } + + paths.addAll(subPaths); + } + + return paths; + } + + private Node merge(Node other) { + // Merge executable status + boolean mergeExecutable = this.executable || other.executable; + + // Merge children + Map childrenByName = new HashMap<>(); + for (Node child : this.children) { + childrenByName.put(child.nodeName, child); + } + for (Node child : other.children) { + childrenByName.compute(child.nodeName, (key, value) -> value == null ? child : value.merge(child)); + } + List mergeChildren = new ArrayList<>(childrenByName.values()); + + // Other information defaults to the node that was registered first (this) + return new Node(nodeName, className, helpString, mergeExecutable, mergeChildren); + } + } /** - * @return The namespace for this command + * Merges the given command into this command, returning the result. The result should accurately represent + * the command that would result from registering the commands in order. For example, registering two commands + * with different permissions results in the first command's permission being used, and so the result of this + * method will have the first command's permission. + * + * @param other The {@link RegisteredCommand} that was registered after this one. + * @return The {@link RegisteredCommand} that results from merging this and the other command. */ - String namespace) { - public static List fromExecutableCommand(ExecutableCommand command, String namespace) { - // Unpack command parameters - String commandName = command.getName(); - List> argumentsAsStrings = command.getArgumentsAsStrings(); - - Optional shortDescription = Optional.ofNullable(command.getShortDescription()); - Optional fullDescription = Optional.ofNullable(command.getFullDescription()); - Optional usageDescription = Optional.ofNullable(command.getUsage()); - Optional helpTopic = Optional.ofNullable(command.getHelpTopic()); - - String[] aliases = command.getAliases(); - CommandPermission permission = command.getPermission(); - - List result = new ArrayList<>(argumentsAsStrings.size()); - for (List argumentString : argumentsAsStrings) { - result.add(new RegisteredCommand( - commandName, argumentString, null, // TODO: Need to fix this. Trying to rebase changes from https://github.com/JorelAli/CommandAPI/pull/537 into `dev/command-build-rewrite` - shortDescription, fullDescription, usageDescription, - helpTopic, aliases, permission, namespace - )); - } + public RegisteredCommand mergeCommandInformation(RegisteredCommand other) { + // Merge aliases + String[] mergedAliases = new String[this.aliases.length + other.aliases.length]; + System.arraycopy(this.aliases, 0, mergedAliases, 0, this.aliases.length); + System.arraycopy(other.aliases, 0, mergedAliases, this.aliases.length, other.aliases.length); - return result; + // Merge arguments + Node mergedRootNode = this.rootNode.merge(other.rootNode); + + // Other information defaults to the command that was registered first (this) + return new RegisteredCommand(commandName, mergedAliases, namespace, permission, shortDescription, fullDescription, usageDescription, helpTopic, mergedRootNode); + } + + /** + * @return A copy of this {@link RegisteredCommand}, but with {@link RegisteredCommand#namespace()} as {@code ""}. + */ + public RegisteredCommand copyWithEmptyNamespace() { + return new RegisteredCommand(commandName, aliases, "", permission, shortDescription, fullDescription, usageDescription, helpTopic, rootNode); } - // As https://stackoverflow.com/a/32083420 mentions, Optional's hashCode, equals, and toString method don't work if the - // Optional wraps an array, like `Optional usageDescription`, so we have to use the Arrays methods ourselves + // The default implementation of `hashCode`, `equals`, and `toString` don't work for arrays, + // so we need to override and specifically use the Arrays methods for `String[] aliases` + // As https://stackoverflow.com/a/32083420 mentions, the same thing happens when Optionals wrap an array, like `Optional usageDescription` @Override public int hashCode() { @@ -107,7 +192,7 @@ public int hashCode() { int result = 1; result = prime * result + Arrays.hashCode(aliases); result = prime * result + Arrays.hashCode(usageDescription.orElse(null)); - result = prime * result + Objects.hash(argsAsStr, commandName, fullDescription, permission, shortDescription, helpTopic, namespace); + result = prime * result + Objects.hash(commandName, namespace, permission, shortDescription, fullDescription, helpTopic, rootNode); return result; } @@ -120,7 +205,7 @@ public boolean equals(Object obj) { return false; } RegisteredCommand other = (RegisteredCommand) obj; - return Arrays.equals(aliases, other.aliases) && Objects.equals(argsAsStr, other.argsAsStr) && Objects.equals(commandName, other.commandName) + return Arrays.equals(aliases, other.aliases) && Objects.equals(rootNode, other.rootNode) && Objects.equals(commandName, other.commandName) && Objects.equals(namespace, other.namespace) && Arrays.equals(usageDescription.orElse(null), other.usageDescription.orElse(null)) && Objects.equals(fullDescription, other.fullDescription) && Objects.equals(permission, other.permission) && Objects.equals(shortDescription, other.shortDescription) && Objects.equals(helpTopic, other.helpTopic); @@ -128,8 +213,10 @@ public boolean equals(Object obj) { @Override public String toString() { - return "RegisteredCommand [commandName=" + commandName + ", argsAsStr=" + argsAsStr + ", shortDescription=" + shortDescription + ", fullDescription=" + fullDescription + return "RegisteredCommand [commandName=" + commandName + ", aliases=" + Arrays.toString(aliases) + + ", namespace=" + namespace + ", permission=" + permission + + ", shortDescription=" + shortDescription + ", fullDescription=" + fullDescription + ", usageDescription=" + (usageDescription.isPresent() ? "Optional[" + Arrays.toString(usageDescription.get()) + "]" : "Optional.empty") - + ", aliases=" + Arrays.toString(aliases) + ", permission=" + permission + ", namespace=" + namespace + ", helpTopic=" + helpTopic + "]"; + + ", helpTopic=" + helpTopic + ", rootNode=" + rootNode + "]"; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index fcbc6c68a2..c8b4e05d86 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -30,6 +30,7 @@ import dev.jorel.commandapi.AbstractArgumentTree; import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.commandnodes.UnnamedRequiredArgumentBuilder; import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; import dev.jorel.commandapi.exceptions.GreedyArgumentException; @@ -321,33 +322,24 @@ public final Impl combineWith(Argument... combinedArguments) { ////////////////////// // Command Building // ////////////////////// - - public String getHelpString() { - return "<" + this.getNodeName() + ">"; - } - @Override public String toString() { return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">"; } /** - * Adds this argument to the end of the all the current possible paths given. The added entry is built as {code nodeName:argumentClass}. - * Any arguments combined with this one are also added. - * - * @param argumentStrings A list of possible paths up to this argument so far. + * A record of the information needed to stack one argument onto another. + * + * @param lastCommandNodes A {@link List} of {@link CommandNode}s that were the last nodes in the structure of the previous argument. + * @param childrenConsumer A callback that accepts a {@link List} of all the {@link RegisteredCommand.Node}s that represent the nodes + * added as children to the previous argument. */ - public void appendToCommandPaths(List> argumentStrings) { - // Create paths for this argument - String argumentString = nodeName + ":" + getClass().getSimpleName(); - for (List path : argumentStrings) { - path.add(argumentString); - } + public static record NodeInformation(List> lastCommandNodes, ChildrenConsumer childrenConsumer) { + } - // Add combined arguments - for (Argument subArgument : combinedArguments) { - subArgument.appendToCommandPaths(argumentStrings); - } + @FunctionalInterface + public static interface ChildrenConsumer { + public void createNodeWithChildren(List children); } /** @@ -358,13 +350,13 @@ public void appendToCommandPaths(List> argumentStrings) { * This process is broken up into 4 other methods for the convenience of defining special node structures for specific * arguments. Those methods are: *
      - *
    • {@link #checkPreconditions(List, List, List)}
    • + *
    • {@link #checkPreconditions(NodeInformation, List, List)}
    • *
    • {@link #createArgumentBuilder(List, List)}
    • *
    • {@link #finishBuildingNode(ArgumentBuilder, List, Function)}
    • - *
    • {@link #linkNode(List, CommandNode, List, List, Function)}
    • + *
    • {@link #linkNode(NodeInformation, CommandNode, List, List, Function)}
    • *
    * - * @param previousNodes A List of {@link CommandNode}s to add this argument onto. + * @param previousNodeInformation The {@link NodeInformation} of the argument this argument is being added to. * @param previousArguments A List of CommandAPI arguments that came before this argument. * @param previousArgumentNames A List of Strings containing the node names that came before this argument. * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an @@ -374,15 +366,15 @@ public void appendToCommandPaths(List> argumentStrings) { * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier node structure for this argument. */ - public List> addArgumentNodes( - List> previousNodes, + public NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); // Check preconditions - checkPreconditions(previousNodes, previousArguments, previousArgumentNames); + checkPreconditions(previousNodeInformation, previousArguments, previousArgumentNames); // Handle previewable argument if (this instanceof Previewable) { @@ -396,23 +388,23 @@ public List> addArgumentNodes( CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalExecutorCreator); // Link node to previous - previousNodes = linkNode(previousNodes, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + previousNodeInformation = linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); // Return last nodes - return previousNodes; + return previousNodeInformation; } /** * Checks for any conditions that mean this argument cannot be built properly. * - * @param previousNodes A List of {@link CommandNode}s to add this argument onto. - * @param previousArguments A List of CommandAPI arguments that came before this argument. - * @param previousArgumentNames A List of Strings containing the node names that came before this argument. + * @param previousNodeInformation The {@link NodeInformation} of the argument this argument is being added to. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param previousArgumentNames A List of Strings containing the node names that came before this argument. */ public void checkPreconditions( - List> previousNodes, List previousArguments, List previousArgumentNames + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames ) { - if(previousNodes.isEmpty()) { + if(previousNodeInformation.lastCommandNodes().isEmpty()) { throw new GreedyArgumentException(previousArguments, (Argument) this); } if (isListed && previousArgumentNames.contains(nodeName)) { @@ -489,7 +481,7 @@ public CommandNode finishBuildingNode(ArgumentBuilder CommandNode finishBuildingNode(ArgumentBuilder List> linkNode( - List> previousNodes, CommandNode rootNode, + public NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { // Add rootNode to the previous nodes - for(CommandNode previousNode : previousNodes) { + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { previousNode.addChild(rootNode); } - // A GreedyArgument cannot have arguments after it - previousNodes = this instanceof GreedyArgument ? List.of() : List.of(rootNode); - // Stack on combined arguments - previousNodes = stackArguments(combinedArguments, previousNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); - - // Return last nodes - return previousNodes; + // Create information for this node + NodeInformation nodeInformation = new NodeInformation<>( + // A GreedyArgument cannot have arguments after it + this instanceof GreedyArgument ? List.of() : List.of(rootNode), + // Create registered node information once children are created + children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( + new RegisteredCommand.Node( + nodeName, getClass().getSimpleName(), "<" + nodeName + ">", + combinedArguments.isEmpty() && terminalExecutorCreator != null, + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + return stackArguments(combinedArguments, nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } /** @@ -527,7 +528,7 @@ public List> linkNode( * CommandAPI arguments. Only the last argument will be executable. * * @param argumentsToStack A List of CommandAPI arguments to put in sequence - * @param previousNodes A List of {@link CommandNode}s that start the stack. + * @param previousNodeInformation The {@link NodeInformation} of the argument this stack is being added to. * @param previousArguments A List of CommandAPI arguments that came before the {@code argumentsToStack}. * @param previousArgumentNames A List of Strings containing the node names that came before the {@code argumentsToStack}. * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an @@ -537,18 +538,21 @@ public List> linkNode( * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing the built argument stack. */ - public static , Source> List> stackArguments( - List argumentsToStack, List> previousNodes, + public static , Source> NodeInformation stackArguments( + List argumentsToStack, NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { int lastIndex = argumentsToStack.size() - 1; for (int i = 0; i < argumentsToStack.size(); i++) { Argument subArgument = argumentsToStack.get(i); - previousNodes = subArgument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, + + previousNodeInformation = subArgument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, // Only apply the `terminalExecutor` to the last argument i == lastIndex ? terminalExecutorCreator : null); } - return previousNodes; + + // Return information + return previousNodeInformation; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java index 28ee81c000..2792155117 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -10,6 +10,8 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.FlagsArgumentEndingNode; import dev.jorel.commandapi.commandnodes.FlagsArgumentRootNode; import dev.jorel.commandapi.executors.CommandArguments; @@ -52,10 +54,10 @@ public interface FlagsArgumentCommon getCombinedArguments(); /** - * Links to {@link AbstractArgument#checkPreconditions(List, List, List)}. + * Links to {@link AbstractArgument#checkPreconditions(NodeInformation, List, List)}. */ void checkPreconditions( - List> previousNodes, List previousArguments, List previousArgumentNames + NodeInformation previousNodes, List previousArguments, List previousArgumentNames ); /** @@ -93,18 +95,18 @@ default List parseArgument(CommandContext cmd } /** - * Overrides {@link AbstractArgument#addArgumentNodes(List, List, List, Function)}. + * Overrides {@link AbstractArgument#addArgumentNodes(NodeInformation, List, List, Function)}. *

    * A FlagsArgument works completely differently from a typical argument, so we need to completely * override the usual logic. */ - default List> addArgumentNodes( - List> previousNodes, + default NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { // Typical preconditions still apply - checkPreconditions(previousNodes, previousArguments, previousArgumentNames); + checkPreconditions(previousNodeInformation, previousArguments, previousArgumentNames); // Add this argument to the lists String nodeName = getNodeName(); @@ -116,7 +118,7 @@ default List> addArgumentNodes( finishBuildingNode(rootBuilder, previousArguments, null); FlagsArgumentRootNode rootNode = new FlagsArgumentRootNode<>(rootBuilder.build()); - for(CommandNode previousNode : previousNodes) { + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { previousNode.addChild(rootNode); } @@ -133,18 +135,28 @@ default List> addArgumentNodes( Function, Command> terminalBranchExecutor = terminalBranchesExecutable ? terminalExecutorCreator : null; // The last nodes here will be our final nodes - previousNodes = new ArrayList<>(); + List> newNodes = new ArrayList<>(); for(List terminalBranch : getTerminalBranches()) { - previousNodes.addAll( + newNodes.addAll( setupBranch(terminalBranch, rootNode, previousArguments, previousArgumentNames, terminalBranchExecutor, this::wrapTerminalBranchNodes) ); } - // Stack on combined arguments - previousNodes = AbstractArgument.stackArguments(getCombinedArguments(), previousNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); - - // Return last nodes - return previousNodes; + // Create information for this node + NodeInformation nodeInformation = new NodeInformation<>( + newNodes, + // Create registered node information once children are created + children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( + new RegisteredCommand.Node( + nodeName, getClass().getSimpleName(), "<" + nodeName + ">", + loopingBranchesExecutable || terminalBranchesExecutable, + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } private static , Source> List> setupBranch( @@ -158,10 +170,10 @@ default List> addArgumentNodes( List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); RootCommandNode branchRoot = new RootCommandNode<>(); - List> branchPreviousNodes = List.of(branchRoot); + NodeInformation branchNodeInformation = new NodeInformation<>(List.of(branchRoot), null); // Stack branch nodes - branchPreviousNodes = AbstractArgument.stackArguments(branchArguments, branchPreviousNodes, branchPreviousArguments, branchPreviousArgumentNames, terminalExecutorCreator); + branchNodeInformation = AbstractArgument.stackArguments(branchArguments, branchNodeInformation, branchPreviousArguments, branchPreviousArgumentNames, terminalExecutorCreator); // Find second-to-last nodes so their children can be modified // Unfortunately, we can't get this while stacking since arguments may (and may not) unpack to multiple layers @@ -187,7 +199,7 @@ default List> addArgumentNodes( CommandAPIHandler.getCommandNodeArguments(rootNode).putAll(CommandAPIHandler.getCommandNodeArguments(branchRoot)); // Return the last nodes in the tree - return branchPreviousNodes; + return branchNodeInformation.lastCommandNodes(); } private void wrapTerminalBranchNodes( diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java index f91dc86661..f9347166c5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java @@ -1,10 +1,16 @@ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; + +import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.List; +import java.util.function.Function; /** * An interface representing literal-based arguments @@ -38,6 +44,11 @@ public interface Literal getCombinedArguments(); + /////////////////////////////////////////////////////////////////////////////////////////////// // OVERRIDING METHODS // // A Literal has special logic that should override the implementations in AbstractArgument // @@ -62,4 +73,36 @@ public interface Literal + * Normally, Arguments use thier node name as their help string. However, a Literal uses its literal as the help string. + */ + default NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, + List previousArguments, List previousArgumentNames, + Function, Command> terminalExecutorCreator + ) { + // Add rootNode to the previous nodes + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { + previousNode.addChild(rootNode); + } + + // Create information for this node + NodeInformation nodeInformation = new NodeInformation<>( + List.of(rootNode), + // Create registered node information once children are created + children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( + new RegisteredCommand.Node( + getNodeName(), getClass().getSimpleName(), getLiteral(), + getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index ab0ea3cbd5..cc4e84c284 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -4,6 +4,9 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; + +import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.ArrayList; @@ -60,41 +63,6 @@ public interface MultiLiteral - * Normally, Arguments only represent a single branching path. However, a MultiLiteral represents multiple literal - * node paths, so we need to branch out the current paths for each literal. - */ - default void appendToCommandPaths(List> argumentStrings) { - // Create paths for this argument - Iterator literals = Arrays.asList(getLiterals()).iterator(); - String firstLiteralArgumentString = literals.next() + ":LiteralArgument"; - - // Copy each path for the other literals - List> newPaths = new ArrayList<>(); - while (literals.hasNext()) { - String literalArgumentString = literals.next() + ":LiteralArgument"; - for (List path : argumentStrings) { - List newPath = new ArrayList<>(path); - newPath.add(literalArgumentString); - newPaths.add(newPath); - } - } - - // Add first literal to the original paths - for (List path : argumentStrings) { - path.add(firstLiteralArgumentString); - } - argumentStrings.addAll(newPaths); - - // Add combined arguments - for (Argument argument : getCombinedArguments()) { - argument.appendToCommandPaths(argumentStrings); - } - } - /** * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. *

    @@ -113,19 +81,19 @@ default void appendToCommandPaths(List> argumentStrings) { } /** - * Overrides {@link AbstractArgument#linkNode(List, CommandNode, List, List, Function)}. + * Overrides {@link AbstractArgument#linkNode(NodeInformation, CommandNode, List, List, Function)}. *

    * Normally, Arguments are only represented by a single node, and so only need to link one node. However, a MultiLiteral * represents multiple literal node paths, which also need to be generated and inserted into the node structure. */ - default List> linkNode( - List> previousNodes, CommandNode rootNode, + default NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { List> newNodes = new ArrayList<>(); // Add root node to previous - for(CommandNode previousNode : previousNodes) { + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { previousNode.addChild(rootNode); } newNodes.add(rootNode); @@ -145,16 +113,27 @@ default List> linkNode( CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalExecutorCreator); // Add node to previous - for(CommandNode previousNode : previousNodes) { + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { previousNode.addChild(literalNode); } newNodes.add(literalNode); } - // Stack on combined arguments - previousNodes = AbstractArgument.stackArguments(getCombinedArguments(), newNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); - - // Return last nodes - return previousNodes; + // Create information for this node + NodeInformation nodeInformation = new NodeInformation<>( + newNodes, + // Create registered node information once children are created + children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( + new RegisteredCommand.Node( + nodeName, getClass().getSimpleName(), + "(" + String.join("|", getLiterals())+ ")", + getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index eaf4dbc878..36683aa167 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -46,7 +46,6 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.LiteralCommandNode; -import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.Argument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; @@ -243,35 +242,34 @@ private void generateHelpUsage(StringBuilder sb, RegisteredCommand command) { } private String[] getUsageList(RegisteredCommand currentCommand) { - List commandsWithIdenticalNames = new ArrayList<>(); + // TODO: I don't think the changes I made here are well tested, so this could have changed behavior + final Optional usageDescription = currentCommand.usageDescription(); + if (usageDescription.isPresent()) return usageDescription.get(); // Usage was overriden - // Collect every command with the same name - for (RegisteredCommand registeredCommand : CommandAPIHandler.getInstance().registeredCommands) { - if (registeredCommand.commandName().equals(currentCommand.commandName())) { - commandsWithIdenticalNames.add(registeredCommand); - } - } + // Generate command usage + // TODO: Should default usage generation be updated? https://github.com/JorelAli/CommandAPI/issues/363 + List usages = new ArrayList<>(); + StringBuilder usageSoFar = new StringBuilder("/"); + addUsageForNode(currentCommand.rootNode(), usages, usageSoFar); + return usages.toArray(String[]::new); + } - // Generate command usage or fill it with a user provided one - final String[] usages; - final Optional usageDescription = currentCommand.usageDescription(); - if (usageDescription.isPresent()) { - usages = usageDescription.get(); - } else { - // TODO: Figure out if default usage generation should be updated - final int numCommandsWithIdenticalNames = commandsWithIdenticalNames.size(); - usages = new String[numCommandsWithIdenticalNames]; - for (int i = 0; i < numCommandsWithIdenticalNames; i++) { - final RegisteredCommand command = commandsWithIdenticalNames.get(i); - StringBuilder usageString = new StringBuilder(); - usageString.append("/").append(command.commandName()).append(" "); - for (AbstractArgument arg : command.arguments()) { - usageString.append(arg.getHelpString()).append(" "); - } - usages[i] = usageString.toString().trim(); - } + private void addUsageForNode(RegisteredCommand.Node node, List usages, StringBuilder usageSoFar) { + // Add node to usage + usageSoFar.append(node.helpString()); + + // Add usage to the list if this is executable + if (node.executable()) usages.add(usageSoFar.toString()); + + // Add children + usageSoFar.append(" "); + int currentLength = usageSoFar.length(); + for (RegisteredCommand.Node child : node.children()) { + // Reset the string builder to the usage up to and including this node + usageSoFar.delete(currentLength, usageSoFar.length()); + + addUsageForNode(child, usages, usageSoFar); } - return usages; } void updateHelpForCommands(List commands) { @@ -419,6 +417,15 @@ public BukkitCommandSender wrapCommandSender(CommandSen throw new RuntimeException("Failed to wrap CommandSender " + sender + " to a CommandAPI-compatible BukkitCommandSender"); } + public void registerPermission(String string) { + try { + Bukkit.getPluginManager().addPermission(new Permission(string)); + } catch (IllegalArgumentException ignored) { + // Exception is thrown if we attempt to register a permission that already exists + // If it already exists, that's totally fine, so just ignore the exception + } + } + @Override @Unimplemented(because = REQUIRES_MINECRAFT_SERVER) public abstract SuggestionProvider getSuggestionProvider(SuggestionProviders suggestionProvider); @@ -463,28 +470,16 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { - commandRegistrationStrategy.postCommandRegistration(registeredCommands, resultantNode, aliasNodes); + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + commandRegistrationStrategy.postCommandRegistration(registeredCommand, resultantNode, aliasNodes); - // Using registeredCommands.get(0) as representation for most command features. - // This is fine, because the only difference between the commands in the list is their argument strings. - RegisteredCommand commonCommandInformation = registeredCommands.get(0); - - // Register the command's permission node to Bukkit's manager, if it exists - CommandPermission permission = commonCommandInformation.permission(); - Optional wrappedPermissionString = permission.getPermission(); - if (wrappedPermissionString.isPresent()) { - try { - Bukkit.getPluginManager().addPermission(new Permission(wrappedPermissionString.get())); - } catch (IllegalArgumentException ignored) { - // Exception is thrown if we attempt to register a permission that already exists - // If it already exists, that's totally fine, so just ignore the exception - } - } + // Register the command's permission string (if it exists) to Bukkit's manager + CommandPermission permission = registeredCommand.permission(); + permission.getPermission().ifPresent(this::registerPermission); if (!CommandAPI.canRegister()) { // Adding the command to the help map usually happens in `CommandAPIBukkit#onEnable` - updateHelpForCommands(registeredCommands); + updateHelpForCommands(List.of(registeredCommand)); // Sending command dispatcher packets usually happens when Players join the server for (Player p : Bukkit.getOnlinePlayers()) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java index 477d8a24b7..59f7892cc2 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java @@ -58,7 +58,7 @@ protected static boolean isThisTheCommandButNamespaced(String commandName, Strin public abstract void runTasksAfterServerStart(); - public abstract void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes); + public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); public abstract void registerCommandNode(LiteralCommandNode node, String namespace); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java index fce5f7fe18..e501f88c3a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java @@ -49,7 +49,7 @@ public void runTasksAfterServerStart() { } @Override - public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { // Nothing to do } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index 3bf6ffc8b4..47bff20693 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -183,11 +183,7 @@ private void fixPermissions() { } @Override - public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { - // Using registeredCommands.get(0) as representation for most command features. - // This is fine, because the only difference between the commands in the list is their argument strings. - RegisteredCommand commonCommandInformation = registeredCommands.get(0); - + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { if (!CommandAPI.canRegister()) { // Usually, when registering commands during server startup, we can just put our commands into the // `net.minecraft.server.MinecraftServer#vanillaCommandDispatcher` and leave it. As the server finishes setup, @@ -201,8 +197,8 @@ public void postCommandRegistration(List registeredCommands, RootCommandNode root = getResourcesDispatcher.get().getRoot(); String name = resultantNode.getLiteral(); - String namespace = commonCommandInformation.namespace(); - String permNode = unpackInternalPermissionNodeString(commonCommandInformation.permission()); + String namespace = registeredCommand.namespace(); + String permNode = unpackInternalPermissionNodeString(registeredCommand.permission()); registerCommand(knownCommands, root, name, permNode, namespace, resultantNode); @@ -228,20 +224,20 @@ public void postCommandRegistration(List registeredCommands, minecraftCommandNamespaces = new RootCommandNode<>(); } } else { - CommandPermission permission = commonCommandInformation.permission(); + CommandPermission permission = registeredCommand.permission(); // Since the VanillaCommandWrappers aren't created yet, we need to remember to // fix those permissions once the server is enabled. Using `putIfAbsent` to // default to the first permission associated with this command. - String commandName = commonCommandInformation.commandName().toLowerCase(); + String commandName = registeredCommand.commandName().toLowerCase(); permissionsToFix.putIfAbsent(commandName, permission); // Do the same for the namespaced version of the command (which is never empty on Bukkit forks) - String namespace = commonCommandInformation.namespace().toLowerCase(); + String namespace = registeredCommand.namespace().toLowerCase(); permissionsToFix.putIfAbsent(namespace + ":" + commandName, permission); // Do the same for the aliases - for (String alias : commonCommandInformation.aliases()) { + for (String alias : registeredCommand.aliases()) { alias = alias.toLowerCase(); permissionsToFix.putIfAbsent(alias, permission); permissionsToFix.putIfAbsent(namespace + ":" + alias, permission); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java index ca0663f7b0..5e5d73539b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java @@ -26,7 +26,6 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.BukkitTooltip; import dev.jorel.commandapi.CommandAPIBukkit; import dev.jorel.commandapi.CommandAPIHandler; @@ -200,13 +199,13 @@ public Argument combineWith(List> combinedArguments) { } @Override - public List> addArgumentNodes( - List> previousNodes, + public NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator ) { // Node structure is determined by the base argument - previousNodes = base.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, + previousNodeInformation = base.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, // Replace the base argument with this argument when executing the command terminalExecutorCreator == null ? null : args -> { @@ -219,7 +218,7 @@ public List> addArgumentNodes( // Replace the base argument with this argument when executing the command previousArguments.set(previousArguments.indexOf(base), this); - return previousNodes; + return previousNodeInformation; } /** diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java index 5642becf89..b1cb0155bc 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java @@ -3,7 +3,6 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.executors.CommandArguments; import org.bukkit.command.CommandSender; @@ -74,7 +73,7 @@ public List parseArgument(CommandContext cmdC } @Override - public List> addArgumentNodes(List> previousNodes, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return FlagsArgumentCommon.super.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, terminalExecutorCreator); + public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return FlagsArgumentCommon.super.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index 1740f54b2f..45c3dac01c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -20,14 +20,18 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.CommandNode; + import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; import org.bukkit.command.CommandSender; import java.util.List; +import java.util.function.Function; /** * A pseudo-argument representing a single literal string @@ -136,11 +140,6 @@ public CommandAPIArgumentType getArgumentType() { return CommandAPIArgumentType.LITERAL; } - @Override - public String getHelpString() { - return literal; - } - @Override public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { return literal; @@ -156,4 +155,9 @@ public String parseArgument(CommandContext cmdCtx, String key, public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { return Literal.super.createArgumentBuilder(previousArguments, previousArgumentNames); } + + @Override + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index 42f0b93caf..b3d95b1e60 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -106,18 +106,13 @@ public String parseArgument(CommandContext cmdCtx, String key, // method by default. However, we want to use the implementations found in the MultiLiteral interface. // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void appendToCommandPaths(List> argumentStrings) { - MultiLiteral.super.appendToCommandPaths(argumentStrings); - } - @Override public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { return MultiLiteral.super.createArgumentBuilder(previousArguments, previousArgumentNames); } @Override - public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java index fbe4ea7b49..ac053a096c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java @@ -1,9 +1,9 @@ package dev.jorel.commandapi.test; -import dev.jorel.commandapi.CommandAPI; import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.RegisteredCommand.Node; import dev.jorel.commandapi.arguments.IntegerArgument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; @@ -12,17 +12,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.fail; +import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; +import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.children; /** * Tests for making sure the {@link RegisteredCommand} information is correct when registering {@link CommandAPICommand}s */ -class CommandAPICommandRegisteredCommandTests extends TestBase { +class CommandAPICommandRegisteredCommandTests extends RegisteredCommandTestBase { /********* * Setup * @@ -38,64 +37,8 @@ public void tearDown() { super.tearDown(); } - private RegisteredCommand registeredCommandNoHelpOrPermission(String name, List argsAsStr, String... aliases) { - return new RegisteredCommand(name, argsAsStr, - Optional.empty(), Optional.empty(), Optional.empty(), - aliases, - CommandPermission.NONE, "minecraft" - ); - } - - private void assertCreatedRegisteredCommands(RegisteredCommand... commands) { - List expectedCommands = Arrays.asList(commands); - List actualCommands = CommandAPI.getRegisteredCommands(); - - if (expectedCommands.size() != actualCommands.size()) { - StringBuilder builder = new StringBuilder(); - builder.append("Expected ").append(expectedCommands.size()).append(" command(s), found ").append(actualCommands.size()).append(" command(s)."); - - builder.append("\nExpected: "); - addRegisteredCommandList(builder, expectedCommands); - builder.append("\nActual: "); - addRegisteredCommandList(builder, actualCommands); - - fail(builder.toString()); - } - - for (int i = 0; i < expectedCommands.size(); i++) { - RegisteredCommand expectedCommand = expectedCommands.get(i); - RegisteredCommand actualCommand = actualCommands.get(i); - - if (!Objects.equals(expectedCommand, actualCommand)) { - StringBuilder builder = new StringBuilder(); - builder.append("Command #").append(i + 1).append(" differed. Expected:\n"); - builder.append(expectedCommand); - builder.append("\nActual:\n"); - builder.append(actualCommand); - - builder.append("\nExpected list: "); - addRegisteredCommandList(builder, expectedCommands); - builder.append("\nActual list: "); - addRegisteredCommandList(builder, actualCommands); - - fail(builder.toString()); - } - } - } - - private void addRegisteredCommandList(StringBuilder builder, List commands) { - if (commands.isEmpty()) { - builder.append("[]"); - return; - } - - builder.append("[\n"); - for (RegisteredCommand command : commands) { - builder.append("\t"); - builder.append(command); - builder.append("\n"); - } - builder.append("]"); + private NodeBuilder commandNode(String nodeName, boolean executable) { + return node(nodeName, CommandAPICommand.class, executable).helpString(nodeName); } /********* @@ -108,7 +51,11 @@ void testRegister() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of())); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true), + List.of("command:CommandAPICommand") + ); } @Test @@ -123,13 +70,13 @@ void testRegisterHelpInformation() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - new RegisteredCommand( - "command", List.of(), - Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), - new String[0], CommandPermission.NONE, "minecraft" - ) + RegisteredCommand expectedCommand = new RegisteredCommand( + "command", new String[0], "minecraft", CommandPermission.NONE, + Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + commandNode("command", true).build() ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -139,13 +86,13 @@ void testRegisterOpPermission() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - new RegisteredCommand( - "command", List.of(), - Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.OP, "minecraft" - ) + RegisteredCommand expectedCommand = new RegisteredCommand( + "command", new String[0], "minecraft", CommandPermission.OP, + Optional.empty(), Optional.empty(), Optional.empty(), + commandNode("command", true).build() ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -155,13 +102,13 @@ void testRegisterStringPermission() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - new RegisteredCommand( - "command", List.of(), - Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.fromString("permission"), "minecraft" - ) + RegisteredCommand expectedCommand = new RegisteredCommand( + "command", new String[0], "minecraft", CommandPermission.fromString("permission"), + Optional.empty(), Optional.empty(), Optional.empty(), + commandNode("command", true).build() ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -171,7 +118,9 @@ void testRegisterOneAlias() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1")); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -181,7 +130,20 @@ void testRegisterTwoAliases() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1", "alias2")); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterNamespace() { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .register("custom"); + + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -191,7 +153,12 @@ void testRegisterOneArgument() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument"))); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("string", StringArgument.class, true)), + List.of("command:CommandAPICommand", "string:StringArgument") + ); } @Test @@ -204,7 +171,14 @@ void testRegisterTwoArguments() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument", "integer:IntegerArgument"))); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("string", StringArgument.class, false) + .withChildren(node("integer", IntegerArgument.class, true)) + ), + List.of("command:CommandAPICommand", "string:StringArgument", "integer:IntegerArgument") + ); } @Test @@ -217,16 +191,14 @@ void testRegisterMultiLiteralArguments() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "e:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "e:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "e:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "f:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "f:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "f:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("literal1", MultiLiteralArgument.class, false).helpString("(a|b|c)").withChildren( + node("literal2", MultiLiteralArgument.class, true).helpString("(d|e|f)") + ) + ), + List.of("command:CommandAPICommand", "literal1:MultiLiteralArgument", "literal2:MultiLiteralArgument") ); } @@ -237,9 +209,12 @@ void testRegisterOneOptionalArgument() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(node("string", StringArgument.class, true)), + List.of("command:CommandAPICommand"), + List.of("command:CommandAPICommand", "string:StringArgument") ); } @@ -253,10 +228,15 @@ void testRegisterTwoOptionalArguments() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")), - registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument", "integer:IntegerArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(node("string", StringArgument.class, true) + .withChildren(node("integer", IntegerArgument.class, true)) + ), + List.of("command:CommandAPICommand"), + List.of("command:CommandAPICommand", "string:StringArgument"), + List.of("command:CommandAPICommand", "string:StringArgument", "integer:IntegerArgument") ); } @@ -270,10 +250,17 @@ void testRegisterCombinedOptionalArguments() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(node("1", LiteralArgument.class, false).helpString("1") + .withChildren(node("2", LiteralArgument.class, true).helpString("2") + .withChildren(node("3", LiteralArgument.class, false).helpString("3") + .withChildren(node("4", LiteralArgument.class, true).helpString("4") + )))), + List.of("command:CommandAPICommand"), + List.of("command:CommandAPICommand", "1:LiteralArgument", "2:LiteralArgument"), + List.of("command:CommandAPICommand", "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument") ); } @@ -291,19 +278,30 @@ void testRegisterCombinedRequiredAndOptionalArguments() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of( - "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" - )), - registeredCommandNoHelpOrPermission("command", List.of( - "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", - "5:LiteralArgument", "6:LiteralArgument" - )), - registeredCommandNoHelpOrPermission("command", List.of( - "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", - "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" - )) - ); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("1", LiteralArgument.class, false).helpString("1") + .withChildren(node("2", LiteralArgument.class, false).helpString("2") + .withChildren(node("3", LiteralArgument.class, false).helpString("3") + .withChildren(node("4", LiteralArgument.class, true).helpString("4") + .withChildren(node("5", LiteralArgument.class, false).helpString("5") + .withChildren(node("6", LiteralArgument.class, true).helpString("6") + .withChildren(node("7", LiteralArgument.class, false).helpString("7") + .withChildren(node("8", LiteralArgument.class, true).helpString("8") + )))))))), + List.of("command:CommandAPICommand", + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" + ), + List.of("command:CommandAPICommand", + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument" + ), + List.of("command:CommandAPICommand", + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" + ) + ); } ////////////////////////////////////// @@ -320,7 +318,12 @@ void testRegisterOneSubcommand() { ) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument"))); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(commandNode("subcommand", true)), + List.of("command:CommandAPICommand", "subcommand:CommandAPICommand") + ); } @Test @@ -334,9 +337,14 @@ void testRegisterTwoSubcommands() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + commandNode("subcommand1", true), + commandNode("subcommand2", true) + ), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand") ); } @@ -350,9 +358,12 @@ void testRegisterOneSubcommandAndBaseExecutable() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(commandNode("subcommand", true)), + List.of("command:CommandAPICommand"), + List.of("command:CommandAPICommand", "subcommand:CommandAPICommand") ); } @@ -366,10 +377,16 @@ void testRegisterSubcommandWithAliases() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + commandNode("subcommand", true), + commandNode("alias1", true), + commandNode("alias2", true) + ), + List.of("command:CommandAPICommand", "subcommand:CommandAPICommand"), + List.of("command:CommandAPICommand", "alias1:CommandAPICommand"), + List.of("command:CommandAPICommand", "alias2:CommandAPICommand") ); } @@ -392,9 +409,22 @@ void testRegisterSubcommandsWithArguments() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "string1:StringArgument", "integer1:IntegerArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "string2:StringArgument", "integer2:IntegerArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + commandNode("subcommand1", false).withChildren( + node("string1", StringArgument.class, false).withChildren( + node("integer1", IntegerArgument.class, true) + ) + ), + commandNode("subcommand2", false).withChildren( + node("string2", StringArgument.class, false).withChildren( + node("integer2", IntegerArgument.class, true) + ) + ) + ), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand", "string1:StringArgument", "integer1:IntegerArgument"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand", "string2:StringArgument", "integer2:IntegerArgument") ); } @@ -412,19 +442,26 @@ void testRegisterSubcommandWithAliasesAndMultiLiteralArgument() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "a:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "a:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "a:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "b:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "b:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "b:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "a:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "a:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "a:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument", "b:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias1:LiteralArgument", "b:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("alias2:LiteralArgument", "b:LiteralArgument", "d:LiteralArgument")) + List literal2 = children( + node("literal2", MultiLiteralArgument.class, true).helpString("(c|d)") + ); + + List literal1 = children( + node("literal1", MultiLiteralArgument.class, false).helpString("(a|b)").withChildren(literal2) + ); + + List subcommands = children( + commandNode("subcommand", false).withChildren(literal1), + commandNode("alias1", false).withChildren(literal1), + commandNode("alias2", false).withChildren(literal1) + ); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren(subcommands), + List.of("command:CommandAPICommand", "subcommand:CommandAPICommand", "literal1:MultiLiteralArgument", "literal2:MultiLiteralArgument"), + List.of("command:CommandAPICommand", "alias1:CommandAPICommand", "literal1:MultiLiteralArgument", "literal2:MultiLiteralArgument"), + List.of("command:CommandAPICommand", "alias2:CommandAPICommand", "literal1:MultiLiteralArgument", "literal2:MultiLiteralArgument") ); } @@ -449,13 +486,26 @@ void testRegisterSubcommandsWithOptionalArguments() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "a:LiteralArgument", "b:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "c:LiteralArgument", "d:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + commandNode("subcommand1", true).withChildren( + node("a", LiteralArgument.class, true).helpString("a").withChildren( + node("b", LiteralArgument.class, true).helpString("b") + ) + ), + commandNode("subcommand2", true).withChildren( + node("c", LiteralArgument.class, true).helpString("c").withChildren( + node("d", LiteralArgument.class, true).helpString("d") + ) + ) + ), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand"), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand", "a:LiteralArgument"), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand", "a:LiteralArgument", "b:LiteralArgument"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand", "c:LiteralArgument"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand", "c:LiteralArgument", "d:LiteralArgument") ); } @@ -484,11 +534,109 @@ void testRegisterSubcommandsWithCombinedRequiredAndOptionalArguments() { ) .register(); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + commandNode("subcommand1", false) + .withChildren(node("1a", LiteralArgument.class, false).helpString("1a") + .withChildren(node("1b", LiteralArgument.class, true).helpString("1b") + .withChildren(node("1c", LiteralArgument.class, false).helpString("1c") + .withChildren(node("1d", LiteralArgument.class, true).helpString("1d") + )))), + commandNode("subcommand2", false) + .withChildren(node("2a", LiteralArgument.class, false).helpString("2a") + .withChildren(node("2b", LiteralArgument.class, true).helpString("2b") + .withChildren(node("2c", LiteralArgument.class, false).helpString("2c") + .withChildren(node("2d", LiteralArgument.class, true).helpString("2d") + )))) + ), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand", "1a:LiteralArgument", "1b:LiteralArgument"), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand", "1a:LiteralArgument", "1b:LiteralArgument", "1c:LiteralArgument", "1d:LiteralArgument"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand", "2a:LiteralArgument", "2b:LiteralArgument"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand", "2a:LiteralArgument", "2b:LiteralArgument", "2c:LiteralArgument", "2d:LiteralArgument") + ); + } + + ///////////////////////// + // Information merging // + ///////////////////////// + + @Test + void testRegisterTwoSeparateCommands() { + new CommandAPICommand("command1") + .executesPlayer(P_EXEC) + .register(); + + new CommandAPICommand("command2") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); + RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument", "1c:LiteralArgument", "1d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument", "2c:LiteralArgument", "2d:LiteralArgument")) + command1.copyWithEmptyNamespace(), command1, + command2.copyWithEmptyNamespace(), command2 + ); + } + + @Test + void testRegisterMergeArguments() { + new CommandAPICommand("command") + .withArguments(new StringArgument("string")) + .executesPlayer(P_EXEC) + .register(); + + new CommandAPICommand("command") + .withArguments(new IntegerArgument("integer")) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("string", StringArgument.class, true), + node("integer", IntegerArgument.class, true) + ), + List.of("command:CommandAPICommand", "string:StringArgument"), + List.of("command:CommandAPICommand", "integer:IntegerArgument") ); } + + @Test + void testRegisterMergeNamespaces() { + new CommandAPICommand("command") + .withArguments(new LiteralArgument("first")) + .executesPlayer(P_EXEC) + .register("first"); + + new CommandAPICommand("command") + .withArguments(new LiteralArgument("second")) + .executesPlayer(P_EXEC) + .register("second"); + + RegisteredCommand first = simpleRegisteredCommand( + "command", "first", + commandNode("command", false).withChildren( + node("first", LiteralArgument.class, true).helpString("first") + ) + ); + + RegisteredCommand second = simpleRegisteredCommand( + "command", "second", + commandNode("command", false).withChildren( + node("second", LiteralArgument.class, true).helpString("second") + ) + ); + + RegisteredCommand merged = simpleRegisteredCommand( + "command", "", + commandNode("command", false).withChildren( + node("first", LiteralArgument.class, true).helpString("first"), + node("second", LiteralArgument.class, true).helpString("second") + ) + ); + + assertCreatedRegisteredCommands(merged, first, second); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java index 1c3b5764b0..9e772b2003 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java @@ -1,6 +1,5 @@ package dev.jorel.commandapi.test; -import dev.jorel.commandapi.CommandAPI; import dev.jorel.commandapi.CommandTree; import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; @@ -12,17 +11,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.fail; +import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; /** * Tests for making sure the {@link RegisteredCommand} information is correct when registering {@link CommandTree}s */ -class CommandTreeRegisteredCommandTests extends TestBase { +class CommandTreeRegisteredCommandTests extends RegisteredCommandTestBase { /********* * Setup * @@ -38,64 +35,8 @@ public void tearDown() { super.tearDown(); } - private RegisteredCommand registeredCommandNoHelpOrPermission(String name, List argsAsStr, String... aliases) { - return new RegisteredCommand(name, argsAsStr, - Optional.empty(), Optional.empty(), Optional.empty(), - aliases, - CommandPermission.NONE, "minecraft" - ); - } - - private void assertCreatedRegisteredCommands(RegisteredCommand... commands) { - List expectedCommands = Arrays.asList(commands); - List actualCommands = CommandAPI.getRegisteredCommands(); - - if (expectedCommands.size() != actualCommands.size()) { - StringBuilder builder = new StringBuilder(); - builder.append("Expected ").append(expectedCommands.size()).append(" command(s), found ").append(actualCommands.size()).append(" command(s)."); - - builder.append("\nExpected: "); - addRegisteredCommandList(builder, expectedCommands); - builder.append("\nActual: "); - addRegisteredCommandList(builder, actualCommands); - - fail(builder.toString()); - } - - for (int i = 0; i < expectedCommands.size(); i++) { - RegisteredCommand expectedCommand = expectedCommands.get(i); - RegisteredCommand actualCommand = actualCommands.get(i); - - if (!Objects.equals(expectedCommand, actualCommand)) { - StringBuilder builder = new StringBuilder(); - builder.append("Command #").append(i + 1).append(" differed. Expected:\n"); - builder.append(expectedCommand); - builder.append("\nActual:\n"); - builder.append(actualCommand); - - builder.append("\nExpected list: "); - addRegisteredCommandList(builder, expectedCommands); - builder.append("\nActual list: "); - addRegisteredCommandList(builder, actualCommands); - - fail(builder.toString()); - } - } - } - - private void addRegisteredCommandList(StringBuilder builder, List commands) { - if (commands.isEmpty()) { - builder.append("[]"); - return; - } - - builder.append("[\n"); - for (RegisteredCommand command : commands) { - builder.append("\t"); - builder.append(command); - builder.append("\n"); - } - builder.append("]"); + private NodeBuilder commandNode(String nodeName, boolean executable) { + return node(nodeName, CommandTree.class, executable).helpString(nodeName); } /********* @@ -108,7 +49,11 @@ void testRegister() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of())); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true), + List.of("command:CommandTree") + ); } @Test @@ -123,13 +68,13 @@ void testRegisterHelpInformation() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - new RegisteredCommand( - "command", List.of(), - Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), - new String[0], CommandPermission.NONE, "minecraft" - ) + RegisteredCommand expectedCommand = new RegisteredCommand( + "command", new String[0], "minecraft", CommandPermission.NONE, + Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + commandNode("command", true).build() ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -139,13 +84,13 @@ void testRegisterOpPermission() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - new RegisteredCommand( - "command", List.of(), - Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.OP, "minecraft" - ) + RegisteredCommand expectedCommand = new RegisteredCommand( + "command", new String[0], "minecraft", CommandPermission.OP, + Optional.empty(), Optional.empty(), Optional.empty(), + commandNode("command", true).build() ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -155,13 +100,13 @@ void testRegisterStringPermission() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands( - new RegisteredCommand( - "command", List.of(), - Optional.empty(), Optional.empty(), Optional.empty(), - new String[0], CommandPermission.fromString("permission"), "minecraft" - ) + RegisteredCommand expectedCommand = new RegisteredCommand( + "command", new String[0], "minecraft", CommandPermission.fromString("permission"), + Optional.empty(), Optional.empty(), Optional.empty(), + commandNode("command", true).build() ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -171,7 +116,9 @@ void testRegisterOneAlias() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1")); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -181,7 +128,20 @@ void testRegisterTwoAliases() { .executesPlayer(P_EXEC) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of(), "alias1", "alias2")); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterNamespace() { + new CommandTree("command") + .executesPlayer(P_EXEC) + .register("custom"); + + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @Test @@ -190,7 +150,12 @@ void testRegisterOneBranch() { .then(new StringArgument("string").executesPlayer(P_EXEC)) .register(); - assertCreatedRegisteredCommands(registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument"))); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("string", StringArgument.class, true)), + List.of("command:CommandTree", "string:StringArgument") + ); } @Test @@ -200,9 +165,14 @@ void testRegisterTwoBranches() { .then(new IntegerArgument("integer").executesPlayer(P_EXEC)) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("string:StringArgument")), - registeredCommandNoHelpOrPermission("command", List.of("integer:IntegerArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("string", StringArgument.class, true), + node("integer", IntegerArgument.class, true) + ), + List.of("command:CommandTree", "string:StringArgument"), + List.of("command:CommandTree", "integer:IntegerArgument") ); } @@ -215,16 +185,14 @@ void testRegisterMultiLiteralArguments() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "e:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "e:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "e:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("a:LiteralArgument", "f:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("b:LiteralArgument", "f:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("c:LiteralArgument", "f:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("literal1", MultiLiteralArgument.class, false).helpString("(a|b|c)").withChildren( + node("literal2", MultiLiteralArgument.class, true).helpString("(d|e|f)") + ) + ), + List.of("command:CommandTree", "literal1:MultiLiteralArgument", "literal2:MultiLiteralArgument") ); } @@ -248,18 +216,29 @@ void testRegisterCombinedArguments() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of( + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("1", LiteralArgument.class, false).helpString("1") + .withChildren(node("2", LiteralArgument.class, false).helpString("2") + .withChildren(node("3", LiteralArgument.class, false).helpString("3") + .withChildren(node("4", LiteralArgument.class, true).helpString("4") + .withChildren(node("5", LiteralArgument.class, false).helpString("5") + .withChildren(node("6", LiteralArgument.class, true).helpString("6") + .withChildren(node("7", LiteralArgument.class, false).helpString("7") + .withChildren(node("8", LiteralArgument.class, true).helpString("8") + )))))))), + List.of("command:CommandTree", "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" - )), - registeredCommandNoHelpOrPermission("command", List.of( + ), + List.of("command:CommandTree", "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", "5:LiteralArgument", "6:LiteralArgument" - )), - registeredCommandNoHelpOrPermission("command", List.of( + ), + List.of("command:CommandTree", "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" - )) + ) ); } @@ -278,9 +257,12 @@ void testRegisterOneBranchAndBaseExecutable() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of()), - registeredCommandNoHelpOrPermission("command", List.of("subcommand:LiteralArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(node("subcommand", LiteralArgument.class, true).helpString("subcommand")), + List.of("command:CommandTree"), + List.of("command:CommandTree", "subcommand:LiteralArgument") ); } @@ -299,11 +281,22 @@ void testRegisterBranchesWithBranches() { ) .register(); - assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "string1:StringArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "integer1:IntegerArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "string2:StringArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "integer2:IntegerArgument")) + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("subcommand1", LiteralArgument.class, false).helpString("subcommand1").withChildren( + node("string1", StringArgument.class, true), + node("integer1", IntegerArgument.class, true) + ), + node("subcommand2", LiteralArgument.class, false).helpString("subcommand2").withChildren( + node("string2", StringArgument.class, true), + node("integer2", IntegerArgument.class, true) + ) + ), + List.of("command:CommandTree", "subcommand1:LiteralArgument", "string1:StringArgument"), + List.of("command:CommandTree", "subcommand1:LiteralArgument", "integer1:IntegerArgument"), + List.of("command:CommandTree", "subcommand2:LiteralArgument", "string2:StringArgument"), + List.of("command:CommandTree", "subcommand2:LiteralArgument", "integer2:IntegerArgument") ); } @@ -334,11 +327,150 @@ void testRegisterBranchesWithCombinedArguments() { ) .register(); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("subcommand1", LiteralArgument.class, false).helpString("subcommand1") + .withChildren(node("1a", LiteralArgument.class, false).helpString("1a") + .withChildren(node("1b", LiteralArgument.class, true).helpString("1b") + .withChildren(node("1c", LiteralArgument.class, false).helpString("1c") + .withChildren(node("1d", LiteralArgument.class, true).helpString("1d") + )))), + node("subcommand2", LiteralArgument.class, false).helpString("subcommand2") + .withChildren(node("2a", LiteralArgument.class, false).helpString("2a") + .withChildren(node("2b", LiteralArgument.class, true).helpString("2b") + .withChildren(node("2c", LiteralArgument.class, false).helpString("2c") + .withChildren(node("2d", LiteralArgument.class, true).helpString("2d") + )))) + ), + List.of("command:CommandTree", "subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument"), + List.of("command:CommandTree", "subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument", "1c:LiteralArgument", "1d:LiteralArgument"), + List.of("command:CommandTree", "subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument"), + List.of("command:CommandTree", "subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument", "2c:LiteralArgument", "2d:LiteralArgument") + ); + } + + ///////////////////////// + // Information merging // + ///////////////////////// + + ///////////////////////// + // Information merging // + ///////////////////////// + + @Test + void testRegisterTwoSeparateCommands() { + new CommandTree("command1") + .executesPlayer(P_EXEC) + .register(); + + new CommandTree("command2") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); + RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + assertCreatedRegisteredCommands( - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand1:LiteralArgument", "1a:LiteralArgument", "1b:LiteralArgument", "1c:LiteralArgument", "1d:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument")), - registeredCommandNoHelpOrPermission("command", List.of("subcommand2:LiteralArgument", "2a:LiteralArgument", "2b:LiteralArgument", "2c:LiteralArgument", "2d:LiteralArgument")) + command1.copyWithEmptyNamespace(), command1, + command2.copyWithEmptyNamespace(), command2 ); } + + @Test + void testRegisterMergeArguments() { + new CommandTree("command") + .then(new StringArgument("string").executesPlayer(P_EXEC)) + .register(); + + new CommandTree("command") + .then(new IntegerArgument("integer").executesPlayer(P_EXEC)) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("string", StringArgument.class, true), + node("integer", IntegerArgument.class, true) + ), + List.of("command:CommandTree", "string:StringArgument"), + List.of("command:CommandTree", "integer:IntegerArgument") + ); + } + + @Test + void testRegisterMergeDifferentLengthBranches() { + new CommandTree("command") + .then( + new LiteralArgument("first").then( + new StringArgument("argument").executesPlayer(P_EXEC) + ) + ) + .then( + new LiteralArgument("second").executesPlayer(P_EXEC) + ) + .register(); + + new CommandTree("command") + .then( + new LiteralArgument("first").executesPlayer(P_EXEC) + ) + .then( + new LiteralArgument("second").then( + new IntegerArgument("argument").executesPlayer(P_EXEC) + ) + ) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("first", LiteralArgument.class, true).helpString("first").withChildren( + node("argument", StringArgument.class, true) + ), + node("second", LiteralArgument.class, true).helpString("second").withChildren( + node("argument", IntegerArgument.class, true) + ) + ), + List.of("command:CommandTree", "first:LiteralArgument"), + List.of("command:CommandTree", "first:LiteralArgument", "argument:StringArgument"), + List.of("command:CommandTree", "second:LiteralArgument"), + List.of("command:CommandTree", "second:LiteralArgument", "argument:IntegerArgument") + ); + } + + @Test + void testRegisterMergeNamespaces() { + new CommandTree("command") + .then(new LiteralArgument("first").executesPlayer(P_EXEC)) + .register("first"); + + new CommandTree("command") + .then(new LiteralArgument("second").executesPlayer(P_EXEC)) + .register("second"); + + RegisteredCommand first = simpleRegisteredCommand( + "command", "first", + commandNode("command", false).withChildren( + node("first", LiteralArgument.class, true).helpString("first") + ) + ); + + RegisteredCommand second = simpleRegisteredCommand( + "command", "second", + commandNode("command", false).withChildren( + node("second", LiteralArgument.class, true).helpString("second") + ) + ); + + RegisteredCommand merged = simpleRegisteredCommand( + "command", "", + commandNode("command", false).withChildren( + node("first", LiteralArgument.class, true).helpString("first"), + node("second", LiteralArgument.class, true).helpString("second") + ) + ); + + assertCreatedRegisteredCommands(merged, first, second); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java new file mode 100644 index 0000000000..a0a1bc5105 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java @@ -0,0 +1,164 @@ +package dev.jorel.commandapi.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import dev.jorel.commandapi.CommandAPI; +import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.RegisteredCommand.Node; + +public abstract class RegisteredCommandTestBase extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /******************* + * Utility methods * + *******************/ + + @SafeVarargs + public final void assertCreatedSimpleRegisteredCommand(String name, NodeBuilder args, List... argsAsStr) { + RegisteredCommand expectedCommand = simpleRegisteredCommand(name, "minecraft", args); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + + // `.get(0)` makes sense since we just verified `CommandAPI#getRegisteredCommands` has elements + assertEquals(Arrays.asList(argsAsStr), CommandAPI.getRegisteredCommands().get(0).rootNode().argsAsStr()); + } + + public RegisteredCommand simpleRegisteredCommand(String name, String namespace, NodeBuilder args, String... aliases) { + return new RegisteredCommand( + name, aliases, namespace, CommandPermission.NONE, + Optional.empty(), Optional.empty(), Optional.empty(), + args.build() + ); + } + + public static class NodeBuilder { + public static NodeBuilder node(String name, Class clazz, boolean executable) { + return new NodeBuilder(name, clazz, executable); + } + + public static List children(NodeBuilder... children) { + List result = new ArrayList<>(children.length); + for (NodeBuilder child : children) { + result.add(child.build()); + } + return result; + } + + private final String nodeName; + private final String className; + private final boolean executable; + + private String helpString; + private final List children; + + public NodeBuilder(String nodeName, Class clazz, boolean executable) { + this.nodeName = nodeName; + this.className = clazz.getSimpleName(); + this.executable = executable; + + this.helpString = "<" + nodeName + ">"; + this.children = new ArrayList<>(); + } + + public NodeBuilder helpString(String helpString) { + this.helpString = helpString; + return this; + } + + public NodeBuilder withChildren(NodeBuilder... children) { + for (NodeBuilder child : children) { + this.children.add(child.build()); + } + return this; + } + + public NodeBuilder withChildren(Node... children) { + return withChildren(Arrays.asList(children)); + } + + public NodeBuilder withChildren(List children) { + this.children.addAll(children); + return this; + } + + public Node build() { + return new Node(nodeName, className, helpString, executable, children); + } + } + + public void assertCreatedRegisteredCommands(RegisteredCommand... commands) { + List expectedCommands = Arrays.asList(commands); + List actualCommands = CommandAPI.getRegisteredCommands(); + + if (expectedCommands.size() != actualCommands.size()) { + StringBuilder builder = new StringBuilder(); + builder.append("Expected ").append(expectedCommands.size()).append(" command(s), found ").append(actualCommands.size()).append(" command(s)."); + + builder.append("\nExpected: "); + addRegisteredCommandList(builder, expectedCommands); + builder.append("\nActual: "); + addRegisteredCommandList(builder, actualCommands); + + fail(builder.toString()); + } + + for (int i = 0; i < expectedCommands.size(); i++) { + RegisteredCommand expectedCommand = expectedCommands.get(i); + RegisteredCommand actualCommand = actualCommands.get(i); + + if (!Objects.equals(expectedCommand, actualCommand)) { + StringBuilder builder = new StringBuilder(); + builder.append("Command #").append(i + 1).append(" differed. Expected:\n"); + builder.append(expectedCommand); + builder.append("\nActual:\n"); + builder.append(actualCommand); + + builder.append("\nExpected list: "); + addRegisteredCommandList(builder, expectedCommands); + builder.append("\nActual list: "); + addRegisteredCommandList(builder, actualCommands); + + fail(builder.toString()); + } + } + } + + private void addRegisteredCommandList(StringBuilder builder, List commands) { + if (commands.isEmpty()) { + builder.append("[]"); + return; + } + + builder.append("[\n"); + for (RegisteredCommand command : commands) { + builder.append("\t"); + builder.append(command); + builder.append("\n"); + } + builder.append("]"); + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index 2f7deee7c6..8da4c35299 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -219,7 +219,7 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(List registeredCommands, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { // Nothing to do } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index 4cc2bd8cfa..a49151f55e 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -20,14 +20,18 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.CommandNode; import com.velocitypowered.api.command.CommandSource; + import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; import java.util.List; +import java.util.function.Function; /** * A pseudo-argument representing a single literal string @@ -132,11 +136,6 @@ public CommandAPIArgumentType getArgumentType() { return CommandAPIArgumentType.LITERAL; } - @Override - public String getHelpString() { - return literal; - } - @Override public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { return literal; @@ -152,4 +151,9 @@ public String parseArgument(CommandContext cmdCtx, String key, public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { return Literal.super.createArgumentBuilder(previousArguments, previousArgumentNames); } + + @Override + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames,terminalExecutorCreator); + } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index 10ba0e5c25..cef66a147b 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -104,18 +104,13 @@ public String parseArgument(CommandContext cmdCtx, String key, // method by default. However, we want to use the implementations found in the MultiLiteral interface. // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void appendToCommandPaths(List> argumentStrings) { - MultiLiteral.super.appendToCommandPaths(argumentStrings); - } - @Override public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { return MultiLiteral.super.createArgumentBuilder(previousArguments, previousArgumentNames); } @Override - public List> linkNode(List> previousNodes, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return MultiLiteral.super.linkNode(previousNodes, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } From 6f8b66ce4d97485d1e56071db6551e34976cb7f7 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:46:09 -0400 Subject: [PATCH 23/42] Fix & update `Previewable` arguments - Removed `CommandAPIHandler#previewableArguments` and related methods - Added `PreviewableCommandNode` for storing `Previewable` information directly in Brigadier's tree - Tweak `NMS_1_19_Common_ChatPreviewHandler` to build previews from the node tree rather than by the node path TODO: Should probably test these changes on a real server to verify. Also, another example of https://github.com/Mojang/brigadier/pull/144 being annoying. --- .../jorel/commandapi/CommandAPIHandler.java | 75 ------------ .../arguments/AbstractArgument.java | 33 +++--- .../PreviewableArgumentBuilder.java | 80 +++++++++++++ .../commandnodes/PreviewableCommandNode.java | 108 ++++++++++++++++++ .../UnnamedArgumentCommandNode.java | 2 +- .../NMS_1_19_Common_ChatPreviewHandler.java | 39 ++++--- 6 files changed, 229 insertions(+), 108 deletions(-) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index 8bd4179e5f..ab939cabd1 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -49,7 +49,6 @@ import dev.jorel.commandapi.executors.CommandArguments; import dev.jorel.commandapi.executors.ExecutionInfo; import dev.jorel.commandapi.preprocessor.RequireField; -import dev.jorel.commandapi.wrappers.PreviewableFunction; import java.util.concurrent.CompletableFuture; @@ -93,7 +92,6 @@ public class CommandAPIHandler platform; final Map registeredCommands; // Keep track of what has been registered for type checking - final Map, Previewable> previewableArguments; // Arguments with previewable chat static final Pattern NAMESPACE_PATTERN = Pattern.compile("[0-9a-z_.-]+"); private static CommandAPIHandler instance; @@ -105,7 +103,6 @@ public class CommandAPIHandler platform) { this.platform = platform; this.registeredCommands = new LinkedHashMap<>(); // This should be a LinkedHashMap to preserve insertion order - this.previewableArguments = new HashMap<>(); CommandAPIHandler.instance = this; } @@ -629,78 +626,6 @@ public Object parseArgument(CommandContext cmdCtx, String key, Argument } } - //////////////////////////////////// - // SECTION: Previewable Arguments // - //////////////////////////////////// - - /** - * Handles a previewable argument. This stores the path to the previewable argument - * in {@link CommandAPIHandler#previewableArguments} for runtime resolving - * - * @param previousArguments The list of arguments that came before this argument - * @param previewableArgument The {@link Previewable} argument - */ - public void addPreviewableArgument(List previousArguments, Argument previewableArgument) { - if (!(previewableArgument instanceof Previewable previewable)) { - throw new IllegalArgumentException("An argument must implement Previewable to be added as previewable argument"); - } - - // Generate all paths to the argument - List> paths = new ArrayList<>(); - paths.add(new ArrayList<>()); - - // TODO: Fix this, the `appendToCommandPaths` method was removed - // A smarter way to get this information should exist - // It probably makes sense to make a custom CommandNode for PreviewableArgument - if(true) throw new IllegalStateException("TODO: Fix this method"); - - // for (Argument argument : previousArguments) { - // argument.appendToCommandPaths(paths); - // } - // previewableArgument.appendToCommandPaths(paths); - - // Insert paths to our map - for (List path : paths) { - previewableArguments.put(path, previewable); - } - } - - /** - * Looks up the function to generate a chat preview for a path of nodes in the - * command tree. This is a method internal to the CommandAPI and isn't expected - * to be used by plugin developers (but you're more than welcome to use it as - * you see fit). - * - * @param path a list of Strings representing the path (names of command nodes) - * to (and including) the previewable argument - * @return a {@link PreviewableFunction} that takes in a {@link PreviewInfo} and returns a - * text Component. If such a function is not available, this will - * return a function that always returns null. - */ - @SuppressWarnings("unchecked") - public Optional> lookupPreviewable(List path) { - final Previewable previewable = previewableArguments.get(path); - if (previewable != null) { - return (Optional>) (Optional) previewable.getPreview(); - } else { - return Optional.empty(); - } - } - - /** - * @param path a list of Strings representing the path (names of command nodes) - * to (and including) the previewable argument - * @return Whether a previewable is legacy (non-Adventure) or not - */ - public boolean lookupPreviewableLegacyStatus(List path) { - final Previewable previewable = previewableArguments.get(path); - if (previewable != null && previewable.getPreview().isPresent()) { - return previewable.isLegacy(); - } else { - return true; - } - } - ///////////////////////// // SECTION: Reflection // ///////////////////////// diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index c8b4e05d86..705d317c33 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -26,11 +26,13 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.AbstractArgumentTree; import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.commandnodes.PreviewableArgumentBuilder; import dev.jorel.commandapi.commandnodes.UnnamedRequiredArgumentBuilder; import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; import dev.jorel.commandapi.exceptions.GreedyArgumentException; @@ -371,16 +373,9 @@ public NodeInformation addArgumentNodes( List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { - CommandAPIHandler handler = CommandAPIHandler.getInstance(); - // Check preconditions checkPreconditions(previousNodeInformation, previousArguments, previousArgumentNames); - // Handle previewable argument - if (this instanceof Previewable) { - handler.addPreviewableArgument(previousArguments, (Argument) this); - } - // Create node ArgumentBuilder rootBuilder = createArgumentBuilder(previousArguments, previousArgumentNames); @@ -426,19 +421,29 @@ public void checkPreconditions( CommandAPIHandler handler = CommandAPIHandler.getInstance(); // Create node and add suggestions - // Note: I would like to combine these two `build.suggests(...)` calls, but they are actually two unrelated - // methods since UnnamedRequiredArgumentBuilder does not extend RequiredArgumentBuilder (see - // UnnamedRequiredArgumentBuilder for why). If UnnamedRequiredArgumentBuilder *does* extend - // RequiredArgumentBuilder, please simplify this if statement, like what Literal#createArgumentBuilder does. + // Note: I would like to combine these `builder.suggests(...)` calls, but they are actually unrelated + // methods since UnnamedRequiredArgumentBuilder and PreviewableArgumentBuilder do not extend RequiredArgumentBuilder + // (see those classes for why). If this has been fixed and they do extend RequiredArgumentBuilder, please simplify + // this if statement, like what Literal#createArgumentBuilder does. + SuggestionProvider suggestions = handler.generateBrigadierSuggestions(previousArguments, (Argument) this); ArgumentBuilder rootBuilder; - if(isListed) { + if (this instanceof Previewable previewable) { + // Handle previewable argument + PreviewableArgumentBuilder builder = PreviewableArgumentBuilder.previewableArgument( + nodeName, rawType, + previewable.getPreview().orElse(null), previewable.isLegacy(), isListed + ); + builder.suggests(suggestions); + + rootBuilder = builder; + } else if (isListed) { RequiredArgumentBuilder builder = RequiredArgumentBuilder.argument(nodeName, rawType); - builder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this)); + builder.suggests(suggestions); rootBuilder = builder; } else { UnnamedRequiredArgumentBuilder builder = UnnamedRequiredArgumentBuilder.unnamedArgument(nodeName, rawType); - builder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this)); + builder.suggests(suggestions); rootBuilder = builder; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java new file mode 100644 index 0000000000..ad6d11f950 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java @@ -0,0 +1,80 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.CommandNode; + +import dev.jorel.commandapi.arguments.Previewable; +import dev.jorel.commandapi.wrappers.PreviewableFunction; + +/** + * A special type of {@link RequiredArgumentBuilder} for {@link Previewable} Arguments. Compared to the + * {@link RequiredArgumentBuilder}, this class builds a {@link PreviewableCommandNode} + * + * @param The Brigadier Source object for running commands. + * @param The type returned when this argument is parsed. + */ +// We can't actually extend RequiredArgumentBuilder since its only constructor is private :( +// See https://github.com/Mojang/brigadier/pull/144 +public class PreviewableArgumentBuilder extends ArgumentBuilder> { + // Everything here is copied from RequiredArgumentBuilder, which is why it would be nice to extend that directly + private final String name; + private final ArgumentType type; + private SuggestionProvider suggestionsProvider = null; + + // `Previewable` information + private final PreviewableFunction previewableFunction; + private final boolean legacy; + private final boolean isListed; + + private PreviewableArgumentBuilder(String name, ArgumentType type, PreviewableFunction previewableFunction, boolean legacy, boolean isListed) { + this.name = name; + this.type = type; + + this.previewableFunction = previewableFunction; + this.legacy = legacy; + this.isListed = isListed; + } + + public static PreviewableArgumentBuilder previewableArgument(String name, ArgumentType type, PreviewableFunction previewableFunction, boolean legacy, boolean isListed) { + return new PreviewableArgumentBuilder<>(name, type, previewableFunction, legacy, isListed); + } + + public PreviewableArgumentBuilder suggests(final SuggestionProvider provider) { + this.suggestionsProvider = provider; + return getThis(); + } + + public SuggestionProvider getSuggestionsProvider() { + return suggestionsProvider; + } + + @Override + protected PreviewableArgumentBuilder getThis() { + return this; + } + + public ArgumentType getType() { + return type; + } + + public String getName() { + return name; + } + + public PreviewableCommandNode build() { + final PreviewableCommandNode result = new PreviewableCommandNode( + previewableFunction, legacy, isListed, + getName(), getType(), + getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider() + ); + + for (final CommandNode argument : getArguments()) { + result.addChild(argument); + } + + return result; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java new file mode 100644 index 0000000000..4093c54a4f --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java @@ -0,0 +1,108 @@ +package dev.jorel.commandapi.commandnodes; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.RedirectModifier; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedArgument; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; + +import dev.jorel.commandapi.arguments.Previewable; +import dev.jorel.commandapi.wrappers.PreviewableFunction; + +/** + * A special type of {@link ArgumentCommandNode} for {@link Previewable} arguments. Compared to the + * {@link ArgumentCommandNode}, this class also has the methods {@link #getPreview()} and {@link #isLegacy()}, + * which are used when players try to use the chat preview feature. + * + * @param The Brigadier Source object for running commands. + * @param The type returned when this argument is parsed. + */ +public class PreviewableCommandNode extends ArgumentCommandNode { + private final PreviewableFunction preview; + private final boolean legacy; + + // Instead of having a listed and unlisted copy of this class, we can just handle this with this boolean + private final boolean isListed; + + public PreviewableCommandNode( + PreviewableFunction preview, boolean legacy, boolean isListed, + String name, ArgumentType type, + Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions + ) { + super(name, type, command, requirement, redirect, modifier, forks, customSuggestions); + this.preview = preview; + this.legacy = legacy; + this.isListed = isListed; + } + + // Methods needed to generate a preview + public Optional> getPreview() { + return Optional.ofNullable(preview); + } + + public boolean isLegacy() { + return legacy; + } + + // If we are unlisted, then when parsed, don't add the argument result to the CommandContext + public boolean isListed() { + return isListed; + } + + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + // Copied from `super#parse`, but with listability added + int start = reader.getCursor(); + + T result = this.getType().parse(reader); + ParsedArgument parsed = new ParsedArgument<>(start, reader.getCursor(), result); + + if(isListed) contextBuilder.withArgument(this.getName(), parsed); + + contextBuilder.withNode(this, parsed.getRange()); + } + + // Typical ArgumentCommandNode methods, but make it our classes + // Mostly copied and inspired by the implementations for these methods in ArgumentCommandNode + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof PreviewableCommandNode other)) return false; + + if (!Objects.equals(this.preview, other.preview)) return false; + if (this.legacy != other.legacy) return false; + if (this.isListed != other.isListed) return false; + return super.equals(other); + } + + @Override + public int hashCode() { + int result = Objects.hash(this.preview, this.legacy, this.isListed); + result = 31*result + super.hashCode(); + return result; + } + + // TODO: Um, this currently doesn't work since PreviewableArgumentBuilder does not extend RequiredArgumentBuilder + // See PreviewableArgumentBuilder for why + // I hope no one tries to use this method! + // @Override + // public PreviewableArgumentBuilder createBuilder() { + // PreviewableArgumentBuilder builder = PreviewableArgumentBuilder.previewableArgument(getName(), getType(), preview, legacy, isListed); + + // builder.requires(getRequirement()); + // builder.forward(getRedirect(), getRedirectModifier(), isFork()); + // if (getCommand() != null) { + // builder.executes(getCommand()); + // } + // return builder; + // } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java index ae2cf6105b..c8a5ecac82 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java @@ -26,7 +26,7 @@ public UnnamedArgumentCommandNode(String name, ArgumentType type, Command contextBuilder) throws CommandSyntaxException { final int start = reader.getCursor(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java index ec7402c1af..ebbd69b31f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java @@ -3,10 +3,9 @@ import com.mojang.brigadier.ParseResults; import com.mojang.brigadier.context.ParsedCommandNode; import com.mojang.brigadier.exceptions.CommandSyntaxException; - -import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandAPIBukkit; import dev.jorel.commandapi.arguments.PreviewInfo; +import dev.jorel.commandapi.commandnodes.PreviewableCommandNode; import dev.jorel.commandapi.commandsenders.BukkitPlayer; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import dev.jorel.commandapi.wrappers.PreviewableFunction; @@ -26,7 +25,6 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -51,7 +49,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception // Is command if (!chatPreview.query().isEmpty() && chatPreview.query().charAt(0) == '/') { // Is previewable argument - if(InitialParse.processChatPreviewQuery(chatPreview.query(), platform, player).preview.isPresent()){ + if(InitialParse.processChatPreviewQuery(chatPreview.query(), platform, player).previewableNode.getPreview().isPresent()){ handleChatPreviewPacket(chatPreview); return; } @@ -65,28 +63,32 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception protected abstract void handleChatPreviewPacket(ServerboundChatPreviewPacket chatPreview); public MutableComponent parseChatPreviewQuery(String chatPreviewQuery) { + // Parse the string final InitialParse ip = InitialParse.processChatPreviewQuery(chatPreviewQuery, platform, player); - final Optional> preview = ip.preview; + + // Return early if the node is not previewable + final PreviewableCommandNode previewableNode = ip.previewableNode; + final Optional> preview = previewableNode.getPreview(); if (preview.isEmpty()) { return null; } final String fullInput = ip.fullInput; final ParseResults results = ip.results; - final List path = ip.path; + final ParsedCommandNode parsedNode = ip.parsedNode; // Calculate the (argument) input and generate the component to send - String input = results.getContext().getNodes().get(results.getContext().getNodes().size() - 1).getRange().get(fullInput); + String input = parsedNode.getRange().get(fullInput); final String jsonToSend; Object component; try { @SuppressWarnings("rawtypes") final PreviewInfo previewInfo; - if (CommandAPIHandler.getInstance().lookupPreviewableLegacyStatus(path)) { + if (previewableNode.isLegacy()) { BaseComponent[] parsedInput; try { - parsedInput = platform.getChat(results.getContext().build(fullInput), path.get(path.size() - 1)); + parsedInput = platform.getChat(results.getContext().build(fullInput), previewableNode.getName()); } catch (CommandSyntaxException e) { throw new WrapperCommandSyntaxException(e); } @@ -94,7 +96,7 @@ public MutableComponent parseChatPreviewQuery(String chatPreviewQuery) { } else { Component parsedInput; try { - parsedInput = platform.getAdventureChat(results.getContext().build(fullInput), path.get(path.size() - 1)); + parsedInput = platform.getAdventureChat(results.getContext().build(fullInput), previewableNode.getName()); } catch (CommandSyntaxException e) { throw new WrapperCommandSyntaxException(e); } @@ -125,7 +127,7 @@ public MutableComponent parseChatPreviewQuery(String chatPreviewQuery) { return Serializer.fromJson(jsonToSend); } - private record InitialParse(String fullInput, ParseResults results, List path, Optional> preview){ + private record InitialParse(String fullInput, ParseResults results, ParsedCommandNode parsedNode, PreviewableCommandNode previewableNode){ private static InitialParse cachedResult = null; public static InitialParse processChatPreviewQuery(String chatPreviewQuery, CommandAPIBukkit platform, Player player){ // Substring 1 to get rid of the leading / @@ -136,14 +138,15 @@ public static InitialParse processChatPreviewQuery(String chatPreviewQuery, Comm ParseResults results = platform.getBrigadierDispatcher() .parse(fullInput, platform.getBrigadierSourceFromCommandSender(new BukkitPlayer(player))); - // Generate the path for lookup - List path = new ArrayList<>(); - for (ParsedCommandNode commandNode : results.getContext().getNodes()) { - path.add(commandNode.getNode().getName()); - } - Optional> preview = CommandAPIHandler.getInstance().lookupPreviewable(path); + // Get the last node + List> nodes = results.getContext().getNodes(); + ParsedCommandNode parsedNode = nodes.get(nodes.size()-1); + + // Get the parsed node, if it exists + PreviewableCommandNode previewableNode = parsedNode.getNode() instanceof PreviewableCommandNode pn ? pn : null; - cachedResult = new InitialParse(fullInput, results, path, preview); + // Cache the result and return + cachedResult = new InitialParse(fullInput, results, parsedNode, previewableNode); return cachedResult; } } From 8d7a315925928ed12378efb2d9a6536df6ecda01 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 13 May 2024 11:46:54 -0400 Subject: [PATCH 24/42] Help topic rewrite Revises github.com/JorelAli/CommandAPI/commit/ac8c06086f2e05015416b939028e1301ff95a87b Notable changes: - `RegisteredCommand` and `RegisteredCommand.Node` now have generic `` parameter - `RegisteredCommand.Node` includes the `CommandPermission permission` and `Predicate requirements`, copied from the argument/command the node represents - `RegisteredCommand#permission` is now acessibile as the permission of the `Node rootNode` - Created `CommandAPIHelpTopic` in `dev.jorel.commandapi.help` package - Replaced `shortDescription`, `fullDescription`, `usageDescription`, and `Object helpTopic` fields in `ExecutableCommand` and `RegisteredCommand` with `CommandAPIHelpTopic helpTopic` - Extended by `EditableHelpTopic` - Help builder methods of `ExecutableCommand` are delegated to its `CommandAPIHelpTopic` if it is editable - More general API created for https://github.com/JorelAli/CommandAPI/issues/528: short description, full description, and usage can be provided separately to take advantage of the formatting the CommandAPI uses for static Strings - Extended by `BukkitHelpTopicWrapper` in `commandapi-bukkit-core` - Wraps Bukkit's `HelpTopic` as a `CommandAPIHelpTopic` so full `HelpTopic` customization can still be used - Created `CustomCommandAPIHelpTopic` in `commandapi-bukkit` - Converts a `CommandAPIHelpTopic` into a Bukkit `HelpTopic` for adding to the help map - Replaces usage of `NMS#generateHelpTopic` - Help formatting code extracted from `CommandAPIBukkit#updateHelpForCommands` - Resolves https://github.com/JorelAli/CommandAPI/issues/470: `CommandSender` permissions and requirements are now checked when generating usage - Changed the treatement of namespaced help topics (https://github.com/JorelAli/CommandAPI/pull/546). Namespaced help topics are now created. In the case where the same command name is registered with different namespaces, this allows the user to see the unmerged help using `/help namespace:commandName` (see `CommandHelpTests#testRegisterMergeNamespaces`). - Updated tests to fully cover changes TODO: Update and write documentation --- .../commandapi/AbstractArgumentTree.java | 6 +- .../commandapi/AbstractCommandAPICommand.java | 15 +- .../jorel/commandapi/AbstractCommandTree.java | 6 +- .../java/dev/jorel/commandapi/CommandAPI.java | 5 +- .../jorel/commandapi/CommandAPIHandler.java | 9 +- .../jorel/commandapi/CommandAPIPlatform.java | 2 +- .../jorel/commandapi/CommandPermission.java | 9 +- .../jorel/commandapi/ExecutableCommand.java | 143 ++-- .../jorel/commandapi/RegisteredCommand.java | 142 ++-- .../arguments/AbstractArgument.java | 27 +- .../arguments/FlagsArgumentCommon.java | 27 +- .../jorel/commandapi/arguments/Literal.java | 21 +- .../commandapi/arguments/MultiLiteral.java | 21 +- .../commandapi/help/CommandAPIHelpTopic.java | 11 + .../commandapi/help/EditableHelpTopic.java | 193 +++++ .../help/FullDescriptionGenerator.java | 24 + .../help/ShortDescriptionGenerator.java | 15 + .../jorel/commandapi/help/UsageGenerator.java | 29 + .../jorel/commandapi/CommandAPIBukkit.java | 169 +---- .../jorel/commandapi/CommandAPICommand.java | 11 +- .../CommandRegistrationStrategy.java | 3 +- .../dev/jorel/commandapi/CommandTree.java | 19 +- .../commandapi/PaperCommandRegistration.java | 3 +- .../commandapi/SpigotCommandRegistration.java | 7 +- .../commandapi/arguments/CustomArgument.java | 4 +- .../commandapi/arguments/FlagsArgument.java | 2 +- .../commandapi/arguments/LiteralArgument.java | 2 +- .../arguments/MultiLiteralArgument.java | 2 +- .../help/BukkitHelpTopicWrapper.java | 41 + .../help/CustomCommandAPIHelpTopic.java | 136 ++++ .../java/dev/jorel/commandapi/nms/NMS.java | 2 - .../dev/jorel/commandapi/nms/NMS_1_16_R3.java | 6 - .../jorel/commandapi/nms/NMS_1_17_Common.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_18_R2.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_18_R1.java | 6 - .../jorel/commandapi/nms/NMS_1_19_Common.java | 6 - .../jorel/commandapi/nms/NMS_1_19_3_R2.java | 6 - .../jorel/commandapi/nms/NMS_1_19_4_R3.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_20_R2.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_20_R3.java | 7 - .../dev/jorel/commandapi/nms/NMS_1_20_R4.java | 7 - .../dev/jorel/commandapi/nms/NMS_1_20_R1.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_21_R1.java | 7 - .../dev/jorel/commandapi/nms/NMS_Common.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - ...mmandAPICommandRegisteredCommandTests.java | 119 ++- .../commandapi/test/CommandHelpTests.java | 703 ++++++++++++++++-- .../test/CommandHelpTestsPlugin.java | 35 + .../CommandTreeRegisteredCommandTests.java | 118 ++- .../jorel/commandapi/test/OnEnableTests.java | 2 +- .../test/RegisteredCommandTestBase.java | 57 +- .../jorel/commandapi/CommandAPIVelocity.java | 2 +- .../commandapi/arguments/LiteralArgument.java | 2 +- .../arguments/MultiLiteralArgument.java | 2 +- 62 files changed, 1677 insertions(+), 594 deletions(-) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index acaa7bb0af..a510841a98 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -99,7 +99,7 @@ public void setArguments(List> * @param The Brigadier Source object for running commands. */ public void buildBrigadierNode( - NodeInformation previousNodeInformation, + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames ) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); @@ -115,12 +115,12 @@ public void buildBrigadierNode( executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null); // Collect children into our own list - List childrenNodeInformation = new ArrayList<>(); + List> childrenNodeInformation = new ArrayList<>(); // Add our branches as children to the node for (AbstractArgumentTree child : arguments) { // Collect children into our own list - NodeInformation newPreviousNodeInformation = new NodeInformation<>( + NodeInformation newPreviousNodeInformation = new NodeInformation<>( previousNodeInformation.lastCommandNodes(), children -> childrenNodeInformation.addAll(children) ); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 3fede09760..81db8a8bbe 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -238,14 +238,14 @@ protected boolean isRootExecutable() { } @Override - protected List createArgumentNodes(LiteralCommandNode rootNode) { + protected List> createArgumentNodes(LiteralCommandNode rootNode) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); - List childrenNodes = new ArrayList<>(); + List> childrenNodes = new ArrayList<>(); // Create arguments if (hasAnyArguments()) { - NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); @@ -279,12 +279,12 @@ protected List createArgumentNodes(LiteralComma // Add subcommands for (Impl subCommand : subcommands) { - CommandInformation nodes = subCommand.createCommandInformation(""); + CommandInformation nodes = subCommand.createCommandInformation(""); // Add root node rootNode.addChild(nodes.rootNode()); - RegisteredCommand.Node rootNodeInformation = nodes.command().rootNode(); + RegisteredCommand.Node rootNodeInformation = nodes.command().rootNode(); childrenNodes.add(rootNodeInformation); // Add aliases @@ -294,9 +294,10 @@ protected List createArgumentNodes(LiteralComma // Create node information for the alias String aliasName = aliasNode.getLiteral(); childrenNodes.add( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( aliasName, rootNodeInformation.className(), aliasName, - rootNodeInformation.executable(), rootNodeInformation.children() + rootNodeInformation.executable(), rootNodeInformation.permission(), rootNodeInformation.requirements(), + rootNodeInformation.children() ) ); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index c2520e39c1..4ba7fb0a09 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -87,10 +87,10 @@ protected boolean isRootExecutable() { } @Override - protected List createArgumentNodes(LiteralCommandNode rootNode) { + protected List> createArgumentNodes(LiteralCommandNode rootNode) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); - List childrenNodes = new ArrayList<>(); + List> childrenNodes = new ArrayList<>(); // The previous arguments include an unlisted MultiLiteral representing the command name and aliases // This doesn't affect how the command acts, but it helps represent the command path in exceptions @@ -103,7 +103,7 @@ protected List createArgumentNodes(LiteralComma // Build branches for (AbstractArgumentTree argument : arguments) { // We need new previousArguments lists for each branch so they don't interfere - NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java index 83716f36a2..2eccd9508e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java @@ -307,7 +307,8 @@ public static void registerCommand(Class commandClass) { * @return A list of all {@link RegisteredCommand}{@code s} that have been * registered by the CommandAPI so far. The returned list is immutable. */ - public static List getRegisteredCommands() { - return Collections.unmodifiableList(new ArrayList<>(CommandAPIHandler.getInstance().registeredCommands.values())); + public static List> getRegisteredCommands() { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + return Collections.unmodifiableList(new ArrayList<>(handler.registeredCommands.values())); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index ab939cabd1..5f8ab70478 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -91,7 +91,7 @@ public class CommandAPIHandler platform; - final Map registeredCommands; // Keep track of what has been registered for type checking + final Map> registeredCommands; // Keep track of what has been registered for type checking static final Pattern NAMESPACE_PATTERN = Pattern.compile("[0-9a-z_.-]+"); private static CommandAPIHandler instance; @@ -171,11 +171,11 @@ public void registerCommand(ExecutableCommand command, String platform.preCommandRegistration(command.getName()); // Generate command information - ExecutableCommand.CommandInformation commandInformation = command.createCommandInformation(namespace); + ExecutableCommand.CommandInformation commandInformation = command.createCommandInformation(namespace); LiteralCommandNode resultantNode = commandInformation.rootNode(); List> aliasNodes = commandInformation.aliasNodes(); - RegisteredCommand registeredCommand = commandInformation.command(); + RegisteredCommand registeredCommand = commandInformation.command(); // Log the commands being registered for (List argsAsStr : registeredCommand.rootNode().argsAsStr()) { @@ -209,7 +209,8 @@ public void registerCommand(ExecutableCommand command, String writeDispatcherToFile(); // Merge RegisteredCommand into map - BiFunction mergeRegisteredCommands = (key, value) -> value == null ? registeredCommand : value.mergeCommandInformation(registeredCommand); + BiFunction, RegisteredCommand> mergeRegisteredCommands = + (key, value) -> value == null ? registeredCommand : value.mergeCommandInformation(registeredCommand); if (registeredCommand.namespace().isEmpty()) { registeredCommands.compute(registeredCommand.commandName(), mergeRegisteredCommands); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index e7134e3cf5..967c05fc07 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -108,7 +108,7 @@ public interface CommandAPIPlatform resultantNode, List> aliasNodes); + public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); /** * Builds and registers a Brigadier command node. diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java index adcdcb884c..962da1d689 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java @@ -147,9 +147,14 @@ PermissionNode getPermissionNode() { return this.permissionNode; } - CommandPermission negate() { + /** + * Sets this permission to be negated. + * You can also achieve this using {@link ExecutableCommand#withoutPermission(CommandPermission)}. + * + * @return This permission object + */ + public CommandPermission negate() { this.negated = true; return this; } - } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 34a62566af..04cf16c906 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.function.Predicate; import com.mojang.brigadier.builder.LiteralArgumentBuilder; @@ -10,6 +9,11 @@ import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; +import dev.jorel.commandapi.help.EditableHelpTopic; +import dev.jorel.commandapi.help.FullDescriptionGenerator; +import dev.jorel.commandapi.help.ShortDescriptionGenerator; +import dev.jorel.commandapi.help.UsageGenerator; /** * This is a base class for {@link AbstractCommandAPICommand} and {@link AbstractCommandTree} command definitions @@ -43,24 +47,7 @@ public abstract class ExecutableCommand requirements = s -> true; // Command help - /** - * An optional short description for the command - */ - protected String shortDescription = null; - /** - * An optional full description for the command - */ - protected String fullDescription = null; - /** - * An optional usage description for the command - */ - protected String[] usageDescription = null; - // TODO: Bukkit specific fields probably should not be in platform agnostic classes - // Either make HelpTopic platform agnostic or move this field into bukkit-core - /** - * An optional HelpTopic object for the command (for Bukkit) - */ - protected Object helpTopic = null; + protected CommandAPIHelpTopic helpTopic = new EditableHelpTopic<>(); ExecutableCommand(final String name) { if (name == null || name.isEmpty() || name.contains(" ")) { @@ -142,6 +129,26 @@ public Impl withRequirement(Predicate requirement) { return instance(); } + /** + * Sets the {@link CommandAPIHelpTopic} for this command. Using this method will override + * any declared short description, full description or usage description provided + * via the following methods and similar overloads: + *

      + *
    • {@link ExecutableCommand#withShortDescription(String)}
    • + *
    • {@link ExecutableCommand#withFullDescription(String)}
    • + *
    • {@link ExecutableCommand#withUsage(String...)}
    • + *
    • {@link ExecutableCommand#withHelp(String, String)}
    • + *
    + * Further calls to these methods will also be ignored. + * + * @param helpTopic the help topic to use for this command + * @return this command builder + */ + public Impl withHelp(CommandAPIHelpTopic helpTopic) { + this.helpTopic = helpTopic; + return instance(); + } + /** * Sets the short description for this command. This is the help which is * shown in the main /help menu. @@ -150,7 +157,9 @@ public Impl withRequirement(Predicate requirement) { * @return this command builder */ public Impl withShortDescription(String description) { - this.shortDescription = description; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withShortDescription(description); + } return instance(); } @@ -162,7 +171,9 @@ public Impl withShortDescription(String description) { * @return this command builder */ public Impl withFullDescription(String description) { - this.fullDescription = description; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withFullDescription(description); + } return instance(); } @@ -176,8 +187,9 @@ public Impl withFullDescription(String description) { * @return this command builder */ public Impl withHelp(String shortDescription, String fullDescription) { - this.shortDescription = shortDescription; - this.fullDescription = fullDescription; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withHelp(shortDescription, fullDescription); + } return instance(); } @@ -189,7 +201,51 @@ public Impl withHelp(String shortDescription, String fullDescription) { * @return this command builder */ public Impl withUsage(String... usage) { - this.usageDescription = usage; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withUsage(usage); + } + return instance(); + } + + /** + * Sets the short description of this command to be generated using the given {@link ShortDescriptionGenerator}. + * This is the help which is shown in the main /help menu. + * + * @param description The {@link ShortDescriptionGenerator} to use to generate the short description. + * @return this command builder + */ + public Impl withShortDescription(ShortDescriptionGenerator description) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withShortDescription(description); + } + return instance(); + } + + /** + * Sets the full description of this command to be generated using the given {@link FullDescriptionGenerator}. + * This is the help which is shown in the specific /help page for this command (e.g. /help mycommand). + * + * @param description The {@link FullDescriptionGenerator} to use to generate the full description. + * @return this command builder + */ + public Impl withFullDescription(FullDescriptionGenerator description) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withFullDescription(description); + } + return instance(); + } + + /** + * Sets the usage of this command to be generated using the given {@link UsageGenerator}. + * This is the usage which is shown in the specific /help page for this command (e.g. /help mycommand). + * + * @param usage The {@link UsageGenerator} to use to generate the usage. + * @return this command builder + */ + public Impl withUsage(UsageGenerator usage) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withUsage(usage); + } return instance(); } @@ -260,13 +316,22 @@ public void setRequirements(Predicate requirements) { this.requirements = requirements; } + /** + * Returns the {@link CommandAPIHelpTopic} for this command + * + * @return the {@link CommandAPIHelpTopic} for this command + */ + public CommandAPIHelpTopic getHelpTopic() { + return helpTopic; + } + /** * Returns the short description for this command * * @return the short description for this command */ public String getShortDescription() { - return this.shortDescription; + return this.helpTopic.getShortDescription().orElse(null); } /** @@ -275,7 +340,7 @@ public String getShortDescription() { * @return the full description for this command */ public String getFullDescription() { - return this.fullDescription; + return this.helpTopic.getFullDescription(null).orElse(null); } /** @@ -284,16 +349,7 @@ public String getFullDescription() { * @return the usage for this command */ public String[] getUsage() { - return this.usageDescription; - } - - /** - * Returns the {@code HelpTopic} object assigned to this command (For Bukkit) - * - * @return the {@code HelpTopic} object assigned to this command (For Bukkit) - */ - public Object getHelpTopic() { - return helpTopic; + return this.helpTopic.getUsage(null, null).orElse(null); } ////////////////// @@ -326,25 +382,24 @@ public void register(String namespace) { ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); } - protected static record CommandInformation(LiteralCommandNode rootNode, List> aliasNodes, RegisteredCommand command) { + protected static record CommandInformation(LiteralCommandNode rootNode, List> aliasNodes, RegisteredCommand command) { } - protected CommandInformation createCommandInformation(String namespace) { + protected CommandInformation createCommandInformation(String namespace) { checkPreconditions(); // Create rootNode LiteralCommandNode rootNode = this.createCommandNodeBuilder(name).build(); - List children = createArgumentNodes(rootNode); + List> children = createArgumentNodes(rootNode); // Create aliaseNodes List> aliasNodes = createAliasNodes(rootNode); // Create command information - RegisteredCommand command = new RegisteredCommand( - name, aliases, namespace, permission, - Optional.ofNullable(shortDescription), Optional.ofNullable(fullDescription), Optional.ofNullable(usageDescription), Optional.ofNullable(helpTopic), - new RegisteredCommand.Node(name, getClass().getSimpleName(), name, isRootExecutable(), children) + RegisteredCommand command = new RegisteredCommand<>( + name, aliases, namespace, helpTopic, + new RegisteredCommand.Node<>(name, getClass().getSimpleName(), name, isRootExecutable(), permission, requirements, children) ); return new CommandInformation<>(rootNode, aliasNodes, command); @@ -387,5 +442,5 @@ protected List> createAliasNodes(LiteralComm protected abstract boolean isRootExecutable(); - protected abstract List createArgumentNodes(LiteralCommandNode rootNode); + protected abstract List> createArgumentNodes(LiteralCommandNode rootNode); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java index 0243d52599..b130d295b2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java @@ -6,22 +6,22 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; +import java.util.function.Predicate; + +import dev.jorel.commandapi.help.CommandAPIHelpTopic; /** * Class to store a registered command which has its command name and a tree representing its arguments. * This class also contains the information required to construct a meaningful help topic for a command. * - * @param commandName The name of this command, without any leading {@code /} characters - * @param aliases A {@link String}{@code []} of aliases for this command - * @param namespace The namespace for this command - * @param permission The {@link CommandPermission} required to run this command - * @param shortDescription An {@link Optional} containing this command's help's short description - * @param fullDescription An {@link Optional} containing this command's help's full description - * @param fullDescription An {@link Optional} containing this command's help's usage - * @param rootNode The root {@link Node} in the tree structure that holds the arguments of this command + * @param commandName The name of this command, without any leading {@code /} characters + * @param aliases A {@link String}{@code []} of aliases for this command + * @param namespace The namespace for this command + * @param helpTopic The {@link CommandAPIHelpTopic} that stores the help information for this command + * @param rootNode The root {@link Node} in the tree structure that holds the arguments of this command + * @param The class for running platform commands */ -public record RegisteredCommand( +public record RegisteredCommand( /** * @return The name of this command, without any leading {@code /} characters @@ -39,47 +39,28 @@ public record RegisteredCommand( String namespace, /** - * @return The {@link CommandPermission} required to run this command - */ - CommandPermission permission, - - /** - * @return An {@link Optional} containing this command's help's short description - */ - Optional shortDescription, - - /** - * @return An {@link Optional} containing this command's help's full description - */ - Optional fullDescription, - - /** - * @return An {@link Optional} containing this command's help's usage - */ - Optional usageDescription, - - // TODO: Bukkit specific fields probably should not be in platform agnostic classes - // Either make HelpTopic platform agnostic or move this field into bukkit-core - /** - * @return An {@link Optional} containing this command's help topic (for Bukkit) + * @return The {@link CommandAPIHelpTopic} that stores the help information for this command */ - Optional helpTopic, + CommandAPIHelpTopic helpTopic, /** * @return The root {@link Node} in the tree structure that holds the arguments of this command */ - Node rootNode) { + Node rootNode) { /** * Class to store information about each argument in a command's tree. * - * @param nodeName The name of this argument node - * @param className The name of the CommandAPI object that this node represents - * @param helpString The string that should be used to represent this node when automatically generating help usage. - * @param executable True if this node can be executed, and false otherwise - * @param children A {@link List} of nodes that are children to this node + * @param nodeName The name of this argument node + * @param className The name of the CommandAPI object that this node represents + * @param helpString The string that should be used to represent this node when automatically generating help usage. + * @param permission The {@link CommandPermission} object that determines if someone can see this node + * @param requirements An arbitrary additional check to perform to determine if someone can see this node + * @param executable True if this node can be executed, and false otherwise + * @param children A {@link List} of nodes that are children to this node + * @param The class for running platform commands */ - public static record Node( + public record Node( /** * @return The name of this argument node @@ -92,19 +73,29 @@ public static record Node( String className, /** - * @return The string that should be used to represent this node when automatically generating help usage. + * @return The string that should be used to represent this node when automatically generating help usage */ String helpString, /** * @return True if this node can be executed, and false otherwise */ - boolean executable, + boolean executable, + + /** + * @return The {@link CommandPermission} object that determines if someone can see this node + */ + CommandPermission permission, + + /** + * @return An arbitrary additional check to perform to determine if someone can see this node + */ + Predicate requirements, /** * @return A {@link List} of nodes that are children to this node */ - List children) { + List> children) { /** * @return A {@link List} of each executable path starting at this node. Each path is represented as a {@link List} of @@ -121,7 +112,7 @@ public List> argsAsStr() { } // Add children nodes - for (Node node : children) { + for (Node node : children) { List> subPaths = node.argsAsStr(); for (List subPath : subPaths) { @@ -134,22 +125,44 @@ public List> argsAsStr() { return paths; } - private Node merge(Node other) { + private Node merge(Node other) { // Merge executable status boolean mergeExecutable = this.executable || other.executable; // Merge children - Map childrenByName = new HashMap<>(); - for (Node child : this.children) { + Map> childrenByName = new HashMap<>(); + for (Node child : this.children) { childrenByName.put(child.nodeName, child); } - for (Node child : other.children) { + for (Node child : other.children) { childrenByName.compute(child.nodeName, (key, value) -> value == null ? child : value.merge(child)); } - List mergeChildren = new ArrayList<>(childrenByName.values()); + List> mergeChildren = new ArrayList<>(childrenByName.values()); // Other information defaults to the node that was registered first (this) - return new Node(nodeName, className, helpString, mergeExecutable, mergeChildren); + return new Node<>(nodeName, className, helpString, mergeExecutable, permission, requirements, mergeChildren); + } + + // There is no good way to check equality between Predicates, it's just a strict `==` + // However, record's impelementation of `.equals` tries to include `Predicate requirement` anyway + // So, overide equals to ignore that (and update hashCode so it matches) + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Node)) { + return false; + } + Node other = (Node) obj; + return Objects.equals(nodeName, other.nodeName) && Objects.equals(className, other.className) && Objects.equals(helpString, other.helpString) + && Objects.equals(executable, other.executable) && Objects.equals(permission, other.permission) + && Objects.equals(children, other.children); + } + + @Override + public final int hashCode() { + return Objects.hash(nodeName, className, helpString, executable, permission, children); } } @@ -162,37 +175,35 @@ private Node merge(Node other) { * @param other The {@link RegisteredCommand} that was registered after this one. * @return The {@link RegisteredCommand} that results from merging this and the other command. */ - public RegisteredCommand mergeCommandInformation(RegisteredCommand other) { + public RegisteredCommand mergeCommandInformation(RegisteredCommand other) { // Merge aliases String[] mergedAliases = new String[this.aliases.length + other.aliases.length]; System.arraycopy(this.aliases, 0, mergedAliases, 0, this.aliases.length); System.arraycopy(other.aliases, 0, mergedAliases, this.aliases.length, other.aliases.length); // Merge arguments - Node mergedRootNode = this.rootNode.merge(other.rootNode); + Node mergedRootNode = this.rootNode.merge(other.rootNode); // Other information defaults to the command that was registered first (this) - return new RegisteredCommand(commandName, mergedAliases, namespace, permission, shortDescription, fullDescription, usageDescription, helpTopic, mergedRootNode); + return new RegisteredCommand<>(commandName, mergedAliases, namespace, helpTopic, mergedRootNode); } /** * @return A copy of this {@link RegisteredCommand}, but with {@link RegisteredCommand#namespace()} as {@code ""}. */ - public RegisteredCommand copyWithEmptyNamespace() { - return new RegisteredCommand(commandName, aliases, "", permission, shortDescription, fullDescription, usageDescription, helpTopic, rootNode); + public RegisteredCommand copyWithEmptyNamespace() { + return new RegisteredCommand<>(commandName, aliases, "", helpTopic, rootNode); } // The default implementation of `hashCode`, `equals`, and `toString` don't work for arrays, // so we need to override and specifically use the Arrays methods for `String[] aliases` - // As https://stackoverflow.com/a/32083420 mentions, the same thing happens when Optionals wrap an array, like `Optional usageDescription` @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(aliases); - result = prime * result + Arrays.hashCode(usageDescription.orElse(null)); - result = prime * result + Objects.hash(commandName, namespace, permission, shortDescription, fullDescription, helpTopic, rootNode); + result = prime * result + Objects.hash(commandName, namespace, helpTopic, rootNode); return result; } @@ -204,19 +215,14 @@ public boolean equals(Object obj) { if (!(obj instanceof RegisteredCommand)) { return false; } - RegisteredCommand other = (RegisteredCommand) obj; - return Arrays.equals(aliases, other.aliases) && Objects.equals(rootNode, other.rootNode) && Objects.equals(commandName, other.commandName) - && Objects.equals(namespace, other.namespace) && Arrays.equals(usageDescription.orElse(null), other.usageDescription.orElse(null)) - && Objects.equals(fullDescription, other.fullDescription) && Objects.equals(permission, other.permission) && Objects.equals(shortDescription, other.shortDescription) - && Objects.equals(helpTopic, other.helpTopic); + RegisteredCommand other = (RegisteredCommand) obj; + return Objects.equals(commandName, other.commandName) && Arrays.equals(aliases, other.aliases) && Objects.equals(namespace, other.namespace) + && Objects.equals(helpTopic, other.helpTopic) && Objects.equals(rootNode, other.rootNode); } @Override public String toString() { - return "RegisteredCommand [commandName=" + commandName + ", aliases=" + Arrays.toString(aliases) - + ", namespace=" + namespace + ", permission=" + permission - + ", shortDescription=" + shortDescription + ", fullDescription=" + fullDescription - + ", usageDescription=" + (usageDescription.isPresent() ? "Optional[" + Arrays.toString(usageDescription.get()) + "]" : "Optional.empty") + return "RegisteredCommand [commandName=" + commandName + ", aliases=" + Arrays.toString(aliases) + ", namespace=" + namespace + ", helpTopic=" + helpTopic + ", rootNode=" + rootNode + "]"; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 705d317c33..d60ae696b7 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -336,12 +336,12 @@ public String toString() { * @param childrenConsumer A callback that accepts a {@link List} of all the {@link RegisteredCommand.Node}s that represent the nodes * added as children to the previous argument. */ - public static record NodeInformation(List> lastCommandNodes, ChildrenConsumer childrenConsumer) { + public static record NodeInformation(List> lastCommandNodes, ChildrenConsumer childrenConsumer) { } @FunctionalInterface - public static interface ChildrenConsumer { - public void createNodeWithChildren(List children); + public static interface ChildrenConsumer { + public void createNodeWithChildren(List> children); } /** @@ -368,8 +368,8 @@ public static interface ChildrenConsumer { * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier node structure for this argument. */ - public NodeInformation addArgumentNodes( - NodeInformation previousNodeInformation, + public NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -397,7 +397,7 @@ public NodeInformation addArgumentNodes( * @param previousArgumentNames A List of Strings containing the node names that came before this argument. */ public void checkPreconditions( - NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames ) { if(previousNodeInformation.lastCommandNodes().isEmpty()) { throw new GreedyArgumentException(previousArguments, (Argument) this); @@ -500,8 +500,8 @@ public CommandNode finishBuildingNode(ArgumentBuilder NodeInformation linkNode( - NodeInformation previousNodeInformation, CommandNode rootNode, + public NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -511,14 +511,15 @@ public NodeInformation linkNode( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( // A GreedyArgument cannot have arguments after it this instanceof GreedyArgument ? List.of() : List.of(rootNode), // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "<" + nodeName + ">", combinedArguments.isEmpty() && terminalExecutorCreator != null, + permission, requirements, children ) )) @@ -540,11 +541,13 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", * appropriate Brigadier {@link Command} which should be applied to the last * stacked argument of the node structure. This parameter can be null to indicate * that the argument stack should not be executable. + * @param The implementation of AbstractArgument being used. + * @param The class for running platform commands. * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing the built argument stack. */ - public static , Source> NodeInformation stackArguments( - List argumentsToStack, NodeInformation previousNodeInformation, + public static , CommandSender, Source> NodeInformation stackArguments( + List argumentsToStack, NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java index 2792155117..ef94b0e907 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -10,6 +10,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.FlagsArgumentEndingNode; @@ -19,6 +20,7 @@ import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.Predicate; public interface FlagsArgumentCommon getRequirements(); + /** * Links to {@link AbstractArgument#getCombinedArguments()}. */ @@ -57,7 +69,7 @@ public interface FlagsArgumentCommon void checkPreconditions( - NodeInformation previousNodes, List previousArguments, List previousArgumentNames + NodeInformation previousNodes, List previousArguments, List previousArgumentNames ); /** @@ -100,8 +112,8 @@ default List parseArgument(CommandContext cmd * A FlagsArgument works completely differently from a typical argument, so we need to completely * override the usual logic. */ - default NodeInformation addArgumentNodes( - NodeInformation previousNodeInformation, + default NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -143,13 +155,14 @@ default NodeInformation addArgumentNodes( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( newNodes, // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "<" + nodeName + ">", loopingBranchesExecutable || terminalBranchesExecutable, + getArgumentPermission(), getRequirements(), children ) )) @@ -159,7 +172,7 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } - private static , Source> List> setupBranch( + private static , CommandSender, Source> List> setupBranch( List branchArguments, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator, @@ -170,7 +183,7 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); RootCommandNode branchRoot = new RootCommandNode<>(); - NodeInformation branchNodeInformation = new NodeInformation<>(List.of(branchRoot), null); + NodeInformation branchNodeInformation = new NodeInformation<>(List.of(branchRoot), null); // Stack branch nodes branchNodeInformation = AbstractArgument.stackArguments(branchArguments, branchNodeInformation, branchPreviousArguments, branchPreviousArgumentNames, terminalExecutorCreator); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java index f9347166c5..a28973b0e6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java @@ -5,12 +5,14 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; /** * An interface representing literal-based arguments @@ -39,6 +41,16 @@ public interface Literal getRequirements(); + /** * Links to {@link AbstractArgument#isListed()}. */ @@ -79,8 +91,8 @@ public interface Literal * Normally, Arguments use thier node name as their help string. However, a Literal uses its literal as the help string. */ - default NodeInformation linkNode( - NodeInformation previousNodeInformation, CommandNode rootNode, + default NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -90,13 +102,14 @@ default NodeInformation linkNode( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( List.of(rootNode), // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( getNodeName(), getClass().getSimpleName(), getLiteral(), getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + getArgumentPermission(), getRequirements(), children ) )) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index cc4e84c284..a1fdea5c74 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -5,6 +5,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; @@ -14,6 +15,7 @@ import java.util.Iterator; import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; /** * An interface representing arguments with multiple literal string definitions @@ -44,6 +46,16 @@ public interface MultiLiteral getRequirements(); + /** * Links to {@link AbstractArgument#isListed()}. */ @@ -86,8 +98,8 @@ public interface MultiLiteral NodeInformation linkNode( - NodeInformation previousNodeInformation, CommandNode rootNode, + default NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -120,14 +132,15 @@ default NodeInformation linkNode( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( newNodes, // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "(" + String.join("|", getLiterals())+ ")", getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + getArgumentPermission(), getRequirements(), children ) )) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java new file mode 100644 index 0000000000..34f402728d --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java @@ -0,0 +1,11 @@ +package dev.jorel.commandapi.help; + +/** + * An interface for providing the short description, full description, and usage in a command help. + * This combines the {@link FunctionalInterface}s {@link ShortDescriptionGenerator}, {@link FullDescriptionGenerator}, and {@link UsageGenerator}. + * + * @param The class for running platform commands. + */ +public interface CommandAPIHelpTopic extends ShortDescriptionGenerator, FullDescriptionGenerator, UsageGenerator { + +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java new file mode 100644 index 0000000000..4a47f1c003 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java @@ -0,0 +1,193 @@ +package dev.jorel.commandapi.help; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +import javax.annotation.Nullable; + +import dev.jorel.commandapi.RegisteredCommand; + +/** + * An {@link CommandAPIHelpTopic} that can have its short description, full description, and usage edited. + */ +public class EditableHelpTopic implements CommandAPIHelpTopic { + private ShortDescriptionGenerator shortDescription = () -> Optional.empty(); + private FullDescriptionGenerator fullDescription = forWho -> Optional.empty(); + private UsageGenerator usage = (forWho, argumentTree) -> Optional.empty(); + + /** + * Creates a new {@link EditableHelpTopic} that returns empty {@link Optional}s + * by default for its short description, full description, and usage. + */ + public EditableHelpTopic() { + + } + + /** + * Creates a new {@link EditableHelpTopic} that returns the given short description, full description, and usage by default. + * + * @param shortDescription The short description {@link String} for this command help. + * @param fullDescription The full description {@link String} for this command help. + * @param usage The {@link String} array that holds the usage for this command help. + */ + public EditableHelpTopic(@Nullable String shortDescription, @Nullable String fullDescription, @Nullable String[] usage) { + Optional shortDescriptionResult = Optional.ofNullable(shortDescription); + this.shortDescription = () -> shortDescriptionResult; + + Optional fullDescriptionResult = Optional.ofNullable(fullDescription); + this.fullDescription = forWho -> fullDescriptionResult; + + Optional usageResult = Optional.ofNullable(usage); + this.usage = (forWho, argumentTree) -> usageResult; + } + + // Static help results + /** + * Sets the short description for this command help. This is the help which is + * shown in the main /help menu. + * + * @param description the short description for the command help + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withShortDescription(@Nullable String description) { + Optional result = Optional.ofNullable(description); + this.shortDescription = () -> result; + + return this; + } + + /** + * Sets the full description for this command help. This is the help which is + * shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param description the full description for this command help + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withFullDescription(@Nullable String description) { + Optional result = Optional.ofNullable(description); + this.fullDescription = forWho -> result; + + return this; + } + + /** + * Sets the short and full description for this command help. This is a short-hand + * for the {@link #withShortDescription} and {@link #withFullDescription} methods. + * + * @param shortDescription the short description for this command help + * @param fullDescription the full description for this command help + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withHelp(@Nullable String shortDescription, @Nullable String fullDescription) { + this.withShortDescription(shortDescription); + this.withFullDescription(fullDescription); + + return this; + } + + /** + * Sets the usage for this command help. This is the usage which is + * shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param usage the full usage for this command + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withUsage(@Nullable String... usage) { + Optional result = Optional.ofNullable(usage); + this.usage = (forWho, argumentTree) -> result; + + return this; + } + + // Dynamic help results + /** + * Sets the short description of this command help to be generated using the given {@link ShortDescriptionGenerator}. + * This is the help which is shown in the main /help menu. + * + * @param description The {@link ShortDescriptionGenerator} to use to generate the short description. + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withShortDescription(ShortDescriptionGenerator description) { + this.shortDescription = description; + + return this; + } + + /** + * Sets the full description of this command help to be generated using the given {@link FullDescriptionGenerator}. + * This is the help which is shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param description The {@link FullDescriptionGenerator} to use to generate the full description. + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withFullDescription(FullDescriptionGenerator description) { + this.fullDescription = description; + + return this; + } + + /** + * Sets the usage of this command help to be generated using the given {@link UsageGenerator}. + * This is the usage which is shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param usage The {@link UsageGenerator} to use to generate the usage. + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withUsage(UsageGenerator usage) { + this.usage = usage; + + return this; + } + + // Implement CommandAPIHelpTopic methods + @Override + public Optional getShortDescription() { + return shortDescription.getShortDescription(); + } + + @Override + public Optional getFullDescription(@Nullable CommandSender forWho) { + return fullDescription.getFullDescription(forWho); + } + + @Override + public Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree) { + return usage.getUsage(forWho, argumentTree); + } + + // equals, hashCode, toString + // Since our fields are functions, they aren't easily compared + // However, the 'default' values returned by passing null parameters tend to make sense + // Just keep in mind that these return Optionals, and in the case of usage, the String[] should use Arrays methods + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof EditableHelpTopic)) { + return false; + } + EditableHelpTopic other = (EditableHelpTopic) obj; + return Objects.equals(shortDescription.getShortDescription(), other.shortDescription.getShortDescription()) + && Objects.equals(fullDescription.getFullDescription(null), other.fullDescription.getFullDescription(null)) + && Arrays.equals(usage.getUsage(null, null).orElse(null), other.usage.getUsage(null, null).orElse(null)); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(usage.getUsage(null, null).orElse(null)); + result = prime * result + Objects.hash(shortDescription.getShortDescription(), fullDescription.getFullDescription(null)); + return result; + } + + @Override + public final String toString() { + return "EditableHelpTopic [" + + "shortDescription=" + shortDescription.getShortDescription() + + ", fullDescription=" + fullDescription.getFullDescription(null) + + ", usage=" + usage.getUsage(null, null).map(Arrays::toString) + "]"; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java new file mode 100644 index 0000000000..2c4839fe0c --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java @@ -0,0 +1,24 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +import javax.annotation.Nullable; + +/** + * A {@link FunctionalInterface} for generating command help full descriptions. + * See {@link #getFullDescription(Object)}. + * + * @param The class for running platform commands. + */ +@FunctionalInterface +public interface FullDescriptionGenerator { + /** + * Returns an {@link Optional} containing the {@link String} that is the full description for this command help. + * + * @param forWho The {@code CommandSender} the full description should be generated for. For example, you + * could test if this sender has permission to see a branch in your command. This + * parameter may be null. + * @return An {@link Optional} {@link String} that is the full description for this command help. + */ + public Optional getFullDescription(@Nullable CommandSender forWho); +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java new file mode 100644 index 0000000000..25b17c1d3e --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java @@ -0,0 +1,15 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +/** + * A {@link FunctionalInterface} for generating command help short descriptions. + * See {@link #getShortDescription()}. + */ +@FunctionalInterface +public interface ShortDescriptionGenerator { + /** + * @return An {@link Optional} {@link String} that is the short description for this command help. + */ + public Optional getShortDescription(); +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java new file mode 100644 index 0000000000..5f18333828 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java @@ -0,0 +1,29 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +import javax.annotation.Nullable; + +import dev.jorel.commandapi.RegisteredCommand; + +/** + * A {@link FunctionalInterface} for generating command help usages. + * See {@link #getUsage(Object, RegisteredCommand.Node)}. + * + * @param The class for running platform commands. + */ +@FunctionalInterface +public interface UsageGenerator { + /** + * Returns an {@link Optional} containing a {@code String[]}, where each item in the array + * represents a possible way to use the command. + * + * @param forWho The {@code CommandSender} the usage should be generated for. For example, you + * could test if this sender has permission to see a branch in your command. This + * parameter may be null. + * @param argumentTree The {@link RegisteredCommand.Node} that is the root node of the command the usage + * should be generated for. This parameter may be null. + * @return An {@link Optional} {@link String} array with the usage for this command help. + */ + public Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree); +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index 36683aa167..c7a4942961 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -10,19 +10,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.Set; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import net.kyori.adventure.text.ComponentLike; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.Keyed; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; @@ -62,6 +58,9 @@ import dev.jorel.commandapi.commandsenders.BukkitProxiedCommandSender; import dev.jorel.commandapi.commandsenders.BukkitRemoteConsoleCommandSender; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; +import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; +import dev.jorel.commandapi.help.CustomCommandAPIHelpTopic; import dev.jorel.commandapi.nms.NMS; import dev.jorel.commandapi.preprocessor.Unimplemented; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; @@ -212,153 +211,51 @@ public void onServerLoad(ServerLoadEvent event) { /* * Generate and register help topics */ - private String generateCommandHelpPrefix(String command) { - return (Bukkit.getPluginCommand(command) == null ? "/" : "/minecraft:") + command; - } - - private String generateCommandHelpPrefix(String command, String namespace) { - return (Bukkit.getPluginCommand(command) == null ? "/" + namespace + ":" : "/minecraft:") + command; - } - - private void generateHelpUsage(StringBuilder sb, RegisteredCommand command) { - // Generate usages - String[] usages = getUsageList(command); - - if (usages.length == 0) { - // Might happen if the developer calls `.withUsage()` with no parameters - // They didn't give any usage, so we won't put any there - return; - } - - sb.append(ChatColor.GOLD).append("Usage: ").append(ChatColor.WHITE); - // If 1 usage, put it on the same line, otherwise format like a list - if (usages.length == 1) { - sb.append(usages[0]); - } else { - for (String usage : usages) { - sb.append("\n- ").append(usage); - } - } - } - - private String[] getUsageList(RegisteredCommand currentCommand) { - // TODO: I don't think the changes I made here are well tested, so this could have changed behavior - final Optional usageDescription = currentCommand.usageDescription(); - if (usageDescription.isPresent()) return usageDescription.get(); // Usage was overriden - - // Generate command usage - // TODO: Should default usage generation be updated? https://github.com/JorelAli/CommandAPI/issues/363 - List usages = new ArrayList<>(); - StringBuilder usageSoFar = new StringBuilder("/"); - addUsageForNode(currentCommand.rootNode(), usages, usageSoFar); - return usages.toArray(String[]::new); - } - - private void addUsageForNode(RegisteredCommand.Node node, List usages, StringBuilder usageSoFar) { - // Add node to usage - usageSoFar.append(node.helpString()); - - // Add usage to the list if this is executable - if (node.executable()) usages.add(usageSoFar.toString()); - - // Add children - usageSoFar.append(" "); - int currentLength = usageSoFar.length(); - for (RegisteredCommand.Node child : node.children()) { - // Reset the string builder to the usage up to and including this node - usageSoFar.delete(currentLength, usageSoFar.length()); - - addUsageForNode(child, usages, usageSoFar); - } - } - - void updateHelpForCommands(List commands) { + void updateHelpForCommands(List> commands) { Map helpTopicsToAdd = new HashMap<>(); - Set namespacedCommandNames = new HashSet<>(); - - for (RegisteredCommand command : commands) { - // Don't override the plugin help topic - String commandPrefix = generateCommandHelpPrefix(command.commandName()); - // Namespaced commands shouldn't have a help topic, we should save the namespaced command name - namespacedCommandNames.add(generateCommandHelpPrefix(command.commandName(), command.namespace())); - - StringBuilder aliasSb = new StringBuilder(); - final String shortDescription; + for (RegisteredCommand command : commands) { + String namespaceAddon = (command.namespace().isEmpty() ? "" : command.namespace() + ":"); + String commandName = namespaceAddon + command.commandName(); + CommandAPIHelpTopic commandAPIHelpTopic = command.helpTopic(); - // Must be empty string, not null as defined by OBC::CustomHelpTopic - final String permission = command.permission().getPermission().orElse(""); - - HelpTopic helpTopic; - final Optional commandHelpTopic = command.helpTopic(); - if (commandHelpTopic.isPresent()) { - helpTopic = (HelpTopic) commandHelpTopic.get(); - shortDescription = ""; - } else { - // Generate short description - final Optional shortDescriptionOptional = command.shortDescription(); - final Optional fullDescriptionOptional = command.fullDescription(); - if (shortDescriptionOptional.isPresent()) { - shortDescription = shortDescriptionOptional.get(); - } else if (fullDescriptionOptional.isPresent()) { - shortDescription = fullDescriptionOptional.get(); + // Don't override other plugin's help topics + if(Bukkit.getPluginCommand(commandName) == null) { + final HelpTopic helpTopic; + if (commandAPIHelpTopic instanceof BukkitHelpTopicWrapper bukkitHelpTopic) { + helpTopic = bukkitHelpTopic.helpTopic(); } else { - shortDescription = "A command by the " + config.getPlugin().getName() + " plugin."; - } - - // Generate full description - StringBuilder sb = new StringBuilder(); - if (fullDescriptionOptional.isPresent()) { - sb.append(ChatColor.GOLD).append("Description: ").append(ChatColor.WHITE).append(fullDescriptionOptional.get()).append("\n"); - } - - generateHelpUsage(sb, command); - sb.append("\n"); - - // Generate aliases. We make a copy of the StringBuilder because we - // want to change the output when we register aliases - aliasSb = new StringBuilder(sb.toString()); - if (command.aliases().length > 0) { - sb.append(ChatColor.GOLD).append("Aliases: ").append(ChatColor.WHITE).append(String.join(", ", command.aliases())); + helpTopic = new CustomCommandAPIHelpTopic(commandName, command.aliases(), commandAPIHelpTopic, command.rootNode()); } - - helpTopic = generateHelpTopic(commandPrefix, shortDescription, sb.toString().trim(), permission); + helpTopicsToAdd.put("/" + commandName, helpTopic); } - helpTopicsToAdd.put(commandPrefix, helpTopic); for (String alias : command.aliases()) { - if (commandHelpTopic.isPresent()) { - helpTopic = (HelpTopic) commandHelpTopic.get(); + String aliasName = namespaceAddon + alias; + + // Don't override other plugin's help topics + if(Bukkit.getPluginCommand(aliasName) != null) { + continue; + } + + final HelpTopic helpTopic; + if (commandAPIHelpTopic instanceof BukkitHelpTopicWrapper bukkitHelpTopic) { + helpTopic = bukkitHelpTopic.helpTopic(); } else { - StringBuilder currentAliasSb = new StringBuilder(aliasSb.toString()); - currentAliasSb.append(ChatColor.GOLD).append("Aliases: ").append(ChatColor.WHITE); - // We want to get all aliases (including the original command name), // except for the current alias List aliases = new ArrayList<>(Arrays.asList(command.aliases())); aliases.add(command.commandName()); aliases.remove(alias); - - currentAliasSb.append(String.join(", ", aliases)); - - // Don't override the plugin help topic - commandPrefix = generateCommandHelpPrefix(alias); - helpTopic = generateHelpTopic(commandPrefix, shortDescription, currentAliasSb.toString().trim(), permission); - // Namespaced commands shouldn't have a help topic, we should save the namespaced alias name - namespacedCommandNames.add(generateCommandHelpPrefix(alias, command.namespace())); + helpTopic = new CustomCommandAPIHelpTopic(aliasName, aliases.toArray(String[]::new), commandAPIHelpTopic, command.rootNode()); } - helpTopicsToAdd.put(commandPrefix, helpTopic); + helpTopicsToAdd.put("/" + aliasName, helpTopic); } } // We have to use helpTopics.put (instead of .addTopic) because we're overwriting an existing help topic, not adding a new help topic getHelpMap().putAll(helpTopicsToAdd); - - // We also have to remove help topics for namespaced command names - for (String namespacedCommandName : namespacedCommandNames) { - getHelpMap().remove(namespacedCommandName); - } } @Override @@ -470,16 +367,22 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { commandRegistrationStrategy.postCommandRegistration(registeredCommand, resultantNode, aliasNodes); // Register the command's permission string (if it exists) to Bukkit's manager - CommandPermission permission = registeredCommand.permission(); + CommandPermission permission = registeredCommand.rootNode().permission(); permission.getPermission().ifPresent(this::registerPermission); if (!CommandAPI.canRegister()) { // Adding the command to the help map usually happens in `CommandAPIBukkit#onEnable` - updateHelpForCommands(List.of(registeredCommand)); + // We'll make sure to retrieve the merged versions from CommandAPIHandler + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + Map> registeredCommands = handler.registeredCommands; + updateHelpForCommands(List.of( + registeredCommands.get(registeredCommand.commandName()), + registeredCommands.get(registeredCommand.namespace() + ":" + registeredCommand.commandName()) + )); // Sending command dispatcher packets usually happens when Players join the server for (Player p : Bukkit.getOnlinePlayers()) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index 3a8cd57426..171aed7c3c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java @@ -1,8 +1,8 @@ package dev.jorel.commandapi; import dev.jorel.commandapi.arguments.Argument; - -import java.util.Optional; +import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; import org.bukkit.command.CommandSender; import org.bukkit.help.HelpTopic; @@ -26,18 +26,21 @@ public CommandAPICommand instance() { /** * Sets the {@link HelpTopic} for this command. Using this method will override * any declared short description, full description or usage description provided - * via the following methods: + * via the following methods and similar overloads: *
      *
    • {@link CommandAPICommand#withShortDescription(String)}
    • *
    • {@link CommandAPICommand#withFullDescription(String)}
    • *
    • {@link CommandAPICommand#withUsage(String...)}
    • *
    • {@link CommandAPICommand#withHelp(String, String)}
    • *
    + * Further calls to these methods will also be ignored. + * See also {@link ExecutableCommand#withHelp(CommandAPIHelpTopic)}. + * * @param helpTopic the help topic to use for this command * @return this command builder */ public CommandAPICommand withHelp(HelpTopic helpTopic) { - this.helpTopic = Optional.of(helpTopic); + this.helpTopic = new BukkitHelpTopicWrapper(helpTopic); return instance(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java index 59f7892cc2..98253f2387 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java @@ -5,6 +5,7 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; +import org.bukkit.command.CommandSender; import java.util.HashSet; import java.util.List; @@ -58,7 +59,7 @@ protected static boolean isThisTheCommandButNamespaced(String commandName, Strin public abstract void runTasksAfterServerStart(); - public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); + public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); public abstract void registerCommandNode(LiteralCommandNode node, String namespace); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java index 5cd69a3789..659d14d91b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java @@ -1,8 +1,8 @@ package dev.jorel.commandapi; import dev.jorel.commandapi.arguments.Argument; - -import java.util.Optional; +import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; import org.bukkit.command.CommandSender; import org.bukkit.help.HelpTopic; @@ -26,18 +26,21 @@ public CommandTree instance() { /** * Sets the {@link HelpTopic} for this command. Using this method will override * any declared short description, full description or usage description provided - * via the following methods: + * via the following methods and similar overloads: *
      - *
    • {@link CommandAPICommand#withShortDescription(String)}
    • - *
    • {@link CommandAPICommand#withFullDescription(String)}
    • - *
    • {@link CommandAPICommand#withUsage(String...)}
    • - *
    • {@link CommandAPICommand#withHelp(String, String)}
    • + *
    • {@link CommandTree#withShortDescription(String)}
    • + *
    • {@link CommandTree#withFullDescription(String)}
    • + *
    • {@link CommandTree#withUsage(String...)}
    • + *
    • {@link CommandTree#withHelp(String, String)}
    • *
    + * Further calls to these methods will also be ignored. + * See also {@link ExecutableCommand#withHelp(CommandAPIHelpTopic)}. + * * @param helpTopic the help topic to use for this command * @return this command builder */ public CommandTree withHelp(HelpTopic helpTopic) { - this.helpTopic = Optional.of(helpTopic); + this.helpTopic = new BukkitHelpTopicWrapper(helpTopic); return instance(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java index e501f88c3a..f6de166be3 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java @@ -4,6 +4,7 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; +import org.bukkit.command.CommandSender; import java.util.List; import java.util.function.Predicate; @@ -49,7 +50,7 @@ public void runTasksAfterServerStart() { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { // Nothing to do } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index 47bff20693..8775599765 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -6,6 +6,7 @@ import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.preprocessor.RequireField; import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; import org.bukkit.command.SimpleCommandMap; import java.util.*; @@ -183,7 +184,7 @@ private void fixPermissions() { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { if (!CommandAPI.canRegister()) { // Usually, when registering commands during server startup, we can just put our commands into the // `net.minecraft.server.MinecraftServer#vanillaCommandDispatcher` and leave it. As the server finishes setup, @@ -198,7 +199,7 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal String name = resultantNode.getLiteral(); String namespace = registeredCommand.namespace(); - String permNode = unpackInternalPermissionNodeString(registeredCommand.permission()); + String permNode = unpackInternalPermissionNodeString(registeredCommand.rootNode().permission()); registerCommand(knownCommands, root, name, permNode, namespace, resultantNode); @@ -224,7 +225,7 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal minecraftCommandNamespaces = new RootCommandNode<>(); } } else { - CommandPermission permission = registeredCommand.permission(); + CommandPermission permission = registeredCommand.rootNode().permission(); // Since the VanillaCommandWrappers aren't created yet, we need to remember to // fix those permissions once the server is enabled. Using `putIfAbsent` to diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java index 5e5d73539b..3d5a273170 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java @@ -199,8 +199,8 @@ public Argument combineWith(List> combinedArguments) { } @Override - public NodeInformation addArgumentNodes( - NodeInformation previousNodeInformation, + public NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator ) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java index b1cb0155bc..30e8765ca9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java @@ -73,7 +73,7 @@ public List parseArgument(CommandContext cmdC } @Override - public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return FlagsArgumentCommon.super.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index 45c3dac01c..1e96c986db 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -157,7 +157,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index b3d95b1e60..f3b1b799d9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -112,7 +112,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java new file mode 100644 index 0000000000..6947e4f957 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java @@ -0,0 +1,41 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +import javax.annotation.Nullable; + +import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpTopic; + +import dev.jorel.commandapi.RegisteredCommand.Node; + +/** + * A {@link CommandAPIHelpTopic} that wraps Bukkit's {@link HelpTopic}. + * + * @param helpTopic The Bukkit {@link HelpTopic} being wrapped + */ +public record BukkitHelpTopicWrapper( + + /** + * @return The Bukkit {@link HelpTopic} being wrapped + */ + HelpTopic helpTopic) implements CommandAPIHelpTopic { + + @Override + public Optional getShortDescription() { + return Optional.of(helpTopic.getShortText()); + } + + @Override + public Optional getFullDescription(@Nullable CommandSender forWho) { + if (forWho == null) return Optional.empty(); + + return Optional.of(helpTopic.getFullText(forWho)); + } + + @Override + public Optional getUsage(@Nullable CommandSender forWho, @Nullable Node argumentTree) { + return Optional.empty(); + } + +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java new file mode 100644 index 0000000000..de4ec4dec1 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java @@ -0,0 +1,136 @@ +package dev.jorel.commandapi.help; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpTopic; +import org.jetbrains.annotations.NotNull; + +import dev.jorel.commandapi.CommandAPIBukkit; +import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.RegisteredCommand; + +public class CustomCommandAPIHelpTopic extends HelpTopic { + private final String aliasString; + private final CommandAPIHelpTopic helpTopic; + private final RegisteredCommand.Node argumentTree; + + public CustomCommandAPIHelpTopic(String name, String[] aliases, CommandAPIHelpTopic helpTopic, RegisteredCommand.Node argumentTree) { + this.name = name; + + // Pre-generate alias help text since it doesn't depend on sender + if (aliases.length > 0) { + this.aliasString = "\n" + ChatColor.GOLD + "Aliases: " + ChatColor.WHITE + String.join(", ", aliases); + } else { + this.aliasString = ""; + } + + this.helpTopic = helpTopic; + this.argumentTree = argumentTree; + } + + @Override + public boolean canSee(@NotNull CommandSender sender) { + // Check if sender can see root node + return canSeeNode(sender, argumentTree); + } + + private boolean canSeeNode(CommandSender sender, RegisteredCommand.Node node) { + final boolean hasPermission; + + // Evaluate the CommandPermission + CommandPermission permission = node.permission(); + if (permission.equals(CommandPermission.NONE)) { + hasPermission = true; + } else if(permission.equals(CommandPermission.OP)) { + hasPermission = sender.isOp(); + } else { + Optional optionalStringPermission = permission.getPermission(); + if (optionalStringPermission.isPresent()) { + hasPermission = sender.hasPermission(optionalStringPermission.get()); + } else { + hasPermission = true; + } + } + + // If sender doesn't have permission (when negated if needed), they can't see this help + if (!hasPermission ^ permission.isNegated()) return false; + + // Check requirements + return node.requirements().test(sender); + } + + @Override + public @NotNull String getShortText() { + Optional shortDescriptionOptional = helpTopic.getShortDescription(); + if (shortDescriptionOptional.isPresent()) { + return shortDescriptionOptional.get(); + } else { + return helpTopic.getFullDescription(null) + .orElse("A command by the " + CommandAPIBukkit.getConfiguration().getPlugin().getName() + " plugin."); + } + } + + @Override + public @NotNull String getFullText(@NotNull CommandSender forWho) { + // Generate full text for the given sender + StringBuilder sb = new StringBuilder(this.getShortText()); + + // Add fullDescription if present + Optional fullDescriptionOptional = this.helpTopic.getFullDescription(forWho); + if (fullDescriptionOptional.isPresent()) { + sb.append("\n").append(ChatColor.GOLD).append("Description: ").append(ChatColor.WHITE).append(fullDescriptionOptional.get()); + } + + // Add usage if present, and otherwise generate default usage + String[] usages = this.helpTopic.getUsage(forWho, this.argumentTree).orElseGet(() -> generateDefaultUsage(forWho)); + + if (usages.length > 0) { + sb.append("\n").append(ChatColor.GOLD).append("Usage: ").append(ChatColor.WHITE); + // If 1 usage, put it on the same line, otherwise format like a list + if (usages.length == 1) { + sb.append(usages[0]); + } else { + for (String usage : usages) { + sb.append("\n- ").append(usage); + } + } + } + + // Add aliases + sb.append(this.aliasString); + + return sb.toString(); + } + + private String[] generateDefaultUsage(CommandSender forWho) { + List usages = new ArrayList<>(); + StringBuilder usageSoFar = new StringBuilder("/"); + addUsageForNode(this.argumentTree, usages, usageSoFar, forWho); + return usages.toArray(String[]::new); + } + + private void addUsageForNode(RegisteredCommand.Node node, List usages, StringBuilder usageSoFar, CommandSender forWho) { + // If sender can't see the node, don't include it in the usage + if(!canSeeNode(forWho, node)) return; + + // Add node to usage + usageSoFar.append(node.helpString()); + + // Add usage to the list if this is executable + if (node.executable()) usages.add(usageSoFar.toString()); + + // Add children + usageSoFar.append(" "); + int currentLength = usageSoFar.length(); + for (RegisteredCommand.Node child : node.children()) { + // Reset the string builder to the usage up to and including this node + usageSoFar.delete(currentLength, usageSoFar.length()); + + addUsageForNode(child, usages, usageSoFar, forWho); + } + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java index 2263603350..002927d26a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java @@ -410,8 +410,6 @@ String getScoreHolderSingle(CommandContext cmdCtx, Strin */ void reloadDataPacks(); - HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission); - Map getHelpMap(); Message generateMessageFromJson(String json); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java index b19d588e30..4064ec960a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_16_R3.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_16_R3.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.v1_16_R3.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_16_R3.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_16_R3.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_16_R3.potion.CraftPotionEffectType; @@ -406,11 +405,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ArgumentMinecraftKeyRegistered.a(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java index 7e4c688686..dee8b13188 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java @@ -64,7 +64,6 @@ import org.bukkit.craftbukkit.v1_17_R1.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_17_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_17_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_17_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_17_R1.potion.CraftPotionEffectType; @@ -260,11 +259,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java index 5ec15656cb..ee7009f4d1 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java @@ -67,7 +67,6 @@ import org.bukkit.craftbukkit.v1_18_R2.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_18_R2.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_18_R2.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_18_R2.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_18_R2.potion.CraftPotionEffectType; @@ -282,11 +281,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java index edc2c0b9cb..65a3076256 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_18_R1.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_18_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_18_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_18_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_18_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_18_R1.potion.CraftPotionEffectType; @@ -268,11 +267,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java index a44a8503fc..0dfb840475 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java @@ -117,7 +117,6 @@ import org.bukkit.craftbukkit.v1_19_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_19_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_19_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_19_R1.potion.CraftPotionEffectType; @@ -352,11 +351,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java index 1bdee5aa3e..d77e7f6e3f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java @@ -114,7 +114,6 @@ import org.bukkit.craftbukkit.v1_19_R2.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_19_R2.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_19_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_19_R2.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_19_R2.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_19_R2.potion.CraftPotionEffectType; @@ -262,11 +261,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java index 288a5d7f6d..4febcec271 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java @@ -114,7 +114,6 @@ import org.bukkit.craftbukkit.v1_19_R3.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_19_R3.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_19_R3.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_19_R3.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_19_R3.potion.CraftPotionEffectType; @@ -261,11 +260,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java index ab5c435600..92b7781637 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_20_R2.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R2.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R2.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R2.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R2.potion.CraftPotionEffectType; @@ -283,11 +282,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).toBukkit(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java index ba9df06193..cc46076b05 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_20_R3.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R3.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R3.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R3.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R3.potion.CraftPotionEffectType; @@ -352,12 +351,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java index 9be7ede63f..b8fcb01598 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_20_R4.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R4.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R4.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R4.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R4.potion.CraftPotionEffectType; @@ -394,12 +393,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java index 4b30a7136b..d15570524a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java @@ -115,7 +115,6 @@ import org.bukkit.craftbukkit.v1_20_R1.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R1.potion.CraftPotionEffectType; @@ -260,11 +259,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java index 40382f44ca..58463efbc2 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java @@ -65,7 +65,6 @@ import org.bukkit.craftbukkit.v1_21_R1.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_21_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_21_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_21_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_21_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_21_R1.potion.CraftPotionEffectType; @@ -394,12 +393,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java index f08e7ecf5b..2d5fc161b1 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java @@ -297,11 +297,6 @@ public final String convert(Sound sound) { @Unimplemented(because = VERSION_SPECIFIC_IMPLEMENTATION, introducedIn = "1.19") public abstract void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException; - @Override - @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CustomHelpTopic") - public abstract HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, - String permission); - @Override @Unimplemented(because = VERSION_SPECIFIC_IMPLEMENTATION, introducedIn = "1.20.2") public abstract org.bukkit.advancement.Advancement getAdvancement(CommandContext cmdCtx, String key) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index e97020bcb2..7c9bb5e891 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -642,11 +642,6 @@ public BukkitCommandSender getCommandSenderFromCommandS return null; } } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java index c706d469ca..765030ec49 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -626,11 +626,6 @@ public BukkitCommandSender getCommandSenderFromCommandS return null; } } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 64f8f229eb..b978113c70 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -661,11 +661,6 @@ public BukkitCommandSender getCommandSenderFromCommandS return null; } } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index e499d26c86..936dc35099 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -842,11 +842,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } // } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 6d9d04bbc0..fada5fcb92 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -853,11 +853,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 0ccfd6003d..e2959928b5 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -853,11 +853,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 3d77a346ad..198d16fb8d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -871,11 +871,6 @@ public int popFunctionCallbackResult() { // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 266ce4a17f..1a9a47b3d8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -1000,11 +1000,6 @@ public int popFunctionCallbackResult() { // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 36fe844713..ba0cee5b54 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -851,11 +851,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java index ac053a096c..0c4cd060ff 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java @@ -8,12 +8,16 @@ import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.help.EditableHelpTopic; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.List; -import java.util.Optional; +import java.util.function.Predicate; import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.children; @@ -70,9 +74,9 @@ void testRegisterHelpInformation() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.NONE, - Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>("short description", "full description", new String[]{"usage 1", "usage 2", "usage 3"}), commandNode("command", true).build() ); @@ -86,10 +90,10 @@ void testRegisterOpPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.OP, - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.OP).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -102,10 +106,28 @@ void testRegisterStringPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.fromString("permission"), - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.fromString("permission")).build() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandAPICommand("command") + .withRequirement(requirement) + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).requirements(requirement).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -118,7 +140,7 @@ void testRegisterOneAlias() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -130,7 +152,7 @@ void testRegisterTwoAliases() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -141,7 +163,7 @@ void testRegisterNamespace() { .executesPlayer(P_EXEC) .register("custom"); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -181,6 +203,46 @@ void testRegisterTwoArguments() { ); } + @Test + void testRegisterArgumentPermissions() { + new CommandAPICommand("command") + .withArguments( + new StringArgument("noPermission"), + new StringArgument("opPermission").withPermission(CommandPermission.OP), + new StringArgument("stringPermission").withPermission(CommandPermission.fromString("permission")) + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("noPermission", StringArgument.class, false).withChildren( + node("opPermission", StringArgument.class, false).permission(CommandPermission.OP).withChildren( + node("stringPermission", StringArgument.class, true).permission(CommandPermission.fromString("permission")) + ))), + List.of("command:CommandAPICommand", "noPermission:StringArgument", "opPermission:StringArgument", "stringPermission:StringArgument") + ); + } + + @Test + void testRegisterArgumentRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandAPICommand("command") + .withArguments(new StringArgument("string").withRequirement(requirement)) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("string", StringArgument.class, true).requirements(requirement) + ), + List.of("command:CommandAPICommand", "string:StringArgument") + ); + } + @Test void testRegisterMultiLiteralArguments() { new CommandAPICommand("command") @@ -442,15 +504,15 @@ void testRegisterSubcommandWithAliasesAndMultiLiteralArgument() { ) .register(); - List literal2 = children( + List> literal2 = children( node("literal2", MultiLiteralArgument.class, true).helpString("(c|d)") ); - List literal1 = children( + List> literal1 = children( node("literal1", MultiLiteralArgument.class, false).helpString("(a|b)").withChildren(literal2) ); - List subcommands = children( + List> subcommands = children( commandNode("subcommand", false).withChildren(literal1), commandNode("alias1", false).withChildren(literal1), commandNode("alias2", false).withChildren(literal1) @@ -571,8 +633,8 @@ void testRegisterTwoSeparateCommands() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); - RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); + RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); assertCreatedRegisteredCommands( command1.copyWithEmptyNamespace(), command1, @@ -608,33 +670,38 @@ void testRegisterMergeNamespaces() { new CommandAPICommand("command") .withArguments(new LiteralArgument("first")) .executesPlayer(P_EXEC) + .withAliases("first") .register("first"); new CommandAPICommand("command") .withArguments(new LiteralArgument("second")) .executesPlayer(P_EXEC) + .withAliases("second") .register("second"); - RegisteredCommand first = simpleRegisteredCommand( + RegisteredCommand first = simpleRegisteredCommand( "command", "first", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first") - ) + ), + "first" ); - RegisteredCommand second = simpleRegisteredCommand( + RegisteredCommand second = simpleRegisteredCommand( "command", "second", commandNode("command", false).withChildren( node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "second" ); - RegisteredCommand merged = simpleRegisteredCommand( + RegisteredCommand merged = simpleRegisteredCommand( "command", "", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first"), node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "first", "second" ); assertCreatedRegisteredCommands(merged, first, second); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java index 16be82cfb9..11652686b8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java @@ -1,17 +1,27 @@ package dev.jorel.commandapi.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.help.HelpTopic; +import org.bukkit.permissions.PermissionAttachment; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import be.seeseemelk.mockbukkit.MockBukkit; import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.CommandTree; import dev.jorel.commandapi.arguments.IntegerArgument; import dev.jorel.commandapi.arguments.LiteralArgument; @@ -38,9 +48,16 @@ public void tearDown() { } private void assertHelpTopicCreated(String name, String shortDescription, String fullDescription, CommandSender forWho) { + assertHelpTopicCreated("/" + name, name, shortDescription, fullDescription, forWho); + } + + private void assertHelpTopicCreated(String entryName, String topicName, String shortDescription, String fullDescription, CommandSender forWho) { // Check the help topic was added - HelpTopic helpTopic = server.getHelpMap().getHelpTopic(name); - assertNotNull(helpTopic, "Expected to find help topic called <" + name + ">, but null was found."); + HelpTopic helpTopic = server.getHelpMap().getHelpTopic(entryName); + assertNotNull(helpTopic, "Expected to find help topic called <" + entryName + ">, but null was found."); + + // Check the help topic name + assertEquals(topicName, helpTopic.getName()); // Check the short description assertEquals(shortDescription, helpTopic.getShortText()); @@ -66,7 +83,7 @@ void testRegisterCommandWithHelp() { // Short and full description are inserted assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -89,7 +106,7 @@ void testRegisterCommandWithShortDescription() { // Short description appears at the start of full text because that's how `CustomHelpTopic` works assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -111,7 +128,7 @@ void testRegisterCommandFullDescription() { // Full description replaces short description when not specified assertHelpTopicCreated( - "/test", + "test", "full description", """ full description @@ -133,7 +150,7 @@ void testRegisterCommandNoDescription() { // Check default message assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -154,15 +171,11 @@ void testRegisterWithRemovedUsage() { Player player = server.addPlayer("APlayer"); // Usage can be removed - // Note that the result returned by `getFullText` has a trailing \n because `CustomHelpTopic` uses - // `shortText + "\n" + fullText`. In this situation, we have no full description, no usage, and no - // aliases, so the `fullText` generated by the CommandAPI is acutally an empty string "". assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ - A command by the CommandAPITest plugin. - """, + A command by the CommandAPITest plugin.""", player ); } @@ -180,7 +193,7 @@ void testRegisterWithOneUsage() { // Usage generation can be overriden with one line assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -206,7 +219,7 @@ void testRegisterWithMultipleUsage() { // Usage generation can be overriden with multiple lines assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -218,6 +231,240 @@ void testRegisterWithMultipleUsage() { ); } + @Test + void testRegisterDynamicShortDescription() { + String[] shortDescription = new String[]{""}; + + new CommandAPICommand("test") + .withShortDescription(() -> Optional.of(shortDescription[0])) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer(); + + // Check changing shortDescription + shortDescription[0] = "First description"; + assertHelpTopicCreated( + "test", + "First description", + """ + First description + &6Usage: &f/test""", + player + ); + + shortDescription[0] = "Second description"; + assertHelpTopicCreated( + "test", + "Second description", + """ + Second description + &6Usage: &f/test""", + player + ); + } + + @Test + void testRegisterDynamicFullDescription() { + new CommandAPICommand("test") + .withFullDescription(forWho -> { + if (forWho == null) return Optional.empty(); + + return Optional.of("Special full text just for " + (forWho instanceof Player player ? player.getName() : forWho)); + }) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player1 = server.addPlayer("Player1"); + Player player2 = server.addPlayer("Player2"); + + // Check changing text for different CommandSenders + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Description: &fSpecial full text just for Player1 + &6Usage: &f/test""", + player1 + ); + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Description: &fSpecial full text just for Player2 + &6Usage: &f/test""", + player2 + ); + } + + @Test + void testRegisterDynamicUsage() { + new CommandAPICommand("test") + .withUsage((forWho, argumentTree) -> { + if (forWho == null) return Optional.empty(); + + return Optional.of(new String[]{ + "/test " + (forWho instanceof Player player ? player.getName() : forWho) + }); + }) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player1 = server.addPlayer("Player1"); + Player player2 = server.addPlayer("Player2"); + + // Check changing text for different CommandSenders + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/test Player1""", + player1 + ); + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/test Player2""", + player2 + ); + } + + @Test + void testRegisterCustomHelpTopic() { + HelpTopic helpTopic = new HelpTopic() { + { + this.name = "custom name"; + this.shortText = "short description"; + } + + @Override + public String getFullText(CommandSender forWho) { + return "Special full text just for " + (forWho instanceof Player player ? player.getName() : forWho); + } + + @Override + public boolean canSee(CommandSender sender) { + return true; + } + }; + + // `CommandAPICommand#withHelp(HelpTopic)` and `CommandTree#withHelp(HelpTopic)` are separate methods, so check both for coverage + new CommandAPICommand("commandAPICommand") + // Generally, calls to these methods before `withHelp(HelpTopic)` will be overridden + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Add the custom help + .withHelp(helpTopic) + // Generally, calls to these methods after `withHelp(HelpTopic)` should be ignored + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Finish building + .withAliases("alias") // Also check alias + .executesPlayer(P_EXEC) + .register(); + + new CommandTree("commandTree") + // Generally, calls to these methods before `withHelp(HelpTopic)` will be overridden + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Add the custom help + .withHelp(helpTopic) + // Generally, calls to these methods after `withHelp(HelpTopic)` should be ignored + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Finish building + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player1 = server.addPlayer("Player1"); + Player player2 = server.addPlayer("Player2"); + + // Custom HelpTopic allows changing text for different CommandSenders + assertHelpTopicCreated( + "/commandAPICommand", + "custom name", + "short description", + """ + Special full text just for Player1""", + player1 + ); + assertHelpTopicCreated( + "/commandAPICommand", + "custom name", + "short description", + """ + Special full text just for Player2""", + player2 + ); + assertHelpTopicCreated( + "/commandTree", + "custom name", + "short description", + """ + Special full text just for Player1""", + player1 + ); + assertHelpTopicCreated( + "/commandTree", + "custom name", + "short description", + """ + Special full text just for Player2""", + player2 + ); + + // Aliases should also use the custom HelpTopic + assertHelpTopicCreated( + "/alias", + "custom name", + "short description", + """ + Special full text just for Player1""", + player1 + ); + assertHelpTopicCreated( + "/alias", + "custom name", + "short description", + """ + Special full text just for Player2""", + player2 + ); + } + @Test void testRegisterCommandWithHelpWithAliases() { new CommandAPICommand("test") @@ -232,7 +479,7 @@ void testRegisterCommandWithHelpWithAliases() { // Check the main help topic was added assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -246,7 +493,7 @@ void testRegisterCommandWithHelpWithAliases() { // The alias section of each alias does not include itself, but does include the main name // Otherwise everything is the same as the main command assertHelpTopicCreated( - "/othertest", + "othertest", "short description", """ short description @@ -256,7 +503,7 @@ void testRegisterCommandWithHelpWithAliases() { player ); assertHelpTopicCreated( - "/othercommand", + "othercommand", "short description", """ short description @@ -282,7 +529,7 @@ void testRegisterCommandWithMultipleArguments() { // Multiple arguments are stacked in the usage assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -295,13 +542,13 @@ void testRegisterCommandWithMultipleArguments() { @Test void testRegisterMultipleCommands() { new CommandAPICommand("test") - .withHelp("short description", "full description") + .withHelp("short description 1", "full description 1") .withArguments(new StringArgument("arg1")) .executesPlayer(P_EXEC) .register(); new CommandAPICommand("test") - .withHelp("short description", "full description") + .withHelp("short description 2", "full description 2") .withArguments(new StringArgument("arg1")) .withArguments(new IntegerArgument("arg2")) .executesPlayer(P_EXEC) @@ -311,13 +558,13 @@ void testRegisterMultipleCommands() { enableServer(); Player player = server.addPlayer("APlayer"); - // Usage should be merged + // The first command registered gets priority, but usage should be merged assertHelpTopicCreated( - "/test", - "short description", + "test", + "short description 1", """ - short description - &6Description: &ffull description + short description 1 + &6Description: &ffull description 1 &6Usage: &f - /test - /test """, @@ -349,7 +596,7 @@ void testRegisterDeepBranches() { // Each executable node should appear in the usage assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -378,64 +625,17 @@ void testRegisterLiteralArguments() { enableServer(); Player player = server.addPlayer("APlayer"); - // MultiLiteralArgument unpacks to multiple LiteralArguments - // LiteralArgument has a differnt help string, using its literal rather than its node name + // Literal and MultiLiteral arguments have different help strings, using thier literals rather than thier node name assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. - &6Usage: &f - - /test a d - - /test b d - - /test c d """, + &6Usage: &f/test (a|b|c) d """, player ); } - @Test - void testRegisterCustomHelpTopic() { - new CommandAPICommand("test") - .withHelp(new HelpTopic() { - { - this.shortText = "short description"; - } - - @Override - public String getFullText(CommandSender forWho) { - return "Special full text just for " + (forWho instanceof Player player ? player.getName() : forWho); - } - - @Override - public boolean canSee(CommandSender sender) { - return true; - } - }) - .executesPlayer(P_EXEC) - .register(); - - // Enable server to register help topics - enableServer(); - Player player1 = server.addPlayer("Player1"); - Player player2 = server.addPlayer("Player2"); - - // Custom HelpTopic allows changing text for different CommandSenders - assertHelpTopicCreated( - "/test", - "short description", - """ - Special full text just for Player1""", - player1 - ); - assertHelpTopicCreated( - "/test", - "short description", - """ - Special full text just for Player2""", - player2 - ); - } - @Test void testRegisterAfterServerEnabled() { // Enable server early @@ -456,7 +656,7 @@ void testRegisterAfterServerEnabled() { // Ensure main and alias help topics exist assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -466,7 +666,7 @@ void testRegisterAfterServerEnabled() { player ); assertHelpTopicCreated( - "/alias", + "alias", "short description", """ short description @@ -476,4 +676,351 @@ void testRegisterAfterServerEnabled() { player ); } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + // Test enabling before and after registering since these take different code paths + void testRegisterMergeNamespaces(boolean enableBeforeRegistering) { + if (enableBeforeRegistering) enableServer(); + + new CommandAPICommand("test") + .withHelp("first short", "first full") + .withArguments(new LiteralArgument("first")) + .executesPlayer(P_EXEC) + .withAliases("first") + .register("first"); + + new CommandAPICommand("test") + .withHelp("second short", "second full") + .withArguments(new LiteralArgument("second")) + .executesPlayer(P_EXEC) + .withAliases("second") + .register("second"); + + if (!enableBeforeRegistering) enableServer(); + + Player player = server.addPlayer("APlayer"); + + // Unnamespaced help should merge usage and aliases together, which is how the command appears for execution + // The command registered first determines the descriptions + assertHelpTopicCreated( + "test", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f + - /test first + - /test second + &6Aliases: &ffirst, second""", + player + ); + assertHelpTopicCreated( + "first", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f + - /test first + - /test second + &6Aliases: &fsecond, test""", + player + ); + assertHelpTopicCreated( + "second", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f + - /test first + - /test second + &6Aliases: &ffirst, test""", + player + ); + + // Namespaced version of help should remain separated + assertHelpTopicCreated( + "first:test", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f/test first + &6Aliases: &ffirst""", + player + ); + assertHelpTopicCreated( + "first:first", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f/test first + &6Aliases: &ftest""", + player + ); + assertNull(server.getHelpMap().getHelpTopic("/first:second")); + + assertHelpTopicCreated( + "second:test", + "second short", + """ + second short + &6Description: &fsecond full + &6Usage: &f/test second + &6Aliases: &fsecond""", + player + ); + assertHelpTopicCreated( + "second:second", + "second short", + """ + second short + &6Description: &fsecond full + &6Usage: &f/test second + &6Aliases: &ftest""", + player + ); + assertNull(server.getHelpMap().getHelpTopic("/second:first")); + } + + @ParameterizedTest + @ValueSource(strings = {"minecraft", "custom"}) + // Test with the default ("minecraft") and a custom namespace + void testRegisterConflictWithPluginCommand(String namespace) { + // Register plugin command + // MockBukkit does not simulate `org.bukkit.craftbukkit.help.SimpleHelpMap#initializeCommands`, + // which would usually create a help topic for our plugin command. + // This is acutally kinda useful, because we can make sure we wouldn't override the help topic + // by making sure a help entry is still not there. + MockBukkit.loadWith(CommandHelpTestsPlugin.class, CommandHelpTestsPlugin.pluginYaml()); + + // Register conflicting CommandAPI commands + new CommandAPICommand("registeredCommand") + .withAliases("unregisteredAlias") + .executesPlayer(P_EXEC) + .register(namespace); + + new CommandAPICommand("unregisteredCommand") + .withAliases("registeredAlias") + .executesPlayer(P_EXEC) + .register(namespace); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer("APlayer"); + + // Unnamespaced help topics are taken by the plugin command by default + assertNull(server.getHelpMap().getHelpTopic("/registeredCommand")); + assertNull(server.getHelpMap().getHelpTopic("/registeredAlias")); + + // Commands that don't conflict still show up unnamespaced + assertHelpTopicCreated( + "unregisteredCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/unregisteredCommand + &6Aliases: &fregisteredAlias""", + player + ); + assertHelpTopicCreated( + "unregisteredAlias", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/registeredCommand + &6Aliases: &fregisteredCommand""", + player + ); + + // Our help topics defer to the namespaced version so they're at least accessible somewhere + assertHelpTopicCreated( + namespace + ":registeredCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/registeredCommand + &6Aliases: &funregisteredAlias""", + player + ); + assertHelpTopicCreated( + namespace + ":registeredAlias", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/unregisteredCommand + &6Aliases: &funregisteredCommand""", + player + ); + + // Namespaced versions of commands that don't conflict also show up as usual + assertHelpTopicCreated( + namespace + ":unregisteredCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/unregisteredCommand + &6Aliases: &fregisteredAlias""", + player + ); + assertHelpTopicCreated( + namespace + ":unregisteredAlias", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/registeredCommand + &6Aliases: &fregisteredCommand""", + player + ); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + // Test with permission normal and negated + void testOpPermissionsHidingHelp(boolean needsOP) { + CommandPermission permission = CommandPermission.OP; + permission = needsOP ? permission : permission.negate(); + + new CommandAPICommand("opCommand") + .withPermission(permission) + .executesPlayer(P_EXEC) + .register(); + + new CommandTree("opArgument") + .then(new StringArgument("string").withPermission(permission).executesPlayer(P_EXEC)) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer("APlayer"); + + // Player dose not have permission + player.setOp(!needsOP); + + // Without permission, usage is hidden + assertHelpTopicCreated( + "opCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin.""", + player + ); + assertHelpTopicCreated( + "opArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/opArgument""", + player + ); + + // `canSee` just checks the root node + assertFalse(server.getHelpMap().getHelpTopic("/opCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/opArgument").canSee(player)); + + // Player has permission + player.setOp(needsOP); + + // With permission, usage is visible + assertHelpTopicCreated( + "opCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/opCommand""", + player + ); + assertHelpTopicCreated( + "opArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f + - /opArgument + - /opArgument """, + player + ); + + assertTrue(server.getHelpMap().getHelpTopic("/opCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/opArgument").canSee(player)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + // Test with permission normal and negated + void testStringPermissionsHidingHelp(boolean needsPermission) { + CommandPermission permission = CommandPermission.fromString("permission"); + permission = needsPermission ? permission : permission.negate(); + + new CommandAPICommand("permissionCommand") + .withPermission(permission) + .executesPlayer(P_EXEC) + .register(); + + new CommandTree("permissionArgument") + .then(new StringArgument("string").withPermission(permission).executesPlayer(P_EXEC)) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer("APlayer"); + PermissionAttachment permissionAttachment = player.addAttachment(super.plugin); + + // Player dose not have permission + permissionAttachment.setPermission("permission", !needsPermission); + player.recalculatePermissions(); + + // Without permission, usage is hidden + assertHelpTopicCreated( + "permissionCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin.""", + player + ); + assertHelpTopicCreated( + "permissionArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/permissionArgument""", + player + ); + + // `canSee` just checks the root node + assertFalse(server.getHelpMap().getHelpTopic("/permissionCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/permissionArgument").canSee(player)); + + // Player has permission + permissionAttachment.setPermission("permission", needsPermission); + player.recalculatePermissions(); + + // With permission, usage is visible + assertHelpTopicCreated( + "permissionCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/permissionCommand""", + player + ); + assertHelpTopicCreated( + "permissionArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f + - /permissionArgument + - /permissionArgument """, + player + ); + + assertTrue(server.getHelpMap().getHelpTopic("/permissionCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/permissionArgument").canSee(player)); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java new file mode 100644 index 0000000000..083a296a97 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java @@ -0,0 +1,35 @@ +package dev.jorel.commandapi.test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; + +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.java.JavaPluginLoader; + +public class CommandHelpTestsPlugin extends JavaPlugin { + // Additional constructors required for MockBukkit + public CommandHelpTestsPlugin() { + super(); + } + + public CommandHelpTestsPlugin(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { + super(loader, description, dataFolder, file); + } + + public static InputStream pluginYaml() { + return new ByteArrayInputStream(""" + name: CommandHelpTestsPlugin + main: dev.jorel.commandapi.test.CommandHelpTestsPlugin + version: 0.0.1 + description: A mock Bukkit plugin for CommandAPI testing + author: Will Kroboth + website: https://www.jorel.dev/CommandAPI/ + api-version: 1.13 + commands: + registeredCommand: + registeredAlias: + """.getBytes()); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java index 9e772b2003..7a6e85ee99 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java @@ -7,12 +7,16 @@ import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.help.EditableHelpTopic; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.List; -import java.util.Optional; +import java.util.function.Predicate; import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; @@ -68,9 +72,9 @@ void testRegisterHelpInformation() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.NONE, - Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>("short description", "full description", new String[]{"usage 1", "usage 2", "usage 3"}), commandNode("command", true).build() ); @@ -84,10 +88,10 @@ void testRegisterOpPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.OP, - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.OP).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -100,10 +104,28 @@ void testRegisterStringPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.fromString("permission"), - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.fromString("permission")).build() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandTree("command") + .withRequirement(requirement) + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).requirements(requirement).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -116,7 +138,7 @@ void testRegisterOneAlias() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -128,7 +150,7 @@ void testRegisterTwoAliases() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -139,7 +161,7 @@ void testRegisterNamespace() { .executesPlayer(P_EXEC) .register("custom"); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -176,6 +198,51 @@ void testRegisterTwoBranches() { ); } + @Test + void testRegisterArgumentPermissions() { + new CommandTree("command") + .then( + new StringArgument("noPermission") + .then( + new StringArgument("opPermission") + .withPermission(CommandPermission.OP) + .then( + new StringArgument("stringPermission") + .withPermission(CommandPermission.fromString("permission")) + .executesPlayer(P_EXEC) + ) + ) + ) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("noPermission", StringArgument.class, false).withChildren( + node("opPermission", StringArgument.class, false).permission(CommandPermission.OP).withChildren( + node("stringPermission", StringArgument.class, true).permission(CommandPermission.fromString("permission")) + ))), + List.of("command:CommandTree", "noPermission:StringArgument", "opPermission:StringArgument", "stringPermission:StringArgument") + ); + } + + @Test + void testRegisterArgumentRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandTree("command") + .then(new StringArgument("string").withRequirement(requirement).executesPlayer(P_EXEC)) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("string", StringArgument.class, true).requirements(requirement) + ), + List.of("command:CommandTree", "string:StringArgument") + ); + } + @Test void testRegisterMultiLiteralArguments() { new CommandTree("command") @@ -368,8 +435,8 @@ void testRegisterTwoSeparateCommands() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); - RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); + RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); assertCreatedRegisteredCommands( command1.copyWithEmptyNamespace(), command1, @@ -443,32 +510,37 @@ void testRegisterMergeDifferentLengthBranches() { void testRegisterMergeNamespaces() { new CommandTree("command") .then(new LiteralArgument("first").executesPlayer(P_EXEC)) + .withAliases("first") .register("first"); new CommandTree("command") .then(new LiteralArgument("second").executesPlayer(P_EXEC)) + .withAliases("second") .register("second"); - RegisteredCommand first = simpleRegisteredCommand( + RegisteredCommand first = simpleRegisteredCommand( "command", "first", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first") - ) + ), + "first" ); - RegisteredCommand second = simpleRegisteredCommand( + RegisteredCommand second = simpleRegisteredCommand( "command", "second", commandNode("command", false).withChildren( node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "second" ); - RegisteredCommand merged = simpleRegisteredCommand( + RegisteredCommand merged = simpleRegisteredCommand( "command", "", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first"), node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "first", "second" ); assertCreatedRegisteredCommands(merged, first, second); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java index 86c61932db..401b8bfab7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java @@ -162,7 +162,7 @@ void testOnEnableRegisterAndUnregisterCommand() { short description &6Description: &ffull description &6Usage: &f/command - &6Aliases: &falias1, alias2"""), topic.getFullText(null)); + &6Aliases: &falias1, alias2"""), topic.getFullText(runCommandsPlayer)); // Make sure commands run correctly diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java index a0a1bc5105..cb0c316e78 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java @@ -7,8 +7,9 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.Optional; +import java.util.function.Predicate; +import org.bukkit.command.CommandSender; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -16,6 +17,7 @@ import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.RegisteredCommand.Node; +import dev.jorel.commandapi.help.EditableHelpTopic; public abstract class RegisteredCommandTestBase extends TestBase { @@ -39,7 +41,7 @@ public void tearDown() { @SafeVarargs public final void assertCreatedSimpleRegisteredCommand(String name, NodeBuilder args, List... argsAsStr) { - RegisteredCommand expectedCommand = simpleRegisteredCommand(name, "minecraft", args); + RegisteredCommand expectedCommand = simpleRegisteredCommand(name, "minecraft", args); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -47,10 +49,10 @@ public final void assertCreatedSimpleRegisteredCommand(String name, NodeBuilder assertEquals(Arrays.asList(argsAsStr), CommandAPI.getRegisteredCommands().get(0).rootNode().argsAsStr()); } - public RegisteredCommand simpleRegisteredCommand(String name, String namespace, NodeBuilder args, String... aliases) { - return new RegisteredCommand( - name, aliases, namespace, CommandPermission.NONE, - Optional.empty(), Optional.empty(), Optional.empty(), + public RegisteredCommand simpleRegisteredCommand(String name, String namespace, NodeBuilder args, String... aliases) { + return new RegisteredCommand<>( + name, aliases, namespace, + new EditableHelpTopic<>(), args.build() ); } @@ -60,8 +62,8 @@ public static NodeBuilder node(String name, Class clazz, boolean executable) return new NodeBuilder(name, clazz, executable); } - public static List children(NodeBuilder... children) { - List result = new ArrayList<>(children.length); + public static List> children(NodeBuilder... children) { + List> result = new ArrayList<>(children.length); for (NodeBuilder child : children) { result.add(child.build()); } @@ -73,7 +75,10 @@ public static List children(NodeBuilder... children) { private final boolean executable; private String helpString; - private final List children; + private CommandPermission permission = CommandPermission.NONE; + private Predicate requirements = sender -> true; + + private final List> children; public NodeBuilder(String nodeName, Class clazz, boolean executable) { this.nodeName = nodeName; @@ -89,6 +94,16 @@ public NodeBuilder helpString(String helpString) { return this; } + public NodeBuilder permission(CommandPermission permission) { + this.permission = permission; + return this; + } + + public NodeBuilder requirements(Predicate requirements) { + this.requirements = requirements; + return this; + } + public NodeBuilder withChildren(NodeBuilder... children) { for (NodeBuilder child : children) { this.children.add(child.build()); @@ -96,23 +111,25 @@ public NodeBuilder withChildren(NodeBuilder... children) { return this; } - public NodeBuilder withChildren(Node... children) { + @SafeVarargs + public final NodeBuilder withChildren(Node... children) { return withChildren(Arrays.asList(children)); } - public NodeBuilder withChildren(List children) { + public NodeBuilder withChildren(List> children) { this.children.addAll(children); return this; } - public Node build() { - return new Node(nodeName, className, helpString, executable, children); + public Node build() { + return new Node(nodeName, className, helpString, executable, permission, requirements, children); } } - public void assertCreatedRegisteredCommands(RegisteredCommand... commands) { - List expectedCommands = Arrays.asList(commands); - List actualCommands = CommandAPI.getRegisteredCommands(); + @SafeVarargs + public final void assertCreatedRegisteredCommands(RegisteredCommand... commands) { + List> expectedCommands = Arrays.asList(commands); + List> actualCommands = CommandAPI.getRegisteredCommands(); if (expectedCommands.size() != actualCommands.size()) { StringBuilder builder = new StringBuilder(); @@ -127,8 +144,8 @@ public void assertCreatedRegisteredCommands(RegisteredCommand... commands) { } for (int i = 0; i < expectedCommands.size(); i++) { - RegisteredCommand expectedCommand = expectedCommands.get(i); - RegisteredCommand actualCommand = actualCommands.get(i); + RegisteredCommand expectedCommand = expectedCommands.get(i); + RegisteredCommand actualCommand = actualCommands.get(i); if (!Objects.equals(expectedCommand, actualCommand)) { StringBuilder builder = new StringBuilder(); @@ -147,14 +164,14 @@ public void assertCreatedRegisteredCommands(RegisteredCommand... commands) { } } - private void addRegisteredCommandList(StringBuilder builder, List commands) { + private void addRegisteredCommandList(StringBuilder builder, List> commands) { if (commands.isEmpty()) { builder.append("[]"); return; } builder.append("[\n"); - for (RegisteredCommand command : commands) { + for (RegisteredCommand command : commands) { builder.append("\t"); builder.append(command); builder.append("\n"); diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index 8da4c35299..fedbaaf446 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -219,7 +219,7 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { // Nothing to do } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index a49151f55e..42e40ac687 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -153,7 +153,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames,terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index cef66a147b..766e5188ae 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -110,7 +110,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } From 2d1187a8afbdc4da9d698f23a1ba67cd0f1cc274 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 13 May 2024 13:28:46 -0400 Subject: [PATCH 25/42] Address a few SonarCloud code smells --- .../main/java/dev/jorel/commandapi/AbstractArgumentTree.java | 2 +- .../java/dev/jorel/commandapi/AbstractCommandAPICommand.java | 2 +- .../main/java/dev/jorel/commandapi/AbstractCommandTree.java | 2 +- .../commandapi/commandnodes/PreviewableArgumentBuilder.java | 2 +- .../java/dev/jorel/commandapi/help/EditableHelpTopic.java | 2 +- .../kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt | 4 ++-- .../jorel/commandapi/test/arguments/ArgumentLiteralTests.java | 2 +- .../commandapi/test/arguments/ArgumentMultiLiteralTests.java | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index a510841a98..810b23b218 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -122,7 +122,7 @@ public void buildBrigadierNode( // Collect children into our own list NodeInformation newPreviousNodeInformation = new NodeInformation<>( previousNodeInformation.lastCommandNodes(), - children -> childrenNodeInformation.addAll(children) + childrenNodeInformation::addAll ); // We need a new list so each branch acts independently diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 81db8a8bbe..8f2cfb12fd 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -245,7 +245,7 @@ protected List> createArgumentNod // Create arguments if (hasAnyArguments()) { - NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), childrenNodes::addAll); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index 4ba7fb0a09..70f53dae99 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -103,7 +103,7 @@ protected List> createArgumentNod // Build branches for (AbstractArgumentTree argument : arguments) { // We need new previousArguments lists for each branch so they don't interfere - NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), childrenNodes::addAll); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java index ad6d11f950..e5e7a16145 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java @@ -65,7 +65,7 @@ public String getName() { } public PreviewableCommandNode build() { - final PreviewableCommandNode result = new PreviewableCommandNode( + final PreviewableCommandNode result = new PreviewableCommandNode<>( previewableFunction, legacy, isListed, getName(), getType(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider() diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java index 4a47f1c003..367275a966 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java @@ -12,7 +12,7 @@ * An {@link CommandAPIHelpTopic} that can have its short description, full description, and usage edited. */ public class EditableHelpTopic implements CommandAPIHelpTopic { - private ShortDescriptionGenerator shortDescription = () -> Optional.empty(); + private ShortDescriptionGenerator shortDescription = Optional::empty; private FullDescriptionGenerator fullDescription = forWho -> Optional.empty(); private UsageGenerator usage = (forWho, argumentTree) -> Optional.empty(); diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index 07b06ed38e..e4e92522c5 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -84,7 +84,7 @@ inline fun CommandTree.lootTableArgument(nodeName: String, block: Argument<*>.() inline fun CommandTree.mathOperationArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(MathOperationArgument(nodeName).apply(block)) inline fun CommandTree.namespacedKeyArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(NamespacedKeyArgument(nodeName).apply(block)) inline fun CommandTree.particleArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(ParticleArgument(nodeName).apply(block)) -inline fun CommandTree.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = +inline fun CommandTree.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandTree = if (useNamespacedKey) then(PotionEffectArgument.NamespacedKey(nodeName).apply(block)) else then(PotionEffectArgument(nodeName).apply(block)) inline fun CommandTree.recipeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(RecipeArgument(nodeName).apply(block)) @@ -188,7 +188,7 @@ inline fun Argument<*>.lootTableArgument(nodeName: String, block: Argument<*>.() inline fun Argument<*>.mathOperationArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MathOperationArgument(nodeName).apply(block)) inline fun Argument<*>.namespacedKeyArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(NamespacedKeyArgument(nodeName).apply(block)) inline fun Argument<*>.particleArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(ParticleArgument(nodeName).apply(block)) -inline fun Argument<*>.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = +inline fun Argument<*>.potionEffectArgument(nodeName: String, useNamespacedKey: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = if (useNamespacedKey) then(PotionEffectArgument.NamespacedKey(nodeName).apply(block)) else then(PotionEffectArgument(nodeName).apply(block)) inline fun Argument<*>.recipeArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(RecipeArgument(nodeName).apply(block)) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentLiteralTests.java index f1d3e272ec..89edf2061c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentLiteralTests.java @@ -83,7 +83,7 @@ void executionTestWithLiteralArgumentListed() { } @Test - public void executionTestWithLiteralArgumentListedAndNodeName() { + void executionTestWithLiteralArgumentListedAndNodeName() { Mut results = Mut.of(); new CommandAPICommand("test") diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java index 16b6f7e71f..b72f375311 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java @@ -21,7 +21,7 @@ /** * Tests for the {@link MultiLiteralArgument} */ -public class ArgumentMultiLiteralTests extends TestBase { +class ArgumentMultiLiteralTests extends TestBase { /********* * Setup * From c874ab53cd4d058a74b03e6ecbecacba902c6d98 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:22:07 -0400 Subject: [PATCH 26/42] Executor Rewrite Note: This commit makes this branch backwards incompatible, especially for non-DSL Kotlin code (see `commandapi-documentation-code/.../Examples.kt`). "Standard" Java and DSL API code usage looks the same, but I'm pretty sure plugins will need to recompile due to changes to the `FunctionalInterface`s. There are also some smaller public API changes, mentioned below. Notable changes: - Removed `AbstractCommandSender` and all its implemenations (i.e. the `dev.jorel.commandapi.commandsenders` package is completely gone) - Logic in `CommandAPIHandler#generateBrigadierRequirements` for checking if a sender satisfies a `CommandPermission` was moved to `CommandAPIPlatform#getPermissionCheck` - Previously, methods in `AbstractCommandSender` provided access to `hasPermission` and `isOp`. `CommandAPIBukkit` and `CommandAPIVelocity` now handle these definitions. - `CommandPermission.TRUE()` and `CommandPermission.FALSE()` added for computing short circuits when combining permissions and requirements - `PreviewInfo` now has the generic parameter `Player` rather than an `AbstractPlayer` - Backwards-incompatible (`(Player) player.getSource()` becomes `player`) - Generic parameter propogates to `PreviewableFunction` and `Previewable` - `CommandAPIPlatform` methods for convert Brigadier Source to CommandSender simplified - `getSenderForCommand` removed - just pass `CommandContext#getSource` into `getCommandSenderFromCommandSource` - `forceNative` parameter was only used on Bukkit, which is now handled by `NMS#getNativeProxyCommandSender` (more on that below) - `wrapCommandSender` removed - Logic from https://github.com/JorelAli/CommandAPI/pull/580 removed, since wrapping the NullCommandSender no longer needs to be handled as a special case - Wrapping the sender no longer necessary for `getBrigadierSourceFromCommandSender` - `CommandAPIVelocity` now does nothing in these methods :P - `CommandAPIExecutor` reworked - `ExecutorType` moved to platform modules (so Velocity can no longer try to define sender types that don't exist) - Detecting whether a certain executor can be run by the given class delegated to `BukkitTypedExecutor` and `VelocityTypedExecutor` - Priority of executors now depends on order of calling the `executes` methods (i.e the priority listed here https://commandapi.jorel.dev/9.4.1/normalexecutors.html#multiple-command-executor-implementations no longer applies) - Normal executors are no longer ignored if resulting executors exist (not sure if this was a bug or intended) - Tweaked `ExecutionInfo` - Added `CommandContext cmdCtx` to `ExecutionInfo` - This allows passing the information needed for a `NATIVE` executor on Bukkit to create a `NativeProxyCommandSender` - Uses `NMS#getNativeProxyCommandSender`, which was adapted from the removed `getSenderForCommand` method - Note: conflicts with https://github.com/JorelAli/CommandAPI/pull/478, though should be easily resolved - Note: Velocity can define the `CommandSource` object, though Bukkit has it as `?`. This makes it hard for non DSL Kotlin to infer the type parameters for some reason, which is annoying because you now need to specify the type parameter. I don't know if there's a better way to handle that. - TODO: Make sure depdendents can still use `ExecutionInfo` without including Brigadier as a dependency - `ExecutionInfo` is now a record (`BukkitExecutionInfo` and `VelocityExecutionInfo` removed) - Simplified `dev.jorel.commandapi.executors` package - Platform and sender specific executor classes (e.g. `PlayerCommandExecutor` and `EntityResultingExecutionInfo`) removed - Just the functional interfaces `NormalExecutorInfo`, `NormalExecutor`, `ResultingExecutorInfo`, and `ResultingExecutor` now - `BukkitExecutable` and `VelocityExecutable` link different command sender classes as type parameters to the `ExecutorType` enum values TODO: - Add executor tests to `dev/dev` to ensure same behavior - Especially for `PROXY` and `NATIVE` senders - Especially for non-DSL Kotlin to see how its lamdba type inference works - Update documentation --- .../java/dev/jorel/commandapi/Brigadier.java | 15 +- .../java/dev/jorel/commandapi/CommandAPI.java | 6 +- .../jorel/commandapi/CommandAPIExecutor.java | 164 +++------ .../jorel/commandapi/CommandAPIHandler.java | 70 +--- .../jorel/commandapi/CommandAPIPlatform.java | 50 +-- .../jorel/commandapi/CommandPermission.java | 72 ++++ .../java/dev/jorel/commandapi/Executable.java | 10 +- .../jorel/commandapi/ExecutableCommand.java | 5 +- .../jorel/commandapi/PlatformExecutable.java | 8 +- .../arguments/AbstractArgument.java | 4 +- .../commandapi/arguments/PreviewInfo.java | 6 +- .../commandapi/arguments/Previewable.java | 10 +- .../PreviewableArgumentBuilder.java | 6 +- .../commandnodes/PreviewableCommandNode.java | 6 +- .../AbstractBlockCommandSender.java | 4 - .../commandsenders/AbstractCommandSender.java | 29 -- .../AbstractConsoleCommandSender.java | 4 - .../commandsenders/AbstractEntity.java | 4 - ...stractFeedbackForwardingCommandSender.java | 4 - .../AbstractNativeProxyCommandSender.java | 4 - .../commandsenders/AbstractPlayer.java | 4 - .../AbstractProxiedCommandSender.java | 4 - .../AbstractRemoteConsoleCommandSender.java | 4 - .../commandapi/executors/ExecutionInfo.java | 40 ++- .../commandapi/executors/NormalExecutor.java | 67 ++-- .../executors/NormalExecutorInfo.java | 22 ++ .../executors/ResultingExecutor.java | 67 ++-- .../executors/ResultingExecutorInfo.java | 22 ++ .../commandapi/executors/TypedExecutor.java | 53 +-- .../wrappers/PreviewableFunction.java | 6 +- .../commandapi/examples/kotlin/Examples.kt | 263 +++++++------- .../commandapi/examples/kotlin/Examples.kt | 4 +- .../jorel/commandapi/kotlindsl/ExecutorDSL.kt | 108 ++---- .../jorel/commandapi/kotlindsl/ExecutorDSL.kt | 27 +- .../jorel/commandapi/BukkitExecutable.java | 328 +++++++----------- .../jorel/commandapi/CommandAPIBukkit.java | 101 ++---- .../java/dev/jorel/commandapi/Converter.java | 4 +- .../commandapi/PaperImplementations.java | 17 - .../arguments/AdventureChatArgument.java | 15 +- .../commandapi/arguments/ChatArgument.java | 15 +- .../commandapi/arguments/CommandArgument.java | 3 +- .../commandapi/arguments/CustomArgument.java | 2 +- .../arguments/ListArgumentCommon.java | 2 +- .../BukkitBlockCommandSender.java | 28 -- .../commandsenders/BukkitCommandSender.java | 6 - .../BukkitConsoleCommandSender.java | 28 -- .../commandsenders/BukkitEntity.java | 28 -- ...BukkitFeedbackForwardingCommandSender.java | 28 -- .../BukkitNativeProxyCommandSender.java | 27 -- .../commandsenders/BukkitPlayer.java | 27 -- .../BukkitProxiedCommandSender.java | 27 -- .../BukkitRemoteConsoleCommandSender.java | 27 -- .../executors/BukkitExecutionInfo.java | 30 -- .../executors/BukkitNormalTypedExecutor.java | 33 ++ .../BukkitResultingTypedExecutor.java | 31 ++ .../executors/BukkitTypedExecutor.java | 58 ++++ .../CommandBlockCommandExecutor.java | 60 ---- .../executors/CommandBlockExecutionInfo.java | 28 -- .../CommandBlockResultingCommandExecutor.java | 65 ---- .../CommandBlockResultingExecutionInfo.java | 27 -- .../executors/CommandExecutionInfo.java | 28 -- .../commandapi/executors/CommandExecutor.java | 60 ---- .../executors/ConsoleCommandExecutor.java | 60 ---- .../executors/ConsoleExecutionInfo.java | 28 -- .../ConsoleResultingCommandExecutor.java | 62 ---- .../ConsoleResultingExecutionInfo.java | 26 -- .../executors/EntityCommandExecutor.java | 60 ---- .../executors/EntityExecutionInfo.java | 28 -- .../EntityResultingCommandExecutor.java | 61 ---- .../EntityResultingExecutionInfo.java | 27 -- ...CommandExecutor.java => ExecutorType.java} | 70 ++-- .../FeedbackForwardingCommandExecutor.java | 60 ---- .../FeedbackForwardingExecutionInfo.java | 29 -- ...ackForwardingResultingCommandExecutor.java | 65 ---- ...dbackForwardingResultingExecutionInfo.java | 28 -- .../executors/NativeCommandExecutor.java | 60 ---- .../executors/NativeExecutionInfo.java | 28 -- .../NativeResultingCommandExecutor.java | 61 ---- .../NativeResultingExecutionInfo.java | 26 -- .../executors/PlayerCommandExecutor.java | 59 ---- .../executors/PlayerExecutionInfo.java | 27 -- .../PlayerResultingCommandExecutor.java | 62 ---- .../PlayerResultingExecutionInfo.java | 24 -- .../executors/ProxyExecutionInfo.java | 28 -- .../ProxyResultingCommandExecutor.java | 62 ---- .../ProxyResultingExecutionInfo.java | 27 -- .../RemoteConsoleCommandExecutor.java | 38 -- .../executors/RemoteConsoleExecutionInfo.java | 27 -- ...RemoteConsoleResultingCommandExecutor.java | 40 --- .../RemoteConsoleResultingExecutionInfo.java | 28 -- .../ResultingCommandExecutionInfo.java | 27 -- .../executors/ResultingCommandExecutor.java | 63 ---- .../java/dev/jorel/commandapi/nms/NMS.java | 3 + .../jorel/commandapi/wrappers/Preview.java | 7 - .../commandapi/wrappers/PreviewLegacy.java | 7 - .../wrappers/SimpleFunctionWrapper.java | 2 +- .../dev/jorel/commandapi/nms/NMS_1_16_R3.java | 41 +-- .../jorel/commandapi/nms/NMS_1_17_Common.java | 33 +- .../dev/jorel/commandapi/nms/NMS_1_18_R2.java | 33 +- .../dev/jorel/commandapi/nms/NMS_1_18_R1.java | 33 +- .../jorel/commandapi/nms/NMS_1_19_Common.java | 24 +- .../NMS_1_19_Common_ChatPreviewHandler.java | 9 +- .../jorel/commandapi/nms/NMS_1_19_3_R2.java | 33 +- .../jorel/commandapi/nms/NMS_1_19_4_R3.java | 34 +- .../dev/jorel/commandapi/nms/NMS_1_20_R2.java | 34 +- .../dev/jorel/commandapi/nms/NMS_1_20_R3.java | 36 +- .../dev/jorel/commandapi/nms/NMS_1_20_R4.java | 36 +- .../dev/jorel/commandapi/nms/NMS_1_20_R1.java | 34 +- .../dev/jorel/commandapi/nms/NMS_1_21_R1.java | 36 +- .../dev/jorel/commandapi/nms/NMS_Common.java | 15 +- .../dev/jorel/commandapi/test/TestBase.kt | 4 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../dev/jorel/commandapi/test/MockNMS.java | 31 +- .../test/CommandConvertedTests.java | 4 +- .../test/CommandRegistrationTests.java | 6 +- .../test/RegisteredCommandTestBase.java | 2 +- .../dev/jorel/commandapi/test/TestBase.java | 5 +- .../test/arguments/ArgumentTests.java | 5 +- .../jorel/commandapi/CommandAPIVelocity.java | 59 ++-- .../jorel/commandapi/VelocityExecutable.java | 163 +++------ .../commandsenders/VelocityCommandSender.java | 6 - .../VelocityConsoleCommandSender.java | 27 -- .../commandsenders/VelocityPlayer.java | 27 -- .../executors/CommandExecutionInfo.java | 28 -- .../commandapi/executors/CommandExecutor.java | 39 --- .../executors/ConsoleCommandExecutor.java | 59 ---- .../executors/ConsoleExecutionInfo.java | 28 -- .../ConsoleResultingCommandExecutor.java | 59 ---- .../ConsoleResultingExecutionInfo.java | 27 -- .../commandapi/executors/ExecutorType.java | 46 +-- .../executors/PlayerCommandExecutor.java | 59 ---- .../executors/PlayerExecutionInfo.java | 28 -- .../PlayerResultingCommandExecutor.java | 59 ---- .../PlayerResultingExecutionInfo.java | 27 -- .../ResultingCommandExecutionInfo.java | 27 -- .../executors/ResultingCommandExecutor.java | 43 --- .../executors/VelocityExecutionInfo.java | 30 -- .../VelocityNormalTypedExecutor.java | 32 ++ .../VelocityResultingTypedExecutor.java | 30 ++ .../executors/VelocityTypedExecutor.java | 35 ++ 147 files changed, 1311 insertions(+), 3978 deletions(-) delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractBlockCommandSender.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractCommandSender.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractConsoleCommandSender.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractEntity.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractFeedbackForwardingCommandSender.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractNativeProxyCommandSender.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractPlayer.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractProxiedCommandSender.java delete mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractRemoteConsoleCommandSender.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitBlockCommandSender.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitCommandSender.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitConsoleCommandSender.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitEntity.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitFeedbackForwardingCommandSender.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitNativeProxyCommandSender.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitPlayer.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitProxiedCommandSender.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitRemoteConsoleCommandSender.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitExecutionInfo.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingExecutionInfo.java rename commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/{ProxyCommandExecutor.java => ExecutorType.java} (54%) delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/Preview.java delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewLegacy.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityCommandSender.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityConsoleCommandSender.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityPlayer.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java rename {commandapi-core => commandapi-platforms/commandapi-velocity/commandapi-velocity-core}/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java (65%) delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java delete mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityExecutionInfo.java create mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java create mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java create mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java b/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java index 99b0a6e423..5b137c0e87 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java @@ -32,7 +32,6 @@ import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.ArgumentSuggestions; import dev.jorel.commandapi.arguments.Literal; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import java.util.Collections; import java.util.List; @@ -206,19 +205,17 @@ Command fromCommand(AbstractCommandAPICommand comman */ public static Object getBrigadierSourceFromCommandSender(CommandSender sender) { CommandAPIPlatform platform = (CommandAPIPlatform) CommandAPIHandler.getInstance().getPlatform(); - return platform.getBrigadierSourceFromCommandSender(platform.wrapCommandSender(sender)); + return platform.getBrigadierSourceFromCommandSender(sender); } /** - * Returns a Bukkit CommandSender from a Brigadier CommandContext + * Returns the current platform's command sender from a Brigadier CommandContext * * @param cmdCtx the command context to get the CommandSender from - * @return a Bukkit CommandSender from the provided Brigadier CommandContext + * @return a command sender from the provided Brigadier CommandContext */ - public static CommandSender getCommandSenderFromContext(CommandContext cmdCtx) { - CommandAPIPlatform platform = (CommandAPIPlatform) CommandAPIHandler.getInstance().getPlatform(); - // For some reason putting this on one line doesn't work - very weird - AbstractCommandSender abstractSender = platform.getSenderForCommand(cmdCtx, false); - return abstractSender.getSource(); + public static CommandSender getCommandSenderFromContext(CommandContext cmdCtx) { + CommandAPIPlatform platform = (CommandAPIPlatform) CommandAPIHandler.getInstance().getPlatform(); + return platform.getCommandSenderFromCommandSource((Source) cmdCtx.getSource()); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java index 2eccd9508e..2d94bc121a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java @@ -3,7 +3,6 @@ import com.mojang.brigadier.Message; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import dev.jorel.commandapi.commandsenders.AbstractPlayer; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import java.util.ArrayList; @@ -232,14 +231,13 @@ public static void reloadDatapacks() { } /** - * Updates the requirements required for a given player to execute a command. + * Updates the player's view of the requirements for them to execute a command. * * @param player the player whose requirements should be updated */ public static void updateRequirements(Player player) { - @SuppressWarnings("unchecked") CommandAPIPlatform platform = (CommandAPIPlatform) CommandAPIHandler.getInstance().getPlatform(); - platform.updateRequirements((AbstractPlayer) platform.wrapCommandSender(player)); + platform.updateRequirements(player); } // Produce WrapperCommandSyntaxException diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIExecutor.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIExecutor.java index e4c616ccdf..a0c8dd041a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIExecutor.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIExecutor.java @@ -22,18 +22,14 @@ import java.util.ArrayList; import java.util.List; -import java.util.NoSuchElementException; +import java.util.Optional; import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import dev.jorel.commandapi.commandsenders.*; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import dev.jorel.commandapi.executors.ExecutionInfo; -import dev.jorel.commandapi.executors.ExecutorType; -import dev.jorel.commandapi.executors.NormalExecutor; -import dev.jorel.commandapi.executors.ResultingExecutor; import dev.jorel.commandapi.executors.TypedExecutor; /** @@ -42,143 +38,67 @@ * executors) and switches its execution implementation based on the provided * command executor types. * - * @param The CommandSender for this executor - * @param The AbstractCommandSender that wraps the CommandSender + * @param The class for running platform commands */ -public class CommandAPIExecutor -/// @endcond -> { +public class CommandAPIExecutor { - private List> normalExecutors; - private List> resultingExecutors; + // Setup executors + private List> executors; public CommandAPIExecutor() { - normalExecutors = new ArrayList<>(); - resultingExecutors = new ArrayList<>(); + this.executors = new ArrayList<>(); } - @SuppressWarnings("unchecked") - public void addNormalExecutor(NormalExecutor executor) { - this.normalExecutors.add((NormalExecutor) executor); + public void addExecutor(TypedExecutor executor) { + this.executors.add(executor); } - @SuppressWarnings("unchecked") - public void addResultingExecutor(ResultingExecutor executor) { - this.resultingExecutors.add((ResultingExecutor) executor); + public void setExecutors(List> executors) { + this.executors = executors; } - public int execute(ExecutionInfo info) throws CommandSyntaxException { - // Parse executor type - if (!resultingExecutors.isEmpty()) { - // Run resulting executor - try { - return execute(resultingExecutors, info); - } catch (WrapperCommandSyntaxException e) { - throw e.getException(); - } catch (Throwable ex) { - CommandAPI.getLogger().severe("Unhandled exception executing '" + info.args().fullInput() + "'", ex); - if (ex instanceof Exception) { - throw ex; - } else { - throw new RuntimeException(ex); - } - } - } else { - // Run normal executor - try { - return execute(normalExecutors, info); - } catch (WrapperCommandSyntaxException e) { - throw e.getException(); - } catch (Throwable ex) { - CommandAPI.getLogger().severe("Unhandled exception executing '" + info.args().fullInput() + "'", ex); - if (ex instanceof Exception) { - throw ex; - } else { - throw new RuntimeException(ex); - } - } - } - } - - private int execute(List> executors, ExecutionInfo info) - throws WrapperCommandSyntaxException { - if (isForceNative()) { - return execute(executors, info, ExecutorType.NATIVE); - } else if (info.senderWrapper() instanceof AbstractPlayer && matches(executors, ExecutorType.PLAYER)) { - return execute(executors, info, ExecutorType.PLAYER); - } else if (info.senderWrapper() instanceof AbstractEntity && matches(executors, ExecutorType.ENTITY)) { - return execute(executors, info, ExecutorType.ENTITY); - } else if (info.senderWrapper() instanceof AbstractConsoleCommandSender && matches(executors, ExecutorType.CONSOLE)) { - return execute(executors, info, ExecutorType.CONSOLE); - } else if (info.senderWrapper() instanceof AbstractBlockCommandSender && matches(executors, ExecutorType.BLOCK)) { - return execute(executors, info, ExecutorType.BLOCK); - } else if (info.senderWrapper() instanceof AbstractProxiedCommandSender && matches(executors, ExecutorType.PROXY)) { - return execute(executors, info, ExecutorType.PROXY); - } else if (info.senderWrapper() instanceof AbstractRemoteConsoleCommandSender && matches(executors, ExecutorType.REMOTE)) { - return execute(executors, info, ExecutorType.REMOTE); - } else if (info.senderWrapper() instanceof AbstractFeedbackForwardingCommandSender && matches(executors, ExecutorType.FEEDBACK_FORWARDING)) { - return execute(executors, info, ExecutorType.FEEDBACK_FORWARDING); - } else if (matches(executors, ExecutorType.ALL)) { - return execute(executors, info, ExecutorType.ALL); - } else { - throw new WrapperCommandSyntaxException(new SimpleCommandExceptionType( - new LiteralMessage(CommandAPI.getConfiguration().getMissingImplementationMessage() - .replace("%s", info.sender().getClass().getSimpleName().toLowerCase()) - .replace("%S", info.sender().getClass().getSimpleName()))).create()); - } - } - - private int execute(List> executors, - ExecutionInfo info, ExecutorType type) throws WrapperCommandSyntaxException { - for (TypedExecutor executor : executors) { - if (executor.getType() == type) { - return executor.executeWith(info); - } - } - throw new NoSuchElementException("Executor had no valid executors for type " + type.toString()); - } - - public List> getNormalExecutors() { - return normalExecutors; - } - - public List> getResultingExecutors() { - return resultingExecutors; + public List> getExecutors() { + return this.executors; } public boolean hasAnyExecutors() { - return !(normalExecutors.isEmpty() && resultingExecutors.isEmpty()); - } - - public boolean isForceNative() { - return matches(normalExecutors, ExecutorType.NATIVE) || matches(resultingExecutors, ExecutorType.NATIVE); + return !executors.isEmpty(); } - private boolean matches(List> executors, ExecutorType type) { - for (TypedExecutor executor : executors) { - if (executor.getType() == type) { - return true; + // Use executors + public int execute(ExecutionInfo info) throws CommandSyntaxException { + try { + for (TypedExecutor executor : executors) { + Optional result = tryExecutor(executor, info); + + if (result.isPresent()) return result.get(); + } + } catch (WrapperCommandSyntaxException e) { + throw e.getException(); + } catch (Throwable ex) { + CommandAPI.getLogger().severe("Unhandled exception executing '" + info.args().fullInput() + "'", ex); + if (ex instanceof Exception) { + throw ex; + } else { + throw new RuntimeException(ex); } } - return false; - } - CommandAPIExecutor mergeExecutor(CommandAPIExecutor executor) { - CommandAPIExecutor result = new CommandAPIExecutor<>(); - result.normalExecutors = new ArrayList<>(normalExecutors); - result.resultingExecutors = new ArrayList<>(resultingExecutors); - result.normalExecutors.addAll(executor.normalExecutors); - result.resultingExecutors.addAll(executor.resultingExecutors); - return result; + // Executor not found + throw new SimpleCommandExceptionType(new LiteralMessage( + CommandAPI.getConfiguration().getMissingImplementationMessage() + .replace("%s", info.sender().getClass().getSimpleName().toLowerCase()) + .replace("%S", info.sender().getClass().getSimpleName()) + )).create(); } - public void setNormalExecutors(List> normalExecutors) { - this.normalExecutors = normalExecutors; - } + // This needs to be extracted into another method to name the `Sender` and `Source` generic, which allows `executeWith` to accept the converted info + private Optional tryExecutor(TypedExecutor executor, ExecutionInfo info) throws WrapperCommandSyntaxException { + ExecutionInfo convertedInfo = executor.tryForSender((ExecutionInfo) info); - public void setResultingExecutors(List> resultingExecutors) { - this.resultingExecutors = resultingExecutors; + if (convertedInfo != null) { + return Optional.of(executor.executeWith(convertedInfo)); + } + return Optional.empty(); } } \ No newline at end of file diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index 5f8ab70478..abc1544737 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -44,7 +44,6 @@ import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.*; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.CommandConflictException; import dev.jorel.commandapi.executors.CommandArguments; import dev.jorel.commandapi.executors.ExecutionInfo; @@ -271,7 +270,7 @@ private void ensureNoCommandConflict(CommandNode nodeToRegister, Command * @param executor Code to run when the command is executed. * @return A Brigadier Command object that runs the given execution with the given arguments as input. */ - public Command generateBrigadierCommand(List args, CommandAPIExecutor> executor) { + public Command generateBrigadierCommand(List args, CommandAPIExecutor executor) { // We need to make sure our arguments list is never changed // If we just used the reference to the list, the caller might add arguments that aren't actually previous // arguments for this suggestion node, and we would be confused because the new arguments don't exist @@ -279,25 +278,10 @@ public Command generateBrigadierCommand(List args, CommandAPIE // Generate our command from executor return cmdCtx -> { // Construct the execution info - AbstractCommandSender sender = platform.getSenderForCommand(cmdCtx, executor.isForceNative()); + CommandSender sender = platform.getCommandSenderFromCommandSource(cmdCtx.getSource()); CommandArguments commandArguments = argsToCommandArgs(cmdCtx, immutableArguments); - ExecutionInfo> executionInfo = new ExecutionInfo<>() { - @Override - public CommandSender sender() { - return sender.getSource(); - } - - @Override - public AbstractCommandSender senderWrapper() { - return sender; - } - - @Override - public CommandArguments args() { - return commandArguments; - } - }; + ExecutionInfo executionInfo = new ExecutionInfo<>(sender, commandArguments, cmdCtx); // Apply the executor return executor.execute(executionInfo); @@ -375,7 +359,7 @@ public SuggestionProvider generateBrigadierSuggestions(List pr return (context, builder) -> { // Construct the suggestion info SuggestionInfo suggestionInfo = new SuggestionInfo<>( - platform.getCommandSenderFromCommandSource(context.getSource()).getSource(), + platform.getCommandSenderFromCommandSource(context.getSource()), argsToCommandArgs(context, immutableArguments), builder.getInput(), builder.getRemaining() ); @@ -393,44 +377,24 @@ public SuggestionProvider generateBrigadierSuggestions(List pr * @return A Predicate that makes sure a Brigadier source object satisfies the given permission and arbitrary requirements. */ public Predicate generateBrigadierRequirements(CommandPermission permission, Predicate requirements) { + // If requirements are always false, result is always false + if (requirements == CommandPermission.FALSE()) return CommandPermission.FALSE(); + // Find the intial check for the given CommandPermission - Predicate> senderCheck; - if (permission.equals(CommandPermission.NONE)) { - // No permissions always passes - senderCheck = null; - } else if (permission.equals(CommandPermission.OP)) { - // Check op status - senderCheck = AbstractCommandSender::isOp; - } else { - Optional permissionStringWrapper = permission.getPermission(); - if (permissionStringWrapper.isPresent()) { - String permissionString = permissionStringWrapper.get(); - // check permission - senderCheck = sender -> sender.hasPermission(permissionString); - } else { - // No permission always passes - senderCheck = null; - } - } + Predicate senderCheck = platform.getPermissionCheck(permission); - if (senderCheck == null) { - // Short circuit permissions check if it doesn't depend on source - if (permission.isNegated()) { - // A negated NONE permission never passes - return source -> false; - } else { - // Only need to check the requirements - return source -> requirements.test(platform.getCommandSenderFromCommandSource(source).getSource()); - } - } + // Merge with requirements (unless its always true, which wouldn't add anything) + if (requirements != CommandPermission.TRUE()) senderCheck = senderCheck.and(requirements); - // Negate permission check if appropriate - Predicate> finalSenderCheck = permission.isNegated() ? senderCheck.negate() : senderCheck; + // Short circuit if the final test is always true or false + if (senderCheck == CommandPermission.TRUE()) return CommandPermission.TRUE(); + if (senderCheck == CommandPermission.FALSE()) return CommandPermission.FALSE(); - // Merge permission check and requirements + // Otherwise, convert Brigadier Source to CommandSender and run the check + final Predicate finalSenderCheck = senderCheck; return source -> { - AbstractCommandSender sender = platform.getCommandSenderFromCommandSource(source); - return finalSenderCheck.test(sender) && requirements.test(sender.getSource()); + CommandSender sender = platform.getCommandSenderFromCommandSource(source); + return finalSenderCheck.test(sender); }; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index 967c05fc07..7b4e4704d2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -2,17 +2,15 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.AbstractPlayer; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.function.Predicate; /** * @param The implementation of AbstractArgument used for the platform @@ -45,40 +43,22 @@ public interface CommandAPIPlatform getSenderForCommand(CommandContext cmdCtx, boolean forceNative); - - /** - * Converts the class used by Brigadier when running commands into an AbstractCommandSender wrapping the platform's CommandSender + * Converts the class used by Brigadier when running commands into the platform's CommandSender * * @param source The Brigadier source object - * @return An AbstractCommandSender wrapping the CommandSender represented by the source object + * @return The CommandSender represented by the source object */ - public abstract AbstractCommandSender getCommandSenderFromCommandSource(Source source); + public abstract CommandSender getCommandSenderFromCommandSource(Source source); /** - * Converts a CommandSender wrapped in an AbstractCommandSender to an object Brigadier can use when running its commands + * Converts a CommandSender to an object Brigadier can use when running its commands * - * @param sender The CommandSender to convert, wrapped in an AbstractCommandSender + * @param sender The CommandSender to convert * @return The Brigadier Source object represented by the sender */ - public abstract Source getBrigadierSourceFromCommandSender(AbstractCommandSender sender); - - /** - * Wraps a CommandSender in an AbstractCommandSender class, the inverse operation to {@link AbstractCommandSender#getSource()} - * - * @param sender The CommandSender to wrap - * @return An AbstractCommandSender with a class appropriate to the underlying class of the CommandSender - */ - public abstract AbstractCommandSender wrapCommandSender(CommandSender sender); + public abstract Source getBrigadierSourceFromCommandSender(CommandSender sender); // Some commands have existing suggestion providers public abstract SuggestionProvider getSuggestionProvider(SuggestionProviders suggestionProvider); @@ -196,13 +176,21 @@ public void severe(String message, Throwable throwable) { public abstract void reloadDataPacks(); /** - * Updates the requirements required for a given player to execute a command. + * Updates the player's view of the requirements for them to execute a command. * - * @param player the player to update + * @param player the player whose requirements should be updated */ - public abstract void updateRequirements(AbstractPlayer player); + public abstract void updateRequirements(CommandSender player); public abstract Argument newConcreteMultiLiteralArgument(String nodeName, String[] literals); public abstract Argument newConcreteLiteralArgument(String nodeName, String literal); + + /** + * Generates a {@link Predicate} that evaluates whether a command sender meets the given permission. + * + * @param permission The {@link CommandPermission} to check for. + * @return A {@link Predicate} that tests if a command sender meets the given permission. + */ + public abstract Predicate getPermissionCheck(CommandPermission permission); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java index 962da1d689..fc89a84c20 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.Optional; +import java.util.function.Predicate; /** * A representation of permission nodes for commands. Represents permission @@ -51,6 +52,77 @@ private enum PermissionNode { */ public static final CommandPermission OP = new CommandPermission(PermissionNode.OP); + // Always true and always false Predicates allow us to short-circuit when combining predicates + // Unfortunately Java dosen't seem to have something like this built-in + // See https://bugs.openjdk.org/browse/JDK-8067971 + /** + * Returns a singleton {@link Predicate} that always returns true. When modifying this predicate + * using {@link Predicate#and(Predicate)}, {@link Predicate#negate()}, or {@link Predicate#or(Predicate)}, + * the result is short-circuited to avoid unecessarily creating and nesting predicate objects. + * + * @return A {@link Predicate} that always returns true. + */ + public static final Predicate TRUE() { + return (Predicate) TRUE; + } + + private static final Predicate TRUE = new Predicate<>() { + @Override + public boolean test(Object t) { + return true; + } + + @Override + public Predicate and(Predicate other) { + return Objects.requireNonNull(other); + } + + @Override + public Predicate negate() { + return FALSE; + } + + @Override + public Predicate or(Predicate other) { + Objects.requireNonNull(other); + return TRUE; + } + }; + + /** + * Returns a singleton {@link Predicate} that always returns false. When modifying this predicate + * using {@link Predicate#and(Predicate)}, {@link Predicate#negate()}, or {@link Predicate#or(Predicate)}, + * the result is short-circuited to avoid unecessarily creating and nesting predicate objects. + * + * @return A {@link Predicate} that always returns false + */ + public static final Predicate FALSE() { + return (Predicate) FALSE; + } + + private static final Predicate FALSE = new Predicate() { + @Override + public boolean test(Object t) { + return false; + } + + @Override + public Predicate and(Predicate other) { + Objects.requireNonNull(other); + return FALSE; + } + + @Override + public Predicate negate() { + return TRUE; + } + + @Override + public Predicate or(Predicate other) { + return Objects.requireNonNull(other); + } + }; + /** * Generates a new CommandPermission from a permission node * diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/Executable.java b/commandapi-core/src/main/java/dev/jorel/commandapi/Executable.java index 64deeb7ab2..c93418ee9b 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/Executable.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/Executable.java @@ -1,7 +1,6 @@ package dev.jorel.commandapi; import dev.jorel.commandapi.arguments.AbstractArgument; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import java.util.ArrayList; @@ -20,13 +19,13 @@ abstract class Executable> executor = new CommandAPIExecutor<>(); + protected CommandAPIExecutor executor = new CommandAPIExecutor<>(); /** * Returns the executors that this command has * @return the executors that this command has */ - public CommandAPIExecutor> getExecutor() { + public CommandAPIExecutor getExecutor() { return executor; } @@ -34,7 +33,7 @@ public CommandAPIExecutor> executor) { + public void setExecutor(CommandAPIExecutor executor) { this.executor = executor; } @@ -43,8 +42,7 @@ public void setExecutor(CommandAPIExecutor()); - this.executor.setResultingExecutors(new ArrayList<>()); + this.executor.setExecutors(new ArrayList<>()); return instance(); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 04cf16c906..9b312ecdfd 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -7,7 +7,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; import dev.jorel.commandapi.help.CommandAPIHelpTopic; import dev.jorel.commandapi.help.EditableHelpTopic; @@ -42,9 +41,9 @@ public abstract class ExecutableCommand requirements = s -> true; + protected Predicate requirements = CommandPermission.TRUE(); // Command help protected CommandAPIHelpTopic helpTopic = new EditableHelpTopic<>(); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/PlatformExecutable.java b/commandapi-core/src/main/java/dev/jorel/commandapi/PlatformExecutable.java index df8682f41b..c8e4f9e53f 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/PlatformExecutable.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/PlatformExecutable.java @@ -1,12 +1,12 @@ package dev.jorel.commandapi; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; - public interface PlatformExecutable /// @endcond , CommandSender> extends ChainableBuilder { - // Automatically links to Executable#getExecutor (make sure it has the same signature) - CommandAPIExecutor> getExecutor(); + /** + * Links to {@link Executable#getExecutor()} (make sure it has the same signature) + */ + CommandAPIExecutor getExecutor(); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index d60ae696b7..88e46d1c80 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -222,7 +222,7 @@ public CommandPermission getArgumentPermission() { // Requirements // ////////////////// - private Predicate requirements = s -> true; + private Predicate requirements = CommandPermission.TRUE(); /** * Returns the requirements required to run this command @@ -427,7 +427,7 @@ public void checkPreconditions( // this if statement, like what Literal#createArgumentBuilder does. SuggestionProvider suggestions = handler.generateBrigadierSuggestions(previousArguments, (Argument) this); ArgumentBuilder rootBuilder; - if (this instanceof Previewable previewable) { + if (this instanceof Previewable previewable) { // Handle previewable argument PreviewableArgumentBuilder builder = PreviewableArgumentBuilder.previewableArgument( nodeName, rawType, diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/PreviewInfo.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/PreviewInfo.java index 6c5ca64025..b07891bae9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/PreviewInfo.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/PreviewInfo.java @@ -1,10 +1,8 @@ package dev.jorel.commandapi.arguments; -import dev.jorel.commandapi.commandsenders.AbstractPlayer; - -public record PreviewInfo ( +public record PreviewInfo ( /** @param player the Player typing this command */ - AbstractPlayer player, + Player player, /** * @param input the current partially typed argument. For example "/mycmd tes" diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Previewable.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Previewable.java index d145a0b956..a773ae8ac8 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Previewable.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Previewable.java @@ -9,11 +9,11 @@ * Minecraft's chat preview feature. To use this, the server must have * {@code previews-chat=true} set in its {@code server.properties} file */ -public interface Previewable /// @endcond -, A> { +, T, Player> { /** * Sets the {@link PreviewableFunction} for this argument. This function will @@ -24,13 +24,13 @@ public interface Previewable preview); + public Impl withPreview(PreviewableFunction preview); /** * @return the {@link PreviewableFunction} for this argument, as an * {@link Optional}. */ - public Optional> getPreview(); + public Optional> getPreview(); /** * Whether this argument should use the preview result as the argument value for @@ -41,7 +41,7 @@ public interface Previewable extends ArgumentBuilder suggestionsProvider = null; // `Previewable` information - private final PreviewableFunction previewableFunction; + private final PreviewableFunction previewableFunction; private final boolean legacy; private final boolean isListed; - private PreviewableArgumentBuilder(String name, ArgumentType type, PreviewableFunction previewableFunction, boolean legacy, boolean isListed) { + private PreviewableArgumentBuilder(String name, ArgumentType type, PreviewableFunction previewableFunction, boolean legacy, boolean isListed) { this.name = name; this.type = type; @@ -38,7 +38,7 @@ private PreviewableArgumentBuilder(String name, ArgumentType type, Previewabl this.isListed = isListed; } - public static PreviewableArgumentBuilder previewableArgument(String name, ArgumentType type, PreviewableFunction previewableFunction, boolean legacy, boolean isListed) { + public static PreviewableArgumentBuilder previewableArgument(String name, ArgumentType type, PreviewableFunction previewableFunction, boolean legacy, boolean isListed) { return new PreviewableArgumentBuilder<>(name, type, previewableFunction, legacy, isListed); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java index 4093c54a4f..c3ad0d05ed 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java @@ -27,14 +27,14 @@ * @param The type returned when this argument is parsed. */ public class PreviewableCommandNode extends ArgumentCommandNode { - private final PreviewableFunction preview; + private final PreviewableFunction preview; private final boolean legacy; // Instead of having a listed and unlisted copy of this class, we can just handle this with this boolean private final boolean isListed; public PreviewableCommandNode( - PreviewableFunction preview, boolean legacy, boolean isListed, + PreviewableFunction preview, boolean legacy, boolean isListed, String name, ArgumentType type, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions ) { @@ -45,7 +45,7 @@ public PreviewableCommandNode( } // Methods needed to generate a preview - public Optional> getPreview() { + public Optional> getPreview() { return Optional.ofNullable(preview); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractBlockCommandSender.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractBlockCommandSender.java deleted file mode 100644 index 0ac8564103..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractBlockCommandSender.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractBlockCommandSender extends AbstractCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractCommandSender.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractCommandSender.java deleted file mode 100644 index 806ba11c42..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractCommandSender.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -/** - * An interface that represents an object sends commands on some platform - * - * @param The class of the CommandSender being represented - */ -public interface AbstractCommandSender { - - /** - * Tests if this CommandSender has permission to use the given permission node - * - * @param permissionNode The node to check for - * @return True if this CommandSender holds the permission node, and false otherwise - */ - boolean hasPermission(String permissionNode); - - /** - * Tests if this CommandSender has `operator` status, or the ability to run any command - * - * @return True if this CommandSender is an operator, and false otherwise - */ - boolean isOp(); - - /** - * @return The underlying CommandSender object - */ - Source getSource(); -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractConsoleCommandSender.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractConsoleCommandSender.java deleted file mode 100644 index 05fdc81b34..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractConsoleCommandSender.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractConsoleCommandSender extends AbstractCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractEntity.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractEntity.java deleted file mode 100644 index c167eb3a07..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractEntity.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractEntity extends AbstractCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractFeedbackForwardingCommandSender.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractFeedbackForwardingCommandSender.java deleted file mode 100644 index 66e544d3bd..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractFeedbackForwardingCommandSender.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractFeedbackForwardingCommandSender extends AbstractCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractNativeProxyCommandSender.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractNativeProxyCommandSender.java deleted file mode 100644 index e008064d21..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractNativeProxyCommandSender.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractNativeProxyCommandSender extends AbstractProxiedCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractPlayer.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractPlayer.java deleted file mode 100644 index 6fa29febfd..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractPlayer.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractPlayer extends AbstractCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractProxiedCommandSender.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractProxiedCommandSender.java deleted file mode 100644 index c02b9f7096..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractProxiedCommandSender.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractProxiedCommandSender extends AbstractCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractRemoteConsoleCommandSender.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractRemoteConsoleCommandSender.java deleted file mode 100644 index f198138ec7..0000000000 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandsenders/AbstractRemoteConsoleCommandSender.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -public interface AbstractRemoteConsoleCommandSender extends AbstractCommandSender { -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ExecutionInfo.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ExecutionInfo.java index 45996a4917..3cb70e53d7 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ExecutionInfo.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ExecutionInfo.java @@ -1,33 +1,41 @@ package dev.jorel.commandapi.executors; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; +import com.mojang.brigadier.context.CommandContext; /** - * This interface represents an ExecutionInfo for a command. It provides the sender of a command, as well as it's arguments + * This interface represents the information of running a command. It provides the sender of a command, as well as its arguments. * - * @param The type of the sender of a command this ExecutionInfo belongs to + * @param sender The sender of this command + * @param args The arguments of this command + * @param cmdCtx The Brigadier {@link CommandContext} that is running the commands + * @param The type of the sender of a command this ExecutionInfo belongs to + * @param The class for running Brigadier commands */ -public interface ExecutionInfo -/// @endcond -> { +public record ExecutionInfo( /** * @return The sender of this command */ - Sender sender(); + CommandSender sender, /** - * This is not intended for public use and is only used internally. The {@link ExecutionInfo#sender()} method should be used instead! - * - * @return The wrapper type of this command + * @return The arguments of this command */ - WrapperType senderWrapper(); + CommandArguments args, /** - * @return The arguments of this command + * @return cmdCtx The Brigadier {@link CommandContext} that is running the commands */ - CommandArguments args(); - + CommandContext cmdCtx +) { + /** + * Copies this {@link ExecutionInfo} for a different command sender + * + * @param The class of the new command sender + * @param newSender The new command sender + * @return A new {@link ExecutionInfo} object that uses the given command sender + */ + public ExecutionInfo copyWithNewSender(Sender newSender) { + return new ExecutionInfo<>(newSender, args, cmdCtx); + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java index c6c481d68a..0864bd2b3f 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java @@ -1,55 +1,28 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ package dev.jorel.commandapi.executors; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; /** - * The interface for normal command executors - * @param The CommandSender for this executor - * @param The AbstractCommandSender that wraps the CommandSender + * A {@link FunctionalInterface} for a command execution that inputs a command sender and {@link CommandArguments} + * and returns no result (see {@link NormalExecutor#run(Object, CommandArguments)}). When running this executor, + * the result will by 1 if successful and 0 otherwise. + * + * @param The class of the command sender for this executor. + * @param The class for running Brigadier commands. */ -public interface NormalExecutor -/// @endcond -> extends TypedExecutor { - /** - * Executes the command executor with the provided command sender and the provided arguments. - * @param info The ExecutionInfo for this command - * @return 1 if the command succeeds, 0 if the command fails - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - @Override - default int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info); - return 1; - } - - /** - * Executes the command. - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; +@FunctionalInterface +public interface NormalExecutor extends NormalExecutorInfo { + @Override + default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { + this.run(info.sender(), info.args()); + } + /** + * Runs this executor. + * + * @param sender The command sender. + * @param arguments The {@link CommandArguments} for this command. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ + void run(Sender sender, CommandArguments arguments) throws WrapperCommandSyntaxException; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java new file mode 100644 index 0000000000..228d894ad7 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java @@ -0,0 +1,22 @@ +package dev.jorel.commandapi.executors; + +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; + +/** + * A {@link FunctionalInterface} for a command execution that inputs a {@link ExecutionInfo} + * and returns no result (see {@link NormalExecutorInfo#run(ExecutionInfo)}). When running + * this executor, the result will by 1 if successful and 0 otherwise. + * + * @param The class of the command sender for this executor. + * @param The class for running Brigadier commands. + */ +@FunctionalInterface +public interface NormalExecutorInfo { + /** + * Runs this executor. + * + * @param info The {@link ExecutionInfo} for this command. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ + void run(ExecutionInfo info) throws WrapperCommandSyntaxException; +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java index 9444ae7432..b46bbb34a5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java @@ -1,55 +1,28 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ package dev.jorel.commandapi.executors; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; /** - * The interface for resulting command executors - * @param The CommandSender for this executor - * @param The AbstractCommandSender that wraps the CommandSender + * A {@link FunctionalInterface} for a command execution that inputs a command sender and {@link CommandArguments} + * and returns an int result (see {@link ResultingExecutor#run(Object, CommandArguments)}). + * + * @param The class of the command sender for this executor. + * @param The class for running Brigadier commands. */ -public interface ResultingExecutor -/// @endcond -> extends TypedExecutor { +@FunctionalInterface +public interface ResultingExecutor extends ResultingExecutorInfo { + @Override + default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { + return this.run(info.sender(), info.args()); + } - /** - * Executes the command executor with the provided command sender and the provided arguments. - * @param info The ExecutionInfo for this command - * @return the value returned by this command if the command succeeds, 0 if the command fails - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - @Override - default int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info); - } - - /** - * Executes the command. - * @param info The ExecutionInfo for this command - * @return the value returned by this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; + /** + * Runs this executor. + * + * @param sender The command sender. + * @param arguments The {@link CommandArguments} for this command. + * @return The int result of running this executor. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ + int run(Sender sender, CommandArguments arguments) throws WrapperCommandSyntaxException; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java new file mode 100644 index 0000000000..b50aaa8be3 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java @@ -0,0 +1,22 @@ +package dev.jorel.commandapi.executors; + +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; + +/** + * A {@link FunctionalInterface} for a command execution that inputs a {@link ExecutionInfo} + * and returns an int result (see {@link ResultingExecutorInfo#run(ExecutionInfo)}). + * + * @param The class of the command sender for this executor. + * @param The class for running Brigadier commands. + */ +@FunctionalInterface +public interface ResultingExecutorInfo { + /** + * Runs this executor. + * + * @param info The {@link ExecutionInfo} for this command. + * @return The int result of running this executor. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ + int run(ExecutionInfo info) throws WrapperCommandSyntaxException; +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/TypedExecutor.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/TypedExecutor.java index cd95fac8c5..55a1c825d0 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/TypedExecutor.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/TypedExecutor.java @@ -1,54 +1,33 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ package dev.jorel.commandapi.executors; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; /** - * An interface that includes the type of an executor (what command senders it - * can execute) and has a method that executes an executor with a given command - * sender and arguments - * @param The AbstractCommandSenderClass for this executor + * An interface that includes what command senders it can execute ({@link #tryForSender(ExecutionInfo)}) + * and has a method that executes an executor with a given {@link ExecutionInfo} ({@link #executeWith(ExecutionInfo)}). + * + * @param The class for executing platform commands. + * @param The class that this executor accepts. + * @param The class for running Brigadier commands. */ -public interface TypedExecutor -/// @endcond -> { +public interface TypedExecutor { /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor + * Checks if the sender in the given {@link ExecutionInfo} can use this executor. + * If it can, an {@link ExecutionInfo} compatible with this executor is returned. + * If not, null is returned. + * + * @param info The {@link ExecutionInfo} that is trying to use this executor. + * @return An {@link ExecutionInfo} object that can use this executor, and null otherwise. */ - default ExecutorType getType() { - return ExecutorType.ALL; - } + ExecutionInfo tryForSender(ExecutionInfo info); /** * Executes the command executor with the provided command sender and the provided arguments. + * * @param info The ExecutionInfo for this command * @return the value returned by this command if the command succeeds, 0 if the command fails * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command */ - int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException; - + int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewableFunction.java b/commandapi-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewableFunction.java index ff70a70e60..fe01885ba6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewableFunction.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewableFunction.java @@ -3,8 +3,8 @@ import dev.jorel.commandapi.arguments.PreviewInfo; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -public interface PreviewableFunction { +@FunctionalInterface +public interface PreviewableFunction { - public T generatePreview(PreviewInfo info) throws WrapperCommandSyntaxException; - + public T generatePreview(PreviewInfo info) throws WrapperCommandSyntaxException; } diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt index b95e4c92f5..f45f9ad453 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt @@ -32,6 +32,7 @@ import org.bukkit.* import org.bukkit.advancement.Advancement import org.bukkit.block.* import org.bukkit.block.data.BlockData +import org.bukkit.command.BlockCommandSender import org.bukkit.command.CommandSender import org.bukkit.command.ProxiedCommandSender import org.bukkit.enchantments.Enchantment @@ -68,7 +69,7 @@ fun advancementArgument() { CommandAPICommand("award") .withArguments(PlayerArgument("player")) .withArguments(AdvancementArgument("advancement")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["player"] as Player val advancement = args["advancement"] as Advancement @@ -89,11 +90,11 @@ CommandAPICommand("getpos") .withAliases("getposition", "getloc", "getlocation", "whereami") // Declare your implementation - .executesEntity(EntityCommandExecutor { entity, _ -> + .executesEntity(NormalExecutor { entity, _ -> val loc = entity.location entity.sendMessage("You are at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ}") }) - .executesCommandBlock(CommandBlockCommandExecutor { block, _ -> + .executesCommandBlock(NormalExecutor { block, _ -> val loc = block.block.location block.sendMessage("You are at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ}") }) @@ -108,7 +109,7 @@ fun argument_angle() { /* ANCHOR: argumentAngle1 */ CommandAPICommand("yaw") .withArguments(AngleArgument("amount")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val newLocation = player.location newLocation.yaw = args["amount"] as Float player.teleport(newLocation) @@ -121,7 +122,7 @@ fun argument_biome() { /* ANCHOR: argumentBiome1 */ CommandAPICommand("setbiome") .withArguments(BiomeArgument("biome")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val biome = args["biome"] as Biome val chunk = player.location.chunk @@ -143,7 +144,7 @@ val arguments = arrayOf>( /* ANCHOR: argumentBlockPredicate2 */ CommandAPICommand("replace") .withArguments(*arguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // Parse the arguments val radius = args["radius"] as Int @@ -176,7 +177,7 @@ fun argument_blockState() { /* ANCHOR: argumentBlockState1 */ CommandAPICommand("set") .withArguments(BlockStateArgument("block")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val blockdata = args["block"] as BlockData val targetBlock = player.getTargetBlockExact(256) @@ -192,7 +193,7 @@ fun argument_chatAdventure() { /* ANCHOR: argumentChatAdventure1 */ CommandAPICommand("namecolor") .withArguments(AdventureChatColorArgument("chatcolor")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val color = args["chatcolor"] as NamedTextColor player.displayName(Component.text().color(color).append(Component.text(player.name)).build()) }) @@ -205,7 +206,7 @@ CommandAPICommand("showbook") .withArguments(TextArgument("title")) .withArguments(StringArgument("author")) .withArguments(AdventureChatComponentArgument("contents")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["target"] as Player val title = args["title"] as String val author = args["author"] as String @@ -221,7 +222,7 @@ CommandAPICommand("showbook") /* ANCHOR: argumentChatAdventure3 */ CommandAPICommand("pbroadcast") .withArguments(AdventureChatArgument("message")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val message = args["message"] as Component // Broadcast the message to everyone with broadcast permissions. @@ -236,7 +237,7 @@ fun argument_chatSpigot() { /* ANCHOR: argumentChatSpigot1 */ CommandAPICommand("namecolor") .withArguments(ChatColorArgument("chatColor")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val color = args["chatColor"] as ChatColor player.setDisplayName("$color${player.name}") }) @@ -247,7 +248,7 @@ CommandAPICommand("namecolor") CommandAPICommand("makebook") .withArguments(PlayerArgument("player")) .withArguments(ChatComponentArgument("contents")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val player = args["player"] as Player val arr = args["contents"] as Array @@ -268,7 +269,7 @@ CommandAPICommand("makebook") /* ANCHOR: argumentChatSpigot3 */ CommandAPICommand("pbroadcast") .withArguments(ChatArgument("message")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val message = args["message"] as Array // Broadcast the message to everyone on the server @@ -283,7 +284,7 @@ fun argument_command() { CommandAPICommand("sudo") .withArguments(PlayerArgument("target")) .withArguments(CommandArgument("command")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["target"] as Player val command = args["command"] as CommandResult @@ -369,7 +370,7 @@ fun argumentCustom2() { /* ANCHOR: argumentCustom2 */ CommandAPICommand("tpworld") .withArguments(worldArgument("world")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> player.teleport((args["world"] as World).spawnLocation) }) .register() @@ -382,7 +383,7 @@ fun argument_enchantment() { CommandAPICommand("enchantitem") .withArguments(EnchantmentArgument("enchantment")) .withArguments(IntegerArgument("level", 1, 5)) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val enchantment = args["enchantment"] as Enchantment val level = args["level"] as Int @@ -398,7 +399,7 @@ fun argument_entities() { CommandAPICommand("remove") // Using a collective entity selector to select multiple entities .withArguments(EntitySelectorArgument.ManyEntities("entities")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // Parse the argument as a collection of entities (as stated above in the documentation) val entities = args["entities"] as Collection @@ -431,7 +432,7 @@ CommandAPICommand("warp") CommandAPICommand("spawnmob") .withArguments(EntityTypeArgument("entity")) .withArguments(IntegerArgument("amount", 1, 100)) // Prevent spawning too many entities - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> for (i in 0 until args["amount"] as Int) { player.world.spawnEntity(player.location, args["entity"] as EntityType) } @@ -444,7 +445,7 @@ fun argument_function() { /* ANCHOR: argumentFunction1 */ CommandAPICommand("runfunction") .withArguments(FunctionArgument("function")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val functions = args["function"] as Array // Run all functions in our FunctionWrapper[] @@ -460,7 +461,7 @@ fun argument_itemStack() { /* ANCHOR: argumentItemStack1 */ CommandAPICommand("item") .withArguments(ItemStackArgument("itemStack")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> player.inventory.addItem(args["itemStack"] as ItemStack) }) .register() @@ -472,7 +473,7 @@ fun argument_itemStackPredicate() { // Register our command CommandAPICommand("rem") .withArguments(ItemStackPredicateArgument("items")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // Get our predicate val predicate = args["items"] as Predicate @@ -496,7 +497,7 @@ CommandAPICommand("multigive") .withMapper { material -> material.name.lowercase() } .buildGreedy() ) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val amount = args["amount"] as Int val theList = args["materials"] as List @@ -513,7 +514,7 @@ fun argument_literal() { CommandAPICommand("mycommand") .withArguments(LiteralArgument("hello")) .withArguments(TextArgument("text")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> // This gives the variable "text" the contents of the TextArgument, and not the literal "hello" val text = args[0] as String }) @@ -524,7 +525,7 @@ CommandAPICommand("mycommand") CommandAPICommand("mycommand") .withArguments(LiteralArgument.of("hello")) .withArguments(TextArgument("text")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val text = args[0] as String }) .register() @@ -532,7 +533,7 @@ CommandAPICommand("mycommand") CommandAPICommand("mycommand") .withArguments(LiteralArgument.literal("hello")) .withArguments(TextArgument("text")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val text = args[0] as String }) .register() @@ -553,7 +554,7 @@ for ((key, _) in gamemodes) { // Register the command as usual CommandAPICommand("changegamemode") .withArguments(LiteralArgument(key)) - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> // Retrieve the object from the map via the key and NOT the args[] player.gameMode = gamemodes[key]!! }) @@ -575,7 +576,7 @@ LocationArgument("location", LocationType.PRECISE_POSITION, false) CommandAPICommand("break") // We want to target blocks in particular, so use BLOCK_POSITION .withArguments(LocationArgument("block", LocationType.BLOCK_POSITION)) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> (args["block"] as Location).block.type = Material.AIR }) .register() @@ -587,7 +588,7 @@ fun argument_lootTable() { CommandAPICommand("giveloottable") .withArguments(LootTableArgument("lootTable")) .withArguments(LocationArgument("location", LocationType.BLOCK_POSITION)) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val lootTable = args["lootTable"] as LootTable val location = args["location"] as Location @@ -627,7 +628,7 @@ CommandAPICommand("sendmessage") // Build the MapArgument .build() ) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> // The MapArgument returns a LinkedHashMap val map: LinkedHashMap = args["message"] as LinkedHashMap @@ -646,7 +647,7 @@ CommandAPICommand("changelevel") .withArguments(PlayerArgument("player")) .withArguments(MathOperationArgument("operation")) .withArguments(IntegerArgument("value")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["player"] as Player val op = args["operation"] as MathOperation val value = args["value"] as Int @@ -661,7 +662,7 @@ fun argument_multiLiteral() { /* ANCHOR: argumentMultiLiteral1 */ CommandAPICommand("gamemode") .withArguments(MultiLiteralArgument("gamemodes", "adventure", "creative", "spectator", "survival")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // The literal string that the player enters IS available in the args[] when (args["gamemodes"] as String) { "adventure" -> player.gameMode = GameMode.ADVENTURE @@ -688,7 +689,7 @@ fun argument_nbt2() { /* ANCHOR: argumentNBT2 */ CommandAPICommand("award") .withArguments(NBTCompoundArgument("nbt")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val nbt = args["nbt"] as NBTContainer // Do something with "nbt" here... @@ -703,7 +704,7 @@ fun argument_objectives() { /* ANCHOR: argumentObjectives1 */ CommandAPICommand("sidebar") .withArguments(ObjectiveArgument("objective")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val objective = args["objective"] as Objective // Set display slot @@ -715,7 +716,7 @@ CommandAPICommand("sidebar") /* ANCHOR: argumentObjectives2 */ CommandAPICommand("unregisterall") .withArguments(ObjectiveCriteriaArgument("objective criteria")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val objectiveCriteria = args["objective criteria"] as String val objectives = Bukkit.getScoreboardManager().mainScoreboard.getObjectivesByCriteria(objectiveCriteria) @@ -732,7 +733,7 @@ fun argument_particle() { /* ANCHOR: argumentParticle1 */ CommandAPICommand("showparticle") .withArguments(ParticleArgument("particle")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val particleData = args["particle"] as ParticleData player.world.spawnParticle(particleData.particle(), player.location, 1) }) @@ -742,7 +743,7 @@ CommandAPICommand("showparticle") /* ANCHOR: argumentParticle2 */ CommandAPICommand("showparticle") .withArguments(ParticleArgument("particle")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val particleData = args["particle"] as ParticleData player.world.spawnParticle(particleData.particle(), player.location, 1, particleData.data()) }) @@ -757,7 +758,7 @@ CommandAPICommand("potion") .withArguments(PotionEffectArgument("potion")) .withArguments(TimeArgument("duration")) .withArguments(IntegerArgument("strength")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["target"] as Player val potion = args["potion"] as PotionEffectType val duration = args["duration"] as Int @@ -774,7 +775,7 @@ CommandAPICommand("potion") .withArguments(PotionEffectArgument.NamespacedKey("potion")) .withArguments(TimeArgument("duration")) .withArguments(IntegerArgument("strength")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["target"] as Player val potionKey = args["potion"] as NamespacedKey val duration = args["duration"] as Int @@ -798,7 +799,7 @@ val configKeys: Array = config.getKeys(true).toTypedArray() CommandAPICommand("editconfig") .withArguments(TextArgument("config-key").replaceSuggestions(ArgumentSuggestions.strings { _ -> configKeys })) .withArguments(BooleanArgument("value")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> // Update the config with the boolean argument config.set(args["config-key"] as String, args["value"] as Boolean) }) @@ -811,7 +812,7 @@ fun argument_range() { CommandAPICommand("searchrange") .withArguments(IntegerRangeArgument("range")) // Range argument .withArguments(ItemStackArgument("item")) // The item to search for - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // Retrieve the range from the arguments val range = args["range"] as IntegerRange val itemStack = args["item"] as ItemStack @@ -860,7 +861,7 @@ fun argument_recipe() { /* ANCHOR: argumentRecipe1 */ CommandAPICommand("giverecipe") .withArguments(RecipeArgument("recipe")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val recipe = args["recipe"] as ComplexRecipe player.inventory.addItem(recipe.result) }) @@ -871,7 +872,7 @@ CommandAPICommand("giverecipe") CommandAPICommand("unlockrecipe") .withArguments(PlayerArgument("player")) .withArguments(RecipeArgument("recipe")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["player"] as Player val recipe = args["recipe"] as ComplexRecipe @@ -886,7 +887,7 @@ fun argument_rotation() { CommandAPICommand("rotate") .withArguments(RotationArgument("rotation")) .withArguments(EntitySelectorArgument.OneEntity("target")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val rotation = args["rotation"] as Rotation val target = args["target"] as Entity @@ -903,7 +904,7 @@ fun argument_scoreboards() { CommandAPICommand("reward") // We want multiple players, so we use the ScoreHolderArgument.Multiple constructor .withArguments(ScoreHolderArgument.Multiple("players")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> // Get player names by casting to Collection val players = args["players"] as Collection @@ -917,7 +918,7 @@ CommandAPICommand("reward") /* ANCHOR: argumentScoreboards2 */ CommandAPICommand("clearobjectives") .withArguments(ScoreboardSlotArgument("slot")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val scoreboard = Bukkit.getScoreboardManager().mainScoreboard val slot = (args["slot"] as ScoreboardSlot).displaySlot scoreboard.clearSlot(slot) @@ -930,7 +931,7 @@ fun argument_sound() { /* ANCHOR: argumentSound1 */ CommandAPICommand("sound") .withArguments(SoundArgument("sound")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> player.world.playSound(player.location, args["sound"] as Sound, 100.0f, 1.0f) }) .register() @@ -939,7 +940,7 @@ CommandAPICommand("sound") /* ANCHOR: argumentSound2 */ CommandAPICommand("sound") .withArguments(SoundArgument.NamespacedKey("sound")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> player.world.playSound(player.location, (args["sound"] as NamespacedKey).asString(), 100.0f, 1.0f) }) .register() @@ -951,7 +952,7 @@ fun argument_strings() { CommandAPICommand("message") .withArguments(PlayerArgument("target")) .withArguments(GreedyStringArgument("message")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> (args["target"] as Player).sendMessage(args["message"] as String) }) .register() @@ -962,7 +963,7 @@ fun argument_team() { /* ANCHOR: argumentTeam1 */ CommandAPICommand("togglepvp") .withArguments(TeamArgument("team")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val team = args["team"] as Team // Toggle pvp @@ -977,7 +978,7 @@ fun argument_time() { CommandAPICommand("bigmsg") .withArguments(TimeArgument("duration")) .withArguments(GreedyStringArgument("message")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> // Duration in ticks val duration = args["duration"] as Int val message = args["message"] as String @@ -995,7 +996,7 @@ fun argument_world() { /* ANCHOR: argumentWorld1 */ CommandAPICommand("unloadworld") .withArguments(WorldArgument("world")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> val world = args["world"] as World // Unload the world (and save the world's chunks) @@ -1043,7 +1044,7 @@ val commandArguments = listOf( CommandAPICommand("cmd") .withArguments(commandArguments) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val stringArg = args["arg0"] as String val potionArg = args["arg1"] as PotionEffectType val locationArg = args["arg2"] as Location @@ -1060,7 +1061,7 @@ CommandAPICommand("setconfig") CompletableFuture.supplyAsync { plugin.config.getKeys(false).toTypedArray() } } )) .withArguments(TextArgument("value")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val key = args["key"] as String val value = args["value"] as String plugin.config.set(key, value) @@ -1158,7 +1159,7 @@ val messageArgument = GreedyStringArgument("message") CommandAPICommand("emoji") .withArguments(messageArgument) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> Bukkit.broadcastMessage(args["message"] as String) }) .register() @@ -1205,7 +1206,7 @@ val commandSuggestions: ArgumentSuggestions = ArgumentSuggestions /* ANCHOR: brigadierSuggestions3 */ CommandAPICommand("commandargument") .withArguments(GreedyStringArgument("command").replaceSuggestions(commandSuggestions)) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // Run the command using Bukkit.dispatchCommand() Bukkit.dispatchCommand(sender, args["command"] as String) }) @@ -1223,7 +1224,7 @@ CommandAPICommand("broadcast") // Translate the & in plain text and generate a new BaseComponent[] TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText)) } ) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> // The user still entered legacy text. We need to properly convert this // to a BaseComponent[] by converting to plain text then to BaseComponent[] val plainText: String = BaseComponent.toPlainText(*args["message"] as Array) @@ -1242,7 +1243,7 @@ CommandAPICommand("broadcast") // Translate the & in plain text and generate a new Component LegacyComponentSerializer.legacyAmpersand().deserialize(plainText) } ) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> // The user still entered legacy text. We need to properly convert this // to a Component by converting to plain text then to Component val plainText: String = PlainTextComponentSerializer.plainText().serialize(args["message"] as Component) @@ -1260,7 +1261,7 @@ CommandAPICommand("broadcast") // Translate the & in plain text and generate a new BaseComponent[] TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText)) } ) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> Bukkit.spigot().broadcast(*args["message"] as Array) }) .register() @@ -1275,7 +1276,7 @@ CommandAPICommand("broadcast") // Translate the & in plain text and generate a new Component LegacyComponentSerializer.legacyAmpersand().deserialize(plainText) } ) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> Bukkit.broadcast(args["message"] as Component) }) .register() @@ -1290,7 +1291,7 @@ CommandAPICommand("mycommand") .withOptionalArguments(PlayerArgument("player")) .withOptionalArguments(PlayerArgument("target")) .withOptionalArguments(GreedyStringArgument("message")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val name = args[0] as String // Access arguments by index val amount = args["amount"] as Int // Access arguments by node name val p = args.getOrDefault("player", player) as Player // Access arguments using the getOrDefault(String, Object) method @@ -1305,7 +1306,7 @@ CommandAPICommand("mycommand") /* ANCHOR: commandArguments2 */ CommandAPICommand("mycommand") .withArguments(EntitySelectorArgument.ManyEntities("entities")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val entitySelector = args.getRaw("entities")!! // Access the raw argument with getRaw(String) // Do whatever with the entity selector @@ -1316,7 +1317,7 @@ CommandAPICommand("mycommand") /* ANCHOR: commandArguments3 */ CommandAPICommand("mycommand") .withArguments(PlayerArgument("player")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val p: Player = args.getUnchecked("player")!! // Do whatever with the player @@ -1337,7 +1338,7 @@ CommandAPICommand("mycommand") .withOptionalArguments(playerArgument) .withOptionalArguments(targetArgument) .withOptionalArguments(messageArgument) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val name: String = args.getByArgument(nameArgument)!! val amount: Int = args.getByArgument(amountArgument)!! val p: Player = args.getByArgumentOrDefault(playerArgument, player) @@ -1358,7 +1359,7 @@ val fruit = listOf("banana", "apple", "orange") // Register the command CommandAPICommand("getfruit") .withArguments(StringArgument("item").replaceSuggestions(ArgumentSuggestions.strings(fruit))) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val inputFruit = args["item"] as String if(fruit.any { it == inputFruit }) { @@ -1379,7 +1380,7 @@ CommandAPICommand("broadcastmsg") .withArguments(GreedyStringArgument("message")) // The arguments .withAliases("broadcast", "broadcastmessage") // Command aliases .withPermission(CommandPermission.OP) // Required permissions - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> val message = args["message"] as String Bukkit.getServer().broadcastMessage(message) }) @@ -1391,11 +1392,11 @@ class commandTrees : JavaPlugin() { fun commandTrees1() { /* ANCHOR: commandTrees1 */ CommandTree("sayhi") - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> sender.sendMessage("Hi!") }) .then(PlayerArgument("target") - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val target = args["target"] as Player target.sendMessage("Hi") })) @@ -1407,7 +1408,7 @@ CommandTree("signedit") .then(LiteralArgument("set") .then(IntegerArgument("line_number", 1, 4) .then(GreedyStringArgument("text") - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // /signedit set val sign: Sign = getTargetSign(player) val line_number = args["line_number"] as Int @@ -1417,7 +1418,7 @@ CommandTree("signedit") })))) .then(LiteralArgument("clear") .then(IntegerArgument("line_number", 1, 4) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // /signedit clear val sign: Sign = getTargetSign(player) val line_number = args["line_number"] as Int @@ -1426,7 +1427,7 @@ CommandTree("signedit") }))) .then(LiteralArgument("copy") .then(IntegerArgument("line_number", 1, 4) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // /signedit copy val sign: Sign = getTargetSign(player) val line_number = args["line_number"] as Int @@ -1434,7 +1435,7 @@ CommandTree("signedit") }))) .then(LiteralArgument("paste") .then(IntegerArgument("line_number", 1, 4) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // /signedit copy val sign: Sign = getTargetSign(player) val line_number = args["line_number"] as Int @@ -1481,7 +1482,7 @@ override fun onEnable() { // Register our new /gamemode, with survival, creative, adventure and spectator CommandAPICommand("gamemode") .withArguments(MultiLiteralArgument("gamemodes", "survival", "creative", "adventure", "spectator")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // Implementation of our /gamemode command }) .register() @@ -1598,7 +1599,7 @@ fun delegatedProperties() { CommandAPICommand("mycommand") .withArguments(StringArgument("string")) .withArguments(PlayerArgument("target")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val string: String by args val target: Player by args // Implementation... @@ -1615,7 +1616,7 @@ class Main : JavaPlugin() { // Commands which will be used in Minecraft functions are registered here CommandAPICommand("killall") - .executes(CommandExecutor { _, _ -> + .executes(NormalExecutor { _, _ -> // Kills all enemies in all worlds Bukkit.getWorlds().forEach { world -> world.livingEntities.forEach { entity -> entity.health = 0.0 } } }) @@ -1634,7 +1635,7 @@ fun functionWrapper() { /* ANCHOR: functionWrapper1 */ CommandAPICommand("runfunc") .withArguments(FunctionArgument("function")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val functions = args["function"] as Array for (function in functions) { function.run() // The command executor in this case is 'sender' @@ -1649,7 +1650,7 @@ fun help() { CommandAPICommand("mycmd") .withShortDescription("Says hi") .withFullDescription("Broadcasts hi to everyone on the server") - .executes(CommandExecutor { _, _ -> + .executes(NormalExecutor { _, _ -> Bukkit.broadcastMessage("Hi!") }) .register() @@ -1658,7 +1659,7 @@ CommandAPICommand("mycmd") /* ANCHOR: help2 */ CommandAPICommand("mycmd") .withHelp("Says hi", "Broadcasts hi to everyone on the server") - .executes(CommandExecutor { _, _ -> + .executes(NormalExecutor { _, _ -> Bukkit.broadcastMessage("Hi!") }) .register() @@ -1694,7 +1695,7 @@ fun help2() { /* ANCHOR: help4 */ return CommandAPICommand("mycmd") .withHelp(makeHelp("mycmd")) - .executes(CommandExecutor { _, _ -> + .executes(NormalExecutor { _, _ -> Bukkit.broadcastMessage("Hi!") }) .register() @@ -1707,7 +1708,7 @@ CommandAPICommand("mycommand") .withArguments(PlayerArgument("player")) .withArguments(IntegerArgument("value").setListed(false)) .withArguments(GreedyStringArgument("message")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> // args == [player, message] val player = args["player"] as Player val message = args["message"] as String // Note that the IntegerArgument is not available in the CommandArguments @@ -1720,7 +1721,7 @@ CommandAPICommand("mycommand") fun native() { /* ANCHOR: native1 */ CommandAPICommand("break") - .executesNative(NativeCommandExecutor { sender, _ -> + .executesNative(NormalExecutor { sender, _ -> val location = sender.location location.block.breakNaturally() }) @@ -1735,7 +1736,7 @@ CommandAPICommand("broadcastmsg") .withArguments(GreedyStringArgument("message")) // The arguments .withAliases("broadcast", "broadcastmessage") // Command aliases .withPermission(CommandPermission.OP) // Required permissions - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val message = args["message"] as String Bukkit.getServer().broadcastMessage(message) }) @@ -1744,7 +1745,7 @@ CommandAPICommand("broadcastmsg") /* ANCHOR: normalExecutors2 */ CommandAPICommand("suicide") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> player.setHealth(0.0) }) .register() @@ -1752,10 +1753,10 @@ CommandAPICommand("suicide") /* ANCHOR: normalExecutors3 */ CommandAPICommand("suicide") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> player.setHealth(0.0) }) - .executesEntity(EntityCommandExecutor { entity, _ -> + .executesEntity(NormalExecutor { entity, _ -> entity.world.createExplosion(entity.location, 4f) entity.remove() }) @@ -1764,7 +1765,7 @@ CommandAPICommand("suicide") /* ANCHOR: normalExecutors4 */ CommandAPICommand("suicide") - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> val entity = (if (sender is ProxiedCommandSender) sender.callee else sender) as LivingEntity entity.setHealth(0.0) }, ExecutorType.PLAYER, ExecutorType.PROXY) @@ -1776,7 +1777,7 @@ fun optionalArguments() { /* ANCHOR: optionalArguments1 */ CommandAPICommand("sayhi") .withOptionalArguments(PlayerArgument("target")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target: Player? = args["target"] as Player? if (target != null) { target.sendMessage("Hi!") @@ -1790,7 +1791,7 @@ CommandAPICommand("sayhi") /* ANCHOR: optionalArguments2 */ CommandAPICommand("sayhi") .withOptionalArguments(PlayerArgument("target")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target: Player = args.getOptional("target").orElse(player) as Player target.sendMessage("Hi!") }) @@ -1801,7 +1802,7 @@ CommandAPICommand("sayhi") CommandAPICommand("rate") .withOptionalArguments(StringArgument("topic").combineWith(IntegerArgument("rating", 0, 10))) .withOptionalArguments(PlayerArgument("target")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> val topic: String? = args["topic"] as String? if (topic == null) { sender.sendMessage( @@ -1809,7 +1810,7 @@ CommandAPICommand("rate") "Select a topic to rate, then give a rating between 0 and 10", "You can optionally add a player at the end to give the rating to" ) - return@CommandExecutor + return@NormalExecutor } // We know this is not null because rating is required if topic is given @@ -1829,7 +1830,7 @@ fun permissions() { // Register the /god command with the permission node "command.god" CommandAPICommand("god") .withPermission(CommandPermission.fromString("command.god")) - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> player.isInvulnerable = true }) .register() @@ -1839,7 +1840,7 @@ CommandAPICommand("god") // Register the /god command with the permission node "command.god", without creating a CommandPermission CommandAPICommand("god") .withPermission("command.god") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> player.isInvulnerable = true }) .register() @@ -1848,7 +1849,7 @@ CommandAPICommand("god") /* ANCHOR: permissions3 */ // Register /kill command normally. Since no permissions are applied, anyone can run this command CommandAPICommand("kill") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> player.health = 0.0 }) .register() @@ -1858,7 +1859,7 @@ CommandAPICommand("kill") // Adds the OP permission to the "target" argument. The sender requires OP to execute /kill CommandAPICommand("kill") .withArguments(PlayerArgument("target").withPermission(CommandPermission.OP)) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> (args["target"] as Player).health = 0.0 }) .register() @@ -1868,7 +1869,7 @@ CommandAPICommand("kill") // /economy - requires the permission "economy.self" to exectue CommandAPICommand("economy") .withPermission("economy.self") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> // send the executor their own balance here. }) .register() @@ -1877,7 +1878,7 @@ CommandAPICommand("economy") CommandAPICommand("economy") .withPermission("economy.other") // The important part of this example .withArguments(PlayerArgument("target")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target = args["target"] as Player // send the executor the targets balance here. }) @@ -1888,7 +1889,7 @@ CommandAPICommand("economy") .withPermission("economy.admin.give") // The important part of this example .withArguments(PlayerArgument("target")) .withArguments(DoubleArgument("amount")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target = args["target"] as Player val amount = args["amount"] as Double // update the targets balance here @@ -1899,7 +1900,7 @@ CommandAPICommand("economy") CommandAPICommand("economy") .withPermission("economy.admin.reset") // The important part of this example .withArguments(PlayerArgument("target")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target = args["target"] as Player // reset the targets balance here }) @@ -1948,7 +1949,7 @@ args = listOf>(LiteralArgument("tp").withRequirement(testIfPlayerHas fun proxySender() { /* ANCHOR: proxySender1 */ CommandAPICommand("killme") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> player.setHealth(0.0) }) .register() @@ -1956,10 +1957,10 @@ CommandAPICommand("killme") /* ANCHOR: proxySender2 */ CommandAPICommand("killme") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> player.setHealth(0.0) }) - .executesProxy(ProxyCommandExecutor { proxy, _ -> + .executesProxy(NormalExecutor { proxy, _ -> // Check if the callee (target) is an Entity and kill it if (proxy.callee is LivingEntity) { (proxy.callee as LivingEntity).setHealth(0.0) @@ -1973,7 +1974,7 @@ fun requirements() { /* ANCHOR: requirements1 */ CommandAPICommand("repair") .withRequirement { (it as Player).level >= 30 } - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> // Repair the item back to full durability val item = player.inventory.itemInMainHand @@ -2007,7 +2008,7 @@ arguments.add(StringArgument("partyName")) /* ANCHOR: requirements4 */ CommandAPICommand("party") .withArguments(*arguments.toTypedArray()) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // Get the name of the party to create val partyName = args["partyName"] as String @@ -2056,7 +2057,7 @@ arguments.add(PlayerArgument("player") /* ANCHOR: requirements6 */ CommandAPICommand("party") .withArguments(arguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target = args["player"] as Player player.teleport(target) }) @@ -2066,7 +2067,7 @@ CommandAPICommand("party") /* ANCHOR: requirements7 */ CommandAPICommand("party") .withArguments(arguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // Get the name of the party to create val partyName = args["partyName"] as String @@ -2083,7 +2084,7 @@ CommandAPICommand("someCommand") .withRequirement { (it as Player).level >= 30 } .withRequirement { (it as Player).inventory.contains(Material.DIAMOND_PICKAXE) } .withRequirement { (it as Player).isInvulnerable() } - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> // Code goes here }) .register() @@ -2093,7 +2094,7 @@ CommandAPICommand("someCommand") fun resultingCommandExecutors() { /* ANCHOR: resultingCommandExecutors1 */ CommandAPICommand("randnum") - .executes(ResultingCommandExecutor { _, _ -> + .executes(ResultingExecutor { _, _ -> Random.nextInt() }) .register() @@ -2102,7 +2103,7 @@ CommandAPICommand("randnum") /* ANCHOR: resultingCommandExecutors2 */ // Register random number generator command from 1 to 99 (inclusive) CommandAPICommand("randomnumber") - .executes(ResultingCommandExecutor { _, _ -> + .executes(ResultingExecutor { _, _ -> (1..100).random() // Returns random number from 1 <= x < 100 }) .register() @@ -2112,7 +2113,7 @@ CommandAPICommand("randomnumber") // Register reward giving system for a target player CommandAPICommand("givereward") .withArguments(EntitySelectorArgument.OnePlayer("target")) - .executes(CommandExecutor { _, args -> + .executes(NormalExecutor { _, args -> val player = args["target"] as Player player.inventory.addItem(ItemStack(Material.DIAMOND, 64)) Bukkit.broadcastMessage("${player.name} won a rare 64 diamonds from a loot box!") @@ -2156,7 +2157,7 @@ val arguments = listOf>( // Register our command CommandAPICommand("giverecipe") .withArguments(arguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val recipe = args["recipe"] as Recipe player.inventory.addItem(recipe.result) }) @@ -2188,7 +2189,7 @@ val safeArguments = listOf>( /* ANCHOR: safeArgumentSuggestions5 */ CommandAPICommand("spawnmob") .withArguments(safeArguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val entityType = args["mob"] as EntityType player.world.spawnEntity(player.location, entityType) }) @@ -2211,7 +2212,7 @@ safeArgs.add(PotionEffectArgument("potioneffect").replaceSafeSuggestions(SafeSug /* ANCHOR: safeArgumentSuggestions7 */ CommandAPICommand("removeeffect") .withArguments(safeArgs) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> val target = args["target"] as Player val potionEffect = args["potioneffect"] as PotionEffectType target.removePotionEffect(potionEffect) @@ -2236,7 +2237,7 @@ class MyPlugin : JavaPlugin() { CommandAPI.onLoad(CommandAPIBukkitConfig(this).verboseOutput(true)) // Load with verbose output CommandAPICommand("ping") - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> sender.sendMessage("pong!") }) .register() @@ -2268,7 +2269,7 @@ val arguments = listOf>( CommandAPICommand("warp") .withArguments(arguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val warp = args["world"] as String player.teleport(warps[warp]!!) // Look up the warp in a map, for example }) @@ -2307,7 +2308,7 @@ val arguments = listOf>( CommandAPICommand("friendtp") .withArguments(arguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target = args["friend"] as Player player.teleport(target) }) @@ -2342,7 +2343,7 @@ commandArgs.add(GreedyStringArgument("message")) // Declare our command as normal CommandAPICommand("localmsg") .withArguments(*commandArgs.toTypedArray()) - .executesPlayer(PlayerCommandExecutor { _, args -> + .executesPlayer(NormalExecutor { _, args -> val target = args["target"] as Player val message = args["message"] as String target.sendMessage(message) @@ -2358,7 +2359,7 @@ fun subcommands() { val groupAdd = CommandAPICommand("add") .withArguments(StringArgument("permission")) .withArguments(StringArgument("groupName")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // perm group add code }) /* ANCHOR_END: subcommands1 */ @@ -2367,7 +2368,7 @@ val groupAdd = CommandAPICommand("add") val groupRemove = CommandAPICommand("remove") .withArguments(StringArgument("permission")) .withArguments(StringArgument("groupName")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // perm group remove code }) @@ -2388,14 +2389,14 @@ CommandAPICommand("perm") .withSubcommand(CommandAPICommand("add") .withArguments(StringArgument("permission")) .withArguments(StringArgument("groupName")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // perm group add code }) ) .withSubcommand(CommandAPICommand("remove") .withArguments(StringArgument("permission")) .withArguments(StringArgument("groupName")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // perm group remove code }) ) @@ -2404,14 +2405,14 @@ CommandAPICommand("perm") .withSubcommand(CommandAPICommand("add") .withArguments(StringArgument("permission")) .withArguments(StringArgument("userName")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // perm user add code }) ) .withSubcommand(CommandAPICommand("remove") .withArguments(StringArgument("permission")) .withArguments(StringArgument("userName")) - .executes(CommandExecutor { sender, args -> + .executes(NormalExecutor { sender, args -> // perm user remove code }) ) @@ -2439,7 +2440,7 @@ arguments.add(PlayerArgument("target")) /* ANCHOR: tooltips2 */ CommandAPICommand("emote") .withArguments(*arguments.toTypedArray()) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val emote = args["emote"] as String val target = args["target"] as Player @@ -2479,7 +2480,7 @@ val customItems = arrayOf( CommandAPICommand("giveitem") .withArguments(StringArgument("item").replaceSuggestions(ArgumentSuggestions.stringsWithTooltips(*customItems))) // We use customItems[] as the input for our suggestions with tooltips - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val itemName = args["item"] as String // Give them the item @@ -2511,7 +2512,7 @@ val arguments = listOf>( /* ANCHOR: tooltips6 */ CommandAPICommand("warp") .withArguments(arguments) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> player.teleport(args["location"] as Location) }) .register() @@ -2525,31 +2526,31 @@ CommandTree("treeexample") // Set the aliases as you normally would .withAliases("treealias") // Set an executor on the command itself - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> sender.sendMessage("Root with no arguments") }) // Create a new branch starting with a the literal 'integer' .then(LiteralArgument("integer") // Execute on the literal itself - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> sender.sendMessage("Integer Branch with no arguments") }) // Create a further branch starting with an integer argument, which executes a command - .then(IntegerArgument("integer").executes(CommandExecutor { sender, args -> + .then(IntegerArgument("integer").executes(NormalExecutor { sender, args -> sender.sendMessage("Integer Branch with integer argument: ${args[0]}") }))) .then(LiteralArgument("biome") - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> sender.sendMessage("Biome Branch with no arguments") }) - .then(BiomeArgument("biome").executes(CommandExecutor { sender, args -> + .then(BiomeArgument("biome").executes(NormalExecutor { sender, args -> sender.sendMessage("Biome Branch with biome argument: ${args[0]}") }))) .then(LiteralArgument("string") - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> sender.sendMessage("String Branch with no arguments") }) - .then(StringArgument("string").executes(CommandExecutor { sender, args -> + .then(StringArgument("string").executes(NormalExecutor { sender, args -> sender.sendMessage("String Branch with string argument: ${args[0]}") }))) // Call register to finish as you normally would diff --git a/commandapi-documentation-velocity-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt b/commandapi-documentation-velocity-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt index 6dff041643..58e32243c6 100644 --- a/commandapi-documentation-velocity-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt +++ b/commandapi-documentation-velocity-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt @@ -9,7 +9,7 @@ import dev.jorel.commandapi.CommandAPICommand import dev.jorel.commandapi.CommandAPIVelocity import dev.jorel.commandapi.CommandAPIVelocityConfig import dev.jorel.commandapi.arguments.IntegerArgument -import dev.jorel.commandapi.executors.PlayerCommandExecutor +import dev.jorel.commandapi.executors.NormalExecutor import net.kyori.adventure.text.Component import org.slf4j.Logger import java.util.concurrent.ThreadLocalRandom @@ -22,7 +22,7 @@ fun velocityIntro() { CommandAPICommand("randomnumber") .withArguments(IntegerArgument("min")) .withArguments(IntegerArgument("max")) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val min = args["min"] as Int val max = args["max"] as Int val random = ThreadLocalRandom.current() diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt index f5bc5b0f9c..58115f293e 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt @@ -1,48 +1,14 @@ package dev.jorel.commandapi.kotlindsl import dev.jorel.commandapi.BukkitExecutable -import dev.jorel.commandapi.commandsenders.BukkitBlockCommandSender -import dev.jorel.commandapi.commandsenders.BukkitCommandSender -import dev.jorel.commandapi.commandsenders.BukkitConsoleCommandSender -import dev.jorel.commandapi.commandsenders.BukkitEntity -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender -import dev.jorel.commandapi.commandsenders.BukkitPlayer -import dev.jorel.commandapi.commandsenders.BukkitRemoteConsoleCommandSender import dev.jorel.commandapi.executors.CommandArguments -import dev.jorel.commandapi.executors.CommandBlockCommandExecutor -import dev.jorel.commandapi.executors.CommandBlockExecutionInfo -import dev.jorel.commandapi.executors.CommandBlockResultingCommandExecutor -import dev.jorel.commandapi.executors.CommandBlockResultingExecutionInfo -import dev.jorel.commandapi.executors.CommandExecutionInfo -import dev.jorel.commandapi.executors.CommandExecutor -import dev.jorel.commandapi.executors.ConsoleCommandExecutor -import dev.jorel.commandapi.executors.ConsoleExecutionInfo -import dev.jorel.commandapi.executors.ConsoleResultingCommandExecutor -import dev.jorel.commandapi.executors.ConsoleResultingExecutionInfo -import dev.jorel.commandapi.executors.EntityCommandExecutor -import dev.jorel.commandapi.executors.EntityExecutionInfo -import dev.jorel.commandapi.executors.EntityResultingCommandExecutor -import dev.jorel.commandapi.executors.EntityResultingExecutionInfo import dev.jorel.commandapi.executors.ExecutionInfo -import dev.jorel.commandapi.executors.NativeCommandExecutor -import dev.jorel.commandapi.executors.NativeExecutionInfo -import dev.jorel.commandapi.executors.NativeResultingCommandExecutor -import dev.jorel.commandapi.executors.NativeResultingExecutionInfo -import dev.jorel.commandapi.executors.PlayerCommandExecutor -import dev.jorel.commandapi.executors.PlayerExecutionInfo -import dev.jorel.commandapi.executors.PlayerResultingCommandExecutor -import dev.jorel.commandapi.executors.PlayerResultingExecutionInfo -import dev.jorel.commandapi.executors.ProxyCommandExecutor -import dev.jorel.commandapi.executors.ProxyExecutionInfo -import dev.jorel.commandapi.executors.ProxyResultingCommandExecutor -import dev.jorel.commandapi.executors.ProxyResultingExecutionInfo -import dev.jorel.commandapi.executors.RemoteConsoleCommandExecutor -import dev.jorel.commandapi.executors.RemoteConsoleExecutionInfo -import dev.jorel.commandapi.executors.RemoteConsoleResultingCommandExecutor -import dev.jorel.commandapi.executors.RemoteConsoleResultingExecutionInfo -import dev.jorel.commandapi.executors.ResultingCommandExecutionInfo -import dev.jorel.commandapi.executors.ResultingCommandExecutor +import dev.jorel.commandapi.executors.NormalExecutor +import dev.jorel.commandapi.executors.ResultingExecutor +import dev.jorel.commandapi.executors.NormalExecutorInfo +import dev.jorel.commandapi.executors.ResultingExecutorInfo import dev.jorel.commandapi.wrappers.NativeProxyCommandSender + import org.bukkit.command.BlockCommandSender import org.bukkit.command.CommandSender import org.bukkit.command.ConsoleCommandSender @@ -52,109 +18,109 @@ import org.bukkit.entity.Entity import org.bukkit.entity.Player // Executors for CommandAPICommand, CommandTree and ArgumentTree -inline fun BukkitExecutable<*>.anyExecutor(crossinline executor: (CommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executes(CommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.anyExecutor(crossinline executor: (CommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executes( NormalExecutor { sender: CommandSender, args: CommandArguments -> executor(sender, args) }) -inline fun BukkitExecutable<*>.playerExecutor(crossinline executor: (Player, CommandArguments) -> Unit): BukkitExecutable<*> = executesPlayer(PlayerCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.playerExecutor(crossinline executor: (Player, CommandArguments) -> Unit): BukkitExecutable<*> = executesPlayer(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.entityExecutor(crossinline executor: (Entity, CommandArguments) -> Unit): BukkitExecutable<*> = executesEntity(EntityCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.entityExecutor(crossinline executor: (Entity, CommandArguments) -> Unit): BukkitExecutable<*> = executesEntity(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.consoleExecutor(crossinline executor: (ConsoleCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesConsole(ConsoleCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.consoleExecutor(crossinline executor: (ConsoleCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesConsole(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.commandBlockExecutor(crossinline executor: (BlockCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesCommandBlock(CommandBlockCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.commandBlockExecutor(crossinline executor: (BlockCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesCommandBlock(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.proxyExecutor(crossinline executor: (ProxiedCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesProxy(ProxyCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.proxyExecutor(crossinline executor: (NativeProxyCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesProxy(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.nativeExecutor(crossinline executor: (NativeProxyCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesNative(NativeCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.nativeExecutor(crossinline executor: (NativeProxyCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesNative(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.remoteConsoleExecutor(crossinline executor: (RemoteConsoleCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesRemoteConsole(RemoteConsoleCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.remoteConsoleExecutor(crossinline executor: (RemoteConsoleCommandSender, CommandArguments) -> Unit): BukkitExecutable<*> = executesRemoteConsole(NormalExecutor { sender, args -> executor(sender, args) }) // Resulting executors -inline fun BukkitExecutable<*>.anyResultingExecutor(crossinline executor: (CommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executes(ResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.anyResultingExecutor(crossinline executor: (CommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executes(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.playerResultingExecutor(crossinline executor: (Player, CommandArguments) -> Int): BukkitExecutable<*> = executesPlayer(PlayerResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.playerResultingExecutor(crossinline executor: (Player, CommandArguments) -> Int): BukkitExecutable<*> = executesPlayer(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.entityResultingExecutor(crossinline executor: (Entity, CommandArguments) -> Int): BukkitExecutable<*> = executesEntity(EntityResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.entityResultingExecutor(crossinline executor: (Entity, CommandArguments) -> Int): BukkitExecutable<*> = executesEntity(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.consoleResultingExecutor(crossinline executor: (ConsoleCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesConsole(ConsoleResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.consoleResultingExecutor(crossinline executor: (ConsoleCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesConsole(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.commandBlockResultingExecutor(crossinline executor: (BlockCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesCommandBlock(CommandBlockResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.commandBlockResultingExecutor(crossinline executor: (BlockCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesCommandBlock(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.proxyResultingExecutor(crossinline executor: (ProxiedCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesProxy(ProxyResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.proxyResultingExecutor(crossinline executor: (NativeProxyCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesProxy(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.nativeResultingExecutor(crossinline executor: (NativeProxyCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesNative(NativeResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.nativeResultingExecutor(crossinline executor: (NativeProxyCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesNative(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun BukkitExecutable<*>.remoteConsoleResultingExecutor(crossinline executor: (RemoteConsoleCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesRemoteConsole(RemoteConsoleResultingCommandExecutor { sender, args -> +inline fun BukkitExecutable<*>.remoteConsoleResultingExecutor(crossinline executor: (RemoteConsoleCommandSender, CommandArguments) -> Int): BukkitExecutable<*> = executesRemoteConsole(ResultingExecutor { sender, args -> executor(sender, args) }) // ExecutionInfo normal executors -inline fun BukkitExecutable<*>.anyExecutionInfo(crossinline executor: (ExecutionInfo>) -> Unit): BukkitExecutable<*> = executes(CommandExecutionInfo { info -> +inline fun BukkitExecutable<*>.anyExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executes(NormalExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.playerExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesPlayer(PlayerExecutionInfo { info -> +inline fun BukkitExecutable<*>.playerExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesPlayer(NormalExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.entityExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesEntity(EntityExecutionInfo { info -> +inline fun BukkitExecutable<*>.entityExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesEntity(NormalExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.consoleExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesConsole(ConsoleExecutionInfo { info -> +inline fun BukkitExecutable<*>.consoleExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesConsole(NormalExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.commandBlockExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesCommandBlock(CommandBlockExecutionInfo { info -> +inline fun BukkitExecutable<*>.commandBlockExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesCommandBlock(NormalExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.proxyExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesProxy(ProxyExecutionInfo { info -> +inline fun BukkitExecutable<*>.proxyExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesProxy(NormalExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.nativeExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesNative(NativeExecutionInfo { info -> +inline fun BukkitExecutable<*>.nativeExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesNative(NormalExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.remoteConsoleExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesRemoteConsole(RemoteConsoleExecutionInfo { info -> +inline fun BukkitExecutable<*>.remoteConsoleExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): BukkitExecutable<*> = executesRemoteConsole(NormalExecutorInfo { info -> executor(info) }) // ExecutionInfo resulting executors -inline fun BukkitExecutable<*>.anyResultingExecutionInfo(crossinline executor: (ExecutionInfo>) -> Int): BukkitExecutable<*> = executes(ResultingCommandExecutionInfo { info -> +inline fun BukkitExecutable<*>.anyResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executes(ResultingExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.playerResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesPlayer(PlayerResultingExecutionInfo { info -> +inline fun BukkitExecutable<*>.playerResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesPlayer(ResultingExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.entityResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesEntity(EntityResultingExecutionInfo { info -> +inline fun BukkitExecutable<*>.entityResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesEntity(ResultingExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.consoleResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesConsole(ConsoleResultingExecutionInfo { info -> +inline fun BukkitExecutable<*>.consoleResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesConsole(ResultingExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.commandBlockResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesCommandBlock(CommandBlockResultingExecutionInfo { info -> +inline fun BukkitExecutable<*>.commandBlockResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesCommandBlock(ResultingExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.proxyResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesProxy(ProxyResultingExecutionInfo { info -> +inline fun BukkitExecutable<*>.proxyResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesProxy(ResultingExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.nativeResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesNative(NativeResultingExecutionInfo { info -> +inline fun BukkitExecutable<*>.nativeResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesNative(ResultingExecutorInfo { info -> executor(info) }) -inline fun BukkitExecutable<*>.remoteConsoleResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesRemoteConsole(RemoteConsoleResultingExecutionInfo { info -> +inline fun BukkitExecutable<*>.remoteConsoleResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): BukkitExecutable<*> = executesRemoteConsole(ResultingExecutorInfo { info -> executor(info) }) \ No newline at end of file diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt index 31edac5ae1..7f86caf18a 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt @@ -4,54 +4,51 @@ import com.velocitypowered.api.command.CommandSource import com.velocitypowered.api.proxy.ConsoleCommandSource import com.velocitypowered.api.proxy.Player import dev.jorel.commandapi.VelocityExecutable -import dev.jorel.commandapi.commandsenders.VelocityCommandSender -import dev.jorel.commandapi.commandsenders.VelocityConsoleCommandSender -import dev.jorel.commandapi.commandsenders.VelocityPlayer import dev.jorel.commandapi.executors.* // Executors for CommandAPICommand, CommandTree and ArgumentTree -inline fun VelocityExecutable<*>.anyExecutor(crossinline executor: (CommandSource, CommandArguments) -> Unit): VelocityExecutable<*> = executes(CommandExecutor { sender, args -> +inline fun VelocityExecutable<*>.anyExecutor(crossinline executor: (CommandSource, CommandArguments) -> Unit): VelocityExecutable<*> = executes(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun VelocityExecutable<*>.playerExecutor(crossinline executor: (Player, CommandArguments) -> Unit): VelocityExecutable<*> = executesPlayer(PlayerCommandExecutor { sender, args -> +inline fun VelocityExecutable<*>.playerExecutor(crossinline executor: (Player, CommandArguments) -> Unit): VelocityExecutable<*> = executesPlayer(NormalExecutor { sender, args -> executor(sender, args) }) -inline fun VelocityExecutable<*>.consoleExecutor(crossinline executor: (ConsoleCommandSource, CommandArguments) -> Unit): VelocityExecutable<*> = executesConsole(ConsoleCommandExecutor { sender, args -> +inline fun VelocityExecutable<*>.consoleExecutor(crossinline executor: (ConsoleCommandSource, CommandArguments) -> Unit): VelocityExecutable<*> = executesConsole(NormalExecutor { sender, args -> executor(sender, args) }) // Resulting executors -inline fun VelocityExecutable<*>.anyResultingExecutor(crossinline executor: (CommandSource, CommandArguments) -> Int): VelocityExecutable<*> = executes(ResultingCommandExecutor { sender, args -> +inline fun VelocityExecutable<*>.anyResultingExecutor(crossinline executor: (CommandSource, CommandArguments) -> Int): VelocityExecutable<*> = executes(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun VelocityExecutable<*>.playerResultingExecutor(crossinline executor: (Player, CommandArguments) -> Int): VelocityExecutable<*> = executesPlayer(PlayerResultingCommandExecutor { sender, args -> +inline fun VelocityExecutable<*>.playerResultingExecutor(crossinline executor: (Player, CommandArguments) -> Int): VelocityExecutable<*> = executesPlayer(ResultingExecutor { sender, args -> executor(sender, args) }) -inline fun VelocityExecutable<*>.consoleResultingExecutor(crossinline executor: (ConsoleCommandSource, CommandArguments) -> Int): VelocityExecutable<*> = executesConsole(ConsoleResultingCommandExecutor { sender, args -> +inline fun VelocityExecutable<*>.consoleResultingExecutor(crossinline executor: (ConsoleCommandSource, CommandArguments) -> Int): VelocityExecutable<*> = executesConsole(ResultingExecutor { sender, args -> executor(sender, args) }) // ExecutionInfo normal executors -inline fun VelocityExecutable<*>.anyExecutionInfo(crossinline executor: (ExecutionInfo>) -> Unit): VelocityExecutable<*> = executes(CommandExecutionInfo { info -> +inline fun VelocityExecutable<*>.anyExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): VelocityExecutable<*> = executes(NormalExecutorInfo { info -> executor(info) }) -inline fun VelocityExecutable<*>.playerExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): VelocityExecutable<*> = executesPlayer(PlayerExecutionInfo { info -> +inline fun VelocityExecutable<*>.playerExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): VelocityExecutable<*> = executesPlayer(NormalExecutorInfo { info -> executor(info) }) -inline fun VelocityExecutable<*>.consoleExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): VelocityExecutable<*> = executesConsole(ConsoleExecutionInfo { info -> +inline fun VelocityExecutable<*>.consoleExecutionInfo(crossinline executor: (ExecutionInfo) -> Unit): VelocityExecutable<*> = executesConsole(NormalExecutorInfo { info -> executor(info) }) // ExecutionInfo resulting executors -inline fun VelocityExecutable<*>.anyResultingExecutionInfo(crossinline executor: (ExecutionInfo>) -> Int): VelocityExecutable<*> = executes(ResultingCommandExecutionInfo { info -> +inline fun VelocityExecutable<*>.anyResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): VelocityExecutable<*> = executes(ResultingExecutorInfo { info -> executor(info) }) -inline fun VelocityExecutable<*>.playerResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): VelocityExecutable<*> = executesPlayer(PlayerResultingExecutionInfo { info -> +inline fun VelocityExecutable<*>.playerResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): VelocityExecutable<*> = executesPlayer(ResultingExecutorInfo { info -> executor(info) }) -inline fun VelocityExecutable<*>.consoleResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): VelocityExecutable<*> = executesConsole(ConsoleResultingExecutionInfo { info -> +inline fun VelocityExecutable<*>.consoleResultingExecutionInfo(crossinline executor: (ExecutionInfo) -> Int): VelocityExecutable<*> = executesConsole(ResultingExecutorInfo { info -> executor(info) }) \ No newline at end of file diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java index 08d26ffa64..d02ca16783 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java @@ -1,180 +1,128 @@ package dev.jorel.commandapi; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.executors.*; +import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.ProxiedCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import dev.jorel.commandapi.executors.BukkitNormalTypedExecutor; +import dev.jorel.commandapi.executors.BukkitResultingTypedExecutor; +import dev.jorel.commandapi.executors.ExecutorType; +import dev.jorel.commandapi.executors.NormalExecutor; +import dev.jorel.commandapi.executors.NormalExecutorInfo; +import dev.jorel.commandapi.executors.ResultingExecutor; +import dev.jorel.commandapi.executors.ResultingExecutorInfo; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; public interface BukkitExecutable /// @endcond > extends PlatformExecutable { - // Regular command executor /** * Adds an executor to the current command builder * - * @param executor A lambda of type (CommandSender, Object[]) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<CommandSender, ?> -> () that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(CommandExecutor executor, ExecutorType... types) { + default Impl executes(NormalExecutorInfo executor, ExecutorType... types) { if (types == null || types.length == 0) { - getExecutor().addNormalExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addNormalExecutor(new CommandExecutor() { - @Override - public void run(CommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException { - executor.executeWith(new BukkitExecutionInfo<>(sender, CommandAPIBukkit.get().wrapCommandSender(sender), args)); - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } + types = new ExecutorType[]{ExecutorType.ALL}; } + + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, types)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (BukkitCommandExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (CommandSender, CommandArguments) -> () that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(CommandExecutionInfo executor, ExecutorType... types) { - if (types == null || types.length == 0) { - getExecutor().addNormalExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addNormalExecutor(new CommandExecutionInfo() { - - @Override - public void run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - executor.executeWith(info); - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } - } - return instance(); + default Impl executes(NormalExecutor executor, ExecutorType... types) { + // While we can cast directly to `NormalExecutorInfo` (because `NormalExecutor` extends it), this method + // is necessary to help Java identify the expression signature of user defined lambdas. + // The same applies for the rest of the executes methods. + return executes((NormalExecutorInfo) executor, types); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (CommandSender, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<CommandSender, ?> -> int that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(ResultingCommandExecutor executor, ExecutorType... types) { + default Impl executes(ResultingExecutorInfo executor, ExecutorType... types) { if (types == null || types.length == 0) { - getExecutor().addResultingExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addResultingExecutor(new ResultingCommandExecutor() { - - @Override - public int run(CommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException { - executor.executeWith(new BukkitExecutionInfo<>(sender, CommandAPIBukkit.get().wrapCommandSender(sender), args)); - return 1; - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } + types = new ExecutorType[]{ExecutorType.ALL}; } + + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, types)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (BukkitCommandExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (CommandSender, CommandArguments) -> int that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(ResultingCommandExecutionInfo executor, ExecutorType... types) { - if (types == null || types.length == 0) { - getExecutor().addResultingExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addResultingExecutor(new ResultingCommandExecutionInfo() { - - @Override - public int run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - executor.executeWith(info); - return 1; - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } - } - return instance(); + default Impl executes(ResultingExecutor executor, ExecutorType... types) { + return executes((ResultingExecutorInfo) executor, types); } - // Player command executor /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Player, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<Player, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesPlayer(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.PLAYER)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (Player, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesPlayer(NormalExecutor executor) { + return executesPlayer((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Player, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<Player, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesPlayer(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.PLAYER)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (Player, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesPlayer(ResultingExecutor executor) { + return executesPlayer((ResultingExecutorInfo) executor); } // Entity command executor @@ -182,45 +130,43 @@ default Impl executesPlayer(PlayerResultingExecutionInfo info) { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Entity, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<Entity, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesEntity(EntityCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesEntity(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.ENTITY)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (Entity, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesEntity(EntityExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesEntity(NormalExecutor executor) { + return executesEntity((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Entity, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<Entity, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesEntity(EntityResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesEntity(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.ENTITY)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (Entity, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesEntity(EntityResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesEntity(ResultingExecutor executor) { + return executesEntity((ResultingExecutorInfo) executor); } // Proxy command executor @@ -228,45 +174,43 @@ default Impl executesEntity(EntityResultingExecutionInfo info) { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Entity, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<NativeProxyCommandSender, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesProxy(ProxyCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesProxy(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.PROXY)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (NativeProxyCommandSender, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesProxy(ProxyExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesProxy(NormalExecutor executor) { + return executesProxy((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Entity, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<ProxiedCommandSender, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesProxy(ProxyResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesProxy(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.PROXY)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (NativeProxyCommandSender, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesProxy(ProxyResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesProxy(ResultingExecutor executor) { + return executesProxy((ResultingExecutorInfo) executor); } // Command block command executor @@ -274,45 +218,43 @@ default Impl executesProxy(ProxyResultingExecutionInfo info) { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (BlockCommandSender, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<BlockCommandSender, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesCommandBlock(CommandBlockCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesCommandBlock(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.BLOCK)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (BlockCommandSender, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesCommandBlock(CommandBlockExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesCommandBlock(NormalExecutor executor) { + return executesCommandBlock((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (BlockCommandSender, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<BlockCommandSender, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesCommandBlock(CommandBlockResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesCommandBlock(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.BLOCK)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (BlockCommandSender, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesCommandBlock(CommandBlockResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesCommandBlock(ResultingExecutor executor) { + return executesCommandBlock((ResultingExecutorInfo) executor); } // Console command executor @@ -320,45 +262,43 @@ default Impl executesCommandBlock(CommandBlockResultingExecutionInfo info) { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ConsoleCommandSender, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<ConsoleCommandSender, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesConsole(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.CONSOLE)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (ConsoleCommandSender, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesConsole(NormalExecutor executor) { + return executesConsole((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ConsoleCommandSender, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<ConsoleCommandSender, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesConsole(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.CONSOLE)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (ConsoleCommandSender, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesConsole(ResultingExecutor executor) { + return executesConsole((ResultingExecutorInfo) executor); } // Native command executor @@ -366,45 +306,43 @@ default Impl executesConsole(ConsoleResultingExecutionInfo info) { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (NativeCommandExecutor, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<NativeProxyCommandSender, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesNative(NativeCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesNative(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.NATIVE)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (NativeProxyCommandSender, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesNative(NativeExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesNative(NormalExecutor executor) { + return executesNative((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (NativeCommandExecutor, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<NativeProxyCommandSender, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesNative(NativeResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesNative(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.NATIVE)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (NativeProxyCommandSender, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesNative(NativeResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesNative(ResultingExecutor executor) { + return executesNative((ResultingExecutorInfo) executor); } // RemoteConsole command executor @@ -412,90 +350,86 @@ default Impl executesNative(NativeResultingExecutionInfo info) { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (RemoteConsoleCommandExecutor, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<RemoteConsoleCommandSender, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesRemoteConsole(RemoteConsoleCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesRemoteConsole(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.REMOTE)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (RemoteConsoleCommandSender, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesRemoteConsole(RemoteConsoleExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesRemoteConsole(NormalExecutor executor) { + return executesRemoteConsole((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (RemoteConsoleResultingCommandExecutor, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<RemoteConsoleCommandSender, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesRemoteConsole(RemoteConsoleResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesRemoteConsole(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.REMOTE)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (RemoteConsoleCommandSender, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesRemoteConsole(RemoteConsoleResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesRemoteConsole(ResultingExecutor executor) { + return executesRemoteConsole((ResultingExecutorInfo) executor); } // Feedback-forwarding command executor - + /** * Adds an executor to the current command builder * - * @param executor A lambda of type (FeedbackForwardingCommandExecutor, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<FeedbackForwardingCommandExecutor, ?> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesFeedbackForwarding(FeedbackForwardingCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesFeedbackForwarding(NormalExecutorInfo executor) { + getExecutor().addExecutor(new BukkitNormalTypedExecutor<>(executor, ExecutorType.FEEDBACK_FORWARDING)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (FeedbackForwardingCommandExecutor, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesFeedbackForwarding(FeedbackForwardingExecutionInfo info) { - getExecutor().addNormalExecutor(info); - return instance(); + default Impl executesFeedbackForwarding(NormalExecutor executor) { + return executesFeedbackForwarding((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (FeedbackForwardingCommandExecutor, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<FeedbackForwardingCommandExecutor, ?> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesFeedbackForwarding(FeedbackForwardingResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesFeedbackForwarding(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new BukkitResultingTypedExecutor<>(executor, ExecutorType.FEEDBACK_FORWARDING)); return instance(); } /** * Adds an executor to the current command builder * - * @param info A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (FeedbackForwardingCommandExecutor, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesFeedbackForwarding(FeedbackForwardingResultingExecutionInfo info) { - getExecutor().addResultingExecutor(info); - return instance(); + default Impl executesFeedbackForwarding(ResultingExecutor executor) { + return executesFeedbackForwarding((ResultingExecutorInfo) executor); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index c7a4942961..31ba71232d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -7,25 +7,17 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; import net.kyori.adventure.text.ComponentLike; import org.bukkit.Bukkit; import org.bukkit.Keyed; -import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.PluginCommand; -import org.bukkit.command.ProxiedCommandSender; -import org.bukkit.command.RemoteConsoleCommandSender; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -37,7 +29,6 @@ import org.bukkit.plugin.java.JavaPlugin; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.LiteralCommandNode; @@ -46,24 +37,12 @@ import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.AbstractPlayer; -import dev.jorel.commandapi.commandsenders.BukkitBlockCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitConsoleCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitEntity; -import dev.jorel.commandapi.commandsenders.BukkitFeedbackForwardingCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; -import dev.jorel.commandapi.commandsenders.BukkitProxiedCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitRemoteConsoleCommandSender; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; import dev.jorel.commandapi.help.CommandAPIHelpTopic; import dev.jorel.commandapi.help.CustomCommandAPIHelpTopic; import dev.jorel.commandapi.nms.NMS; import dev.jorel.commandapi.preprocessor.Unimplemented; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.kyori.adventure.text.Component; import net.md_5.bungee.api.chat.BaseComponent; @@ -265,54 +244,11 @@ public void onDisable() { @Override @Unimplemented(because = REQUIRES_CSS) - public abstract BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative); - - @Override - @Unimplemented(because = REQUIRES_CSS) - public abstract BukkitCommandSender getCommandSenderFromCommandSource(Source cs); + public abstract CommandSender getCommandSenderFromCommandSource(Source cs); @Override @Unimplemented(because = REQUIRES_CRAFTBUKKIT) - public abstract Source getBrigadierSourceFromCommandSender(AbstractCommandSender sender); - - public BukkitCommandSender wrapCommandSender(CommandSender sender) { - if (sender instanceof BlockCommandSender block) { - return new BukkitBlockCommandSender(block); - } - if (sender instanceof ConsoleCommandSender console) { - return new BukkitConsoleCommandSender(console); - } - if (sender instanceof Player player) { - return new BukkitPlayer(player); - } - if (sender instanceof org.bukkit.entity.Entity entity) { - return new BukkitEntity(entity); - } - if (sender instanceof NativeProxyCommandSender nativeProxy) { - return new BukkitNativeProxyCommandSender(nativeProxy); - } - if (sender instanceof ProxiedCommandSender proxy) { - return new BukkitProxiedCommandSender(proxy); - } - if (sender instanceof RemoteConsoleCommandSender remote) { - return new BukkitRemoteConsoleCommandSender(remote); - } - if (paper.isPaperPresent()) { - final Class FeedbackForwardingSender = paper.getFeedbackForwardingCommandSender(); - if (FeedbackForwardingSender.isInstance(sender)) { - // We literally cannot type this at compile-time, so let's use a placeholder CommandSender instance - return new BukkitFeedbackForwardingCommandSender(FeedbackForwardingSender.cast(sender)); - } - - final Class NullCommandSender = paper.getNullCommandSender(); - if (NullCommandSender != null && NullCommandSender.isInstance(sender)) { - // Since this should only be during a function load, this is just a placeholder to evade the exception. - return null; - } - - } - throw new RuntimeException("Failed to wrap CommandSender " + sender + " to a CommandAPI-compatible BukkitCommandSender"); - } + public abstract Source getBrigadierSourceFromCommandSender(CommandSender sender); public void registerPermission(String string) { try { @@ -471,8 +407,8 @@ public void severe(String message, Throwable exception) { public abstract void reloadDataPacks(); @Override - public void updateRequirements(AbstractPlayer player) { - ((Player) player.getSource()).updateCommands(); + public void updateRequirements(CommandSender player) { + ((Player) player).updateCommands(); } @Override @@ -558,4 +494,29 @@ protected void registerBukkitRecipesSafely(Iterator recipes) { } } } + + @Override + public Predicate getPermissionCheck(CommandPermission permission) { + final Predicate senderCheck; + + if (permission.equals(CommandPermission.NONE)) { + // No permissions always passes + senderCheck = CommandPermission.TRUE(); + } else if (permission.equals(CommandPermission.OP)) { + senderCheck = CommandSender::isOp; + } else { + Optional permissionStringWrapper = permission.getPermission(); + if (permissionStringWrapper.isPresent()) { + String permissionString = permissionStringWrapper.get(); + // check permission + senderCheck = sender -> sender.hasPermission(permissionString); + } else { + // No permission always passes + senderCheck = CommandPermission.TRUE(); + } + } + + // Negate if specified + return permission.isNegated() ? senderCheck.negate() : senderCheck; + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java index 82c715067c..0493f118bb 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java @@ -24,7 +24,7 @@ import dev.jorel.commandapi.arguments.FlattenableArgument; import dev.jorel.commandapi.arguments.GreedyStringArgument; import dev.jorel.commandapi.executors.CommandArguments; -import dev.jorel.commandapi.executors.NativeResultingCommandExecutor; +import dev.jorel.commandapi.executors.ResultingExecutor; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -184,7 +184,7 @@ private static void convertPluginCommand(JavaPlugin plugin, String commandName, permissionNode = CommandPermission.fromString(permission); } - NativeResultingCommandExecutor executor = (sender, args) -> { + ResultingExecutor executor = (sender, args) -> { org.bukkit.command.Command command = plugin.getCommand(commandName); if (command == null) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java index c9f04d1b31..018940d568 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java @@ -22,7 +22,6 @@ public class PaperImplementations { private final boolean isFoliaPresent; private final NMS nmsInstance; private final Class feedbackForwardingCommandSender; - private final Class nullCommandSender; /** * Constructs a PaperImplementations object @@ -45,15 +44,6 @@ public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, NMS< } this.feedbackForwardingCommandSender = tempFeedbackForwardingCommandSender; - - Class tempNullCommandSender = null; - try { - tempNullCommandSender = (Class) Class.forName("io.papermc.paper.brigadier.NullCommandSender"); - } catch (ClassNotFoundException e) { - // uhh... - } - - this.nullCommandSender = tempNullCommandSender; } /** @@ -125,13 +115,6 @@ public Class getFeedbackForwardingCommandSender() { return this.feedbackForwardingCommandSender; } - /** - * @return a class reference pointing to {@code io.papermc.paper.brigadier.NullCommandSender} - */ - public Class getNullCommandSender() { - return this.nullCommandSender; - } - /** * Builds a {@link WrapperCommandSyntaxException} from a message with colour codes like {@link ChatColor} or using the § symbol. * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/AdventureChatArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/AdventureChatArgument.java index bcc136de39..7dd3580076 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/AdventureChatArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/AdventureChatArgument.java @@ -24,7 +24,6 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandAPIBukkit; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import dev.jorel.commandapi.exceptions.PaperAdventureNotFoundException; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import dev.jorel.commandapi.executors.CommandArguments; @@ -42,9 +41,9 @@ * * @apiNote Returns a {@link Component} object */ -public class AdventureChatArgument extends Argument implements GreedyArgument, Previewable { +public class AdventureChatArgument extends Argument implements GreedyArgument, Previewable { - private PreviewableFunction preview; + private PreviewableFunction preview; private boolean usePreview; /** @@ -75,14 +74,14 @@ public CommandAPIArgumentType getArgumentType() { @Override public Component parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { - final CommandSender sender = CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()).getSource(); + final CommandSender sender = CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()); Component component = CommandAPIBukkit.get().getAdventureChat(cmdCtx, key); - Optional> previewOptional = getPreview(); + Optional> previewOptional = getPreview(); if (this.usePreview && previewOptional.isPresent() && sender instanceof Player player) { try { Component previewComponent = previewOptional.get() - .generatePreview(new PreviewInfo<>(new BukkitPlayer(player), CommandAPIHandler.getRawArgumentInput(cmdCtx, key), cmdCtx.getInput(), component)); + .generatePreview(new PreviewInfo<>(player, CommandAPIHandler.getRawArgumentInput(cmdCtx, key), cmdCtx.getInput(), component)); component = previewComponent; } catch (WrapperCommandSyntaxException e) { @@ -94,13 +93,13 @@ public Component parseArgument(CommandContext preview) { + public AdventureChatArgument withPreview(PreviewableFunction preview) { this.preview = preview; return this; } @Override - public Optional> getPreview() { + public Optional> getPreview() { return Optional.ofNullable(preview); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ChatArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ChatArgument.java index e028fc7517..1af528e810 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ChatArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ChatArgument.java @@ -31,7 +31,6 @@ import dev.jorel.commandapi.CommandAPIBukkit; import dev.jorel.commandapi.CommandAPIHandler; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import dev.jorel.commandapi.exceptions.SpigotNotFoundException; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import dev.jorel.commandapi.wrappers.PreviewableFunction; @@ -44,9 +43,9 @@ * * @apiNote Returns a {@link BaseComponent}{@code []} object */ -public class ChatArgument extends Argument implements GreedyArgument, Previewable { +public class ChatArgument extends Argument implements GreedyArgument, Previewable { - private PreviewableFunction preview; + private PreviewableFunction preview; private boolean usePreview; /** @@ -77,14 +76,14 @@ public CommandAPIArgumentType getArgumentType() { @Override public BaseComponent[] parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { - final CommandSender sender = CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()).getSource(); + final CommandSender sender = CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()); BaseComponent[] component = CommandAPIBukkit.get().getChat(cmdCtx, key); - Optional> previewOptional = getPreview(); + Optional> previewOptional = getPreview(); if (this.usePreview && previewOptional.isPresent() && sender instanceof Player player) { try { BaseComponent[] previewComponent = previewOptional.get() - .generatePreview(new PreviewInfo<>(new BukkitPlayer(player), CommandAPIHandler.getRawArgumentInput(cmdCtx, key), cmdCtx.getInput(), component)); + .generatePreview(new PreviewInfo<>(player, CommandAPIHandler.getRawArgumentInput(cmdCtx, key), cmdCtx.getInput(), component)); component = previewComponent; } catch (WrapperCommandSyntaxException e) { @@ -95,13 +94,13 @@ public BaseComponent[] parseArgument(CommandContext preview) { + public ChatArgument withPreview(PreviewableFunction preview) { this.preview = preview; return this; } @Override - public Optional> getPreview() { + public Optional> getPreview() { return Optional.ofNullable(preview); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CommandArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CommandArgument.java index faf0543255..4403b316b9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CommandArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CommandArgument.java @@ -17,7 +17,6 @@ import org.bukkit.entity.Player; import java.util.Arrays; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -195,7 +194,7 @@ public CommandResult parseArgument(CommandContextget().getSenderForCommand(cmdCtx, false).getSource(); + CommandSender sender = CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()); StringReader context = new StringReader(command); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java index 3d5a273170..1627cf6631 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java @@ -108,7 +108,7 @@ public T parseArgument(CommandContext c final B parsedInput = base.parseArgument(cmdCtx, key, previousArgs); try { - return infoParser.apply(new CustomArgumentInfo<>(CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()).getSource(), + return infoParser.apply(new CustomArgumentInfo<>(CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()), previousArgs, customresult, parsedInput)); } catch (CustomArgumentException e) { throw e.toCommandSyntax(customresult, cmdCtx); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ListArgumentCommon.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ListArgumentCommon.java index 9fc6d9c5eb..1ca60be8b9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ListArgumentCommon.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/ListArgumentCommon.java @@ -121,7 +121,7 @@ public CommandAPIArgumentType getArgumentType() { @Override public List parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { - final CommandSender sender = CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()).getSource(); + final CommandSender sender = CommandAPIBukkit.get().getCommandSenderFromCommandSource(cmdCtx.getSource()); final SuggestionInfo currentInfo = new SuggestionInfo<>(sender, previousArgs, cmdCtx.getInput(), cmdCtx.getArgument(key, String.class)); // Get the list of values which this can take diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitBlockCommandSender.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitBlockCommandSender.java deleted file mode 100644 index 941595f76a..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitBlockCommandSender.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -import org.bukkit.command.BlockCommandSender; - -public class BukkitBlockCommandSender implements AbstractBlockCommandSender, BukkitCommandSender { - - private final BlockCommandSender commandBlock; - - public BukkitBlockCommandSender(BlockCommandSender commandBlock) { - this.commandBlock = commandBlock; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.commandBlock.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return this.commandBlock.isOp(); - } - - @Override - public BlockCommandSender getSource() { - return this.commandBlock; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitCommandSender.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitCommandSender.java deleted file mode 100644 index 47a868269f..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitCommandSender.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -import org.bukkit.command.CommandSender; - -public interface BukkitCommandSender extends AbstractCommandSender { -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitConsoleCommandSender.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitConsoleCommandSender.java deleted file mode 100644 index 51f500525d..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitConsoleCommandSender.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -import org.bukkit.command.ConsoleCommandSender; - -public class BukkitConsoleCommandSender implements AbstractConsoleCommandSender, BukkitCommandSender { - - private final ConsoleCommandSender sender; - - public BukkitConsoleCommandSender(ConsoleCommandSender sender) { - this.sender = sender; - } - - @Override - public boolean hasPermission(String permissionNode) { - return sender.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return sender.isOp(); - } - - @Override - public ConsoleCommandSender getSource() { - return sender; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitEntity.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitEntity.java deleted file mode 100644 index 7ef481f6cf..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitEntity.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -import org.bukkit.entity.Entity; - -public class BukkitEntity implements AbstractEntity, BukkitCommandSender { - - private final Entity entity; - - public BukkitEntity(Entity entity) { - this.entity = entity; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.entity.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return this.entity.isOp(); - } - - @Override - public Entity getSource() { - return this.entity; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitFeedbackForwardingCommandSender.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitFeedbackForwardingCommandSender.java deleted file mode 100644 index 0a933a1683..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitFeedbackForwardingCommandSender.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -import org.bukkit.command.CommandSender; - -public class BukkitFeedbackForwardingCommandSender implements AbstractFeedbackForwardingCommandSender, BukkitCommandSender { - - private final FeedbackForwardingSender sender; - - public BukkitFeedbackForwardingCommandSender(FeedbackForwardingSender sender) { - this.sender = sender; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.sender.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return this.sender.isOp(); - } - - @Override - public FeedbackForwardingSender getSource() { - return this.sender; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitNativeProxyCommandSender.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitNativeProxyCommandSender.java deleted file mode 100644 index 1ab27622cd..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitNativeProxyCommandSender.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.commandsenders; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -public class BukkitNativeProxyCommandSender implements AbstractNativeProxyCommandSender, BukkitCommandSender { - - private final NativeProxyCommandSender proxySender; - - public BukkitNativeProxyCommandSender(NativeProxyCommandSender player) { - this.proxySender = player; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.proxySender.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return this.proxySender.isOp(); - } - - @Override - public NativeProxyCommandSender getSource() { - return this.proxySender; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitPlayer.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitPlayer.java deleted file mode 100644 index 58aaf19418..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitPlayer.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.commandsenders; -import org.bukkit.entity.Player; - -public class BukkitPlayer implements AbstractPlayer, BukkitCommandSender { - - private final Player player; - - public BukkitPlayer(Player player) { - this.player = player; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.player.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return this.player.isOp(); - } - - @Override - public Player getSource() { - return this.player; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitProxiedCommandSender.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitProxiedCommandSender.java deleted file mode 100644 index 22e465635c..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitProxiedCommandSender.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.commandsenders; -import org.bukkit.command.ProxiedCommandSender; - -public class BukkitProxiedCommandSender implements AbstractProxiedCommandSender, BukkitCommandSender { - - private final ProxiedCommandSender proxySender; - - public BukkitProxiedCommandSender(ProxiedCommandSender player) { - this.proxySender = player; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.proxySender.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return this.proxySender.isOp(); - } - - @Override - public ProxiedCommandSender getSource() { - return this.proxySender; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitRemoteConsoleCommandSender.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitRemoteConsoleCommandSender.java deleted file mode 100644 index 87956682af..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/commandsenders/BukkitRemoteConsoleCommandSender.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -import org.bukkit.command.RemoteConsoleCommandSender; - -public class BukkitRemoteConsoleCommandSender implements AbstractRemoteConsoleCommandSender, BukkitCommandSender { - - private final RemoteConsoleCommandSender remote; - - public BukkitRemoteConsoleCommandSender(RemoteConsoleCommandSender remote) { - this.remote = remote; - } - - @Override - public boolean hasPermission(String permissionNode) { - return remote.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return remote.isOp(); - } - - @Override - public RemoteConsoleCommandSender getSource() { - return remote; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitExecutionInfo.java deleted file mode 100644 index 1c486cf759..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitExecutionInfo.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; - -/** - * This record represents a BukkitExecutionInfo for a command. It provides the sender of a command, as well as it's arguments - * - * @param The type of the sender of a command this BukkitExecutionInfo belongs to - */ -public record BukkitExecutionInfo( - - /** - * @return The sender of this command - */ - Sender sender, - - /** - * This is not intended for public use and is only used internally. The {@link BukkitExecutionInfo#sender()} method should be used instead! - * - * @return The wrapper type of this command - */ - BukkitCommandSender senderWrapper, - - /** - * @return The arguments of this command - */ - CommandArguments args - -) implements ExecutionInfo> { -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java new file mode 100644 index 0000000000..8adffe0631 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java @@ -0,0 +1,33 @@ +package dev.jorel.commandapi.executors; + +import org.bukkit.command.CommandSender; + +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; + +/** + * A {@link BukkitTypedExecutor} for {@link NormalExecutorInfo} lambdas that don't have an int result. + * When running this executor succeeds, it simply returns 1. + * + * @param executor The {@link NormalExecutorInfo} to invoke when running this executor. + * @param types The {@link ExecutorType}s that this executor accepts. + * @param The {@link CommandSender} class that this executor accepts. + * @param The class for executing Brigadier commands. + */ +public record BukkitNormalTypedExecutor( + + /** + * @return The {@link NormalExecutorInfo} to invoke when running this executor. + */ + NormalExecutorInfo executor, + + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types +) implements BukkitTypedExecutor { + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + executor.run(info); + return 1; + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java new file mode 100644 index 0000000000..9deaa1796b --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java @@ -0,0 +1,31 @@ +package dev.jorel.commandapi.executors; + +import org.bukkit.command.CommandSender; + +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; + +/** + * A {@link BukkitTypedExecutor} for {@link ResultingExecutorInfo} lambdas that have an int result. + * + * @param executor The {@link ResultingExecutorInfo} to invoke when running this executor. + * @param types The {@link ExecutorType}s that this executor accepts. + * @param The {@link CommandSender} class that this executor accepts. + * @param The class for executing Brigadier commands. + */ +public record BukkitResultingTypedExecutor( + + /** + * @return The {@link ResultingExecutorInfo} to invoke when running this executor. + */ + ResultingExecutorInfo executor, + + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types +) implements BukkitTypedExecutor { + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + return executor.run(info); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java new file mode 100644 index 0000000000..2368ee4ba7 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java @@ -0,0 +1,58 @@ +package dev.jorel.commandapi.executors; + +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.ProxiedCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import dev.jorel.commandapi.CommandAPIBukkit; +import dev.jorel.commandapi.PaperImplementations; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; + +/** + * A {@link TypedExecutor} for Bukkit. The {@link CommandSender}s accepted by + * this executor can be defined by overriding the method {@link #types()}. + * + * @param The {@link CommandSender} class that this executor accepts. + * @param The class for executing Brigadier commands. + */ +public interface BukkitTypedExecutor extends TypedExecutor { + @Override + default ExecutionInfo tryForSender(ExecutionInfo info) { + CommandSender sender = info.sender(); + + for (ExecutorType type : types()) { + // Check if we can cast to the defined sender type + if (switch (type) { + case ALL -> true; + case PLAYER -> sender instanceof Player; + case ENTITY -> sender instanceof Entity; + case CONSOLE -> sender instanceof ConsoleCommandSender; + case BLOCK -> sender instanceof BlockCommandSender; + case PROXY -> sender instanceof ProxiedCommandSender; + case NATIVE -> { + // If we're a NATIVE executor, always accept and convert sender to a NativeProxyCommandSender + NativeProxyCommandSender proxyCommandSender = CommandAPIBukkit.get().getNativeProxyCommandSender(info.cmdCtx()); + info = info.copyWithNewSender(proxyCommandSender); + yield true; + } + case REMOTE -> sender instanceof RemoteConsoleCommandSender; + case FEEDBACK_FORWARDING -> { + PaperImplementations paper = CommandAPIBukkit.get().getPaper(); + yield paper.isPaperPresent() && paper.getFeedbackForwardingCommandSender().isInstance(sender); + } + }) { + return (ExecutionInfo) info; + } + } + return null; + } + + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType[] types(); +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockCommandExecutor.java deleted file mode 100644 index 6d0d5eca75..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockCommandExecutor.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.BlockCommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitBlockCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A normal command executor for a BlockCommandSender - */ -@FunctionalInterface -public interface CommandBlockCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(BlockCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.BLOCK; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockExecutionInfo.java deleted file mode 100644 index 3e7e4abcc1..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitBlockCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.BlockCommandSender; - -@FunctionalInterface -public interface CommandBlockExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.BLOCK; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingCommandExecutor.java deleted file mode 100644 index 923fc17c0d..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingCommandExecutor.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.BlockCommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitBlockCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A resulting command executor for a BlockCommandSender - */ -@FunctionalInterface -public interface CommandBlockResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * - * @return the result of this command - */ - int run(BlockCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * - * @return the result of this command - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.BLOCK; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingExecutionInfo.java deleted file mode 100644 index e583197cbf..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandBlockResultingExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitBlockCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.BlockCommandSender; - -@FunctionalInterface -public interface CommandBlockResultingExecutionInfo extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.BLOCK; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java deleted file mode 100644 index d510e30a52..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.CommandSender; - -@FunctionalInterface -public interface CommandExecutionInfo extends NormalExecutor> { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo> info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java deleted file mode 100644 index 779b5380ae..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.CommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A normal command executor for a CommandSender - */ -@FunctionalInterface -public interface CommandExecutor extends NormalExecutor> { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(CommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java deleted file mode 100644 index a3bbec1e86..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.ConsoleCommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A normal command executor for a ConsoleCommandSender - */ -@FunctionalInterface -public interface ConsoleCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(ConsoleCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.CONSOLE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java deleted file mode 100644 index 0015c9bd22..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.ConsoleCommandSender; - -@FunctionalInterface -public interface ConsoleExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.CONSOLE; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java deleted file mode 100644 index 3d858606ad..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.ConsoleCommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A resulting command executor for a ConsoleCommandSender - */ -@FunctionalInterface -public interface ConsoleResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(ConsoleCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.CONSOLE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java deleted file mode 100644 index 5eccd634eb..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.ConsoleCommandSender; - -@FunctionalInterface -public interface ConsoleResultingExecutionInfo extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.CONSOLE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityCommandExecutor.java deleted file mode 100644 index 9b0e8ef811..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityCommandExecutor.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitEntity; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Entity; - -/** - * A normal command executor for an Entity - */ -@FunctionalInterface -public interface EntityCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(Entity sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ENTITY; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityExecutionInfo.java deleted file mode 100644 index 90e82cff27..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitEntity; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Entity; - -@FunctionalInterface -public interface EntityExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ENTITY; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingCommandExecutor.java deleted file mode 100644 index 51a0a28190..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingCommandExecutor.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitEntity; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Entity; - -/** - * A resulting command executor for an Entity - */ -@FunctionalInterface -public interface EntityResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(Entity sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ENTITY; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingExecutionInfo.java deleted file mode 100644 index cf28c9a713..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/EntityResultingExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitEntity; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Entity; - -@FunctionalInterface -public interface EntityResultingExecutionInfo extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ENTITY; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java similarity index 54% rename from commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyCommandExecutor.java rename to commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java index fdceb47977..74b6d9bf91 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyCommandExecutor.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java @@ -20,41 +20,63 @@ *******************************************************************************/ package dev.jorel.commandapi.executors; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.ProxiedCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; /** - * A normal command executor for a NativeProxyCommandSender + * An enum representing the type of an executor */ -@FunctionalInterface -public interface ProxyCommandExecutor extends NormalExecutor { +public enum ExecutorType { + + /** + * An executor where the CommandSender is any {@link CommandSender} + */ + ALL, + + /** + * An executor where the CommandSender is a {@link Player} + */ + PLAYER, + + /** + * An executor where the CommandSender is an {@link Entity} + */ + ENTITY, + + /** + * An executor where the CommandSender is a {@link ConsoleCommandSender} + */ + CONSOLE, + + /** + * An executor where the CommandSender is a {@link BlockCommandSender} + */ + BLOCK, + + /** + * An executor where the CommandSender is a {@link ProxiedCommandSender} + */ + PROXY, /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. + * An executor where the CommandSender is (always) a {@link NativeProxyCommandSender} */ - void run(NativeProxyCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; + NATIVE, /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command + * An executor where the CommandSender is a {@link RemoteConsoleCommandSender} */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } + REMOTE, /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor + * An executor where the CommandSender is a {@code io.papermc.paper.commands.FeedbackForwardingSender} */ - @Override - default ExecutorType getType() { - return ExecutorType.PROXY; - } + FEEDBACK_FORWARDING; } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingCommandExecutor.java deleted file mode 100644 index 05ec98c740..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingCommandExecutor.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.CommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitFeedbackForwardingCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A normal command executor for a BlockCommandSender - */ -@FunctionalInterface -public interface FeedbackForwardingCommandExecutor extends NormalExecutor< CommandSender, BukkitFeedbackForwardingCommandSender> { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(CommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.FEEDBACK_FORWARDING; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingExecutionInfo.java deleted file mode 100644 index 886e8b367f..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingExecutionInfo.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.CommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitFeedbackForwardingCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface FeedbackForwardingExecutionInfo extends NormalExecutor> { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo> info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.FEEDBACK_FORWARDING; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingCommandExecutor.java deleted file mode 100644 index 09efe13ae5..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingCommandExecutor.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.CommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitFeedbackForwardingCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A resulting command executor for a BlockCommandSender - */ -@FunctionalInterface -public interface FeedbackForwardingResultingCommandExecutor extends ResultingExecutor> { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * - * @return the result of this command - */ - int run(CommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * - * @return the result of this command - */ - @Override - default int run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.FEEDBACK_FORWARDING ; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingExecutionInfo.java deleted file mode 100644 index a01947f7be..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/FeedbackForwardingResultingExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.CommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitFeedbackForwardingCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface FeedbackForwardingResultingExecutionInfo extends ResultingExecutor> { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - int run(ExecutionInfo> info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.FEEDBACK_FORWARDING; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeCommandExecutor.java deleted file mode 100644 index afb23693f0..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeCommandExecutor.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -/** - * A normal command executor for a NativeProxyCommandSender - */ -@FunctionalInterface -public interface NativeCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(NativeProxyCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.NATIVE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeExecutionInfo.java deleted file mode 100644 index ad5b6e468d..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -@FunctionalInterface -public interface NativeExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.NATIVE; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingCommandExecutor.java deleted file mode 100644 index 3c9b537187..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingCommandExecutor.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -/** - * A resulting command executor for a NativeProxyCommandSender - */ -@FunctionalInterface -public interface NativeResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(NativeProxyCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.NATIVE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingExecutionInfo.java deleted file mode 100644 index 0807a03c8a..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/NativeResultingExecutionInfo.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -public interface NativeResultingExecutionInfo extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.NATIVE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java deleted file mode 100644 index ce7151c1a5..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Player; - -/** - * A normal command executor for a Player - */ -@FunctionalInterface -public interface PlayerCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(Player sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PLAYER; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java deleted file mode 100644 index 6d5a3168e5..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Player; - -@FunctionalInterface -public interface PlayerExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PLAYER; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java deleted file mode 100644 index 729ed8acf2..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Player; - -/** - * A resulting command executor for a Player - */ -@FunctionalInterface -public interface PlayerResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(Player sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PLAYER; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java deleted file mode 100644 index a18df5eb95..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.entity.Player; - -public interface PlayerResultingExecutionInfo extends ResultingExecutor { - - /** - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PLAYER; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyExecutionInfo.java deleted file mode 100644 index 6b54c6f3f7..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -@FunctionalInterface -public interface ProxyExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PROXY; - } - -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingCommandExecutor.java deleted file mode 100644 index 1400c9b265..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingCommandExecutor.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -/** - * A resulting command executor for a NativeProxyCommandSender - */ -@FunctionalInterface -public interface ProxyResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(NativeProxyCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PROXY; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingExecutionInfo.java deleted file mode 100644 index d103f7653e..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ProxyResultingExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; - -@FunctionalInterface -public interface ProxyResultingExecutionInfo extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the command result - * @throws WrapperCommandSyntaxException - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PROXY; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleCommandExecutor.java deleted file mode 100644 index e7b9f92d49..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleCommandExecutor.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitRemoteConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.RemoteConsoleCommandSender; - -@FunctionalInterface -public interface RemoteConsoleCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(RemoteConsoleCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.REMOTE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleExecutionInfo.java deleted file mode 100644 index 5a819cc54a..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitRemoteConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.RemoteConsoleCommandSender; - -@FunctionalInterface -public interface RemoteConsoleExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.REMOTE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingCommandExecutor.java deleted file mode 100644 index 6c1f1b1cb8..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingCommandExecutor.java +++ /dev/null @@ -1,40 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitRemoteConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.RemoteConsoleCommandSender; - -@FunctionalInterface -public interface RemoteConsoleResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(RemoteConsoleCommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @return the value returned by this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.REMOTE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingExecutionInfo.java deleted file mode 100644 index e0bade3ec3..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/RemoteConsoleResultingExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitRemoteConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.RemoteConsoleCommandSender; - -@FunctionalInterface -public interface RemoteConsoleResultingExecutionInfo extends ResultingExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @return the value returned by this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.REMOTE; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java deleted file mode 100644 index 444448fd0b..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import org.bukkit.command.CommandSender; - -@FunctionalInterface -public interface ResultingCommandExecutionInfo extends ResultingExecutor> { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - int run(ExecutionInfo> info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java deleted file mode 100644 index 8641dc40be..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import org.bukkit.command.CommandSender; - -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A resulting command executor for a CommandSender - */ -@FunctionalInterface -public interface ResultingCommandExecutor extends ResultingExecutor> { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(CommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - @Override - default int run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java index 002927d26a..b0bb9e6ec0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java @@ -64,6 +64,7 @@ import dev.jorel.commandapi.wrappers.IntegerRange; import dev.jorel.commandapi.wrappers.Location2D; import dev.jorel.commandapi.wrappers.MathOperation; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import dev.jorel.commandapi.wrappers.ParticleData; import dev.jorel.commandapi.wrappers.Rotation; import dev.jorel.commandapi.wrappers.ScoreboardSlot; @@ -393,6 +394,8 @@ String getScoreHolderSingle(CommandContext cmdCtx, Strin Object getSound(CommandContext cmdCtx, String key, ArgumentSubType subType); + public abstract NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx); + /** * Retrieve a specific NMS implemented SuggestionProvider * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/Preview.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/Preview.java deleted file mode 100644 index 63822e6ccc..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/Preview.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.jorel.commandapi.wrappers; - -import net.kyori.adventure.text.Component; - -@FunctionalInterface -public interface Preview extends PreviewableFunction { -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewLegacy.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewLegacy.java deleted file mode 100644 index ccdaf127f2..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/PreviewLegacy.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.jorel.commandapi.wrappers; - -import net.md_5.bungee.api.chat.BaseComponent; - -@FunctionalInterface -public interface PreviewLegacy extends PreviewableFunction { -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/SimpleFunctionWrapper.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/SimpleFunctionWrapper.java index 1d3398e934..804f2ede08 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/SimpleFunctionWrapper.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/wrappers/SimpleFunctionWrapper.java @@ -98,7 +98,7 @@ public static Set getTags() { */ public int run(CommandSender sender) { CommandAPIBukkit platform = CommandAPIBukkit.get(); - return runInternal(platform.getBrigadierSourceFromCommandSender(platform.wrapCommandSender(sender))); + return runInternal(platform.getBrigadierSourceFromCommandSender(sender)); } /** diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java index 4064ec960a..a09ba4df15 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java @@ -98,9 +98,6 @@ import dev.jorel.commandapi.SpigotCommandRegistration; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -499,14 +496,18 @@ public BaseComponent[] getChatComponent(CommandContext c } @Override - public CommandListenerWrapper getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - return VanillaCommandWrapper.getListener(senderWrapper.getSource()); + public CommandListenerWrapper getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandListenerWrapper clw) { + public CommandSender getCommandSenderFromCommandSource(CommandListenerWrapper clw) { try { - return wrapCommandSender(clw.getBukkitSender()); + CommandSender sender = clw.getBukkitSender(); + // Sender CANNOT be null. This can occur when using a remote console + // sender. You can access it directly using this.getMinecraftServer().remoteConsole + // however this may also be null, so delegate to the next most-meaningful sender. + return sender == null ? Bukkit.getConsoleSender() : sender; } catch (UnsupportedOperationException e) { return null; } @@ -810,31 +811,23 @@ public String getScoreHolderSingle(CommandContext cmdCtx } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandListenerWrapper clw = cmdCtx.getSource(); - CommandSender sender = clw.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(clw); + + // Get position Vec3D pos = clw.getPosition(); Vec2F rot = clw.i(); World world = getWorldForCSS(clw); Location location = new Location(world, pos.getX(), pos.getY(), pos.getZ(), rot.j, rot.i); + // Get proxy sender (default to sender if null) Entity proxyEntity = clw.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java index dee8b13188..c5d7aa7109 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java @@ -87,9 +87,6 @@ import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.RequireField; import dev.jorel.commandapi.preprocessor.Unimplemented; import net.kyori.adventure.text.Component; @@ -305,8 +302,8 @@ public BlockData getBlockState(CommandContext cmdCtx, String } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - return VanillaCommandWrapper.getListener(senderWrapper.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -587,31 +584,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java index ee7009f4d1..c7e316b005 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java @@ -91,9 +91,6 @@ import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -365,8 +362,8 @@ public BlockData getBlockState(CommandContext cmdCtx, String } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - return VanillaCommandWrapper.getListener(senderWrapper.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -637,31 +634,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java index 65a3076256..09bc7395cc 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java @@ -87,9 +87,6 @@ import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -312,8 +309,8 @@ public BlockData getBlockState(CommandContext cmdCtx, String } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - return VanillaCommandWrapper.getListener(senderWrapper.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -587,31 +584,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java index 0dfb840475..5932a4dd75 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java @@ -35,9 +35,6 @@ import dev.jorel.commandapi.*; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.RequireField; import dev.jorel.commandapi.preprocessor.Unimplemented; @@ -434,8 +431,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public final CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - return VanillaCommandWrapper.getListener(senderWrapper.getSource()); + public final CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -692,22 +689,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java index ebbd69b31f..1946a35d10 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common_ChatPreviewHandler.java @@ -6,7 +6,6 @@ import dev.jorel.commandapi.CommandAPIBukkit; import dev.jorel.commandapi.arguments.PreviewInfo; import dev.jorel.commandapi.commandnodes.PreviewableCommandNode; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import dev.jorel.commandapi.wrappers.PreviewableFunction; import io.netty.channel.ChannelDuplexHandler; @@ -68,7 +67,7 @@ public MutableComponent parseChatPreviewQuery(String chatPreviewQuery) { // Return early if the node is not previewable final PreviewableCommandNode previewableNode = ip.previewableNode; - final Optional> preview = previewableNode.getPreview(); + final Optional> preview = previewableNode.getPreview(); if (preview.isEmpty()) { return null; } @@ -92,7 +91,7 @@ public MutableComponent parseChatPreviewQuery(String chatPreviewQuery) { } catch (CommandSyntaxException e) { throw new WrapperCommandSyntaxException(e); } - previewInfo = new PreviewInfo<>(new BukkitPlayer(player), input, chatPreviewQuery, parsedInput); + previewInfo = new PreviewInfo<>(player, input, chatPreviewQuery, parsedInput); } else { Component parsedInput; try { @@ -100,7 +99,7 @@ public MutableComponent parseChatPreviewQuery(String chatPreviewQuery) { } catch (CommandSyntaxException e) { throw new WrapperCommandSyntaxException(e); } - previewInfo = new PreviewInfo<>(new BukkitPlayer(player), input, chatPreviewQuery, parsedInput); + previewInfo = new PreviewInfo<>(player, input, chatPreviewQuery, parsedInput); } component = preview.get().generatePreview(previewInfo); @@ -136,7 +135,7 @@ public static InitialParse processChatPreviewQuery(String chatPreviewQuery, Comm if(cachedResult != null && cachedResult.fullInput.equals(fullInput)) return cachedResult; ParseResults results = platform.getBrigadierDispatcher() - .parse(fullInput, platform.getBrigadierSourceFromCommandSender(new BukkitPlayer(player))); + .parse(fullInput, platform.getBrigadierSourceFromCommandSender(player)); // Get the last node List> nodes = results.getContext().getNodes(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java index d77e7f6e3f..f649ed71c2 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java @@ -34,9 +34,6 @@ import dev.jorel.commandapi.*; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -309,8 +306,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender sender) { - return VanillaCommandWrapper.getListener(sender.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -571,31 +568,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java index 4febcec271..025b2ac88d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java @@ -34,9 +34,6 @@ import dev.jorel.commandapi.*; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -307,8 +304,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender sender) { - return VanillaCommandWrapper.getListener(sender.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -565,32 +562,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java index 92b7781637..bc8bf29521 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java @@ -89,9 +89,6 @@ import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -329,8 +326,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender sender) { - return VanillaCommandWrapper.getListener(sender.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -585,32 +582,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java index cc46076b05..c001defd08 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java @@ -89,9 +89,6 @@ import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -403,9 +400,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender( - AbstractCommandSender sender) { - return VanillaCommandWrapper.getListener(sender.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -712,35 +708,23 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, - boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using - // this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful - // sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java index b8fcb01598..74ae982cc7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java @@ -91,9 +91,6 @@ import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -452,9 +449,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender( - AbstractCommandSender sender) { - return VanillaCommandWrapper.getListener(sender.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Differs(from = "1.20.4", by = "Serializer.toJson now needs a Provider") @@ -784,35 +780,23 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, - boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using - // this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful - // sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java index d15570524a..dd6d9e3a5f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java @@ -34,9 +34,6 @@ import dev.jorel.commandapi.*; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -306,8 +303,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender sender) { - return VanillaCommandWrapper.getListener(sender.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -565,32 +562,23 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } - - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); + + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java index 58463efbc2..458f3423a9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java @@ -96,9 +96,6 @@ import dev.jorel.commandapi.SpigotCommandRegistration; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.NMSMeta; import dev.jorel.commandapi.preprocessor.RequireField; @@ -451,9 +448,8 @@ public final BlockData getBlockState(CommandContext cmdCtx, } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender( - AbstractCommandSender sender) { - return VanillaCommandWrapper.getListener(sender.getSource()); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); } @Override @@ -781,35 +777,23 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, - boolean isNative) { + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { CommandSourceStack css = cmdCtx.getSource(); - CommandSender sender = css.getBukkitSender(); - if (sender == null) { - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using - // this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful - // sender. - sender = Bukkit.getConsoleSender(); - } + // Get original sender + CommandSender sender = getCommandSenderFromCommandSource(css); + + // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); World world = getWorldForCSS(css); Location location = new Location(world, pos.x(), pos.y(), pos.z(), rot.y, rot.x); + // Get proxy sender (default to sender if null) Entity proxyEntity = css.getEntity(); - CommandSender proxy = proxyEntity == null ? null : proxyEntity.getBukkitEntity(); - if (isNative || (proxy != null && !sender.equals(proxy))) { - if (proxy == null) { - proxy = sender; - } + CommandSender proxy = proxyEntity == null ? sender : proxyEntity.getBukkitEntity(); - return new BukkitNativeProxyCommandSender(new NativeProxyCommandSender(sender, proxy, location, world)); - } else { - return wrapCommandSender(sender); - } + return new NativeProxyCommandSender(sender, proxy, location, world); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java index 2d5fc161b1..785c27caf3 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java @@ -30,8 +30,6 @@ import dev.jorel.commandapi.CommandRegistrationStrategy; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; import dev.jorel.commandapi.preprocessor.Differs; import dev.jorel.commandapi.preprocessor.Overridden; import dev.jorel.commandapi.preprocessor.Unimplemented; @@ -357,12 +355,17 @@ public final BaseComponent[] getChatComponent(CommandContext } @Override - public abstract CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender sender); + @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "VanillaCommandWrapper") + public abstract CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender); @Override - public final BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack css) { + public final CommandSender getCommandSenderFromCommandSource(CommandSourceStack css) { try { - return wrapCommandSender(css.getBukkitSender()); + CommandSender sender = css.getBukkitSender(); + // Sender CANNOT be null. This can occur when using a remote console + // sender. You can access it directly using this.getMinecraftServer().remoteConsole + // however this may also be null, so delegate to the next most-meaningful sender. + return sender == null ? Bukkit.getConsoleSender() : sender; } catch (UnsupportedOperationException e) { return null; } @@ -526,7 +529,7 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St @Unimplemented(because = NAME_CHANGED, info = "i (1.17) -> getRotation (1.18) -> l (1.19)") @Unimplemented(because = NAME_CHANGED, info = "getEntity (1.17) -> getEntity (1.18) -> g (1.19)") @Unimplemented(because = NAME_CHANGED, info = "getWorld (1.17) -> getLevel (1.18) -> f (1.19)") - public abstract BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative); + public abstract NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx); @Override @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CraftServer") diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt index 3f6e0ee466..7cd37a2fd6 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt @@ -3,7 +3,7 @@ package dev.jorel.commandapi.test import be.seeseemelk.mockbukkit.MockBukkit import com.mojang.brigadier.exceptions.CommandSyntaxException import dev.jorel.commandapi.executors.CommandArguments -import dev.jorel.commandapi.executors.PlayerCommandExecutor +import dev.jorel.commandapi.executors.NormalExecutor import org.bukkit.Bukkit import org.bukkit.command.Command import org.bukkit.command.CommandMap @@ -87,6 +87,6 @@ abstract class TestBase { } companion object { - val P_EXEC = PlayerCommandExecutor { _: Player, _: CommandArguments -> } + val P_EXEC = NormalExecutor { _: Player, _: CommandArguments -> } } } \ No newline at end of file diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 7c9bb5e891..ee6c916cfb 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -13,6 +13,7 @@ import be.seeseemelk.mockbukkit.help.HelpMapMock; import dev.jorel.commandapi.*; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Location; @@ -45,9 +46,6 @@ import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; import be.seeseemelk.mockbukkit.potion.MockPotionEffectType; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import net.minecraft.server.v1_16_R3.Advancement; import net.minecraft.server.v1_16_R3.AdvancementDataWorld; import net.minecraft.server.v1_16_R3.ArgumentAnchor.Anchor; @@ -277,8 +275,7 @@ public SimpleCommandMap getSimpleCommandMap() { } @Override - public CommandListenerWrapper getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandListenerWrapper getBrigadierSourceFromCommandSender(CommandSender sender) { CommandListenerWrapper clw = Mockito.mock(CommandListenerWrapper.class); Mockito.when(clw.getBukkitSender()).thenReturn(sender); @@ -363,6 +360,11 @@ public CommandListenerWrapper getBrigadierSourceFromCommandSender(AbstractComman return clw; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -556,7 +558,7 @@ public void addFunction(NamespacedKey key, List commands) { } MinecraftKey resourceLocation = new MinecraftKey(key.toString()); - CommandListenerWrapper css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandListenerWrapper css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -571,7 +573,7 @@ public void addTag(NamespacedKey key, List> commands) { } MinecraftKey resourceLocation = new MinecraftKey(key.toString()); - CommandListenerWrapper css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandListenerWrapper css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -594,7 +596,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandListenerWrapper css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandListenerWrapper css = getBrigadierSourceFromCommandSender(player); EntityPlayer handle = Mockito.mock(EntityPlayer.class); Mockito.when(handle.getCommandListener()).thenReturn(css); @@ -630,17 +632,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandListenerWrapper cs) { - try { - return wrapCommandSender(cs.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandListenerWrapper cs) { + return baseNMS.getCommandSenderFromCommandSource(cs); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 765030ec49..3c657fafbf 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -12,6 +12,7 @@ import be.seeseemelk.mockbukkit.help.HelpMapMock; import dev.jorel.commandapi.*; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -43,9 +44,6 @@ import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; import be.seeseemelk.mockbukkit.potion.MockPotionEffectType; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import net.minecraft.SharedConstants; import net.minecraft.advancements.Advancement; import net.minecraft.commands.CommandFunction; @@ -261,8 +259,7 @@ public SimpleCommandMap getSimpleCommandMap() { } @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -346,6 +343,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -541,7 +543,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -556,7 +558,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -579,7 +581,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -614,17 +616,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java index b978113c70..f52c9e2eb2 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -13,6 +13,7 @@ import be.seeseemelk.mockbukkit.help.HelpMapMock; import dev.jorel.commandapi.*; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -47,9 +48,6 @@ import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; import be.seeseemelk.mockbukkit.potion.MockPotionEffectType; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import io.papermc.paper.advancement.AdvancementDisplay; import net.minecraft.SharedConstants; import net.minecraft.advancements.Advancement; @@ -279,8 +277,7 @@ public SimpleCommandMap getSimpleCommandMap() { @SuppressWarnings({ "deprecation", "unchecked" }) @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -363,6 +360,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -556,7 +558,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -571,7 +573,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -594,7 +596,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -649,17 +651,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 936dc35099..9fc949ed73 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -13,6 +13,7 @@ import be.seeseemelk.mockbukkit.help.HelpMapMock; import dev.jorel.commandapi.*; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -47,9 +48,6 @@ import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; import be.seeseemelk.mockbukkit.potion.MockPotionEffectType; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import io.papermc.paper.advancement.AdvancementDisplay; import net.kyori.adventure.text.Component; import net.minecraft.SharedConstants; @@ -275,8 +273,7 @@ public SimpleCommandMap getSimpleCommandMap() { @SuppressWarnings({ "deprecation", "unchecked" }) @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -359,6 +356,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -549,7 +551,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -564,7 +566,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -587,7 +589,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -647,17 +649,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } // @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java index fada5fcb92..3cfebf7fcd 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -13,6 +13,7 @@ import be.seeseemelk.mockbukkit.help.HelpMapMock; import dev.jorel.commandapi.*; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -47,9 +48,6 @@ import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; import be.seeseemelk.mockbukkit.potion.MockPotionEffectType; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; import io.papermc.paper.advancement.AdvancementDisplay; import net.kyori.adventure.text.Component; import net.minecraft.SharedConstants; @@ -276,8 +274,7 @@ public SimpleCommandMap getSimpleCommandMap() { @SuppressWarnings({ "deprecation", "unchecked" }) @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -362,6 +359,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -559,7 +561,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -574,7 +576,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -597,7 +599,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -657,17 +659,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } // @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index e2959928b5..ffd49fc656 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -9,9 +9,7 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import dev.jorel.commandapi.*; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.SharedConstants; import net.minecraft.advancements.Advancement; import net.minecraft.advancements.AdvancementHolder; @@ -260,8 +258,7 @@ public SimpleCommandMap getSimpleCommandMap() { @SuppressWarnings({ "deprecation", "unchecked" }) @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -357,6 +354,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -556,7 +558,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -571,7 +573,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -594,7 +596,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -657,17 +659,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } // @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 198d16fb8d..8f5f6c9261 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -10,9 +10,7 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.JsonOps; import dev.jorel.commandapi.*; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.SharedConstants; import net.minecraft.advancements.Advancement; import net.minecraft.advancements.AdvancementHolder; @@ -269,8 +267,7 @@ public SimpleCommandMap getSimpleCommandMap() { @SuppressWarnings({ "deprecation", "unchecked" }) @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -370,6 +367,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -569,7 +571,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -584,7 +586,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -607,7 +609,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -670,17 +672,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 1a9a47b3d8..629721b52d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -8,9 +8,7 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.JsonOps; import dev.jorel.commandapi.*; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.SharedConstants; import net.minecraft.advancements.Advancement; import net.minecraft.advancements.AdvancementHolder; @@ -288,8 +286,7 @@ public SimpleCommandMap getSimpleCommandMap() { @SuppressWarnings({ "deprecation", "unchecked" }) @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -389,6 +386,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -698,7 +700,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -713,7 +715,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -736,7 +738,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -799,17 +801,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java index ba0cee5b54..5662828842 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -9,9 +9,7 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import dev.jorel.commandapi.*; -import dev.jorel.commandapi.commandsenders.AbstractCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitCommandSender; -import dev.jorel.commandapi.commandsenders.BukkitPlayer; +import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.SharedConstants; import net.minecraft.advancements.Advancement; import net.minecraft.commands.CommandFunction; @@ -260,8 +258,7 @@ public SimpleCommandMap getSimpleCommandMap() { @SuppressWarnings({ "deprecation", "unchecked" }) @Override - public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSender senderWrapper) { - CommandSender sender = senderWrapper.getSource(); + public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender sender) { CommandSourceStack css = Mockito.mock(CommandSourceStack.class); Mockito.when(css.getBukkitSender()).thenReturn(sender); @@ -357,6 +354,11 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } + @Override + public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { + return baseNMS.getNativeProxyCommandSender(cmdCtx); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void createDispatcherFile(File file, CommandDispatcher dispatcher) @@ -557,7 +559,7 @@ public void addFunction(NamespacedKey key, List commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); // So for very interesting reasons, Brigadier.getCommandDispatcher() // gives a different result in this method than using getBrigadierDispatcher() @@ -572,7 +574,7 @@ public void addTag(NamespacedKey key, List> commands) { } ResourceLocation resourceLocation = new ResourceLocation(key.toString()); - CommandSourceStack css = getBrigadierSourceFromCommandSender(new BukkitPlayer(Bukkit.getOnlinePlayers().iterator().next())); + CommandSourceStack css = getBrigadierSourceFromCommandSender(Bukkit.getOnlinePlayers().iterator().next()); List tagFunctions = new ArrayList<>(); for(List functionCommands : commands) { @@ -595,7 +597,7 @@ public Player setupMockedCraftPlayer(String name) { Mockito.when(player.getWorld()).thenReturn(world); // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + CommandSourceStack css = getBrigadierSourceFromCommandSender(player); ServerPlayer handle = Mockito.mock(ServerPlayer.class); Mockito.when(handle.createCommandSourceStack()).thenReturn(css); @@ -655,17 +657,8 @@ public Collection getCriteria() { } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - return baseNMS.getSenderForCommand(cmdCtx, forceNative); - } - - @Override - public BukkitCommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { - try { - return wrapCommandSender(clw.getBukkitSender()); - } catch (UnsupportedOperationException e) { - return null; - } + public CommandSender getCommandSenderFromCommandSource(CommandSourceStack clw) { + return baseNMS.getCommandSenderFromCommandSource(clw); } // @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java index c4d5c144dd..78df148dd0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java @@ -10,7 +10,6 @@ import be.seeseemelk.mockbukkit.MockBukkit; import be.seeseemelk.mockbukkit.entity.PlayerMock; import dev.jorel.commandapi.Converter; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; /** * Tests for converted commands @@ -42,8 +41,7 @@ void test1() { Converter.convert(plugin, "mycommand"); PlayerMock player = server.addPlayer(); - NativeProxyCommandSender nativeProxyMockedPlayer = new NativeProxyCommandSender(player, player, player.getLocation(), player.getWorld()); - server.dispatchBrigadierCommand(nativeProxyMockedPlayer, "mycommand"); + server.dispatchBrigadierCommand(player, "mycommand"); assertEquals("hello", player.nextMessage()); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java index b04ac5c9b1..7f81f5f604 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java @@ -14,7 +14,7 @@ import dev.jorel.commandapi.exceptions.GreedyArgumentException; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; -import dev.jorel.commandapi.executors.PlayerExecutionInfo; +import dev.jorel.commandapi.executors.NormalExecutorInfo; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -380,8 +380,8 @@ void testCommandConflictException() { Player player = server.addPlayer(); // The executor we register first should not be overwritten and should always run - PlayerExecutionInfo firstExecutor = info -> {results.set("first");}; - PlayerExecutionInfo secondExecutor = info -> {results.set("second");}; + NormalExecutorInfo firstExecutor = info -> {results.set("first");}; + NormalExecutorInfo secondExecutor = info -> {results.set("second");}; // No arguments new CommandAPICommand("noArguments") diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java index cb0c316e78..212aaaae0b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java @@ -76,7 +76,7 @@ public static List> children(NodeBuilder... children) { private String helpString; private CommandPermission permission = CommandPermission.NONE; - private Predicate requirements = sender -> true; + private Predicate requirements = CommandPermission.TRUE(); private final List> children; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java index 71b22d7ba8..29a418b170 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java @@ -17,6 +17,7 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.potion.PotionEffectType; @@ -35,7 +36,7 @@ import dev.jorel.commandapi.MCVersion; import dev.jorel.commandapi.PaperImplementations; import dev.jorel.commandapi.SafeVarHandle; -import dev.jorel.commandapi.executors.PlayerCommandExecutor; +import dev.jorel.commandapi.executors.NormalExecutor; import static org.junit.jupiter.api.Assertions.*; @@ -91,7 +92,7 @@ public void enableServer() { assertFalse(CommandAPI.canRegister()); } - public static final PlayerCommandExecutor P_EXEC = (player, args) -> {}; + public static final NormalExecutor P_EXEC = (player, args) -> {}; private void resetAllPotions() { PotionEffectType[] arr = MockPlatform.getFieldAs(PotionEffectType.class, "byId", null, PotionEffectType[].class); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java index c476b14705..3fbc66b74f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java @@ -8,6 +8,7 @@ import java.util.concurrent.ThreadLocalRandom; import org.bukkit.Location; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -26,7 +27,7 @@ import dev.jorel.commandapi.arguments.LocationType; import dev.jorel.commandapi.arguments.PlayerArgument; import dev.jorel.commandapi.arguments.StringArgument; -import dev.jorel.commandapi.executors.CommandExecutor; +import dev.jorel.commandapi.executors.NormalExecutor; import dev.jorel.commandapi.test.Mut; import dev.jorel.commandapi.test.TestBase; import dev.jorel.commandapi.wrappers.Location2D; @@ -262,7 +263,7 @@ void executionTestWithCommandTree() { assertEquals("222", result.get()); } - private CommandExecutor givePosition(String pos, Mut result) { + private NormalExecutor givePosition(String pos, Mut result) { return (sender, args) -> result.set(pos); } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index fedbaaf446..af6cac6e1d 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -6,7 +6,6 @@ import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.tree.ArgumentCommandNode; @@ -16,12 +15,10 @@ import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ConsoleCommandSource; -import com.velocitypowered.api.proxy.Player; import dev.jorel.commandapi.arguments.Argument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; -import dev.jorel.commandapi.commandsenders.*; import org.apache.logging.log4j.LogManager; import java.io.File; @@ -30,6 +27,8 @@ import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; public class CommandAPIVelocity implements CommandAPIPlatform, CommandSource, CommandSource> { @@ -164,31 +163,15 @@ public CommandAPILogger getLogger() { return CommandAPILogger.fromApacheLog4jLogger(LogManager.getLogger("CommandAPI")); } + // Velocity's CommandSender and Source are the same, so these two methods are easy @Override - public VelocityCommandSender getSenderForCommand(CommandContext cmdCtx, boolean forceNative) { - // Velocity doesn't have proxy senders, so nothing needs to be done with forceNative - return getCommandSenderFromCommandSource(cmdCtx.getSource()); + public CommandSource getCommandSenderFromCommandSource(CommandSource commandSource) { + return commandSource; } @Override - public VelocityCommandSender getCommandSenderFromCommandSource(CommandSource cs) { - // Given a Brigadier CommandContext source (result of CommandContext.getSource), - // we need to convert that to an AbstractCommandSender. - if (cs instanceof ConsoleCommandSource ccs) - return new VelocityConsoleCommandSender(ccs); - if (cs instanceof Player p) - return new VelocityPlayer(p); - throw new IllegalArgumentException("Unknown CommandSource: " + cs); - } - - @Override - public VelocityCommandSender wrapCommandSender(CommandSource commandSource) { - return getCommandSenderFromCommandSource(commandSource); - } - - @Override - public CommandSource getBrigadierSourceFromCommandSender(AbstractCommandSender sender) { - return sender.getSource(); + public CommandSource getBrigadierSourceFromCommandSender(CommandSource sender) { + return sender; } @Override @@ -242,7 +225,7 @@ public void reloadDataPacks() { } @Override - public void updateRequirements(AbstractPlayer player) { + public void updateRequirements(CommandSource player) { // TODO Auto-generated method stub } @@ -255,4 +238,30 @@ public Argument newConcreteMultiLiteralArgument(String nodeName, String[ public Argument newConcreteLiteralArgument(String nodeName, String literal) { return new LiteralArgument(nodeName, literal); } + + @Override + public Predicate getPermissionCheck(CommandPermission permission) { + final Predicate senderCheck; + + if (permission.equals(CommandPermission.NONE)) { + // No permissions always passes + senderCheck = CommandPermission.TRUE(); + } else if (permission.equals(CommandPermission.OP)) { + // Console is op, and other senders (Players) are not + senderCheck = ConsoleCommandSource.class::isInstance; + } else { + Optional permissionStringWrapper = permission.getPermission(); + if (permissionStringWrapper.isPresent()) { + String permissionString = permissionStringWrapper.get(); + // check permission + senderCheck = sender -> sender.hasPermission(permissionString); + } else { + // No permission always passes + senderCheck = CommandPermission.TRUE(); + } + } + + // Negate if specified + return permission.isNegated() ? senderCheck.negate() : senderCheck; + } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/VelocityExecutable.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/VelocityExecutable.java index efd0c80af6..412a6cb7e2 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/VelocityExecutable.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/VelocityExecutable.java @@ -1,9 +1,16 @@ package dev.jorel.commandapi; import com.velocitypowered.api.command.CommandSource; -import dev.jorel.commandapi.commandsenders.VelocityCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.executors.*; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import com.velocitypowered.api.proxy.Player; + +import dev.jorel.commandapi.executors.ExecutorType; +import dev.jorel.commandapi.executors.NormalExecutor; +import dev.jorel.commandapi.executors.NormalExecutorInfo; +import dev.jorel.commandapi.executors.ResultingExecutor; +import dev.jorel.commandapi.executors.ResultingExecutorInfo; +import dev.jorel.commandapi.executors.VelocityNormalTypedExecutor; +import dev.jorel.commandapi.executors.VelocityResultingTypedExecutor; public interface VelocityExecutable> extends PlatformExecutable { // Regular command executor @@ -11,116 +18,58 @@ public interface VelocityExecutable> exten /** * Adds an executor to the current command builder * - * @param executor A lambda of type (CommandSource, Object[]) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<CommandSource, CommandSource> -> () that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(CommandExecutor executor, ExecutorType... types) { + default Impl executes(NormalExecutorInfo executor, ExecutorType... types) { if (types == null || types.length == 0) { - getExecutor().addNormalExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addNormalExecutor(new CommandExecutor() { - @Override - public void run(CommandSource sender, CommandArguments args) throws WrapperCommandSyntaxException { - executor.executeWith(new VelocityExecutionInfo<>(sender, CommandAPIVelocity.get().wrapCommandSender(sender), args)); - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } + types = new ExecutorType[]{ExecutorType.ALL}; } + + getExecutor().addExecutor(new VelocityNormalTypedExecutor<>(executor, types)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ExecutionInfo<CommandSender, BukkitCommandSender<? extends CommandSender>>) -> () that will be executed when the command is run + * @param executor A lambda of type (CommandSource, CommandArguments) -> () that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(CommandExecutionInfo executor, ExecutorType... types) { - if (types == null || types.length == 0) { - getExecutor().addNormalExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addNormalExecutor(new CommandExecutionInfo() { - - @Override - public void run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - executor.executeWith(info); - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } - } - return instance(); + default Impl executes(NormalExecutor executor, ExecutorType... types) { + // While we can cast directly to `NormalExecutorInfo` (because `NormalExecutor` extends it), this method + // is necessary to help Java identify the expression signature of user defined lambdas. + // The same applies for the rest of the executes methods + return executes((NormalExecutorInfo) executor, types); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (CommandSource, Object[]) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<CommandSource, CommandSource> -> int that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(ResultingCommandExecutor executor, ExecutorType... types) { + default Impl executes(ResultingExecutorInfo executor, ExecutorType... types) { if (types == null || types.length == 0) { - getExecutor().addResultingExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addResultingExecutor(new ResultingCommandExecutor() { - - @Override - public int run(CommandSource sender, CommandArguments args) throws WrapperCommandSyntaxException { - return executor.executeWith(new VelocityExecutionInfo<>(sender, CommandAPIVelocity.get().wrapCommandSender(sender), args)); - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } + types = new ExecutorType[]{ExecutorType.ALL}; } + + getExecutor().addExecutor(new VelocityResultingTypedExecutor<>(executor, types)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ExecutionInfo<CommandSender, VelocityCommandSender<? extends CommandSender>>) -> () that will be executed when the command is run + * @param executor A lambda of type (CommandSource, CommandArguments) -> int that will be executed when the command is run * @param types A list of executor types to use this executes method for. * @return this command builder */ - default Impl executes(ResultingCommandExecutionInfo executor, ExecutorType... types) { - if (types == null || types.length == 0) { - getExecutor().addResultingExecutor(executor); - } else { - for (ExecutorType type : types) { - getExecutor().addResultingExecutor(new ResultingCommandExecutionInfo() { - - @Override - public int run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - return executor.executeWith(info); - } - - @Override - public ExecutorType getType() { - return type; - } - }); - } - } - return instance(); + default Impl executes(ResultingExecutor executor, ExecutorType... types) { + return executes((ResultingExecutorInfo) executor, types); } // Player command executor @@ -128,45 +77,43 @@ public ExecutorType getType() { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Player, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<Player, CommandSource> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesPlayer(NormalExecutorInfo executor) { + getExecutor().addExecutor(new VelocityNormalTypedExecutor<>(executor, ExecutorType.PLAYER)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (Player, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerExecutionInfo executor) { - getExecutor().addNormalExecutor(executor); - return instance(); + default Impl executesPlayer(NormalExecutor executor) { + return executesPlayer((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (Player, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<Player, CommandSource> -> int that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesPlayer(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new VelocityResultingTypedExecutor<>(executor, ExecutorType.PLAYER)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type (Player, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesPlayer(PlayerResultingExecutionInfo executor) { - getExecutor().addResultingExecutor(executor); - return instance(); + default Impl executesPlayer(ResultingExecutor executor) { + return executesPlayer((ResultingExecutorInfo) executor); } // Console command executor @@ -174,44 +121,44 @@ default Impl executesPlayer(PlayerResultingExecutionInfo executor) { /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ConsoleCommandSource, CommandArguments) -> () that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<ConsoleCommandSource, CommandSource> -> () that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleCommandExecutor executor) { - getExecutor().addNormalExecutor(executor); + default Impl executesConsole(NormalExecutorInfo executor) { + getExecutor().addExecutor(new VelocityNormalTypedExecutor<>(executor, ExecutorType.CONSOLE)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ExecutionInfo) -> () that will be executed when the command is run + * @param executor A lambda of type (ConsoleCommandSource, CommandArguments) -> () that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleExecutionInfo executor) { - getExecutor().addNormalExecutor(executor); - return instance(); + default Impl executesConsole(NormalExecutor executor) { + return executesConsole((NormalExecutorInfo) executor); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ConsoleCommandSource, CommandArguments) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<ConsoleCommandSource, CommandSource> -> int or + * (ConsoleCommandSource, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleResultingCommandExecutor executor) { - getExecutor().addResultingExecutor(executor); + default Impl executesConsole(ResultingExecutorInfo executor) { + getExecutor().addExecutor(new VelocityResultingTypedExecutor<>(executor, ExecutorType.CONSOLE)); return instance(); } /** * Adds an executor to the current command builder * - * @param executor A lambda of type (ExecutionInfo) -> int that will be executed when the command is run + * @param executor A lambda of type ExecutionInfo<ConsoleCommandSource> -> int or + * (ConsoleCommandSource, CommandArguments) -> int that will be executed when the command is run * @return this command builder */ - default Impl executesConsole(ConsoleResultingExecutionInfo executor) { - getExecutor().addResultingExecutor(executor); - return instance(); + default Impl executesConsole(ResultingExecutor executor) { + return executesConsole((ResultingExecutorInfo) executor); } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityCommandSender.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityCommandSender.java deleted file mode 100644 index f7d50c272e..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityCommandSender.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.jorel.commandapi.commandsenders; - -import com.velocitypowered.api.command.CommandSource; - -public interface VelocityCommandSender extends AbstractCommandSender { -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityConsoleCommandSender.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityConsoleCommandSender.java deleted file mode 100644 index 9da55c3726..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityConsoleCommandSender.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.commandsenders; -import com.velocitypowered.api.proxy.ConsoleCommandSource; - -public class VelocityConsoleCommandSender implements AbstractConsoleCommandSender, VelocityCommandSender { - - private final ConsoleCommandSource source; - - public VelocityConsoleCommandSender(ConsoleCommandSource source) { - this.source = source; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.source.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return true; - } - - @Override - public ConsoleCommandSource getSource() { - return this.source; - } - -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityPlayer.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityPlayer.java deleted file mode 100644 index b24476db23..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/commandsenders/VelocityPlayer.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.commandsenders; -import com.velocitypowered.api.proxy.Player; - -public class VelocityPlayer implements AbstractPlayer, VelocityCommandSender { - - private final Player player; - - public VelocityPlayer(Player player) { - this.player = player; - } - - @Override - public boolean hasPermission(String permissionNode) { - return this.player.hasPermission(permissionNode); - } - - @Override - public boolean isOp() { - return false; - } - - @Override - public Player getSource() { - return this.player; - } - -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java deleted file mode 100644 index e95014ac02..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.command.CommandSource; -import dev.jorel.commandapi.commandsenders.VelocityCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface CommandExecutionInfo extends NormalExecutor> { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo> info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } - -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java deleted file mode 100644 index dc86278b19..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/CommandExecutor.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.command.CommandSource; -import dev.jorel.commandapi.commandsenders.VelocityCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A normal CommandExecutor for a CommandSource - */ -@FunctionalInterface -public interface CommandExecutor extends NormalExecutor> { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(CommandSource sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java deleted file mode 100644 index 84dc4b8e4d..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleCommandExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.ConsoleCommandSource; -import dev.jorel.commandapi.commandsenders.VelocityConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A normal command executor for a ConsoleCommandSender - */ -@FunctionalInterface -public interface ConsoleCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(ConsoleCommandSource sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.CONSOLE; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java deleted file mode 100644 index c54a394717..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.ConsoleCommandSource; -import dev.jorel.commandapi.commandsenders.VelocityConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface ConsoleExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } - -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java deleted file mode 100644 index 07e48f8052..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingCommandExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.ConsoleCommandSource; -import dev.jorel.commandapi.commandsenders.VelocityConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A resulting command executor for a ConsoleCommandSender - */ -@FunctionalInterface -public interface ConsoleResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - int run(ConsoleCommandSource sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.CONSOLE; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java deleted file mode 100644 index d558e64320..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ConsoleResultingExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.ConsoleCommandSource; -import dev.jorel.commandapi.commandsenders.VelocityConsoleCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface ConsoleResultingExecutionInfo extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java similarity index 65% rename from commandapi-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java rename to commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java index e705d1eea0..24f209d0ad 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ExecutorType.java @@ -20,53 +20,27 @@ *******************************************************************************/ package dev.jorel.commandapi.executors; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import com.velocitypowered.api.proxy.Player; + /** * An enum representing the type of an executor */ public enum ExecutorType { /** - * An executor where the CommandSender is a Player + * An executor where the CommandSender is any {@link CommandSource} */ - PLAYER, + ALL, /** - * An executor where the CommandSender is an Entity - */ - ENTITY, - - /** - * An executor where the CommandSender is a ConsoleCommandSender - */ - CONSOLE, - - /** - * An executor where the CommandSender is a BlockCommandSender - */ - BLOCK, - - /** - * An executor where the CommandSender is any CommandSender + * An executor where the CommandSender is a {@link Player} */ - ALL, - - /** - * An executor where the CommandSender is a NativeProxyCommandSender - */ - PROXY, - - /** - * An executor where the CommandSender is (always) a NativeProxyCommandSender - */ - NATIVE, + PLAYER, /** - * An executor where the CommandSender is a RemoteConsoleCommandSender - */ - REMOTE, - - /** - * An executor where the CommandSender is a {@code io.papermc.paper.commands.FeedbackForwardingSender} + * An executor where the CommandSender is a {@link ConsoleCommandSource} */ - FEEDBACK_FORWARDING; + CONSOLE; } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java deleted file mode 100644 index 16efabfac3..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerCommandExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.Player; -import dev.jorel.commandapi.commandsenders.VelocityPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A normal command executor for a Player - */ -@FunctionalInterface -public interface PlayerCommandExecutor extends NormalExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - void run(Player sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PLAYER; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java deleted file mode 100644 index 92a30019ec..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerExecutionInfo.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.Player; -import dev.jorel.commandapi.commandsenders.VelocityPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface PlayerExecutionInfo extends NormalExecutor { - - /** - * Executes the command. - * - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command - */ - void run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } - -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java deleted file mode 100644 index 3ff4dfeda4..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingCommandExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *******************************************************************************/ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.Player; -import dev.jorel.commandapi.commandsenders.VelocityPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A resulting command executor for a Player - */ -@FunctionalInterface -public interface PlayerResultingCommandExecutor extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - */ - int run(Player sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - */ - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.PLAYER; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java deleted file mode 100644 index 8186f868ba..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/PlayerResultingExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.proxy.Player; -import dev.jorel.commandapi.commandsenders.VelocityPlayer; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface PlayerResultingExecutionInfo extends ResultingExecutor { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - int run(ExecutionInfo info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java deleted file mode 100644 index 4cdc28b6f7..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutionInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.command.CommandSource; -import dev.jorel.commandapi.commandsenders.VelocityCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -@FunctionalInterface -public interface ResultingCommandExecutionInfo extends ResultingExecutor> { - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - int run(ExecutionInfo> info) throws WrapperCommandSyntaxException; - - /** - * Returns the type of the sender of the current executor. - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java deleted file mode 100644 index 8619ead5c7..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/ResultingCommandExecutor.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.jorel.commandapi.executors; - -import com.velocitypowered.api.command.CommandSource; -import dev.jorel.commandapi.commandsenders.VelocityCommandSender; -import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; - -/** - * A resulting command executor for a CommandSource - */ -@FunctionalInterface -public interface ResultingCommandExecutor extends ResultingExecutor> { - - /** - * The code to run when this command is performed - * - * @param sender The sender of this command (a player, the console etc.) - * @param args The arguments given to this command. - * @return the result of this command - */ - int run(CommandSource sender, CommandArguments args) throws WrapperCommandSyntaxException; - - /** - * The code to run when this command is performed - * - * @param info The ExecutionInfo for this command - * @return the result of this command - * @throws WrapperCommandSyntaxException - */ - @Override - default int run(ExecutionInfo> info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } - - /** - * Returns the type of the sender of the current executor. - * - * @return the type of the sender of the current executor - */ - @Override - default ExecutorType getType() { - return ExecutorType.ALL; - } -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityExecutionInfo.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityExecutionInfo.java deleted file mode 100644 index 45e2b8fbe8..0000000000 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityExecutionInfo.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.jorel.commandapi.executors; - -import dev.jorel.commandapi.commandsenders.VelocityCommandSender; - -/** - * This record represents a VelocityExecutionInfo for a command. It provides the sender of a command, as well as it's arguments - * - * @param The type of the sender of a command this BukkitExecutionInfo belongs to - */ -public record VelocityExecutionInfo( - - /** - * @return The sender of this command - */ - Sender sender, - - /** - * This is not intended for public use and is only used internally. The {@link BukkitExecutionInfo#sender()} method should be used instead! - * - * @return The wrapper type of this command - */ - VelocityCommandSender senderWrapper, - - /** - * @return The arguments of this command - */ - CommandArguments args - -) implements ExecutionInfo> { -} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java new file mode 100644 index 0000000000..d0e700c6d0 --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java @@ -0,0 +1,32 @@ +package dev.jorel.commandapi.executors; + +import com.velocitypowered.api.command.CommandSource; + +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; + +/** + * A {@link VelocityTypedExecutor} for {@link NormalExecutorInfo} lambdas that don't have an int result. + * When running this executor succeeds, it simply returns 1. + * + * @param executor The {@link NormalExecutorInfo} to invoke when running this executor. + * @param types The {@link ExecutorType}s that this executor accepts. + * @param The {@link CommandSource} class that this executor accepts. + */ +public record VelocityNormalTypedExecutor( + + /** + * @return The {@link NormalExecutorInfo} to invoke when running this executor. + */ + NormalExecutorInfo executor, + + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types +) implements VelocityTypedExecutor { + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + executor.run(info); + return 1; + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java new file mode 100644 index 0000000000..5d725c0db1 --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java @@ -0,0 +1,30 @@ +package dev.jorel.commandapi.executors; + +import com.velocitypowered.api.command.CommandSource; + +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; + +/** + * A {@link VelocityTypedExecutor} for {@link ResultingExecutorInfo} lambdas that have an int result. + * + * @param executor The {@link ResultingExecutorInfo} to invoke when running this executor. + * @param types The {@link ExecutorType}s that this executor accepts. + * @param The {@link CommandSource} class that this executor accepts. + */ +public record VelocityResultingTypedExecutor( + + /** + * @return The {@link ResultingExecutorInfo} to invoke when running this executor. + */ + ResultingExecutorInfo executor, + + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types +) implements VelocityTypedExecutor { + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + return executor.run(info); + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java new file mode 100644 index 0000000000..93fb48897a --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java @@ -0,0 +1,35 @@ +package dev.jorel.commandapi.executors; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import com.velocitypowered.api.proxy.Player; + +/** + * A {@link TypedExecutor} for Velocity. The {@link CommandSource}s accepted by + * this executor can be defined by overriding the method {@link #types()}. + * + * @param The {@link CommandSource} class that this executor accepts. + */ +public interface VelocityTypedExecutor extends TypedExecutor { + @Override + default ExecutionInfo tryForSender(ExecutionInfo info) { + CommandSource sender = info.sender(); + + for (ExecutorType type : types()) { + // Check if we can cast to the defined sender type + if (switch (type) { + case ALL -> true; + case PLAYER -> sender instanceof Player; + case CONSOLE -> sender instanceof ConsoleCommandSource; + }) { + return (ExecutionInfo) info; + } + } + return null; + } + + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType[] types(); +} From d390ccb0041ff115c7bda7449f3e295a2e704008 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:57:22 -0400 Subject: [PATCH 27/42] Update CommandExecutionTests after rebasing `dev/command-build-rewrite` Resolves https://github.com/JorelAli/CommandAPI/issues/559 todo --- .../commandapi/test/CommandExecutionTests.kt | 100 +++++++++--------- .../test/dsltests/DSLCommandExecutionTests.kt | 20 ++-- .../test/CommandExecutionTests.java | 12 +-- 3 files changed, 58 insertions(+), 74 deletions(-) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt index 48b653a304..4b013bb601 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt @@ -53,11 +53,10 @@ open class CommandExecutionTests : TestBase() { ExecutorType.BLOCK to { val block : BlockCommandSender = Mockito.mock(); block}, // This is a little odd, but `ProxiedCommandSender`s will always be CommandAPI `NativeProxyCommandSender`s. // It is possible that a different plugin could implement the `ProxiedCommandSender`, which would cause - // a class cast exception if that sender tried to run a `ProxyCommandExecutor`. However, the Spigot/Paper + // a class cast exception if that sender tried to run an `executesProxy` executor. However, the Spigot/Paper // server itself will never use its own `org.bukkit.craftbukkit.command.ProxiedNativeCommandSender` class. // So, if you can make a class cast exception happen on a server, change this mock to `ProxiedCommandSender` - // and fix `ProxiedCommandExecutor`, but otherwise we can provide the more specific `NativeProxyCommandSender` - // class when using `executesProxy`. + // and fix `executesProxy`, but otherwise we can provide the more specific `NativeProxyCommandSender` class. // ExecutorType.PROXY to { val proxy : ProxiedCommandSender = Mockito.mock(); proxy }, ExecutorType.PROXY to { val proxy : NativeProxyCommandSender = Mockito.mock(); proxy }, ExecutorType.REMOTE to { val remoteConsole : RemoteConsoleCommandSender = Mockito.mock(); remoteConsole } @@ -100,26 +99,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesPlayer(PlayerExecutionInfo { info -> + .executesPlayer(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> results.set(player) }) .register() CommandAPICommand("resultinginfo") - .executesPlayer(PlayerResultingExecutionInfo { info -> + .executesPlayer(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesPlayer(PlayerResultingCommandExecutor { player, _ -> + .executesPlayer(ResultingExecutor { player, _ -> results.set(player) 2 }) @@ -141,26 +140,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesEntity(EntityExecutionInfo { info -> + .executesEntity(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesEntity(EntityCommandExecutor { entity, _ -> + .executesEntity(NormalExecutor { entity, _ -> results.set(entity) }) .register() CommandAPICommand("resultinginfo") - .executesEntity(EntityResultingExecutionInfo { info -> + .executesEntity(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesEntity(EntityResultingCommandExecutor { entity, _ -> + .executesEntity(ResultingExecutor { entity, _ -> results.set(entity) 2 }) @@ -171,11 +170,8 @@ open class CommandExecutionTests : TestBase() { { name -> { sender -> assertCommandFailsWith(sender, name, "This command has no implementations for " + sender.javaClass.getSimpleName().lowercase()) } }, listOf("normal", "normalinfo", "resultinginfo", "resulting"), - // TODO: Players should be able to run these commands - // Currently they cannot, since even though `Player extends Entity`, - // `AbstractPlayer` does not extend `AbstractEntity` - // See https://github.com/JorelAli/CommandAPI/issues/559 - ExecutorType.ENTITY/*, ExecutorType.PLAYER */ + // Players count as Entities + ExecutorType.ENTITY, ExecutorType.PLAYER ) assertNoMoreResults(results) @@ -186,26 +182,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesConsole(ConsoleExecutionInfo { info -> + .executesConsole(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesConsole(ConsoleCommandExecutor { console, _ -> + .executesConsole(NormalExecutor { console, _ -> results.set(console) }) .register() CommandAPICommand("resultinginfo") - .executesConsole(ConsoleResultingExecutionInfo { info -> + .executesConsole(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesConsole(ConsoleResultingCommandExecutor { console, _ -> + .executesConsole(ResultingExecutor { console, _ -> results.set(console) 2 }) @@ -227,26 +223,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesCommandBlock(CommandBlockExecutionInfo { info -> + .executesCommandBlock(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesCommandBlock(CommandBlockCommandExecutor { block, _ -> + .executesCommandBlock(NormalExecutor { block, _ -> results.set(block) }) .register() CommandAPICommand("resultinginfo") - .executesCommandBlock(CommandBlockResultingExecutionInfo { info -> + .executesCommandBlock(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesCommandBlock(CommandBlockResultingCommandExecutor { block, _ -> + .executesCommandBlock(ResultingExecutor { block, _ -> results.set(block) 2 }) @@ -266,26 +262,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesProxy(ProxyExecutionInfo { info -> + .executesProxy(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesProxy(ProxyCommandExecutor { proxy, _ -> + .executesProxy(NormalExecutor { proxy, _ -> results.set(proxy) }) .register() CommandAPICommand("resultinginfo") - .executesProxy(ProxyResultingExecutionInfo { info -> + .executesProxy(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesProxy(ProxyResultingCommandExecutor { proxy, _ -> + .executesProxy(ResultingExecutor { proxy, _ -> results.set(proxy) 2 }) @@ -307,26 +303,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesRemoteConsole(RemoteConsoleExecutionInfo { info -> + .executesRemoteConsole(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesRemoteConsole(RemoteConsoleCommandExecutor { console, _ -> + .executesRemoteConsole(NormalExecutor { console, _ -> results.set(console) }) .register() CommandAPICommand("resultinginfo") - .executesRemoteConsole(RemoteConsoleResultingExecutionInfo { info -> + .executesRemoteConsole(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesRemoteConsole(RemoteConsoleResultingCommandExecutor { console, _ -> + .executesRemoteConsole(ResultingExecutor { console, _ -> results.set(console) 2 }) @@ -349,26 +345,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesNative(NativeExecutionInfo { info -> + .executesNative(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesNative(NativeCommandExecutor { proxy, _ -> + .executesNative(NormalExecutor { proxy, _ -> results.set(proxy) }) .register() CommandAPICommand("resultinginfo") - .executesNative(NativeResultingExecutionInfo { info -> + .executesNative(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesNative(NativeResultingCommandExecutor { proxy, _ -> + .executesNative(ResultingExecutor { proxy, _ -> results.set(proxy) 2 }) @@ -406,26 +402,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executes(CommandExecutionInfo { info -> + .executes(NormalExecutorInfo { info -> results.set(info.sender()) }, *types) .register() CommandAPICommand("normal") - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> results.set(sender) }, *types) .register() CommandAPICommand("resultinginfo") - .executes(ResultingCommandExecutionInfo { info -> + .executes(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }, *types) .register() CommandAPICommand("resulting") - .executes(ResultingCommandExecutor { sender, _ -> + .executes(ResultingExecutor { sender, _ -> results.set(sender) 2 }, *types) @@ -447,26 +443,26 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executes(CommandExecutionInfo { info -> + .executes(NormalExecutorInfo { info -> results.set(info.sender()) }, ExecutorType.PLAYER, ExecutorType.CONSOLE) .register() CommandAPICommand("normal") - .executes(CommandExecutor { sender, _ -> + .executes(NormalExecutor { sender, _ -> results.set(sender) }, ExecutorType.PLAYER, ExecutorType.CONSOLE) .register() CommandAPICommand("resultinginfo") - .executes(ResultingCommandExecutionInfo { info -> + .executes(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }, ExecutorType.PLAYER, ExecutorType.CONSOLE) .register() CommandAPICommand("resulting") - .executes(ResultingCommandExecutor { sender, _ -> + .executes( ResultingExecutor { sender, _ -> results.set(sender) 2 }, ExecutorType.PLAYER, ExecutorType.CONSOLE) @@ -487,40 +483,40 @@ open class CommandExecutionTests : TestBase() { val results: Mut = Mut.of() CommandAPICommand("normalinfo") - .executesConsole(ConsoleExecutionInfo { info -> + .executesConsole(NormalExecutorInfo { info -> results.set(info.sender()) }) - .executesPlayer(PlayerExecutionInfo { info -> + .executesPlayer(NormalExecutorInfo { info -> results.set(info.sender()) }) .register() CommandAPICommand("normal") - .executesConsole(ConsoleCommandExecutor { console, _ -> + .executesConsole(NormalExecutor { console, _ -> results.set(console) }) - .executesPlayer(PlayerCommandExecutor { player, _ -> + .executesPlayer(NormalExecutor { player, _ -> results.set(player) }) .register() CommandAPICommand("resultinginfo") - .executesConsole(ConsoleResultingExecutionInfo { info -> + .executesConsole(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) - .executesPlayer(PlayerResultingExecutionInfo { info -> + .executesPlayer(ResultingExecutorInfo { info -> results.set(info.sender()) 2 }) .register() CommandAPICommand("resulting") - .executesConsole(ConsoleResultingCommandExecutor { console, _ -> + .executesConsole(ResultingExecutor { console, _ -> results.set(console) 2 }) - .executesPlayer(PlayerResultingCommandExecutor { player, _ -> + .executesPlayer(ResultingExecutor { player, _ -> results.set(player) 2 }) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt index 25ea953c29..b252da8dc3 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt @@ -53,11 +53,10 @@ class DSLCommandExecutionTests : TestBase() { ExecutorType.BLOCK to { val block : BlockCommandSender = Mockito.mock(); block}, // This is a little odd, but `ProxiedCommandSender`s will always be CommandAPI `NativeProxyCommandSender`s. // It is possible that a different plugin could implement the `ProxiedCommandSender`, which would cause - // a class cast exception if that sender tried to run a `ProxyCommandExecutor`. However, the Spigot/Paper + // a class cast exception if that sender tried to run an `executesProxy` executor. However, the Spigot/Paper // server itself will never use its own `org.bukkit.craftbukkit.command.ProxiedNativeCommandSender` class. // So, if you can make a class cast exception happen on a server, change this mock to `ProxiedCommandSender` - // and fix `ProxiedCommandExecutor`, but otherwise we can provide the more specific `NativeProxyCommandSender` - // class when using `executesProxy`. + // and fix `executesProxy`, but otherwise we can provide the more specific `NativeProxyCommandSender` class. // ExecutorType.PROXY to { val proxy : ProxiedCommandSender = Mockito.mock(); proxy }, ExecutorType.PROXY to { val proxy : NativeProxyCommandSender = Mockito.mock(); proxy }, ExecutorType.REMOTE to { val remoteConsole : RemoteConsoleCommandSender = Mockito.mock(); remoteConsole } @@ -174,11 +173,8 @@ class DSLCommandExecutionTests : TestBase() { { name -> { sender -> assertCommandFailsWith(sender, name, "This command has no implementations for " + sender.javaClass.getSimpleName().lowercase()) } }, listOf("normal", "normalinfo", "resultinginfo", "resulting"), - // TODO: Players should be able to run these commands - // Currently they cannot, since even though `Player extends Entity`, - // `AbstractPlayer` does not extend `AbstractEntity` - // See https://github.com/JorelAli/CommandAPI/issues/559 - ExecutorType.ENTITY/*, ExecutorType.PLAYER */ + // Players count as Entities + ExecutorType.ENTITY, ExecutorType.PLAYER ) assertNoMoreResults(results) @@ -276,11 +272,7 @@ class DSLCommandExecutionTests : TestBase() { commandAPICommand("normal") { proxyExecutor { proxy, _ -> - // TODO: For some reason proxy here is just ProxiedCommandSender. - // This seems wrong considering the `ExecutionInfo` methods are - // parameterized with NativeProxyCommandSender, as is non-DSL. - // Dunno if changing this is breaking though, so I'll just leave it for now. - results.set(proxy as NativeProxyCommandSender) + results.set(proxy) } } @@ -293,7 +285,7 @@ class DSLCommandExecutionTests : TestBase() { commandAPICommand("resulting") { proxyResultingExecutor { proxy, _ -> - results.set(proxy as NativeProxyCommandSender) + results.set(proxy) 2 } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java index 77026a694b..770f234301 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java @@ -59,11 +59,10 @@ public void tearDown() { executorTypeToMockSender.put(ExecutorType.BLOCK, () -> Mockito.mock(BlockCommandSender.class)); // This is a little odd, but `ProxiedCommandSender`s will always be CommandAPI `NativeProxyCommandSender`s. // It is possible that a different plugin could implement the `ProxiedCommandSender`, which would cause - // a class cast exception if that sender tried to run a `ProxyCommandExecutor`. However, the Spigot/Paper + // a class cast exception if that sender tried to run an `executesProxy` executor. However, the Spigot/Paper // server itself will never use its own `org.bukkit.craftbukkit.command.ProxiedNativeCommandSender` class. // So, if you can make a class cast exception happen on a server, change this mock to `ProxiedCommandSender` - // and fix `ProxiedCommandExecutor`, but otherwise we can provide the more specific `NativeProxyCommandSender` - // class when using `executesProxy`. + // and fix `executesProxy`, but otherwise we can provide the more specific `NativeProxyCommandSender` class. // executorTypeToMockSender.put(ExecutorType.PROXY, () -> Mockito.mock(ProxiedCommandSender.class)); executorTypeToMockSender.put(ExecutorType.PROXY, () -> Mockito.mock(NativeProxyCommandSender.class)); executorTypeToMockSender.put(ExecutorType.REMOTE, () -> Mockito.mock(RemoteConsoleCommandSender.class)); @@ -179,11 +178,8 @@ void testEntityExecution() { name -> sender -> assertCommandFailsWith(sender, name, "This command has no implementations for " + sender.getClass().getSimpleName().toLowerCase()), List.of("normal", "normalinfo", "resultinginfo", "resulting"), - // TODO: Players should be able to run these commands - // Currently they cannot, since even though `Player extends Entity`, - // `AbstractPlayer` does not extend `AbstractEntity` - // See https://github.com/JorelAli/CommandAPI/issues/559 - ExecutorType.ENTITY/*, ExecutorType.PLAYER */ + // Players count as Entities + ExecutorType.ENTITY, ExecutorType.PLAYER ); assertNoMoreResults(results); From b27e7d461fc3c602b7fb4cdc037509c899cd0287 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:37:28 -0400 Subject: [PATCH 28/42] Fully split converted arguments Resolves https://github.com/JorelAli/CommandAPI/issues/557 concerns Expand CommandConvertedTests and ArgumentEntitySelectorTests to cover argument flattening Miscellaneous changes to testing framework to make entity selectors work better --- .../java/dev/jorel/commandapi/Converter.java | 48 +++++--- .../commandapi/test/CommandAPIServerMock.kt | 22 +++- .../commandapi/test/CommandExecutionTests.kt | 2 +- .../dev/jorel/commandapi/test/TestBase.kt | 1 + .../test/dsltests/DSLCommandExecutionTests.kt | 2 +- .../dev/jorel/commandapi/test/MockNMS.java | 91 +++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 91 +++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 91 +++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 91 +++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 92 +++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 92 +++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 93 ++++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 93 ++++++++++------ .../dev/jorel/commandapi/test/MockNMS.java | 92 +++++++++------ .../jorel/commandapi/test/MockPlatform.java | 11 +- .../commandapi/test/CommandAPIServerMock.java | 32 +++++- .../test/CommandConvertedTests.java | 83 ++++++++++++-- .../test/CommandConvertedTestsPlugin.java | 41 ++++++- .../test/CommandExecutionTests.java | 2 +- .../test/CommandNamespaceTests.java | 13 +-- .../jorel/commandapi/test/OnEnableTests.java | 8 +- .../test/RegisteredCommandTestBase.java | 2 +- .../dev/jorel/commandapi/test/TestBase.java | 20 +++- .../ArgumentEntitySelectorTests.java | 105 +++++++++++++++++- 24 files changed, 832 insertions(+), 386 deletions(-) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java index 0493f118bb..41e492f27f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Converter.java @@ -133,8 +133,10 @@ private static void convertCommand(String commandName, List> argumen new CommandAPICommand(commandName).withPermission(CommandPermission.NONE) .withArguments(arguments).executesNative((sender, args) -> { CommandSender proxiedSender = mergeProxySender(sender); - return flattenArguments(args, arguments, - flattened -> Bukkit.dispatchCommand(proxiedSender, commandName + " " + String.join(" ", flattened))); + return flattenArguments( + args, arguments, + flattened -> Bukkit.dispatchCommand(proxiedSender, commandName + " " + String.join(" ", flattened)) + ); }).register(); } @@ -197,7 +199,7 @@ private static void convertPluginCommand(JavaPlugin plugin, String commandName, if (args.count() != 0) { org.bukkit.command.Command finalCommand = command; - return flattenArguments(args, arguments, flattened -> finalCommand.execute(proxiedSender, commandName, flattened)); + return flattenArguments(args, arguments, flattened -> finalCommand.execute(proxiedSender, commandName, flattened.toArray(String[]::new))); } else { return command.execute(proxiedSender, commandName, new String[0]) ? 1 : 0; } @@ -221,17 +223,23 @@ private static void convertPluginCommand(JavaPlugin plugin, String commandName, .register(); } - private static int flattenArguments(CommandArguments argumentInfo, List> commandAPIArguments, Function argumentConsumer) { + private static int flattenArguments( + CommandArguments argumentInfo, List> commandAPIArguments, + Function, Boolean> argumentConsumer + ) { // Most arguments stay the same, just pass through the raw input as given String[] rawArguments = argumentInfo.rawArgs(); - return flattenArguments(argumentInfo, commandAPIArguments, argumentConsumer, rawArguments, 0); + return flattenArguments(argumentInfo, commandAPIArguments, argumentConsumer, rawArguments, new ArrayList<>(), 0); } - private static int flattenArguments(CommandArguments argumentInfo, List> commandAPIArguments, Function argumentConsumer, - String[] rawArguments, int argumentIndex) { - if (argumentIndex > commandAPIArguments.size()) { + private static int flattenArguments( + CommandArguments argumentInfo, List> commandAPIArguments, + Function, Boolean> argumentConsumer, + String[] rawArguments, List flattened, int argumentIndex + ) { + if (argumentIndex >= commandAPIArguments.size()) { // Processed all the arguments, use it now - return argumentConsumer.apply(rawArguments) ? 1 : 0; + return argumentConsumer.apply(flattened) ? 1 : 0; } Argument argument = commandAPIArguments.get(argumentIndex); @@ -241,15 +249,25 @@ private static int flattenArguments(CommandArguments argumentInfo, List possibilities = flattenable.flatten(argumentInfo.get(argumentIndex)); int successCount = 0; for (String item : possibilities) { - rawArguments[argumentIndex] = item; - successCount += flattenArguments(argumentInfo, commandAPIArguments, argumentConsumer, - rawArguments, argumentIndex + 1); + // Branch the flattened list so each possibility stays independent + List newFlattened = new ArrayList<>(flattened); + newFlattened.addAll(Arrays.asList(item.split(" "))); + + successCount += flattenArguments( + argumentInfo, commandAPIArguments, + argumentConsumer, + rawArguments, newFlattened, argumentIndex + 1 + ); } return successCount; } else { - // No processing needed for this argument, move to the next - return flattenArguments(argumentInfo, commandAPIArguments, argumentConsumer, - rawArguments, argumentIndex + 1); + // Just split the raw argument into individual pieces + flattened.addAll(Arrays.asList(rawArguments[argumentIndex].split(" "))); + return flattenArguments( + argumentInfo, commandAPIArguments, + argumentConsumer, + rawArguments, flattened, argumentIndex + 1 + ); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandAPIServerMock.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandAPIServerMock.kt index bdb6d3f15b..1c08cbbded 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandAPIServerMock.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandAPIServerMock.kt @@ -70,14 +70,26 @@ class CommandAPIServerMock : ServerMock() { return suggestionsAsStrings } + override fun addPlayer(): PlayerMock { + val player = super.addPlayer() + MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(player) + return player + } + + override fun addPlayer(name: String): PlayerMock { + val player = super.addPlayer(name) + MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(player) + return player + } + /** * Creates a new Bukkit [Player]. Unlike [PlayerMock], this uses Mockito to mock the CraftPlayer class, * which allows the returned object to pass through VanillaCommandWrapper#getListener without error. * - * @return A new [Player]. + * @return A new [Player] with a randome name. */ - fun setupMockedCraftPlayer(): Player { - return setupMockedCraftPlayer("defaultName") + fun addCraftPlayer(): Player { + return MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(super.addPlayer()) } /** @@ -87,8 +99,8 @@ class CommandAPIServerMock : ServerMock() { * @param name The name for the player * @return A new [Player]. */ - fun setupMockedCraftPlayer(name: String?): Player { - return MockPlatform.getInstance().setupMockedCraftPlayer(name) + fun addCraftPlayer(name: String?): Player { + return MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(super.addPlayer(name)) } override fun isTickingWorlds(): Boolean { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt index 4b013bb601..d400e5eeaf 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/CommandExecutionTests.kt @@ -45,7 +45,7 @@ open class CommandExecutionTests : TestBase() { // Using a linked map here to keep the values in insertion order, rather // than in whatever order the hash code feels like, which makes debugging easier private val executorTypeToMockSender: Map CommandSender> = linkedMapOf( - ExecutorType.PLAYER to { server.setupMockedCraftPlayer() }, + ExecutorType.PLAYER to { server.addCraftPlayer() }, ExecutorType.ENTITY to { SimpleEntityMock(server) }, ExecutorType.CONSOLE to { ConsoleCommandSenderMock() }, // Apparently `Mockito.mock(BlockCommandSender::class) as CommandSender` is not correct here ¯\_(ツ)_/¯ diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt index 7cd37a2fd6..e055bf3360 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/TestBase.kt @@ -32,6 +32,7 @@ abstract class TestBase { plugin.onDisable() } MockBukkit.unmock() + MockPlatform.unload() } fun assertStoresResult(sender: CommandSender, command: String, queue: Mut, expected: T) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt index b252da8dc3..4a9cc68412 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/src/test/kotlin/dev/jorel/commandapi/test/dsltests/DSLCommandExecutionTests.kt @@ -45,7 +45,7 @@ class DSLCommandExecutionTests : TestBase() { // Using a linked map here to keep the values in insertion order, rather // than in whatever order the hash code feels like, which makes debugging easier private val executorTypeToMockSender: Map CommandSender> = linkedMapOf( - ExecutorType.PLAYER to { server.setupMockedCraftPlayer() }, + ExecutorType.PLAYER to { server.addCraftPlayer() }, ExecutorType.ENTITY to { SimpleEntityMock(server) }, ExecutorType.CONSOLE to { ConsoleCommandSenderMock() }, // Apparently `Mockito.mock(BlockCommandSender::class) as CommandSender` is not correct here ¯\_(ツ)_/¯ diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index ee6c916cfb..683ebad558 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -56,6 +56,7 @@ import net.minecraft.server.v1_16_R3.CustomFunctionData; import net.minecraft.server.v1_16_R3.DispenserRegistry; import net.minecraft.server.v1_16_R3.EntityPlayer; +import net.minecraft.server.v1_16_R3.EntityTypes; import net.minecraft.server.v1_16_R3.GameProfilerDisabled; import net.minecraft.server.v1_16_R3.GameRules; import net.minecraft.server.v1_16_R3.IChatBaseComponent; @@ -150,6 +151,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -294,26 +296,16 @@ public CommandListenerWrapper getBrigadierSourceFromCommandSender(CommandSender // Mockito.when(clw.getWorld().isInWorldBounds(any(BlockPosition.class))).thenReturn(true); Mockito.when(clw.k()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.server.v1_16_R3.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(clw.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(clw.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - EntityPlayer entityPlayerMock = Mockito.mock(EntityPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(new ChatComponentText(name)); // ChatArgument, AdventureChatArgument - Mockito.when(entityPlayerMock.getScoreboardDisplayName()).thenReturn(new ChatComponentText(name)); // ChatArgument, AdventureChatArgument - players.add(entityPlayerMock); - } - // CommandListenerWrapper#levels Mockito.when(clw.p()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -480,9 +472,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + WorldServer entityWorld = Mockito.mock(WorldServer.class); + Mockito.when(entityWorld.a(any(EntityTypes.class), any())).thenAnswer(invocation -> { + EntityTypes type = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (type == EntityTypes.PLAYER) { + return players; + } + return List.of(); + }); + Mockito.when(minecraftServerMock.getWorlds()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument UserCache userCacheMock = Mockito.mock(UserCache.class); @@ -583,33 +587,46 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + EntityPlayer serverPlayerMock = Mockito.mock(EntityPlayer.class); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS - WorldServer worldServer = Mockito.mock(WorldServer.class); - CraftWorld world = Mockito.mock(CraftWorld.class); - Mockito.when(world.getHandle()).thenReturn(worldServer); - Mockito.when(worldServer.getWorld()).thenReturn(world); + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + // Name + String name = playerMock.getName(); - // Provide proper handle as VanillaCommandWrapper expects - CommandListenerWrapper css = getBrigadierSourceFromCommandSender(player); + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(name); - EntityPlayer handle = Mockito.mock(EntityPlayer.class); - Mockito.when(handle.getCommandListener()).thenReturn(css); + IChatBaseComponent nameComponent = new ChatComponentText(name); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getScoreboardDisplayName()).thenReturn(nameComponent); - Mockito.when(player.getHandle()).thenReturn(handle); + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + // World and Location + WorldServer serverLevel = Mockito.mock(WorldServer.class); + CraftWorld world = Mockito.mock(CraftWorld.class); + Mockito.when(world.getHandle()).thenReturn(serverLevel); + Mockito.when(serverLevel.getWorld()).thenReturn(world); - // getDisplayName and getScoreboardDisplayName are used when CommandSourceStack#withEntity is called - IChatBaseComponent nameComponent = new ChatComponentText(name); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); - Mockito.when(handle.getScoreboardDisplayName()).thenReturn(nameComponent); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); + + // Provide proper handle as VanillaCommandWrapper expects + CommandListenerWrapper css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.getCommandListener()).thenReturn(css); + + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 3c657fafbf..6280f0ff54 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -72,6 +72,7 @@ import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootTables; import net.minecraft.world.phys.Vec2; @@ -145,6 +146,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -278,25 +280,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(new TextComponent(name)); // ChatArgument, AdventureChatArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -463,9 +456,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.when(entityWorld.getEntities(any(EntityTypeTest.class), any())).thenAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + return players; + } + return List.of(); + }); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -568,33 +573,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); + + // Name + String name = playerMock.getName(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + TextComponent nameComponent = new TextComponent(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); - - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = new TextComponent(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java index f52c9e2eb2..4d97aea53c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -76,6 +76,7 @@ import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootTables; import net.minecraft.world.phys.Vec2; @@ -160,6 +161,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -296,25 +298,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(new TextComponent(name)); // ChatArgument, AdventureChatArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -478,9 +471,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.when(entityWorld.getEntities(any(EntityTypeTest.class), any())).thenAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + return players; + } + return List.of(); + }); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // Player argument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -583,33 +588,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); + + // Name + String name = playerMock.getName(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + TextComponent nameComponent = new TextComponent(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); - - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = new TextComponent(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 9fc949ed73..43dbaa37c8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -74,6 +74,7 @@ import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootTables; import net.minecraft.world.phys.Vec2; @@ -155,6 +156,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -292,25 +294,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(net.minecraft.network.chat.Component.literal(name)); // ChatArgument, AdventureChatArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -480,9 +473,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.when(entityWorld.getEntities(any(EntityTypeTest.class), any())).thenAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + return players; + } + return List.of(); + }); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -576,33 +581,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); + + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // Name + String name = playerMock.getName(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); - - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 3cfebf7fcd..67e587e9a5 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -75,6 +75,7 @@ import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootTables; import net.minecraft.world.phys.Vec2; @@ -156,6 +157,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -293,26 +295,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(net.minecraft.network.chat.Component.literal(name)); // ChatArgument, AdventureChatArgument - Mockito.when(entityPlayerMock.getType()).thenReturn((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER); // EntitySelectorArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -490,9 +482,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.doAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + ((List) invocation.getArgument(2)).addAll(players); + } + return null; + }).when(entityWorld).getEntities(any(), any(), any(), anyInt()); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -586,33 +590,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); + + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); + + // Name + String name = playerMock.getName(); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); - - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index ffd49fc656..f3535af193 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -32,6 +32,7 @@ import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootDataManager; import net.minecraft.world.phys.Vec2; @@ -139,6 +140,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -288,26 +290,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(net.minecraft.network.chat.Component.literal(name)); // ChatArgument, AdventureChatArgument - Mockito.when(entityPlayerMock.getType()).thenReturn((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER); // EntitySelectorArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -487,9 +479,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.doAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + ((List) invocation.getArgument(2)).addAll(players); + } + return null; + }).when(entityWorld).getEntities(any(), any(), any(), anyInt()); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -583,33 +587,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); + + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // Name + String name = playerMock.getName(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + Component nameComponent = Component.literal(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); - - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 8f5f6c9261..4ba17c30b0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -27,6 +27,7 @@ import net.minecraft.server.*; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.server.players.GameProfileCache; import net.minecraft.server.players.PlayerList; import net.minecraft.util.profiling.metrics.profiling.InactiveMetricsRecorder; @@ -144,6 +145,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -297,27 +299,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(net.minecraft.network.chat.Component.literal(name)); // ChatArgument, AdventureChatArgument - Mockito.when(entityPlayerMock.getScoreboardName()).thenReturn(name); // ScoreHolderArgument - Mockito.when(entityPlayerMock.getType()).thenReturn((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER); // EntitySelectorArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -500,9 +491,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.doAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + ((List) invocation.getArgument(2)).addAll(players); + } + return null; + }).when(entityWorld).getEntities(any(), any(), any(), anyInt()); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -596,33 +599,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); + + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // Name + String name = playerMock.getName(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + Component nameComponent = Component.literal(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); - - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 629721b52d..cff0086202 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -36,6 +36,7 @@ import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.Objective; @@ -142,6 +143,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); // CraftBukkit's registry is shared across all instances of a test (or all tests?) // Idk if we need to actually implement this, but it seems to make certain issues @@ -316,27 +318,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(net.minecraft.network.chat.Component.literal(name)); // ChatArgument, AdventureChatArgument - Mockito.when(entityPlayerMock.getScoreboardName()).thenReturn(name); // ScoreHolderArgument - Mockito.when(entityPlayerMock.getType()).thenReturn((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER); // EntitySelectorArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -629,9 +620,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.doAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + ((List) invocation.getArgument(2)).addAll(players); + } + return null; + }).when(entityWorld).getEntities(any(), any(), any(), anyInt()); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -725,33 +728,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); + + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); + + // Name + String name = playerMock.getName(); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + Component nameComponent = Component.literal(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); - - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 5662828842..91f99bbc86 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -32,6 +32,7 @@ import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootDataManager; import net.minecraft.world.phys.Vec2; @@ -140,6 +141,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -288,26 +290,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send Mockito.when(css.getLevel().isInWorldBounds(any(BlockPos.class))).thenReturn(true); Mockito.when(css.getAnchor()).thenReturn(Anchor.EYES); + if (entity instanceof CraftPlayer craftPlayer) { + // If the sender is a CraftPlayer, it was probably created by `#wrapPlayerMockIntoCraftPlayer`, + // in which case `getHandle` will return what we want. + net.minecraft.world.entity.Entity nmsEntity = craftPlayer.getHandle(); + Mockito.when(css.getEntity()).thenReturn(nmsEntity); + } + // Get mocked MinecraftServer Mockito.when(css.getServer()).thenAnswer(s -> getMinecraftServer()); - // EntitySelectorArgument - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - ServerPlayer entityPlayerMock = Mockito.mock(ServerPlayer.class); - CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); - - // Extract these variables first in case the onlinePlayer is a Mockito object itself - String name = onlinePlayer.getName(); - UUID uuid = onlinePlayer.getUniqueId(); - - Mockito.when(craftPlayerMock.getName()).thenReturn(name); - Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(entityPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - Mockito.when(entityPlayerMock.getDisplayName()).thenReturn(net.minecraft.network.chat.Component.literal(name)); // ChatArgument, AdventureChatArgument - Mockito.when(entityPlayerMock.getType()).thenReturn((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER); // EntitySelectorArgument - players.add(entityPlayerMock); - } - // CommandSourceStack#levels Mockito.when(css.levels()).thenAnswer(invocation -> { Set> set = new HashSet<>(); @@ -488,9 +480,21 @@ public T getMinecraftServer() { } }); + // EntitySelectorArgument for entities wants to loop over all levels to get all the entities + // We'll just sneakily pass it our players as all the entities + ServerLevel entityWorld = Mockito.mock(ServerLevel.class); + Mockito.doAnswer(invocation -> { + EntityTypeTest typeTest = invocation.getArgument(0); + // Make sure we are actually looking for players first + if (typeTest.tryCast(Mockito.mock(ServerPlayer.class)) != null) { + ((List) invocation.getArgument(2)).addAll(players); + } + return null; + }).when(entityWorld).getEntities(any(), any(), any(), anyInt()); + Mockito.when(minecraftServerMock.getAllLevels()).thenReturn(List.of(entityWorld)); + // Player lists Mockito.when(minecraftServerMock.getPlayerList()).thenAnswer(i -> playerListMock); - Mockito.when(minecraftServerMock.getPlayerList().getPlayers()).thenAnswer(i -> players); // PlayerArgument GameProfileCache userCacheMock = Mockito.mock(GameProfileCache.class); @@ -584,33 +588,51 @@ public void addTag(NamespacedKey key, List> commands) { } @Override - public Player setupMockedCraftPlayer(String name) { - CraftPlayer player = Mockito.mock(CraftPlayer.class); + public Player wrapPlayerMockIntoCraftPlayer(Player playerMock) { + // Create player mock objects + CraftPlayer craftPlayerMock = Mockito.mock(CraftPlayer.class); + ServerPlayer serverPlayerMock = Mockito.mock(ServerPlayer.class); + + // Link handle and player + Mockito.when(craftPlayerMock.getHandle()).thenReturn(serverPlayerMock); + Mockito.when(serverPlayerMock.getBukkitEntity()).thenReturn(craftPlayerMock); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // Name + String name = playerMock.getName(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); + + Component nameComponent = Component.literal(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); + + // World and Location ServerLevel serverLevel = Mockito.mock(ServerLevel.class); CraftWorld world = Mockito.mock(CraftWorld.class); Mockito.when(world.getHandle()).thenReturn(serverLevel); Mockito.when(serverLevel.getWorld()).thenReturn(world); - Mockito.when(player.getLocation()).thenReturn(new Location(world, 0, 0, 0)); - Mockito.when(player.getWorld()).thenReturn(world); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(player); - - ServerPlayer handle = Mockito.mock(ServerPlayer.class); - Mockito.when(handle.createCommandSourceStack()).thenReturn(css); - - Mockito.when(player.getHandle()).thenReturn(handle); + // EntitySelectorArgument + Mockito.when(serverPlayerMock.getType()).thenReturn( + (net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.PLAYER + ); + // Provide proper handle as VanillaCommandWrapper expects + CommandSourceStack css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.createCommandSourceStack()).thenReturn(css); - // getName and getDisplayName are used when CommandSourceStack#withEntity is called - net.minecraft.network.chat.Component nameComponent = net.minecraft.network.chat.Component.literal(name); - Mockito.when(handle.getName()).thenReturn(nameComponent); - Mockito.when(handle.getDisplayName()).thenReturn(nameComponent); + // Add to player list + players.add(serverPlayerMock); - return player; + return craftPlayerMock; } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl/src/main/java/dev/jorel/commandapi/test/MockPlatform.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl/src/main/java/dev/jorel/commandapi/test/MockPlatform.java index 98931d39e8..cb432c36cd 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl/src/main/java/dev/jorel/commandapi/test/MockPlatform.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl/src/main/java/dev/jorel/commandapi/test/MockPlatform.java @@ -52,7 +52,7 @@ protected MockPlatform() { if (MockPlatform.instance == null) { MockPlatform.instance = this; } else { - // wtf why was this called twice? + throw new IllegalStateException("MockPlatform was loaded twice in a row!"); } } @@ -163,7 +163,14 @@ public static T forceGetArgument(CommandContext cmdCtx, String key) { public abstract void addFunction(NamespacedKey key, List commands); public abstract void addTag(NamespacedKey key, List> commands); - public abstract Player setupMockedCraftPlayer(String name); + /** + * Converts a {@code PlayerMock} into a {@code CraftPlayer} which can pass through + * {@code VanillaCommandWrapper#getListener} without error. + * + * @param playerMock The {@code PlayerMock} to wrap. + * @return The resulting {@code CraftPlayer} + */ + public abstract Player wrapPlayerMockIntoCraftPlayer(Player playerMock); /** * Converts 1.16.5 and below potion effect names to NamespacedKey names. For diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPIServerMock.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPIServerMock.java index 9d3d323b19..b91a3d7d71 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPIServerMock.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPIServerMock.java @@ -204,14 +204,36 @@ public WorldMock addSimpleWorld(String name) { // return MockPlatform.getInstance().getItemFactory(); // } + @Override + public @NotNull PlayerMock addPlayer() { + PlayerMock player = super.addPlayer(); + MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(player); + return player; + } + + @Override + public @NotNull PlayerMock addPlayer(@NotNull String name) { + PlayerMock player = super.addPlayer(name); + MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(player); + return player; + } + + @Override + public void addPlayer(@NotNull PlayerMock player) { + // Interrupt normal calls to updateCommands, because PlayerMock throws an UnimplementedOperationException + PlayerMock spy = Mockito.mockingDetails(player).isMock() ? player : Mockito.spy(player); + Mockito.doNothing().when(spy).updateCommands(); + super.addPlayer(spy); + } + /** * Creates a new Bukkit {@link Player}. Unlike {@link PlayerMock}, this uses Mockito to mock the CraftPlayer class, * which allows the returned object to pass through VanillaCommandWrapper#getListener without error. * - * @return A new {@link Player}. + * @return A new {@link Player} with a randome name. */ - public Player setupMockedCraftPlayer() { - return setupMockedCraftPlayer("defaultName"); + public Player addCraftPlayer() { + return MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(super.addPlayer()); } /** @@ -221,8 +243,8 @@ public Player setupMockedCraftPlayer() { * @param name The name for the player * @return A new {@link Player}. */ - public Player setupMockedCraftPlayer(String name) { - return MockPlatform.getInstance().setupMockedCraftPlayer(name); + public Player addCraftPlayer(String name) { + return MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(super.addPlayer(name)); } // Advancements diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java index 78df148dd0..b66da18a48 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTests.java @@ -1,13 +1,16 @@ package dev.jorel.commandapi.test; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.bukkit.plugin.java.JavaPlugin; +import dev.jorel.commandapi.arguments.EntitySelectorArgument; +import dev.jorel.commandapi.arguments.GreedyStringArgument; +import dev.jorel.commandapi.arguments.LocationArgument; +import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import be.seeseemelk.mockbukkit.MockBukkit; import be.seeseemelk.mockbukkit.entity.PlayerMock; import dev.jorel.commandapi.Converter; @@ -20,9 +23,12 @@ class CommandConvertedTests extends TestBase { * Setup * *********/ + private CommandConvertedTestsPlugin plugin; + @BeforeEach public void setUp() { super.setUp(); + plugin = CommandConvertedTestsPlugin.load(); } @AfterEach @@ -33,16 +39,79 @@ public void tearDown() { /********* * Tests * *********/ - + @Test - void test1() { - JavaPlugin plugin = MockBukkit.loadWith(CommandConvertedTestsPlugin.class, CommandConvertedTestsPlugin.pluginYaml()); - + void testConvertedExecution() { Converter.convert(plugin, "mycommand"); - + PlayerMock player = server.addPlayer(); server.dispatchBrigadierCommand(player, "mycommand"); assertEquals("hello", player.nextMessage()); } + + @Test + void testSpaceInArguments() { + Mut results = plugin.getCaptureArgsResults(); + Converter.convert(plugin, "captureargs", + new LocationArgument("location"), + new EntitySelectorArgument.OnePlayer("player"), + new GreedyStringArgument("string") + ); + + // Enable server to get `minecraft` namespace + enableServer(); + Player player = server.addCraftPlayer("Player"); + + // Make sure arguments are fully flattened and split + assertStoresArrayResult(player, "minecraft:captureargs 1 2 3 @p hello world", results, + "1", "2", "3", "Player", "hello", "world"); + + assertNoMoreResults(results); + } + + @Test + void testFlatteningCombinations() { + Mut results = plugin.getCaptureArgsResults(); + Converter.convert(plugin, "captureargs", + new EntitySelectorArgument.ManyPlayers("player1"), + new EntitySelectorArgument.ManyPlayers("player2") + ); + + // Enable server to get `minecraft` namespace + enableServer(); + Player playerA = server.addCraftPlayer("PlayerA"); + server.addPlayer("PlayerB"); + + // Make sure all combinations of flattened arguments are run + server.dispatchCommand(playerA, "minecraft:captureargs @a @a"); + assertArrayEquals(new String[]{"PlayerA", "PlayerA"}, results.get()); + assertArrayEquals(new String[]{"PlayerA", "PlayerB"}, results.get()); + assertArrayEquals(new String[]{"PlayerB", "PlayerA"}, results.get()); + assertArrayEquals(new String[]{"PlayerB", "PlayerB"}, results.get()); + + assertNoMoreResults(results); + } + + @Test + void testSuccessCounting() { + Converter.convert(plugin, "alldifferent", + new EntitySelectorArgument.ManyPlayers("player1"), + new EntitySelectorArgument.ManyPlayers("player2"), + new EntitySelectorArgument.ManyPlayers("player3") + ); + + // Add 3 players + Player sender = server.addPlayer("PlayerA"); + server.addPlayer("PlayerB"); + server.addPlayer("PlayerC"); + + + plugin.resetAllDifferentRunCount(); + // There are 3! = 6 permutations of 3 players + assertEquals(6, server.dispatchBrigadierCommand(sender, "alldifferent @a @a @a")); + + // Command should run 3x3x3 = 27 times total + assertEquals(27, plugin.resetAllDifferentRunCount()); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTestsPlugin.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTestsPlugin.java index 7d4f94cce9..87f5ca6fdc 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTestsPlugin.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandConvertedTestsPlugin.java @@ -3,7 +3,10 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; +import java.util.HashSet; +import java.util.List; +import be.seeseemelk.mockbukkit.MockBukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.plugin.PluginDescriptionFile; @@ -11,6 +14,9 @@ import org.bukkit.plugin.java.JavaPluginLoader; public class CommandConvertedTestsPlugin extends JavaPlugin { + public static CommandConvertedTestsPlugin load() { + return MockBukkit.loadWith(CommandConvertedTestsPlugin.class, CommandConvertedTestsPlugin.pluginYaml()); + } @Override public void onEnable() { @@ -25,13 +31,36 @@ public CommandConvertedTestsPlugin(JavaPluginLoader loader, PluginDescriptionFil super(loader, description, dataFolder, file); } + private final Mut captureArgsResults = Mut.of(); + private int allDifferentRunCount = 0; + @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (label.equalsIgnoreCase("mycommand")) { - sender.sendMessage("hello"); - return true; - } - return false; + return switch (label) { + case "mycommand" -> { + sender.sendMessage("hello"); + yield true; + } + case "captureargs" -> { + captureArgsResults.set(args); + yield true; + } + case "alldifferent" -> { + allDifferentRunCount++; + yield new HashSet<>(List.of(args)).size() == args.length; + } + default -> false; + }; + } + + public Mut getCaptureArgsResults() { + return captureArgsResults; + } + + public int resetAllDifferentRunCount() { + int count = allDifferentRunCount; + allDifferentRunCount = 0; + return count; } public static InputStream pluginYaml() { @@ -45,6 +74,8 @@ public static InputStream pluginYaml() { api-version: 1.13 commands: mycommand: + captureargs: + alldifferent: """.getBytes()); } } \ No newline at end of file diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java index 770f234301..f1e988b2c7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java @@ -53,7 +53,7 @@ public void tearDown() { { // Not using Map.of here because I want the values to be in insertion order, // rather than in whatever order the hash code feels like, which makes debugging easier - executorTypeToMockSender.put(ExecutorType.PLAYER, () -> server.setupMockedCraftPlayer()); + executorTypeToMockSender.put(ExecutorType.PLAYER, () -> server.addCraftPlayer()); executorTypeToMockSender.put(ExecutorType.ENTITY, () -> new SimpleEntityMock(server)); executorTypeToMockSender.put(ExecutorType.CONSOLE, ConsoleCommandSenderMock::new); executorTypeToMockSender.put(ExecutorType.BLOCK, () -> Mockito.mock(BlockCommandSender.class)); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandNamespaceTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandNamespaceTests.java index 78839318d6..e132cb3ac8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandNamespaceTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandNamespaceTests.java @@ -1,7 +1,6 @@ package dev.jorel.commandapi.test; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.*; import dev.jorel.commandapi.arguments.IntegerArgument; @@ -10,7 +9,6 @@ import org.bukkit.ChatColor; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; -import org.bukkit.command.SimpleCommandMap; import org.bukkit.entity.Player; import org.bukkit.permissions.PermissibleBase; import org.bukkit.permissions.PermissionAttachment; @@ -45,20 +43,11 @@ public void tearDown() { } Player enableWithNamespaces() { - // Register minecraft: namespace. MockBukkit doesn't do this on their own - // Simulate `CraftServer#setVanillaCommands` - MockPlatform mockPlatform = MockPlatform.getInstance(); - SimpleCommandMap commandMap = mockPlatform.getSimpleCommandMap(); - SpigotCommandRegistration spigotCommandRegistration = (SpigotCommandRegistration) mockPlatform.getCommandRegistrationStrategy(); - for (CommandNode node : mockPlatform.getBrigadierDispatcher().getRoot().getChildren()) { - commandMap.register("minecraft", spigotCommandRegistration.wrapToVanillaCommandWrapper(node)); - } - // Run the CommandAPI's enable tasks, especially `fixNamespaces` enableServer(); // Get a CraftPlayer for running VanillaCommandWrapper commands - Player runCommandsPlayer = server.setupMockedCraftPlayer(); + Player runCommandsPlayer = server.addCraftPlayer(); // Ensure player can have permissions modified PermissibleBase perm = new PermissibleBase(runCommandsPlayer); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java index 401b8bfab7..349b573765 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java @@ -51,14 +51,12 @@ void testOnEnableExecution() { void testOnEnableRegisterAndUnregisterCommand() { enableServer(); - // Add a PlayerMock to the server to listen for calls to `updateCommands()` - PlayerMock updateCommandsPlayer = Mockito.spy(new PlayerMock(server, "updateCommandsPlayer")); - // Interrupt normal calls to updateCommands, because MockPlayer throws an UnimplementedOperationException - Mockito.doNothing().when(updateCommandsPlayer).updateCommands(); + // Spy a PlayerMock to listen for calls to `updateCommands()` + PlayerMock updateCommandsPlayer = Mockito.spy(new PlayerMock(server, "player")); server.addPlayer(updateCommandsPlayer); // Get a CraftPlayer for running VanillaCommandWrapper commands - Player runCommandsPlayer = server.setupMockedCraftPlayer(); + Player runCommandsPlayer = MockPlatform.getInstance().wrapPlayerMockIntoCraftPlayer(updateCommandsPlayer); // Give player permission to run command Mockito.when(runCommandsPlayer.hasPermission("permission")).thenReturn(true); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java index 212aaaae0b..aef00c7833 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java @@ -1,6 +1,6 @@ package dev.jorel.commandapi.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.util.ArrayList; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java index 29a418b170..de5f103e01 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/TestBase.java @@ -13,10 +13,13 @@ import java.util.function.Function; import java.util.stream.Collectors; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.*; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; import org.bukkit.entity.Player; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.plugin.java.JavaPlugin; @@ -31,11 +34,6 @@ import com.mojang.brigadier.suggestion.Suggestion; import be.seeseemelk.mockbukkit.MockBukkit; -import dev.jorel.commandapi.CommandAPI; -import dev.jorel.commandapi.CommandAPIVersionHandler; -import dev.jorel.commandapi.MCVersion; -import dev.jorel.commandapi.PaperImplementations; -import dev.jorel.commandapi.SafeVarHandle; import dev.jorel.commandapi.executors.NormalExecutor; import static org.junit.jupiter.api.Assertions.*; @@ -84,8 +82,18 @@ public void tearDown() { } public void enableServer() { - // Run the CommandAPI's enable tasks assertTrue(CommandAPI.canRegister(), "Server was already enabled! Cannot enable twice!"); + + // Register minecraft: namespace. MockBukkit doesn't seem to do this on their own + // Simulate `CraftServer#setVanillaCommands` + MockPlatform mockPlatform = MockPlatform.getInstance(); + SimpleCommandMap commandMap = mockPlatform.getSimpleCommandMap(); + SpigotCommandRegistration spigotCommandRegistration = (SpigotCommandRegistration) mockPlatform.getCommandRegistrationStrategy(); + for (CommandNode node : mockPlatform.getBrigadierDispatcher().getRoot().getChildren()) { + commandMap.register("minecraft", spigotCommandRegistration.wrapToVanillaCommandWrapper(node)); + } + + // Run the CommandAPI's enable tasks disablePaperImplementations(); Bukkit.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP)); assertDoesNotThrow(() -> server.getScheduler().performOneTick()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentEntitySelectorTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentEntitySelectorTests.java index eb90954e85..1b520ea609 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentEntitySelectorTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentEntitySelectorTests.java @@ -1,10 +1,13 @@ package dev.jorel.commandapi.test.arguments; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Collection; import java.util.List; +import dev.jorel.commandapi.Converter; +import dev.jorel.commandapi.test.CommandConvertedTestsPlugin; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; @@ -151,13 +154,13 @@ void executionTestWithEntitySelectorArgumentOneEntity() { // Fails because @e can allow more than one entity, but we've specified only one entity assertCommandFailsWith(player, "test @e", "Only one entity is allowed, but the provided selector allows more than one at position 0: <--[HERE]"); - // /test @a[limit=1] + // /test @e[limit=1] // Should NOT fail with "Only one entity is allowed" because we've added a single entity limiter to it. - // Dev note: This fails because no entities are found, most likely due to entity lookups not working in the test environment. - assertNotCommandFailsWith(player, "test @e[limit=1]", "Only one entity is allowed, but the provided selector allows more than one at position 0: <--[HERE]"); + server.dispatchCommand(player, "test @e[limit=1]"); + assertEquals("APlayer", results.get().getName()); // /test @a - // Dev note: This command fails because no entities are found, most likely due to entity lookups not working in the test environment. + // Fails because @a can allow more than one entity (player), but we've specified only one entity assertCommandFailsWith(player, "test @a", "Only one entity is allowed, but the provided selector allows more than one at position 0: <--[HERE]"); assertNoMoreResults(results); @@ -216,4 +219,98 @@ void executionTestWithEntitySelectorArgumentManyEntitiesProhibitEmpty() { assertNoMoreResults(results); } + // Converted tests + @Test + void convertedTestWithOnePlayerSelector() { + CommandConvertedTestsPlugin plugin = CommandConvertedTestsPlugin.load(); + Mut results = plugin.getCaptureArgsResults(); + + Converter.convert(plugin, "captureargs", new EntitySelectorArgument.OnePlayer("player")); + + // Enable server so `minecraft` namespace is accessible by assertStoresArrayResult + // This does mean we have to use CraftPlayer mocks to run the command + enableServer(); + Player playerA = server.addCraftPlayer("PlayerA"); + Player playerB = server.addCraftPlayer("PlayerB"); + + // Selected player should unpack to the player's name + assertStoresArrayResult(playerA, "minecraft:captureargs @s", results, "PlayerA"); + assertStoresArrayResult(playerB, "minecraft:captureargs @s", results, "PlayerB"); + + assertNoMoreResults(results); + } + + @Test + void convertedTestWithOneEntitySelector() { + CommandConvertedTestsPlugin plugin = CommandConvertedTestsPlugin.load(); + Mut results = plugin.getCaptureArgsResults(); + + Converter.convert(plugin, "captureargs", new EntitySelectorArgument.OneEntity("entity")); + + // Enable server so `minecraft` namespace is accessible by assertStoresArrayResult + // This does mean we have to use CraftPlayer mocks to run the command + enableServer(); + Player playerA = server.addCraftPlayer("PlayerA"); + Player playerB = server.addCraftPlayer("PlayerB"); + + // Selected entity should unpack to the player's name + assertStoresArrayResult(playerA, "minecraft:captureargs @s", results, "PlayerA"); + assertStoresArrayResult(playerB, "minecraft:captureargs @s", results, "PlayerB"); + + assertNoMoreResults(results); + } + + @Test + void convertedTestWithManyPlayerSelector() { + CommandConvertedTestsPlugin plugin = CommandConvertedTestsPlugin.load(); + Mut results = plugin.getCaptureArgsResults(); + + Converter.convert(plugin, "captureargs", new EntitySelectorArgument.ManyPlayers("entities")); + + // Enable server so `minecraft` namespace exists + // This does mean we have to use CraftPlayer mocks to run the command + enableServer(); + Player player = server.addCraftPlayer("Player1"); + server.addPlayer("Player2"); + server.addPlayer("Player3"); + server.addPlayer("Player4"); + server.addPlayer("Player5"); + + // Command runs once for each selected entity + server.dispatchCommand(player, "minecraft:captureargs @a"); + assertArrayEquals(new String[]{"Player1"}, results.get()); + assertArrayEquals(new String[]{"Player2"}, results.get()); + assertArrayEquals(new String[]{"Player3"}, results.get()); + assertArrayEquals(new String[]{"Player4"}, results.get()); + assertArrayEquals(new String[]{"Player5"}, results.get()); + + assertNoMoreResults(results); + } + + @Test + void convertedTestWithManyEntitySelector() { + CommandConvertedTestsPlugin plugin = CommandConvertedTestsPlugin.load(); + Mut results = plugin.getCaptureArgsResults(); + + Converter.convert(plugin, "captureargs", new EntitySelectorArgument.ManyEntities("entities")); + + // Enable server so `minecraft` namespace exists + // This does mean we have to use CraftPlayer mocks to run the command + enableServer(); + Player player = server.addCraftPlayer("Player1"); + server.addPlayer("Player2"); + server.addPlayer("Player3"); + server.addPlayer("Player4"); + server.addPlayer("Player5"); + + // Command runs once for each selected entity + server.dispatchCommand(player, "minecraft:captureargs @e"); + assertArrayEquals(new String[]{"Player1"}, results.get()); + assertArrayEquals(new String[]{"Player2"}, results.get()); + assertArrayEquals(new String[]{"Player3"}, results.get()); + assertArrayEquals(new String[]{"Player4"}, results.get()); + assertArrayEquals(new String[]{"Player5"}, results.get()); + + assertNoMoreResults(results); + } } From 2438daf16f7168715c63ee4bf61f21ca66999cc2 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:48:17 -0400 Subject: [PATCH 29/42] Split CommandRegistrationTests into separate classes for each exception --- .../test/CommandRegistrationTests.java | 479 ------------------ .../CommandConflictExceptionTests.java | 157 ++++++ .../DuplicateNodeNameExceptionTests.java | 182 +++++++ .../GreedyArgumentExceptionTests.java | 91 ++++ .../InvalidCommandNameExceptionTests.java | 75 +++ .../MissingCommandExecutorExceptionTests.java | 147 ++++++ 6 files changed, 652 insertions(+), 479 deletions(-) delete mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java deleted file mode 100644 index 7f81f5f604..0000000000 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandRegistrationTests.java +++ /dev/null @@ -1,479 +0,0 @@ -package dev.jorel.commandapi.test; - -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.CommandTree; -import dev.jorel.commandapi.arguments.DoubleArgument; -import dev.jorel.commandapi.arguments.FloatArgument; -import dev.jorel.commandapi.arguments.GreedyStringArgument; -import dev.jorel.commandapi.arguments.IntegerArgument; -import dev.jorel.commandapi.arguments.LiteralArgument; -import dev.jorel.commandapi.arguments.LongArgument; -import dev.jorel.commandapi.arguments.StringArgument; -import dev.jorel.commandapi.exceptions.CommandConflictException; -import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; -import dev.jorel.commandapi.exceptions.GreedyArgumentException; -import dev.jorel.commandapi.exceptions.InvalidCommandNameException; -import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; -import dev.jorel.commandapi.executors.NormalExecutorInfo; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -import org.bukkit.entity.Player; - -/** - * Tests for the semantics of registering commands - */ -class CommandRegistrationTests extends TestBase { - - /********* - * Setup * - *********/ - - @BeforeEach - public void setUp() { - super.setUp(); - } - - @AfterEach - public void tearDown() { - super.tearDown(); - } - - /********* - * Tests * - *********/ - - @Test - void testCommandAPICommandGreedyArgumentException() { - // Shouldn't throw, greedy argument is at the end - CommandAPICommand validGreedyCommand = new CommandAPICommand("test") - .withArguments(new StringArgument("arg1")) - .withArguments(new GreedyStringArgument("arg2")) - .executesPlayer(P_EXEC); - - assertDoesNotThrow(() -> validGreedyCommand.register()); - - // Should throw, greedy argument is not at the end - CommandAPICommand invalidGreedyCommand = new CommandAPICommand("test") - .withArguments(new GreedyStringArgument("arg1")) - .withArguments(new StringArgument("arg2")) - .executesPlayer(P_EXEC); - - assertThrowsWithMessage( - GreedyArgumentException.class, - "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found the greedy argument arg1 followed by arg2", - invalidGreedyCommand::register - ); - } - - @Test - void testCommandTreeGreedyArgumentException() { - // Shouldn't throw, greedy argument is at the end - CommandTree validGreedyCommand = new CommandTree("test") - .then( - new StringArgument("arg1") - .then( - new GreedyStringArgument("arg2") - .executesPlayer(P_EXEC) - ) - ); - - assertDoesNotThrow(() -> validGreedyCommand.register()); - - // Should throw, greedy argument is not at the end - CommandTree invalidGreedyCommand = new CommandTree("test") - .then( - new GreedyStringArgument("arg1") - .then( - new StringArgument("arg2") - .executesPlayer(P_EXEC) - ) - ); - - assertThrowsWithMessage( - GreedyArgumentException.class, - "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found the greedy argument arg1 followed by arg2", - invalidGreedyCommand::register - ); - } - - @Test - void testCommandAPICommandInvalidCommandNameException() { - assertThrowsWithMessage( - InvalidCommandNameException.class, - "Invalid command with name 'null' cannot be registered!", - () -> new CommandAPICommand(null) - ); - - assertThrowsWithMessage( - InvalidCommandNameException.class, - "Invalid command with name '' cannot be registered!", - () -> new CommandAPICommand("") - ); - - assertThrowsWithMessage( - InvalidCommandNameException.class, - "Invalid command with name 'my command' cannot be registered!", - () -> new CommandAPICommand("my command") - ); - } - - @Test - void testCommandTreeInvalidCommandNameException() { - assertThrowsWithMessage( - InvalidCommandNameException.class, - "Invalid command with name 'null' cannot be registered!", - () -> new CommandTree(null) - ); - - assertThrowsWithMessage( - InvalidCommandNameException.class, - "Invalid command with name '' cannot be registered!", - () -> new CommandTree("") - ); - - assertThrowsWithMessage( - InvalidCommandNameException.class, - "Invalid command with name 'my command' cannot be registered!", - () -> new CommandTree("my command") - ); - } - - @Test - void testCommandAPICommandMissingCommandExecutorException() { - // This command has no executor, should complain because this isn't runnable - CommandAPICommand commandWithNoExecutors = new CommandAPICommand("test") - .withArguments(new StringArgument("arg1")); - - assertThrowsWithMessage( - MissingCommandExecutorException.class, - "The command path test is not executable!", - commandWithNoExecutors::register - ); - - // This command has no executable subcommands, should complain because this isn't runnable - CommandAPICommand commandWithNoRunnableSubcommands = new CommandAPICommand("test") - .withSubcommand(new CommandAPICommand("sub")); - - assertThrowsWithMessage( - MissingCommandExecutorException.class, - "The command path sub is not executable!", - commandWithNoRunnableSubcommands::register - ); - - // This command is okay because it is eventually executable through a subcommand - CommandAPICommand commandWithEventuallyRunnableSubcommand = new CommandAPICommand("test") - .withSubcommand(new CommandAPICommand("sub") - .withSubcommand(new CommandAPICommand("sub") - .withSubcommand(new CommandAPICommand("sub") - .withSubcommand(new CommandAPICommand("sub") - .executesPlayer(P_EXEC) - ) - ) - ) - ); - - assertDoesNotThrow(() -> commandWithEventuallyRunnableSubcommand.register()); - - // This command is not okay, because the presence of arguments on - // the root indicate the root path was meant to be executed - CommandAPICommand commandWithNotExecutableRootArgument = new CommandAPICommand("test") - .withArguments(new StringArgument("arg1")) - .withSubcommand( - new CommandAPICommand("sub") - .executesPlayer(P_EXEC) - ); - - assertThrowsWithMessage( - MissingCommandExecutorException.class, - "The command path test is not executable!", - commandWithNotExecutableRootArgument::register - ); - } - - @Test - void testCommandTreeMissingCommandExecutorException() { - // This command has no executor, should complain because this isn't runnable - CommandTree commandWithNoExecutors = new CommandTree("test"); - - assertThrowsWithMessage( - MissingCommandExecutorException.class, - "The command path test is not executable!", - commandWithNoExecutors::register - ); - - // This command has no executable arguments, should complain because this isn't runnable - CommandTree commandWithNoRunnableSubcommands = new CommandTree("test") - .then(new LiteralArgument("sub")); - - assertThrowsWithMessage( - MissingCommandExecutorException.class, - "The command path [test] ending with sub is not executable!", - commandWithNoRunnableSubcommands::register - ); - - // This command is okay because it eventually has an executable argument - CommandTree commandWithEventuallyRunnableSubcommand = new CommandTree("test") - .then(new LiteralArgument("sub") - .then(new LiteralArgument("sub") - .then(new LiteralArgument("sub") - .then(new LiteralArgument("sub") - .executesPlayer(P_EXEC) - ) - ) - ) - ); - - assertDoesNotThrow(() -> commandWithEventuallyRunnableSubcommand.register()); - - // This command is not okay because some paths are not executable - CommandTree commandTreeWithSomeNotExecutablePaths = new CommandTree("test") - .then(new LiteralArgument("executable1").then(new LiteralArgument("sub").executesPlayer(P_EXEC))) - .then(new LiteralArgument("notExecutable1").then(new LiteralArgument("sub"))) - .then(new LiteralArgument("notExecutable2").then(new LiteralArgument("sub"))) - .then(new LiteralArgument("executable2").then(new LiteralArgument("sub").executesPlayer(P_EXEC))); - - assertThrowsWithMessage( - MissingCommandExecutorException.class, - "The command path [test notExecutable1] ending with sub is not executable!", - commandTreeWithSomeNotExecutablePaths::register - ); - } - - @Test - void testCommandAPICommandDuplicateNodeNameException() { - // This command is not okay because it has duplicate names for Arguments 1 and 3 - CommandAPICommand commandWithDuplicateArgumentNames = new CommandAPICommand("test") - .withArguments( - new StringArgument("alice"), - new StringArgument("bob"), - new StringArgument("alice") - ) - .executesPlayer(P_EXEC); - - assertThrowsWithMessage( - DuplicateNodeNameException.class, - "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", - commandWithDuplicateArgumentNames::register - ); - - // This command is okay because unlisted arguments do not cause conflict - CommandAPICommand commandWithDuplicateUnlistedArgumentNames = new CommandAPICommand("test") - .withArguments( - new StringArgument("alice").setListed(false), - new StringArgument("bob"), - new StringArgument("alice"), - new StringArgument("bob").setListed(false) - ) - .executesPlayer(P_EXEC); - - assertDoesNotThrow(() -> commandWithDuplicateUnlistedArgumentNames.register()); - - // This command is okay because LiteralArguments are unlisted by default - CommandAPICommand commandWithDuplicateLiteralArgumentNames = new CommandAPICommand("test") - .withArguments( - new LiteralArgument("alice"), - new LiteralArgument("bob"), - new LiteralArgument("alice") - ) - .executesPlayer(P_EXEC); - - assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); - - // However, listed LiteralArguments do conflict - CommandAPICommand commandWithDuplicateListedLiteralArgumentNames = new CommandAPICommand("test") - .withArguments( - new LiteralArgument("alice").setListed(true), - new LiteralArgument("bob").setListed(true), - new LiteralArgument("alice").setListed(true) - ) - .executesPlayer(P_EXEC); - - assertThrowsWithMessage( - DuplicateNodeNameException.class, - "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", - commandWithDuplicateListedLiteralArgumentNames::register - ); - } - - @Test - void testCommandTreeDuplicateNodeNameException() { - // This command is not okay because it has duplicate names for Arguments 1 and 3 - CommandTree commandWithDuplicateArgumentNames = new CommandTree("test") - .then( - new StringArgument("alice").then( - new StringArgument("bob").then( - new StringArgument("alice") - .executesPlayer(P_EXEC) - ) - ) - ); - - assertThrowsWithMessage( - DuplicateNodeNameException.class, - "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", - commandWithDuplicateArgumentNames::register - ); - - // This command is okay because unlisted arguments do not cause conflict - CommandTree commandWithDuplicateUnlistedArgumentNames = new CommandTree("test") - .then( - new StringArgument("alice").setListed(false).then( - new StringArgument("bob").then( - new StringArgument("alice").then( - new StringArgument("bob").setListed(false) - .executesPlayer(P_EXEC) - ) - ) - ) - ); - - assertDoesNotThrow(() -> commandWithDuplicateUnlistedArgumentNames.register()); - - // This command is okay because LiteralArguments are unlisted by default - CommandTree commandWithDuplicateLiteralArgumentNames = new CommandTree("test") - .then( - new LiteralArgument("alice").then( - new LiteralArgument("bob").then( - new LiteralArgument("alice") - .executesPlayer(P_EXEC) - ) - ) - ); - - assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); - - // However, listed LiteralArguments do conflict - CommandTree commandWithDuplicateListedLiteralArgumentNames = new CommandTree("test") - .then( - new LiteralArgument("alice").setListed(true).then( - new LiteralArgument("bob").setListed(true).then( - new LiteralArgument("alice").setListed(true) - .executesPlayer(P_EXEC) - ) - ) - ); - - assertThrowsWithMessage( - DuplicateNodeNameException.class, - "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", - commandWithDuplicateListedLiteralArgumentNames::register - ); - - // This command is okay because the duplicate names are on different paths - CommandTree commandWithDuplicateNamesSeparated = new CommandTree("test") - .then(new LiteralArgument("path1").then(new StringArgument("alice").executesPlayer(P_EXEC))) - .then(new LiteralArgument("path2").then(new StringArgument("alice").executesPlayer(P_EXEC))) - .then(new LiteralArgument("path3").then(new StringArgument("alice").executesPlayer(P_EXEC))) - .then(new LiteralArgument("path4").then(new StringArgument("alice").executesPlayer(P_EXEC))); - - assertDoesNotThrow(() -> commandWithDuplicateNamesSeparated.register()); - } - - @Test - void testCommandConflictException() { - Mut results = Mut.of(); - Player player = server.addPlayer(); - - // The executor we register first should not be overwritten and should always run - NormalExecutorInfo firstExecutor = info -> {results.set("first");}; - NormalExecutorInfo secondExecutor = info -> {results.set("second");}; - - // No arguments - new CommandAPICommand("noArguments") - .executesPlayer(firstExecutor) - .register(); - - CommandAPICommand noArguments = new CommandAPICommand("noArguments") - .executesPlayer(secondExecutor); - - assertThrowsWithMessage( - CommandConflictException.class, - "The command path \"/noArguments\" could not be registered because it conflicts with a previously registered command.", - noArguments::register - ); - assertStoresResult(player, "noArguments", results, "first"); - - // Arguments - new CommandAPICommand("arguments") - .withArguments( - new LiteralArgument("literal"), - new StringArgument("string") - ) - .executesPlayer(firstExecutor) - .register(); - - CommandAPICommand arguments = new CommandAPICommand("arguments") - .withArguments( - new LiteralArgument("literal"), - new StringArgument("string") - ) - .executesPlayer(secondExecutor); - - assertThrowsWithMessage( - CommandConflictException.class, - "The command path \"/arguments literal string\" could not be registered because it conflicts with a previously registered command.", - arguments::register - ); - assertStoresResult(player, "arguments literal string", results, "first"); - - // Different argument types - new CommandAPICommand("argumentTypes") - .withArguments( - new IntegerArgument("alice"), - new FloatArgument("bob") - ) - .executesPlayer(firstExecutor) - .register(); - - CommandAPICommand argumentTypes = new CommandAPICommand("argumentTypes") - .withArguments( - new LongArgument("alice"), - new DoubleArgument("bob") - ) - .executesPlayer(secondExecutor); - - assertThrowsWithMessage( - CommandConflictException.class, - "The command path \"/argumentTypes alice bob\" could not be registered because it conflicts with a previously registered command.", - argumentTypes::register - ); - assertStoresResult(player, "argumentTypes 0 0", results, "first"); - - // Different branches - new CommandTree("branches") - .then( - new LiteralArgument("alice") - .executesPlayer(firstExecutor) - ) - .then( - new LiteralArgument("bob") - .executesPlayer(firstExecutor) - ) - .register(); - - CommandTree branches = new CommandTree("branches") - .then( - new LiteralArgument("bob") - .executesPlayer(secondExecutor) - ) - .then( - new LiteralArgument("alice") - .executesPlayer(secondExecutor) - ); - - assertThrowsWithMessage( - CommandConflictException.class, - "The command path \"/branches bob\" could not be registered because it conflicts with a previously registered command.", - branches::register - ); - assertStoresResult(player, "branches alice", results, "first"); - assertStoresResult(player, "branches bob", results, "first"); - - assertNoMoreResults(results); - } -} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java new file mode 100644 index 0000000000..26abc83021 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java @@ -0,0 +1,157 @@ +package dev.jorel.commandapi.test.exceptions; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.exceptions.CommandConflictException; +import dev.jorel.commandapi.executors.NormalExecutorInfo; +import dev.jorel.commandapi.test.Mut; +import dev.jorel.commandapi.test.TestBase; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CommandConflictExceptionTests extends TestBase { + + /********* + * Setup * + *********/ + + private Mut results; + private Player player; + private NormalExecutorInfo firstExecutor; + private NormalExecutorInfo secondExecutor; + + @BeforeEach + public void setUp() { + super.setUp(); + + results = Mut.of(); + player = server.addPlayer(); + + // The executor we register first should not be overwritten and should always run + firstExecutor = info -> results.set("first"); + secondExecutor = info -> results.set("second"); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @Test + void testExecutorsAreNotOverridden() { + // No arguments + new CommandAPICommand("noArguments") + .executesPlayer(firstExecutor) + .register(); + + CommandAPICommand noArguments = new CommandAPICommand("noArguments") + .executesPlayer(secondExecutor); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/noArguments\" could not be registered because it conflicts with a previously registered command.", + noArguments::register + ); + assertStoresResult(player, "noArguments", results, "first"); + + assertNoMoreResults(results); + } + + @Test + void testConflictDetectionWithSameArgumentPath() { + // Arguments + new CommandAPICommand("arguments") + .withArguments( + new LiteralArgument("literal"), + new StringArgument("string") + ) + .executesPlayer(firstExecutor) + .register(); + + CommandAPICommand arguments = new CommandAPICommand("arguments") + .withArguments( + new LiteralArgument("literal"), + new StringArgument("string") + ) + .executesPlayer(secondExecutor); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/arguments literal string\" could not be registered because it conflicts with a previously registered command.", + arguments::register + ); + assertStoresResult(player, "arguments literal string", results, "first"); + + assertNoMoreResults(results); + } + + @Test + void testConflictDetectionWithDifferentArgumentTypes() { + // Different argument types + new CommandAPICommand("argumentTypes") + .withArguments( + new IntegerArgument("alice"), + new FloatArgument("bob") + ) + .executesPlayer(firstExecutor) + .register(); + + CommandAPICommand argumentTypes = new CommandAPICommand("argumentTypes") + .withArguments( + new LongArgument("alice"), + new DoubleArgument("bob") + ) + .executesPlayer(secondExecutor); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/argumentTypes alice bob\" could not be registered because it conflicts with a previously registered command.", + argumentTypes::register + ); + assertStoresResult(player, "argumentTypes 0 0", results, "first"); + + assertNoMoreResults(results); + } + + @Test + void testConflictDetectionWithCommandTreeBranches() { + // Different branches + new CommandTree("branches") + .then( + new LiteralArgument("alice") + .executesPlayer(firstExecutor) + ) + .then( + new LiteralArgument("bob") + .executesPlayer(firstExecutor) + ) + .register(); + + CommandTree branches = new CommandTree("branches") + .then( + new LiteralArgument("bob") + .executesPlayer(secondExecutor) + ) + .then( + new LiteralArgument("alice") + .executesPlayer(secondExecutor) + ); + + assertThrowsWithMessage( + CommandConflictException.class, + "The command path \"/branches bob\" could not be registered because it conflicts with a previously registered command.", + branches::register + ); + assertStoresResult(player, "branches alice", results, "first"); + assertStoresResult(player, "branches bob", results, "first"); + + assertNoMoreResults(results); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java new file mode 100644 index 0000000000..58de4dae4b --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java @@ -0,0 +1,182 @@ +package dev.jorel.commandapi.test.exceptions; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; +import dev.jorel.commandapi.test.TestBase; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Tests for the {@link DuplicateNodeNameException}. + */ +public class DuplicateNodeNameExceptionTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @Test + void testCommandAPICommandDuplicateArgumentNames() { + // This command is not okay because it has duplicate names for Arguments 1 and 3 + CommandAPICommand commandWithDuplicateArgumentNames = new CommandAPICommand("test") + .withArguments( + new StringArgument("alice"), + new StringArgument("bob"), + new StringArgument("alice") + ) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", + commandWithDuplicateArgumentNames::register + ); + } + + @Test + void testCommandAPICommandUnlistedArguments() { + // This command is okay because unlisted arguments do not cause conflict + CommandAPICommand commandWithDuplicateUnlistedArgumentNames = new CommandAPICommand("test") + .withArguments( + new StringArgument("alice").setListed(false), + new StringArgument("bob"), + new StringArgument("alice"), + new StringArgument("bob").setListed(false) + ) + .executesPlayer(P_EXEC); + + assertDoesNotThrow(() -> commandWithDuplicateUnlistedArgumentNames.register()); + } + + @Test + void testCommandAPICommandWithLiteralArguments() { + // This command is okay because LiteralArguments are unlisted by default + CommandAPICommand commandWithDuplicateLiteralArgumentNames = new CommandAPICommand("test") + .withArguments( + new LiteralArgument("alice"), + new LiteralArgument("bob"), + new LiteralArgument("alice") + ) + .executesPlayer(P_EXEC); + + assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); + + // However, listed LiteralArguments do conflict + CommandAPICommand commandWithDuplicateListedLiteralArgumentNames = new CommandAPICommand("test") + .withArguments( + new LiteralArgument("alice").setListed(true), + new LiteralArgument("bob").setListed(true), + new LiteralArgument("alice").setListed(true) + ) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", + commandWithDuplicateListedLiteralArgumentNames::register + ); + } + + @Test + void testCommandTreeDuplicateArgumentNames() { + // This command is not okay because it has duplicate names for Arguments 1 and 3 + CommandTree commandWithDuplicateArgumentNames = new CommandTree("test") + .then( + new StringArgument("alice").then( + new StringArgument("bob").then( + new StringArgument("alice") + .executesPlayer(P_EXEC) + ) + ) + ); + + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", + commandWithDuplicateArgumentNames::register + ); + } + + @Test + void testCommandTreeUnlistedArguments() { + // This command is okay because unlisted arguments do not cause conflict + CommandTree commandWithDuplicateUnlistedArgumentNames = new CommandTree("test") + .then( + new StringArgument("alice").setListed(false).then( + new StringArgument("bob").then( + new StringArgument("alice").then( + new StringArgument("bob").setListed(false) + .executesPlayer(P_EXEC) + ) + ) + ) + ); + + assertDoesNotThrow(() -> commandWithDuplicateUnlistedArgumentNames.register()); + } + + @Test + void testCommandTreeWithLiteralArguments() { + // This command is okay because LiteralArguments are unlisted by default + CommandTree commandWithDuplicateLiteralArgumentNames = new CommandTree("test") + .then( + new LiteralArgument("alice").then( + new LiteralArgument("bob").then( + new LiteralArgument("alice") + .executesPlayer(P_EXEC) + ) + ) + ); + + assertDoesNotThrow(() -> commandWithDuplicateLiteralArgumentNames.register()); + + // However, listed LiteralArguments do conflict + CommandTree commandWithDuplicateListedLiteralArgumentNames = new CommandTree("test") + .then( + new LiteralArgument("alice").setListed(true).then( + new LiteralArgument("bob").setListed(true).then( + new LiteralArgument("alice").setListed(true) + .executesPlayer(P_EXEC) + ) + ) + ); + + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", + commandWithDuplicateListedLiteralArgumentNames::register + ); + } + + @Test + void testCommandTreeWithSeparatedDuplicateNames() { + // This command is okay because the duplicate names are on different paths + CommandTree commandWithDuplicateNamesSeparated = new CommandTree("test") + .then(new LiteralArgument("path1").then(new StringArgument("alice").executesPlayer(P_EXEC))) + .then(new LiteralArgument("path2").then(new StringArgument("alice").executesPlayer(P_EXEC))) + .then(new LiteralArgument("path3").then(new StringArgument("alice").executesPlayer(P_EXEC))) + .then(new LiteralArgument("path4").then(new StringArgument("alice").executesPlayer(P_EXEC))); + + assertDoesNotThrow(() -> commandWithDuplicateNamesSeparated.register()); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java new file mode 100644 index 0000000000..27d0ada1dd --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java @@ -0,0 +1,91 @@ +package dev.jorel.commandapi.test.exceptions; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.arguments.GreedyStringArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.exceptions.GreedyArgumentException; +import dev.jorel.commandapi.test.TestBase; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Tests for the {@link GreedyArgumentException}. + */ +public class GreedyArgumentExceptionTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @Test + void testCommandAPICommandGreedyArgumentException() { + // Shouldn't throw, greedy argument is at the end + CommandAPICommand validGreedyCommand = new CommandAPICommand("test") + .withArguments(new StringArgument("arg1")) + .withArguments(new GreedyStringArgument("arg2")) + .executesPlayer(P_EXEC); + + assertDoesNotThrow(() -> validGreedyCommand.register()); + + // Should throw, greedy argument is not at the end + CommandAPICommand invalidGreedyCommand = new CommandAPICommand("test") + .withArguments(new GreedyStringArgument("arg1")) + .withArguments(new StringArgument("arg2")) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + GreedyArgumentException.class, + "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found the greedy argument arg1 followed by arg2", + invalidGreedyCommand::register + ); + } + + @Test + void testCommandTreeGreedyArgumentException() { + // Shouldn't throw, greedy argument is at the end + CommandTree validGreedyCommand = new CommandTree("test") + .then( + new StringArgument("arg1") + .then( + new GreedyStringArgument("arg2") + .executesPlayer(P_EXEC) + ) + ); + + assertDoesNotThrow(() -> validGreedyCommand.register()); + + // Should throw, greedy argument is not at the end + CommandTree invalidGreedyCommand = new CommandTree("test") + .then( + new GreedyStringArgument("arg1") + .then( + new StringArgument("arg2") + .executesPlayer(P_EXEC) + ) + ); + + assertThrowsWithMessage( + GreedyArgumentException.class, + "A greedy argument can only be declared at the end of a command. Going down the [test] branch, found the greedy argument arg1 followed by arg2", + invalidGreedyCommand::register + ); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java new file mode 100644 index 0000000000..c6a0803838 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java @@ -0,0 +1,75 @@ +package dev.jorel.commandapi.test.exceptions; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.exceptions.InvalidCommandNameException; +import dev.jorel.commandapi.test.TestBase; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for the {@link InvalidCommandNameException}. + */ +public class InvalidCommandNameExceptionTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @Test + void testCommandAPICommandInvalidCommandNameException() { + assertThrowsWithMessage( + InvalidCommandNameException.class, + "Invalid command with name 'null' cannot be registered!", + () -> new CommandAPICommand(null) + ); + + assertThrowsWithMessage( + InvalidCommandNameException.class, + "Invalid command with name '' cannot be registered!", + () -> new CommandAPICommand("") + ); + + assertThrowsWithMessage( + InvalidCommandNameException.class, + "Invalid command with name 'my command' cannot be registered!", + () -> new CommandAPICommand("my command") + ); + } + + @Test + void testCommandTreeInvalidCommandNameException() { + assertThrowsWithMessage( + InvalidCommandNameException.class, + "Invalid command with name 'null' cannot be registered!", + () -> new CommandTree(null) + ); + + assertThrowsWithMessage( + InvalidCommandNameException.class, + "Invalid command with name '' cannot be registered!", + () -> new CommandTree("") + ); + + assertThrowsWithMessage( + InvalidCommandNameException.class, + "Invalid command with name 'my command' cannot be registered!", + () -> new CommandTree("my command") + ); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java new file mode 100644 index 0000000000..847320b6de --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java @@ -0,0 +1,147 @@ +package dev.jorel.commandapi.test.exceptions; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandTree; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; +import dev.jorel.commandapi.test.TestBase; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Tests for the {@link MissingCommandExecutorException}. + */ +public class MissingCommandExecutorExceptionTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @Test + void testCommandAPICommandNoBaseExecutor() { + // This command has no executor, should complain because this isn't runnable + CommandAPICommand commandWithNoExecutors = new CommandAPICommand("test") + .withArguments(new StringArgument("arg1")); + + assertThrowsWithMessage( + MissingCommandExecutorException.class, + "The command path test is not executable!", + commandWithNoExecutors::register + ); + } + + @Test + void testCommandAPICommandSubcommandExecutors() { + // This command has no executable subcommands, should complain because this isn't runnable + CommandAPICommand commandWithNoRunnableSubcommands = new CommandAPICommand("test") + .withSubcommand(new CommandAPICommand("sub")); + + assertThrowsWithMessage( + MissingCommandExecutorException.class, + "The command path sub is not executable!", + commandWithNoRunnableSubcommands::register + ); + + // This command is okay because it is eventually executable through a subcommand + CommandAPICommand commandWithEventuallyRunnableSubcommand = new CommandAPICommand("test") + .withSubcommand(new CommandAPICommand("sub") + .withSubcommand(new CommandAPICommand("sub") + .withSubcommand(new CommandAPICommand("sub") + .withSubcommand(new CommandAPICommand("sub") + .executesPlayer(P_EXEC) + ) + ) + ) + ); + + assertDoesNotThrow(() -> commandWithEventuallyRunnableSubcommand.register()); + } + + @Test + void testCommandAPICommandExecutableSubcommandButBaseArguments() { + // This command is not okay, because the presence of arguments on + // the root indicate the root path was meant to be executed + CommandAPICommand commandWithNotExecutableRootArgument = new CommandAPICommand("test") + .withArguments(new StringArgument("arg1")) + .withSubcommand( + new CommandAPICommand("sub") + .executesPlayer(P_EXEC) + ); + + assertThrowsWithMessage( + MissingCommandExecutorException.class, + "The command path test is not executable!", + commandWithNotExecutableRootArgument::register + ); + } + + @Test + void testCommandTreeNoBaseExecutor() { + // This command has no executor, should complain because this isn't runnable + CommandTree commandWithNoExecutors = new CommandTree("test"); + + assertThrowsWithMessage( + MissingCommandExecutorException.class, + "The command path test is not executable!", + commandWithNoExecutors::register + ); + } + + @Test + void testCommandTreeBranchExecutors() { + // This command has no executable arguments, should complain because this isn't runnable + CommandTree commandWithNoRunnableSubcommands = new CommandTree("test") + .then(new LiteralArgument("sub")); + + assertThrowsWithMessage( + MissingCommandExecutorException.class, + "The command path [test] ending with sub is not executable!", + commandWithNoRunnableSubcommands::register + ); + + // This command is okay because it eventually has an executable argument + CommandTree commandWithEventuallyRunnableSubcommand = new CommandTree("test") + .then(new LiteralArgument("sub") + .then(new LiteralArgument("sub") + .then(new LiteralArgument("sub") + .then(new LiteralArgument("sub") + .executesPlayer(P_EXEC) + ) + ) + ) + ); + + assertDoesNotThrow(() -> commandWithEventuallyRunnableSubcommand.register()); + + // This command is not okay because some paths are not executable + CommandTree commandTreeWithSomeNotExecutablePaths = new CommandTree("test") + .then(new LiteralArgument("executable1").then(new LiteralArgument("sub").executesPlayer(P_EXEC))) + .then(new LiteralArgument("notExecutable1").then(new LiteralArgument("sub"))) + .then(new LiteralArgument("notExecutable2").then(new LiteralArgument("sub"))) + .then(new LiteralArgument("executable2").then(new LiteralArgument("sub").executesPlayer(P_EXEC))); + + assertThrowsWithMessage( + MissingCommandExecutorException.class, + "The command path [test notExecutable1] ending with sub is not executable!", + commandTreeWithSomeNotExecutablePaths::register + ); + } +} From 288d6ef6a44679ea2fbf98ac65dfd7cdbcbadf6a Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Mon, 17 Jun 2024 04:50:58 -0400 Subject: [PATCH 30/42] Add ArgumentListabilityTests --- .../arguments/ArgumentListabilityTests.java | 134 ++++++++++++++++++ .../DuplicateNodeNameExceptionTests.java | 14 ++ 2 files changed, 148 insertions(+) create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java new file mode 100644 index 0000000000..86b5ae21f3 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java @@ -0,0 +1,134 @@ +package dev.jorel.commandapi.test.arguments; + +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.test.Mut; +import dev.jorel.commandapi.test.TestBase; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for the effects of the {@link AbstractArgument#setListed(boolean)} method. + * Arguments may use different {@link CommandNode} implementation that implement this with separate code. + */ +public class ArgumentListabilityTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @Test + void testUnlistedRequiredArguments() { + Mut results = Mut.of(); + Player sender = server.addPlayer(); + + // Unlisted arguments before or after should not interfere with parsing + new CommandAPICommand("test") + .withArguments( + new StringArgument("number").setListed(false), + new IntegerArgument("number"), + new StringArgument("number").setListed(false) + ) + .executes(info -> { + results.set(info.args().getUnchecked("number")); + }) + .register(); + + assertStoresResult(sender, "test abc 10 def", results, 10); + assertStoresResult(sender, "test 0 1 2", results, 1); + + assertNoMoreResults(results); + } + + @Test + void testUnlistedLiterals() { + Mut results = Mut.of(); + + // Default unlisted literals should not overwrite parsed results + new CommandAPICommand("test") + .withArguments( + new LiteralArgument("number"), + new IntegerArgument("number"), + new LiteralArgument("number") + ) + .executesPlayer(info -> { + results.set(info.args().getUnchecked("number")); + }) + .register(); + + Player player = server.addPlayer(); + + // Unlisted MultiLiterals before or after should not affect the result given + assertStoresResult(player, "test number 0 number", results, 0); + assertStoresResult(player, "test number 10 number", results, 10); + + assertNoMoreResults(results); + } + + @Test + void testUnlistedMultiLiterals() { + Mut results = Mut.of(); + + // This command is valid because the listed arguments do not repeat names + new CommandAPICommand("test") + .withArguments( + new MultiLiteralArgument("literal", "a", "b", "c").setListed(false), + new MultiLiteralArgument("literal", "c", "d", "e"), + new MultiLiteralArgument("literal", "f", "g", "h").setListed(false) + ) + .executesPlayer(info -> { + results.set(info.args().getUnchecked("literal")); + }) + .register(); + + Player player = server.addPlayer(); + + // Unlisted MultiLiterals before or after should not affect the result given + assertStoresResult(player, "test a c f", results, "c"); + assertStoresResult(player, "test b d g", results, "d"); + assertStoresResult(player, "test c e h", results, "e"); + + assertNoMoreResults(results); + } + + @Test + void testUnlistedPreviewableArguments() { + Mut results = Mut.of(); + Player sender = server.addPlayer(); + + // Unlisted arguments before or after should not interfere with parsing + // Though all current Previewable arguments are also Greedy, so you can't + // have an argument after it anyway ¯\_(ツ)_/¯ + new CommandAPICommand("test") + .withArguments( + new IntegerArgument("number"), + new ChatArgument("number").setListed(false) + ) + .executes(info -> { + results.set(info.args().getUnchecked("number")); + }) + .register(); + + assertStoresResult(sender, "test 10 def", results, 10); + assertStoresResult(sender, "test 1 2", results, 1); + + assertNoMoreResults(results); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java index 58de4dae4b..6aa86b2290 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java @@ -3,6 +3,7 @@ import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.CommandTree; import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; import dev.jorel.commandapi.exceptions.DuplicateNodeNameException; import dev.jorel.commandapi.test.TestBase; @@ -95,6 +96,19 @@ void testCommandAPICommandWithLiteralArguments() { "Duplicate node names for listed arguments are not allowed! Going down the [test alice bob] branch, found alice, which had a duplicated node name", commandWithDuplicateListedLiteralArgumentNames::register ); + + CommandAPICommand command = new CommandAPICommand("test") + .withArguments( + new MultiLiteralArgument("literal", "a", "b", "c"), + new MultiLiteralArgument("literal", "c", "d", "e") + ) + .executesPlayer(P_EXEC); + + assertThrowsWithMessage( + DuplicateNodeNameException.class, + "Duplicate node names for listed arguments are not allowed! Going down the [test literal] branch, found literal, which had a duplicated node name", + command::register + ); } @Test From 3649509ca58ba8a14c34238d3cfbfd8939bdd50c Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:34:51 +0200 Subject: [PATCH 31/42] Implement `DynamicMultiLiteralArgument` Acts like `MultiLiteralArgument`, but literals can be changed and differ for each CommandSender Resolves https://github.com/JorelAli/CommandAPI/issues/513 Uses Paper's `AsyncPlayerSendCommandsEvent` and Velocity's `PlayerAvailableCommandsEvent` to change the client's view of the command tree. Note that a similar event does not exist on Spigot, so the suggestions are not dynamic, though the parsing still is. `DifferentClientNode` class added to handle creating client-server command tree de-syncs (I believe this can be used to make `FlagsArgument` platform-agnostic) --- .../arguments/CommandAPIArgumentType.java | 5 + .../DynamicMultiLiteralArgumentCommon.java | 55 +++++++ .../commandnodes/DifferentClientNode.java | 148 ++++++++++++++++++ .../DynamicMultiLiteralArgumentBuilder.java | 58 +++++++ .../DynamicMultiLiteralCommandNode.java | 136 ++++++++++++++++ .../commandapi-bukkit-core/pom.xml | 6 + .../jorel/commandapi/CommandAPIBukkit.java | 4 +- .../commandapi/PaperImplementations.java | 85 ++++++---- .../commandapi/SpigotCommandRegistration.java | 13 ++ .../DynamicMultiLiteralArgument.java | 53 +++++++ .../dev/jorel/commandapi/CommandAPIMain.java | 47 ++++++ .../commandapi-bukkit-test-tests/pom.xml | 8 + .../jorel/commandapi/CommandAPIVelocity.java | 13 +- .../DynamicMultiLiteralArgument.java | 53 +++++++ .../dev/jorel/commandapi/CommandAPIMain.java | 41 +++++ 15 files changed, 689 insertions(+), 36 deletions(-) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java create mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java index b665e82926..04e7692c8c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java @@ -102,6 +102,11 @@ public enum CommandAPIArgumentType { */ DIMENSION("minecraft:dimension"), + /** + * The DynamicMultiLiteralArgument + */ + DYNAMIC_MULTI_LITERAL, + /** * The EnchantmentArgument * diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java new file mode 100644 index 0000000000..57e2ceaff1 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java @@ -0,0 +1,55 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import dev.jorel.commandapi.commandnodes.DynamicMultiLiteralArgumentBuilder; + +import java.util.List; + +public interface DynamicMultiLiteralArgumentCommon +/// @endcond +, CommandSender> { + // DynamicMultiLiteralArgument info + @FunctionalInterface + interface LiteralsCreator { + List createLiterals(CommandSender sender); + } + + LiteralsCreator getLiteralsCreator(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // LINKED METHODS // + // These automatically link to methods in AbstractArgument (make sure they have the same signature) // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Links to {@link AbstractArgument#getNodeName()}. + */ + String getNodeName(); + + /** + * Links to {@link AbstractArgument#isListed()}. + */ + boolean isListed(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDING METHODS // + // A DynamicMultiLiteralArgument has special logic that should override the implementations in AbstractArgument // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. + *

    + * We want to use DynamicMultiLiteralArgumentBuilder rather than RequiredArgumentBuilder. + */ + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousArgumentNames) { + String name = getNodeName(); + boolean isListed = isListed(); + LiteralsCreator literalsCreator = getLiteralsCreator(); + + previousArguments.add((Argument) this); + if (isListed()) previousArgumentNames.add(name); + + return DynamicMultiLiteralArgumentBuilder.dynamicMultiLiteral(name, isListed, literalsCreator); + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java new file mode 100644 index 0000000000..bb378ff3a3 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java @@ -0,0 +1,148 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.RedirectModifier; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandAPIHandler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public abstract class DifferentClientNode extends ArgumentCommandNode { + // Rewrite node trees for the client + public static void rewriteAllChildren(Source client, CommandNode parent) { + // Copy the children, as we do expect them to be modified + List> children = new ArrayList<>(parent.getChildren()); + for (CommandNode child : children) { + rewriteChild(client, parent, child); + } + } + + public static void rewriteChild(Source client, CommandNode parent, CommandNode child) { + // Get the node creator + DifferentClientNode clientNodeCreator = null; + if (child instanceof DifferentClientNode) { + // child is directly a DifferentClientNode + clientNodeCreator = (DifferentClientNode) child; + } else if (child instanceof ArgumentCommandNode argument && argument.getType() instanceof Type) { + // child was copied from a DifferentClientNode using `createBuilder` (real node hidden in ArgumentType) + Type type = (Type) argument.getType(); + clientNodeCreator = type.node; + } + + if (clientNodeCreator != null) { + // Get the new client nodes + List> clientNodes = clientNodeCreator.rewriteNodeForClient(client); + + // Inject client node + Map> children = CommandAPIHandler.getCommandNodeChildren(parent); + children.remove(child.getName()); + for (CommandNode clientNode : clientNodes) { + children.put(clientNode.getName(), clientNode); + } + } + + // Modify all children + rewriteAllChildren(client, child); + } + + // Node information + private final ArgumentType type; + + public DifferentClientNode( + String name, ArgumentType type, + Command command, Predicate requirement, + CommandNode redirect, RedirectModifier modifier, boolean forks + ) { + // Platforms often copy this node as a normal ArgumentCommandNode, + // but we can hide a reference to ourselves in the ArgumentType used + // when ArgumentCommandNode#createBuilder is called + super(name, new Type(), command, requirement, redirect, modifier, forks, null); + + ((Type) super.getType()).node = this; + + // This type actually represents this argument when serializing nodes to json + // and also helps this argument blend in with ambiguity checks + this.type = type; + } + + // Handle type nonsense + private static class Type implements ArgumentType { + private DifferentClientNode node; + + @Override + public T parse(StringReader stringReader) { + throw new IllegalStateException("Not supposed to be called"); + } + } + + @Override + public ArgumentType getType() { + return type; + } + + @Override + public boolean isValidInput(String input) { + try { + StringReader reader = new StringReader(input); + this.type.parse(reader); + return !reader.canRead() || reader.peek() == ' '; + } catch (CommandSyntaxException var3) { + return false; + } + } + + @Override + public Collection getExamples() { + return this.type.getExamples(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof DifferentClientNode other)) return false; + + return this.type.equals(other.type) && super.equals(other); + } + + @Override + public int hashCode() { + int result = this.type.hashCode(); + result = 31 * result + super.hashCode(); + return result; + } + + // Special client-side nodes + + /** + * Transforms this node into one the client should see. + * + * @param client The client who the new node is for. If this is null, + * the generated node should just be for any general client. + * @return The version of this {@link CommandNode} the client should see. This is a list, so one server + * node may become many on the client. If the list is empty, the client will not see any nodes. + */ + public abstract List> rewriteNodeForClient(Source client); + + // Require inheritors to redefine these methods from ArgumentCommandNode + @Override + public abstract void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException; + + @Override + public abstract CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException; + + @Override + public abstract String toString(); +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java new file mode 100644 index 0000000000..6cb9aa24aa --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java @@ -0,0 +1,58 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.arguments.DynamicMultiLiteralArgumentCommon.LiteralsCreator; + +public class DynamicMultiLiteralArgumentBuilder extends ArgumentBuilder> { + // Build + private final String name; + private final boolean isListed; + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralArgumentBuilder(String name, boolean isListed, LiteralsCreator literalsCreator) { + this.name = name; + this.isListed = isListed; + this.literalsCreator = literalsCreator; + } + + public static DynamicMultiLiteralArgumentBuilder dynamicMultiLiteral( + String name, boolean isListed, LiteralsCreator literalsCreator + ) { + return new DynamicMultiLiteralArgumentBuilder<>(name, isListed, literalsCreator); + } + + // Getters + @Override + protected DynamicMultiLiteralArgumentBuilder getThis() { + return this; + } + + public String getName() { + return name; + } + + public boolean isListed() { + return isListed; + } + + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // Create node + @Override + public DynamicMultiLiteralCommandNode build() { + final DynamicMultiLiteralCommandNode result = new DynamicMultiLiteralCommandNode<>( + name, isListed, literalsCreator, + getCommand(), getRequirement(), + getRedirect(), getRedirectModifier(), isFork() + ); + + for (final CommandNode argument : getArguments()) { + result.addChild(argument); + } + + return result; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java new file mode 100644 index 0000000000..8d8b177fd8 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java @@ -0,0 +1,136 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.RedirectModifier; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedArgument; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.arguments.DynamicMultiLiteralArgumentCommon.LiteralsCreator; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public class DynamicMultiLiteralCommandNode extends DifferentClientNode { + private final boolean isListed; + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralCommandNode( + String name, boolean isListed, LiteralsCreator literalsCreator, + Command command, Predicate requirement, + CommandNode redirect, RedirectModifier modifier, boolean forks + ) { + // This mostly acts like a StringArgument + super(name, StringArgumentType.word(), command, requirement, redirect, modifier, forks); + + this.isListed = isListed; + this.literalsCreator = literalsCreator; + } + + // Getters + public boolean isListed() { + return isListed; + } + + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // On the client, this node looks like a bunch of literal nodes + @Override + public List> rewriteNodeForClient(Source client) { + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + CommandSender sender = commandAPIHandler.getPlatform().getCommandSenderFromCommandSource(client); + List literals = literalsCreator.createLiterals(sender); + + if (literals.isEmpty()) return List.of(); + + List> clientNodes = new ArrayList<>(literals.size()); + for (String literal : literals) { + LiteralCommandNode clientNode = new LiteralCommandNode<>( + literal, + getCommand(), getRequirement(), + getRedirect(), getRedirectModifier(), isFork() + ); + for (CommandNode child : getChildren()) { + clientNode.addChild(child); + } + clientNodes.add(clientNode); + } + + return clientNodes; + } + + // Implement CommandNode methods + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + // Read the input + int start = reader.getCursor(); + String literal = reader.readUnquotedString(); + + // Validate input + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + CommandSender client = commandAPIHandler.getPlatform().getCommandSenderFromCommandSource(contextBuilder.getSource()); + List literals = literalsCreator.createLiterals(client); + if (!literals.contains(literal)) { + reader.setCursor(start); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, literals); + } + + // Add node to list + ParsedArgument parsed = new ParsedArgument<>(start, reader.getCursor(), literal); + if (isListed) contextBuilder.withArgument(this.getName(), parsed); + contextBuilder.withNode(this, parsed.getRange()); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + CommandSender client = commandAPIHandler.getPlatform().getCommandSenderFromCommandSource(context.getSource()); + List literals = literalsCreator.createLiterals(client); + + String remaining = builder.getRemaining().toLowerCase(); + for (String literal : literals) { + if (literal.startsWith(remaining)) { + builder.suggest(literal); + } + } + return builder.buildFuture(); + } + + @Override + public boolean isValidInput(String input) { + // For ambiguity checking purposes, this node could accept any input + return true; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof DynamicMultiLiteralCommandNode other)) return false; + + if (this.isListed != other.isListed) return false; + if (!Objects.equals(this.literalsCreator, other.literalsCreator)) return false; + return super.equals(other); + } + + @Override + public int hashCode() { + int result = Objects.hash(this.isListed, this.literalsCreator); + result = 31 * result + super.hashCode(); + return result; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml index cbeb49f3db..ebb83f797b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml @@ -57,6 +57,12 @@ ${paper.version} provided + + io.papermc.paper + paper-mojangapi + ${paper.version} + provided + dev.jorel diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index 31ba71232d..78499e7186 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -182,9 +182,9 @@ public void onEnable() { public void onServerLoad(ServerLoadEvent event) { CommandAPI.stopCommandRegistration(); } - }, getConfiguration().getPlugin()); + }, plugin); - paper.registerReloadHandler(plugin); + paper.registerEvents(plugin); } /* diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java index 018940d568..cf96526ecd 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java @@ -1,8 +1,10 @@ package dev.jorel.commandapi; +import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.nms.NMS; import io.papermc.paper.event.server.ServerResourcesReloadedEvent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -16,11 +18,11 @@ import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; -public class PaperImplementations { +public class PaperImplementations implements Listener { private final boolean isPaperPresent; private final boolean isFoliaPresent; - private final NMS nmsInstance; + private final CommandAPIBukkit nmsInstance; private final Class feedbackForwardingCommandSender; /** @@ -31,7 +33,7 @@ public class PaperImplementations { * @param nmsInstance The instance of NMS */ @SuppressWarnings("unchecked") - public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, NMS nmsInstance) { + public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, CommandAPIBukkit nmsInstance) { this.isPaperPresent = isPaperPresent; this.isFoliaPresent = isFoliaPresent; this.nmsInstance = nmsInstance; @@ -47,40 +49,57 @@ public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, NMS< } /** - * Hooks into Paper's {@link ServerResourcesReloadedEvent} to detect if - * {@code /minecraft:reload} is called, and registers a reload handler that - * automatically calls the CommandAPI's internal datapack reloading function - * + * Registers paper-specific events (if paper is present), including: + *

      + *
    • {@link #onServerReloadResources(ServerResourcesReloadedEvent)}
    • + *
    • {@link #onCommandsSentToPlayer(AsyncPlayerSendCommandsEvent)}
    • + *
    + * * @param plugin the plugin that the CommandAPI is being used from */ - public void registerReloadHandler(Plugin plugin) { - if (isPaperPresent) { - Bukkit.getServer().getPluginManager().registerEvents(new Listener() { - @EventHandler - public void onServerReloadResources(ServerResourcesReloadedEvent event) { - // This event is called after Paper is done with everything command related - // which means we can put commands back - CommandAPIBukkit.get().getCommandRegistrationStrategy().preReloadDataPacks(); + public void registerEvents(Plugin plugin) { + if (!isPaperPresent) { + CommandAPI.logNormal("Did not hook into Paper events"); + return; + } + CommandAPI.logNormal("Hooking into Paper events"); + Bukkit.getServer().getPluginManager().registerEvents(this, plugin); + } - // Normally, the reloadDataPacks() method is responsible for updating commands for - // online players. If, however, datapacks aren't supposed to be reloaded upon /minecraft:reload - // we have to do this manually here. This won't have any effect on Spigot and Paper version prior to - // paper-1.20.6-65 - if (!CommandAPIBukkit.getConfiguration().shouldHookPaperReload()) { - for (Player player : Bukkit.getOnlinePlayers()) { - player.updateCommands(); - } - return; - } - CommandAPI.logNormal("/minecraft:reload detected. Reloading CommandAPI commands!"); - nmsInstance.reloadDataPacks(); - } + /** + * Automatically calls the CommandAPI's internal datapack + * reloading function when {@code /minecraft:reload} is called. + */ + @EventHandler + public void onServerReloadResources(ServerResourcesReloadedEvent event) { + // This event is called after Paper is done with everything command related + // which means we can put commands back + nmsInstance.getCommandRegistrationStrategy().preReloadDataPacks(); - }, plugin); - CommandAPI.logNormal("Hooked into Paper ServerResourcesReloadedEvent"); - } else { - CommandAPI.logNormal("Did not hook into Paper ServerResourcesReloadedEvent"); + // Normally, the reloadDataPacks() method is responsible for updating commands for + // online players. If, however, datapacks aren't supposed to be reloaded upon /minecraft:reload + // we have to do this manually here. This won't have any effect on Spigot and Paper version prior to + // paper-1.20.6-65 + if (!CommandAPIBukkit.getConfiguration().shouldHookPaperReload()) { + for (Player player : Bukkit.getOnlinePlayers()) { + player.updateCommands(); + } + return; } + CommandAPI.logNormal("/minecraft:reload detected. Reloading CommandAPI commands!"); + nmsInstance.reloadDataPacks(); + } + + @EventHandler + @SuppressWarnings("UnstableApiUsage") // This event is marked @Experimental, but it's been here since 1.15-ish + public void onCommandsSentToPlayer(AsyncPlayerSendCommandsEvent event) { + // This event fires twice, once async then sync + // We only want to do this once, and it is safe to run async + if (!event.isAsynchronous()) return; + + // Rewrite nodes to their client-side version when commands are sent to a client + Source source = nmsInstance.getBrigadierSourceFromCommandSender(event.getPlayer()); + DifferentClientNode.rewriteAllChildren(source, (RootCommandNode) event.getCommandNode()); } /** diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index 8775599765..3c0c0c898d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -4,7 +4,9 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import dev.jorel.commandapi.preprocessor.RequireField; +import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.SimpleCommandMap; @@ -284,6 +286,17 @@ public void registerCommandNode(LiteralCommandNode node, String namespac CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); RootCommandNode rootNode = brigadierDispatcher.getRoot(); + + if (!CommandAPIBukkit.get().getPaper().isPaperPresent()) { + // If Paper is not present, then we don't have the AsyncPlayerSendCommandsEvent, + // which allows us to rewrite nodes for a specific client. Best we can do on Spigot + // is rewrite the nodes now for a general client (represented by the console). This + // can still produce interesting server-client de-sync behavior that we want to use. + CommandSender console = Bukkit.getConsoleSender(); + Source source = CommandAPIBukkit.get().getBrigadierSourceFromCommandSender(console); + DifferentClientNode.rewriteAllChildren(source, node); + } + String name = node.getLiteral(); if (namespace.equals("minecraft")) { if (namespacesToFix.contains("minecraft:" + name)) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java new file mode 100644 index 0000000000..9d4d4e2298 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java @@ -0,0 +1,53 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.jorel.commandapi.executors.CommandArguments; +import org.bukkit.command.CommandSender; + +import java.util.List; + +public class DynamicMultiLiteralArgument extends Argument implements DynamicMultiLiteralArgumentCommon, CommandSender> { + // Setup information + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralArgument(String nodeName, LiteralsCreator literalsCreator) { + super(nodeName, null); + + this.literalsCreator = literalsCreator; + } + + @Override + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // Normal Argument stuff + @Override + public Class getPrimitiveType() { + return String.class; + } + + @Override + public CommandAPIArgumentType getArgumentType() { + return CommandAPIArgumentType.DYNAMIC_MULTI_LITERAL; + } + + @Override + public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { + return cmdCtx.getArgument(key, String.class); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // DynamicMultiLiteralArgumentCommon interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the DynamicMultiLiteralArgumentCommon // + // interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return DynamicMultiLiteralArgumentCommon.super.createArgumentBuilder(previousArguments, previousArgumentNames); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index cb5a44eb15..3697be7065 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -21,13 +21,16 @@ package dev.jorel.commandapi; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import dev.jorel.commandapi.arguments.*; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; import org.bukkit.plugin.InvalidPluginException; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -132,5 +135,49 @@ private JavaPlugin getAndValidatePlugin(String pluginName) { @Override public void onEnable() { CommandAPI.onEnable(); + + List tags = new ArrayList<>(); + tags.add("hello"); + tags.add("world"); + + new CommandTree("server") + .then( + new LiteralArgument("add").then( + new StringArgument("tag").executes(info -> { + String tag = info.args().getUnchecked("tag"); + + tags.add(tag); + }) + ) + ) + .then( + new LiteralArgument("tag").then( + new DynamicMultiLiteralArgument("tag", sender -> { + if (sender instanceof Player player) { + List addPlayer = new ArrayList<>(tags); + addPlayer.add(player.getName()); + return addPlayer; + } else { + return tags; + } + }).then(new IntegerArgument("extra").replaceSafeSuggestions(SafeSuggestions.suggest(1, 2, 3)) + .executes(info -> { + String tag = info.args().getUnchecked("tag"); + int extra = info.args().getUnchecked("extra"); + + info.sender().sendMessage(tag + " " + extra); + } + ) + )) + ) + .register(); + + new CommandAPICommand("updateCommands") + .executes(info -> { + for (Player player : Bukkit.getOnlinePlayers()) { + CommandAPI.updateRequirements(player); + } + }) + .register(); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml index 1cd51fb33c..8d7895baf0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml @@ -103,6 +103,14 @@ provided + + + io.papermc.paper + paper-mojangapi + 1.20.6-R0.1-SNAPSHOT + provided + + com.github.zafarkhaja diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index af6cac6e1d..aadd971b97 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -14,11 +14,14 @@ import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.proxy.ConsoleCommandSource; import dev.jorel.commandapi.arguments.Argument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import org.apache.logging.log4j.LogManager; import java.io.File; @@ -88,7 +91,15 @@ private static void setInternalConfig(InternalVelocityConfig internalVelocityCon @Override public void onEnable() { - // Nothing to do + // Register events + config.getServer().getEventManager().register(config.getPlugin(), this); + } + + @Subscribe + @SuppressWarnings("UnstableApiUsage") // This event is marked @Beta + public void onCommandsSentToPlayer(PlayerAvailableCommandsEvent event) { + // Rewrite nodes to their client-side version when commands are sent to a client + DifferentClientNode.rewriteAllChildren(event.getPlayer(), (RootCommandNode) event.getRootNode()); } @Override diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java new file mode 100644 index 0000000000..dc9c704570 --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java @@ -0,0 +1,53 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.velocitypowered.api.command.CommandSource; +import dev.jorel.commandapi.executors.CommandArguments; + +import java.util.List; + +public class DynamicMultiLiteralArgument extends Argument implements DynamicMultiLiteralArgumentCommon, CommandSource> { + // Setup information + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralArgument(String nodeName, LiteralsCreator literalsCreator) { + super(nodeName, null); + + this.literalsCreator = literalsCreator; + } + + @Override + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // Normal Argument stuff + @Override + public Class getPrimitiveType() { + return String.class; + } + + @Override + public CommandAPIArgumentType getArgumentType() { + return CommandAPIArgumentType.DYNAMIC_MULTI_LITERAL; + } + + @Override + public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { + return cmdCtx.getArgument(key, String.class); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // DynamicMultiLiteralArgumentCommon interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the DynamicMultiLiteralArgumentCommon // + // interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return DynamicMultiLiteralArgumentCommon.super.createArgumentBuilder(previousArguments, previousArgumentNames); + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index b6458cdb61..23da2fe04a 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -7,7 +7,10 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import dev.jorel.commandapi.arguments.*; +import net.kyori.adventure.text.Component; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; @@ -16,6 +19,8 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; /** @@ -73,6 +78,42 @@ public CommandAPIMain(ProxyServer server, Logger logger, @DataDirectory Path dat public void onProxyInitialization(ProxyInitializeEvent event) { // Enable CommandAPI.onEnable(); + + List tags = new ArrayList<>(); + tags.add("hello"); + tags.add("world"); + + new CommandTree("proxy") + .then( + new LiteralArgument("add").then( + new StringArgument("tag").executes(info -> { + String tag = info.args().getUnchecked("tag"); + + tags.add(tag); + }) + ) + ) + .then( + new LiteralArgument("tag").then( + new DynamicMultiLiteralArgument("tag", sender -> { + if (sender instanceof Player player) { + List addPlayer = new ArrayList<>(tags); + addPlayer.add(player.getUsername()); + return addPlayer; + } else { + return tags; + } + }).then(new IntegerArgument("extra").replaceSafeSuggestions(SafeSuggestions.suggest(1, 2, 3)) + .executes(info -> { + String tag = info.args().getUnchecked("tag"); + int extra = info.args().getUnchecked("extra"); + + info.sender().sendMessage(Component.text(tag + " " + extra)); + }) + ) + ) + ) + .register(); } @Subscribe From 9007237dc086efc9f2ac78c4d67eea00128c702d Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Thu, 4 Jul 2024 12:26:48 -0400 Subject: [PATCH 32/42] Add FlagsArgument to Velocity (and other changes) This was not made trivial by DifferentClientNode DifferentClientNode changes: - DifferentClientNode now an interface - Split into Argument and Literal subclasses - Node rewriting happens in two stages, `onRegister = true`, then `onRegister = false` - Each platform uses this differently b/c they're all special - Spigot/Paper run `onRegister = true` when registering to get rid of FlagsArgument loops, which otherwise cause a StackOverflow - Spigot runs `onRegister = false` when registering since it doesn't have a command send event (and so DynamicMultiLiteral can't display suggestions correctly) - Velocity runs `onRegister = true` during the command send event because `CommandNode.literals` doesn't exist FlagsArgument changes: - Generalized `executorCreator` from AbstractArgument node building with TerminalNodeModifier FunctionalInterface - Allows redirecting and wrapping nodes without reflection - FlagsArgumentEndingNode extends DifferentClientNode - Rewriting turns server-side children loop into redirects for the client (https://github.com/Mojang/brigadier/issues/137) - Reference to root for setting the redirect is sneakily stored in HiddenRedirect command node, which gets properly updated when Velocity copies a node tree - Children loops are built by FlagsArgumentRootNode by calling `addChild`, rather than sharing Map instances with reflection, since that could de-sync the `hasLiterals` boolean on Velocity - FlagsArgument now only uses reflection indirectly through DifferentClientNode :O CommandAPIHandler#writeDispatcherToFile changes: - All platforms now use custom algorithm for serializing CommandNode to JSON - Does not StackOverflow if children loop - References duplicate instances of nodes in a tree by their shortest path - Serializes orphaned redirects - Supports serializing non-Brigadier CommandNode classes through NodeTypeSerializer - CommandAPIPlatform#createDispatcherFile replaced by #getArgumentTypeProperties - On Bukkit, SafeStaticMethodHandle used to access private static nms method ArgumentUtils#serializeArgumentToJson - On Velocity, ArgumentTypeSerializer added to extract ArgumentType properties - Slight updates to tests that verify dispatcher JSON Other changes: - Forgot to insert `<>` on PaperImplementations constructors in last commit - Removed deprecated `MultiLiteralArgument(String[] literals)` constructor because the null node name caused a NPE when hashing its command nodes - Tweaked DynamicMultiLiteralCommandNode to make it work when wrapped inside a FlagsArgumentEndingNode - Removed warning when using empty namespace on Velocity - Removed warning when `CommandNode.literals` field not found on Velocity - Switched commandapi-velocity-core to depend on Velocity's custom Brigadier fork TODO: - DynamicMultiLiteral still needs tests - Test NMS ArgumentUtils reflection on relevant Bukkit versions - Remove test commands - Implement custom NodeTypeSerializers --- .../commandapi/AbstractArgumentTree.java | 17 +- .../commandapi/AbstractCommandAPICommand.java | 18 +- .../jorel/commandapi/CommandAPIHandler.java | 198 +++++++++----- .../jorel/commandapi/CommandAPIPlatform.java | 15 +- .../commandapi/SafeStaticMethodHandle.java | 55 ++++ .../dev/jorel/commandapi/SafeVarHandle.java | 6 +- .../arguments/AbstractArgument.java | 111 ++++---- .../arguments/FlagsArgumentCommon.java | 162 +++++------- .../jorel/commandapi/arguments/Literal.java | 11 +- .../commandapi/arguments/MultiLiteral.java | 15 +- .../commandnodes/DifferentClientNode.java | 247 +++++++++++------- .../DynamicMultiLiteralCommandNode.java | 43 ++- .../commandnodes/FlagsArgumentEndingNode.java | 198 ++++++++++++-- .../commandnodes/FlagsArgumentRootNode.java | 90 ++++--- .../commandnodes/NodeTypeSerializer.java | 70 +++++ .../kotlindsl/CommandAPICommandDSL.kt | 2 - .../commandapi/kotlindsl/CommandTreeDSL.kt | 4 - .../kotlindsl/CommandAPICommandDSL.kt | 2 - .../commandapi/kotlindsl/CommandTreeDSL.kt | 4 - .../jorel/commandapi/CommandAPIBukkit.java | 20 +- .../commandapi/PaperImplementations.java | 2 +- .../java/dev/jorel/commandapi/Schedulers.java | 4 +- .../commandapi/SpigotCommandRegistration.java | 2 +- .../commandapi/arguments/CustomArgument.java | 15 +- .../commandapi/arguments/FlagsArgument.java | 6 +- .../commandapi/arguments/LiteralArgument.java | 6 +- .../arguments/MultiLiteralArgument.java | 16 +- .../executors/BukkitTypedExecutor.java | 2 +- .../dev/jorel/commandapi/nms/NMS_1_16_R3.java | 34 +-- .../jorel/commandapi/nms/NMS_1_17_Common.java | 18 +- .../dev/jorel/commandapi/nms/NMS_1_18_R2.java | 16 +- .../dev/jorel/commandapi/nms/NMS_1_18_R1.java | 17 +- .../jorel/commandapi/nms/NMS_1_19_Common.java | 17 +- .../jorel/commandapi/nms/NMS_1_19_3_R2.java | 16 +- .../jorel/commandapi/nms/NMS_1_19_4_R3.java | 16 +- .../dev/jorel/commandapi/nms/NMS_1_20_R2.java | 24 +- .../dev/jorel/commandapi/nms/NMS_1_20_R3.java | 17 +- .../dev/jorel/commandapi/nms/NMS_1_20_R4.java | 17 +- .../dev/jorel/commandapi/nms/NMS_1_20_R1.java | 16 +- .../dev/jorel/commandapi/nms/NMS_1_21_R1.java | 18 +- .../dev/jorel/commandapi/nms/NMS_Common.java | 9 +- .../dev/jorel/commandapi/CommandAPIMain.java | 76 +++++- .../commandapi-bukkit-kotlin-test/pom.xml | 8 + .../dev/jorel/commandapi/test/MockNMS.java | 13 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../dev/jorel/commandapi/test/MockNMS.java | 12 +- .../test/CommandUnregisterTests.java | 8 +- .../jorel/commandapi/test/OnEnableTests.java | 29 +- .../test/arguments/ArgumentFlagsTests.java | 38 ++- .../arguments/ArgumentMultiLiteralTests.java | 245 ++++++++--------- .../test/arguments/ArgumentTests.java | 2 +- .../commandapi-velocity-core/pom.xml | 7 +- .../jorel/commandapi/CommandAPIVelocity.java | 79 ++---- .../commandapi/arguments/FlagsArgument.java | 77 ++++++ .../commandapi/arguments/LiteralArgument.java | 7 +- .../arguments/MultiLiteralArgument.java | 16 +- .../serializer/ArgumentTypeSerializer.java | 91 +++++++ .../NumberArgumentTypeSerializer.java | 43 +++ .../dev/jorel/commandapi/CommandAPIMain.java | 35 ++- 65 files changed, 1533 insertions(+), 913 deletions(-) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/SafeStaticMethodHandle.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java create mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java create mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/ArgumentTypeSerializer.java create mode 100644 commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/NumberArgumentTypeSerializer.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 810b23b218..2c58117cb9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -3,6 +3,7 @@ import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; @@ -110,9 +111,21 @@ public void buildBrigadierNode( throw new MissingCommandExecutorException(previousArguments, argument); } + // Create executor, if it exists + TerminalNodeModifier terminalNodeModifier = (builder, args) -> { + if (executor.hasAnyExecutors()) { + builder.executes(handler.generateBrigadierCommand(args, executor)); + } + + return builder.build(); + }; + // Create node for this argument - previousNodeInformation = argument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, - executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null); + previousNodeInformation = argument.addArgumentNodes( + previousNodeInformation, + previousArguments, previousArgumentNames, + terminalNodeModifier + ); // Collect children into our own list List> childrenNodeInformation = new ArrayList<>(); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 8f2cfb12fd..5d72517542 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -20,17 +20,16 @@ *******************************************************************************/ package dev.jorel.commandapi; -import com.mojang.brigadier.Command; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import dev.jorel.commandapi.exceptions.OptionalArgumentException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.function.Function; /** * A builder used to create commands to be registered by the CommandAPI. @@ -261,16 +260,25 @@ protected List> createArgumentNod // Note that `executor#hasAnyExecutors` must be true here // If not, then `checkPreconditions` would have thrown a `MissingCommandExecutorException` - Function, Command> executorCreator = args -> handler.generateBrigadierCommand(args, executor); + TerminalNodeModifier executorCreator = (builder, args) -> + builder.executes(handler.generateBrigadierCommand(args, executor)).build(); // Stack required arguments // Only the last required argument is executable - previousNodeInformation = AbstractArgument.stackArguments(requiredArguments, previousNodeInformation, previousArguments, previousArgumentNames, executorCreator); + previousNodeInformation = AbstractArgument.stackArguments( + requiredArguments, previousNodeInformation, + previousArguments, previousArgumentNames, + executorCreator + ); // Add optional arguments for (Argument argument : optionalArguments) { // All optional arguments are executable - previousNodeInformation = argument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, executorCreator); + previousNodeInformation = argument.addArgumentNodes( + previousNodeInformation, + previousArguments, previousArgumentNames, + executorCreator + ); } // Create registered nodes now that all children are generated diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index abc1544737..603ea35696 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -20,36 +20,41 @@ *******************************************************************************/ package dev.jorel.commandapi; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.regex.Pattern; - +import com.google.common.io.Files; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; -import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.ArgumentSuggestions; +import dev.jorel.commandapi.arguments.CustomProvidedArgument; +import dev.jorel.commandapi.commandnodes.NodeTypeSerializer; import dev.jorel.commandapi.exceptions.CommandConflictException; import dev.jorel.commandapi.executors.CommandArguments; import dev.jorel.commandapi.executors.ExecutionInfo; import dev.jorel.commandapi.preprocessor.RequireField; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.regex.Pattern; /** * The "brains" behind the CommandAPI. @@ -74,19 +79,27 @@ public class CommandAPIHandler FIELDS; private static final SafeVarHandle, Map>> commandContextArguments; - // VarHandle seems incapable of setting final fields, so we have to use Field here - private static final Field commandNodeChildren; - private static final Field commandNodeLiterals; - private static final Field commandNodeArguments; + // I think these maps need to be raw since the parameter Source is inaccessible, but we want to cast to that + private static final SafeVarHandle, Map> commandNodeChildren; + private static final SafeVarHandle, Map> commandNodeLiterals; + private static final SafeVarHandle, Map> commandNodeArguments; // Compute all var handles all in one go so we don't do this during main server runtime static { FIELDS = new HashMap<>(); - commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", "arguments", Map.class); - commandNodeChildren = CommandAPIHandler.getField(CommandNode.class, "children"); - commandNodeLiterals = CommandAPIHandler.getField(CommandNode.class, "literals"); - commandNodeArguments = CommandAPIHandler.getField(CommandNode.class, "arguments"); + commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", Map.class); + commandNodeChildren = SafeVarHandle.ofOrNull(CommandNode.class, "children", Map.class); + commandNodeArguments = SafeVarHandle.ofOrNull(CommandNode.class, "arguments", Map.class); + + SafeVarHandle, Map> literals; + try { + literals = SafeVarHandle.of(CommandNode.class, "literals", "literals", Map.class); + } catch (ReflectiveOperationException ignored) { + // CommandNode.literals does not exist on Velocity, so we expect this to happen then + literals = null; + } + commandNodeLiterals = literals; } final CommandAPIPlatform platform; @@ -416,13 +429,107 @@ public void writeDispatcherToFile() { try { // Write the dispatcher json - platform.createDispatcherFile(file, platform.getBrigadierDispatcher()); + writeDispatcherToFile(file, platform.getBrigadierDispatcher()); } catch (IOException e) { CommandAPI.logError("Failed to write command registration info to " + file.getName() + ": " + e.getMessage()); } } } + /** + * Creates a JSON file that describes the hierarchical structure of the commands + * that have been registered by the server. + * + * @param file The JSON file to write to + * @param dispatcher The Brigadier CommandDispatcher + * @throws IOException When the file fails to be written to + */ + public void writeDispatcherToFile(File file, CommandDispatcher dispatcher) throws IOException { + Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() + .toJson(serializeNodeToJson(dispatcher.getRoot()))); + } + + private record Node(CommandNode commandNode, Consumer resultConsumer, String[] path) { + } + + public JsonObject serializeNodeToJson(CommandNode rootNode) { + // We preform a breadth-first traversal of the node tree to find the shortest path to each node. + // We prefer that the first path found would not go through a redirect, so nodes found from redirects + // are put in their own lower priority queue. It may be necessary to fully process these nodes though + // in case a node is only accessible via redirects, which may happen for example when the main alias + // of a command is removed. + Queue> nodesToProcess = new LinkedList<>(); + Queue> redirectsToProcess = new LinkedList<>(); + Map, JsonArray> shortestPath = new IdentityHashMap<>(); + + // Extract serialization of the rootNode as our result + JsonObject resultHolder = new JsonObject(); + redirectsToProcess.offer(new Node<>(rootNode, result -> resultHolder.add("result", result), new String[0])); + + Node node; + while ((node = redirectsToProcess.poll()) != null) { + nodesToProcess.offer(node); + + while ((node = nodesToProcess.poll()) != null) { + CommandNode commandNode = node.commandNode; + + // Add information to parent + JsonArray path = shortestPath.get(commandNode); + if (path != null) { + // Node has already appeared earlier in the traversal + node.resultConsumer.accept(path); + continue; + } + // This is the first time finding this node + path = new JsonArray(); + for (String step : node.path) { + path.add(step); + } + shortestPath.put(commandNode, path); + + JsonObject output = new JsonObject(); + node.resultConsumer.accept(output); + + // Node type + NodeTypeSerializer.addTypeInformation(output, commandNode); + + // Children + Collection> children = commandNode.getChildren(); + if (!children.isEmpty()) { + JsonObject childrenHolder = new JsonObject(); + output.add("children", childrenHolder); + + for (CommandNode child : children) { + String name = child.getName(); + + String[] newPath = new String[node.path.length + 1]; + System.arraycopy(node.path, 0, newPath, 0, node.path.length); + newPath[node.path.length] = name; + + nodesToProcess.offer(new Node<>(child, result -> childrenHolder.add(name, result), newPath)); + } + } + + // Command + if (commandNode.getCommand() != null) { + output.addProperty("executable", true); + } + + // Redirect + CommandNode redirect = commandNode.getRedirect(); + if (redirect != null) { + String[] newPath = new String[node.path.length + 1]; + System.arraycopy(node.path, 0, newPath, 0, node.path.length); + newPath[node.path.length] = "redirect " + redirect.getName(); + + redirectsToProcess.offer(new Node<>(redirect, result -> output.add("redirect", result), newPath)); + } + } + } + + return resultHolder.getAsJsonObject("result"); + } + public LiteralCommandNode namespaceNode(LiteralCommandNode original, String namespace) { // Adapted from a section of `CraftServer#syncCommands` LiteralCommandNode clone = new LiteralCommandNode<>( @@ -441,51 +548,16 @@ public LiteralCommandNode namespaceNode(LiteralCommandNode origi } public static Map> getCommandNodeChildren(CommandNode target) { - try { - return (Map>) commandNodeChildren.get(target); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } - } - - public static void setCommandNodeChildren(CommandNode target, Map> children) { - try { - commandNodeChildren.set(target, children); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } + return (Map>) commandNodeChildren.get(target); } public static Map> getCommandNodeLiterals(CommandNode target) { - try { - return (Map>) commandNodeLiterals.get(target); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } - } - - public static void setCommandNodeLiterals(CommandNode target, Map> literals) { - try { - commandNodeLiterals.set(target, literals); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } + if (commandNodeLiterals == null) return null; + return (Map>) commandNodeLiterals.get(target); } public static Map> getCommandNodeArguments(CommandNode target) { - try { - return (Map>) commandNodeArguments.get(target); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } - } - - public static void setCommandNodeArguments(CommandNode target, Map> arguments) { - try { - commandNodeArguments.set(target, arguments); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } + return (Map>) commandNodeArguments.get(target); } //////////////////////////////// diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index 7b4e4704d2..2096b59855 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -1,15 +1,16 @@ package dev.jorel.commandapi; +import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; -import java.io.File; -import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.function.Predicate; /** @@ -127,15 +128,7 @@ default LiteralCommandNode registerCommandNode(LiteralArgumentBuilder getBrigadierDispatcher(); - /** - * Creates a JSON file that describes the hierarchical structure of the commands - * that have been registered by the server. - * - * @param file The JSON file to write to - * @param dispatcher The Brigadier CommandDispatcher - * @throws IOException When the file fails to be written to - */ - public abstract void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException; + public abstract Optional getArgumentTypeProperties(ArgumentType type); /** * @return A new default Logger meant for the CommandAPI to use diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/SafeStaticMethodHandle.java b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeStaticMethodHandle.java new file mode 100644 index 0000000000..01eb1d0ee8 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeStaticMethodHandle.java @@ -0,0 +1,55 @@ +package dev.jorel.commandapi; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +public class SafeStaticMethodHandle { + private MethodHandle handle; + + private SafeStaticMethodHandle(MethodHandle handle) { + this.handle = handle; + } + + private static SafeStaticMethodHandle of( + Class classType, + String methodName, String mojangMappedMethodName, + Class returnType, + Class... parameterTypes + ) throws ReflectiveOperationException { + return new SafeStaticMethodHandle<>( + MethodHandles.privateLookupIn(classType, MethodHandles.lookup()) + .findStatic( + classType, SafeVarHandle.USING_MOJANG_MAPPINGS ? mojangMappedMethodName : methodName, + MethodType.methodType(returnType, parameterTypes) + ) + ); + } + + public static SafeStaticMethodHandle ofOrNull( + Class classType, + String methodName, String mojangMappedMethodName, + Class returnType, + Class... parameterTypes + ) { + try { + return of(classType, methodName, mojangMappedMethodName, returnType, parameterTypes); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + return null; + } + } + + public ReturnType invoke(Object... args) throws Throwable { + return (ReturnType) handle.invokeWithArguments(args); + } + + public ReturnType invokeOrNull(Object... args) { + try { + return invoke(args); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java index 3362dfe6a7..c9ce56639c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java @@ -21,7 +21,7 @@ private SafeVarHandle(VarHandle handle) { this.handle = handle; } - private static SafeVarHandle of(Class classType, String fieldName, String mojangMappedFieldName, Class fieldType) + public static SafeVarHandle of(Class classType, String fieldName, String mojangMappedFieldName, Class fieldType) throws ReflectiveOperationException { return new SafeVarHandle<>( MethodHandles.privateLookupIn(classType, MethodHandles.lookup()).findVarHandle(classType, USING_MOJANG_MAPPINGS ? mojangMappedFieldName : fieldName, fieldType)); @@ -36,6 +36,10 @@ public static SafeVarHandle ofOrNull(Class SafeVarHandle ofOrNull(Class classType, String fieldName, Class fieldType) { + return ofOrNull(classType, fieldName, fieldName, fieldType); + } + public FieldType get(Type instance) { return (FieldType) handle.get(instance); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 88e46d1c80..61e6c00cd3 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -20,7 +20,6 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; @@ -42,7 +41,6 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.function.Function; import java.util.function.Predicate; /** @@ -333,45 +331,71 @@ public String toString() { * A record of the information needed to stack one argument onto another. * * @param lastCommandNodes A {@link List} of {@link CommandNode}s that were the last nodes in the structure of the previous argument. - * @param childrenConsumer A callback that accepts a {@link List} of all the {@link RegisteredCommand.Node}s that represent the nodes - * added as children to the previous argument. + * @param childrenConsumer See {@link ChildrenConsumer#createNodeWithChildren(List)}. */ public static record NodeInformation(List> lastCommandNodes, ChildrenConsumer childrenConsumer) { } + /** + * A callback function used by {@link NodeInformation}. See {@link #createNodeWithChildren(List)}. + */ @FunctionalInterface public static interface ChildrenConsumer { + /** + * Accepts a {@link List} of all the {@link RegisteredCommand.Node}s that + * represent the {@link CommandNode}s added as children to the previous argument. + * @param children The children {@link RegisteredCommand.Node}s + */ public void createNodeWithChildren(List> children); } + /** + * A callback function used by {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}. + * See {@link #finishTerminalNode(ArgumentBuilder, List)}. + */ + @FunctionalInterface + public static interface TerminalNodeModifier + /// @endcond + , CommandSender, Source> { + /** + * Applys any necessary changes to the argument builder of a terminal node. + * This can simply build the builder if no modifications are necessary. + * + * @param builder The {@link ArgumentBuilder} to finish building. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @return The built {@link CommandNode}. + */ + public CommandNode finishTerminalNode(ArgumentBuilder builder, List previousArguments); + } + /** * Builds the Brigadier {@link CommandNode} structure for this argument. Note that the Brigadier node structure may * contain multiple nodes, for example if {@link #combineWith(AbstractArgument[])} was called for this argument to * merge it with other arguments. *

    - * This process is broken up into 4 other methods for the convenience of defining special node structures for specific - * arguments. Those methods are: + * This process is broken up into 4 other methods for the convenience of defining special node structures for + * specific arguments. Those methods are: *

      *
    • {@link #checkPreconditions(NodeInformation, List, List)}
    • *
    • {@link #createArgumentBuilder(List, List)}
    • - *
    • {@link #finishBuildingNode(ArgumentBuilder, List, Function)}
    • - *
    • {@link #linkNode(NodeInformation, CommandNode, List, List, Function)}
    • + *
    • {@link #finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}
    • + *
    • {@link #linkNode(NodeInformation, CommandNode, List, List, TerminalNodeModifier)}
    • *
    * * @param previousNodeInformation The {@link NodeInformation} of the argument this argument is being added to. * @param previousArguments A List of CommandAPI arguments that came before this argument. * @param previousArgumentNames A List of Strings containing the node names that came before this argument. - * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an - * appropriate Brigadier {@link Command} which should be applied at the end of - * the node structure. This parameter can be null to indicate that this argument - * should not be executable. + * @param terminalNodeModifier A {@link TerminalNodeModifier} that will be applied to the final nodes in the + * built node structure. * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier node structure for this argument. */ public NodeInformation addArgumentNodes( NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, - Function, Command> terminalExecutorCreator + TerminalNodeModifier terminalNodeModifier ) { // Check preconditions checkPreconditions(previousNodeInformation, previousArguments, previousArgumentNames); @@ -380,10 +404,10 @@ public NodeInformation addArgumentNodes( ArgumentBuilder rootBuilder = createArgumentBuilder(previousArguments, previousArgumentNames); // Finish building node - CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalExecutorCreator); + CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalNodeModifier); // Link node to previous - previousNodeInformation = linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + previousNodeInformation = linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalNodeModifier); // Return last nodes return previousNodeInformation; @@ -422,9 +446,9 @@ public void checkPreconditions( // Create node and add suggestions // Note: I would like to combine these `builder.suggests(...)` calls, but they are actually unrelated - // methods since UnnamedRequiredArgumentBuilder and PreviewableArgumentBuilder do not extend RequiredArgumentBuilder - // (see those classes for why). If this has been fixed and they do extend RequiredArgumentBuilder, please simplify - // this if statement, like what Literal#createArgumentBuilder does. + // methods since UnnamedRequiredArgumentBuilder and PreviewableArgumentBuilder do not extend + // RequiredArgumentBuilder (see those classes for why). If this has been fixed and they do extend + // RequiredArgumentBuilder, please simplify this if statement, like what Literal#createArgumentBuilder does. SuggestionProvider suggestions = handler.generateBrigadierSuggestions(previousArguments, (Argument) this); ArgumentBuilder rootBuilder; if (this instanceof Previewable previewable) { @@ -460,24 +484,25 @@ public void checkPreconditions( /** * Finishes building the Brigadier {@link ArgumentBuilder} representing this argument. * - * @param rootBuilder The {@link ArgumentBuilder} to finish building. - * @param previousArguments A List of CommandAPI arguments that came before this argument. - * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an - * appropriate Brigadier {@link Command} which should be applied at the end - * of the node structure. This parameter can be null to indicate that this - * argument should not be executable. - * @param The Brigadier Source object for running commands. + * @param rootBuilder The {@link ArgumentBuilder} to finish building. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @param terminalNodeModifier A {@link TerminalNodeModifier} that will be applied to the final nodes in the + * built node structure. + * @param The Brigadier Source object for running commands. * @return The {@link CommandNode} representing this argument created by building the given {@link ArgumentBuilder}. */ - public CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, Function, Command> terminalExecutorCreator) { + public CommandNode finishBuildingNode( + ArgumentBuilder rootBuilder, List previousArguments, + TerminalNodeModifier terminalNodeModifier + ) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); // Add permission and requirements rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); - // Add the given executor if we are the last node - if (combinedArguments.isEmpty() && terminalExecutorCreator != null) { - rootBuilder.executes(terminalExecutorCreator.apply(previousArguments)); + // Apply the terminal modifier if we are the last node + if (combinedArguments.isEmpty()) { + return terminalNodeModifier.finishTerminalNode(rootBuilder, previousArguments); } return rootBuilder.build(); @@ -490,10 +515,8 @@ public CommandNode finishBuildingNode(ArgumentBuilder The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing this Argument. Note * that this is not necessarily the {@code rootNode} for this argument, since the Brigadier node structure may @@ -503,7 +526,7 @@ public CommandNode finishBuildingNode(ArgumentBuilder NodeInformation linkNode( NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, - Function, Command> terminalExecutorCreator + TerminalNodeModifier terminalNodeModifier ) { // Add rootNode to the previous nodes for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { @@ -518,7 +541,7 @@ public NodeInformation linkNode( children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "<" + nodeName + ">", - combinedArguments.isEmpty() && terminalExecutorCreator != null, + rootNode.getCommand() != null, permission, requirements, children ) @@ -526,7 +549,7 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", ); // Stack on combined arguments and return last nodes - return stackArguments(combinedArguments, nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); + return stackArguments(combinedArguments, nodeInformation, previousArguments, previousArgumentNames, terminalNodeModifier); } /** @@ -537,10 +560,8 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", * @param previousNodeInformation The {@link NodeInformation} of the argument this stack is being added to. * @param previousArguments A List of CommandAPI arguments that came before the {@code argumentsToStack}. * @param previousArgumentNames A List of Strings containing the node names that came before the {@code argumentsToStack}. - * @param terminalExecutorCreator A function that transforms the list of {@code previousArguments} into an - * appropriate Brigadier {@link Command} which should be applied to the last - * stacked argument of the node structure. This parameter can be null to indicate - * that the argument stack should not be executable. + * @param terminalNodeModifier A {@link TerminalNodeModifier} that will be applied to the final nodes in the + * built node structure. This can be null to indicate no changes are necessary. * @param The implementation of AbstractArgument being used. * @param The class for running platform commands. * @param The Brigadier Source object for running commands. @@ -549,15 +570,17 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", public static , CommandSender, Source> NodeInformation stackArguments( List argumentsToStack, NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, - Function, Command> terminalExecutorCreator + TerminalNodeModifier terminalNodeModifier ) { int lastIndex = argumentsToStack.size() - 1; for (int i = 0; i < argumentsToStack.size(); i++) { Argument subArgument = argumentsToStack.get(i); - previousNodeInformation = subArgument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, - // Only apply the `terminalExecutor` to the last argument - i == lastIndex ? terminalExecutorCreator : null); + previousNodeInformation = subArgument.addArgumentNodes( + previousNodeInformation, previousArguments, previousArgumentNames, + // Only apply the terminal modifier to the last argument + i == lastIndex ? terminalNodeModifier : (builder, args) -> builder.build() + ); } // Return information diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java index ef94b0e907..2963f3f0a6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -1,25 +1,22 @@ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; -import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; import dev.jorel.commandapi.commandnodes.FlagsArgumentEndingNode; import dev.jorel.commandapi.commandnodes.FlagsArgumentRootNode; import dev.jorel.commandapi.executors.CommandArguments; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Function; +import java.util.ArrayList; +import java.util.List; import java.util.function.Predicate; public interface FlagsArgumentCommon void checkPreconditions( - NodeInformation previousNodes, List previousArguments, List previousArgumentNames - ); + void checkPreconditions(NodeInformation previousNodes, List previousArguments, List previousArgumentNames); /** - * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, Function)}. + * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}. */ - CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, Function, Command> terminalExecutorCreator); + CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, TerminalNodeModifier terminalNodeModifier); ///////////////////////////////////////////////////////////////////////////////////////////////////// // OVERRIDING METHODS // @@ -107,7 +102,7 @@ default List parseArgument(CommandContext cmd } /** - * Overrides {@link AbstractArgument#addArgumentNodes(NodeInformation, List, List, Function)}. + * Overrides {@link AbstractArgument#addArgumentNodes(NodeInformation, List, List, TerminalNodeModifier)}. *

    * A FlagsArgument works completely differently from a typical argument, so we need to completely * override the usual logic. @@ -115,7 +110,7 @@ default List parseArgument(CommandContext cmd default NodeInformation addArgumentNodes( NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, - Function, Command> terminalExecutorCreator + TerminalNodeModifier terminalNodeModifier ) { // Typical preconditions still apply checkPreconditions(previousNodeInformation, previousArguments, previousArgumentNames); @@ -127,31 +122,39 @@ default NodeInformation addArgumentNodes( // Create root node, add to previous LiteralArgumentBuilder rootBuilder = LiteralArgumentBuilder.literal(nodeName); - finishBuildingNode(rootBuilder, previousArguments, null); - FlagsArgumentRootNode rootNode = new FlagsArgumentRootNode<>(rootBuilder.build()); + finishBuildingNode(rootBuilder, previousArguments, (builder, args) -> builder.build()); + FlagsArgumentRootNode rootNode = new FlagsArgumentRootNode<>(rootBuilder.build()); - for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { + for (CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { previousNode.addChild(rootNode); } // Setup looping branches - boolean loopingBranchesExecutable = getTerminalBranches().isEmpty(); - Function, Command> loopingBranchExecutor = loopingBranchesExecutable ? terminalExecutorCreator : null; - - for(List loopingBranch : getLoopingBranches()) { - setupBranch(loopingBranch, rootNode, previousArguments, previousArgumentNames, loopingBranchExecutor, rootNode::makeChildrenLoopBack); + boolean loopingBranchesTerminal = getTerminalBranches().isEmpty(); + TerminalNodeModifier loopingBranchModifier = + loopingBranchesTerminal ? terminalNodeModifier : (builder, args) -> builder.build(); + + for (List loopingBranch : getLoopingBranches()) { + setupBranch( + loopingBranch, rootNode, + previousArguments, previousArgumentNames, + loopingBranchModifier, true + ); } // Setup terminal branches - boolean terminalBranchesExecutable = getCombinedArguments().isEmpty(); - Function, Command> terminalBranchExecutor = terminalBranchesExecutable ? terminalExecutorCreator : null; + boolean terminalBranchesTerminal = getCombinedArguments().isEmpty(); + TerminalNodeModifier terminalBranchModifier = + terminalBranchesTerminal ? terminalNodeModifier : (builder, args) -> builder.build(); // The last nodes here will be our final nodes List> newNodes = new ArrayList<>(); - for(List terminalBranch : getTerminalBranches()) { - newNodes.addAll( - setupBranch(terminalBranch, rootNode, previousArguments, previousArgumentNames, terminalBranchExecutor, this::wrapTerminalBranchNodes) - ); + for (List terminalBranch : getTerminalBranches()) { + newNodes.addAll(setupBranch( + terminalBranch, rootNode, + previousArguments, previousArgumentNames, + terminalBranchModifier, false + )); } // Create information for this node @@ -161,7 +164,7 @@ default NodeInformation addArgumentNodes( children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "<" + nodeName + ">", - loopingBranchesExecutable || terminalBranchesExecutable, + loopingBranchesTerminal || terminalBranchesTerminal, getArgumentPermission(), getRequirements(), children ) @@ -169,88 +172,55 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", ); // Stack on combined arguments and return last nodes - return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); + return AbstractArgument.stackArguments( + getCombinedArguments(), nodeInformation, + previousArguments, previousArgumentNames, + terminalNodeModifier + ); } - private static , CommandSender, Source> List> setupBranch( - List branchArguments, CommandNode rootNode, + private List> setupBranch( + List branchArguments, FlagsArgumentRootNode rootNode, List previousArguments, List previousArgumentNames, - Function, Command> terminalExecutorCreator, - BiConsumer>, List> secondLastNodeProcessor + TerminalNodeModifier terminalNodeModifier, boolean loop ) { - // Make clones of our lists to treat this branch independently - List branchPreviousArguments = new ArrayList<>(previousArguments); - List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); - RootCommandNode branchRoot = new RootCommandNode<>(); NodeInformation branchNodeInformation = new NodeInformation<>(List.of(branchRoot), null); // Stack branch nodes - branchNodeInformation = AbstractArgument.stackArguments(branchArguments, branchNodeInformation, branchPreviousArguments, branchPreviousArgumentNames, terminalExecutorCreator); - - // Find second-to-last nodes so their children can be modified - // Unfortunately, we can't get this while stacking since arguments may (and may not) unpack to multiple layers - Collection> currentNodes = branchRoot.getChildren(); - Collection> lastNodes = List.of(branchRoot); - Collection> secondLastNodes = null; - while (!currentNodes.isEmpty()) { - secondLastNodes = lastNodes; - lastNodes = currentNodes; - currentNodes = new HashSet<>(); - - for (CommandNode node : lastNodes) { - currentNodes.addAll(node.getChildren()); - } - } + String nodeName = getNodeName(); + branchNodeInformation = AbstractArgument.stackArguments( + branchArguments, branchNodeInformation, + // Make clones of our lists to treat this branch independently + new ArrayList<>(previousArguments), new ArrayList<>(previousArgumentNames), + // Wrap terminal nodes into `FlagsArgumentEndingNode` to extract flag values + (builder, branchPreviousArguments) -> { + if (loop) { + // Redirect node to flag root + builder.redirect(rootNode); + } - // Modify the children of the secondLastNodes - secondLastNodeProcessor.accept(secondLastNodes, branchPreviousArguments); + // Finish building the regular node + CommandNode rawEnd = terminalNodeModifier.finishTerminalNode(builder, branchPreviousArguments); - // Copy branch nodes directly to the root node (branchRoot's maps may have been intentionally de-synced) - CommandAPIHandler.getCommandNodeChildren(rootNode).putAll(CommandAPIHandler.getCommandNodeChildren(branchRoot)); - CommandAPIHandler.getCommandNodeLiterals(rootNode).putAll(CommandAPIHandler.getCommandNodeLiterals(branchRoot)); - CommandAPIHandler.getCommandNodeArguments(rootNode).putAll(CommandAPIHandler.getCommandNodeArguments(branchRoot)); + // Wrap node + CommandNode flagEnd = FlagsArgumentEndingNode.wrapNode(rawEnd, nodeName, branchPreviousArguments); - // Return the last nodes in the tree - return branchNodeInformation.lastCommandNodes(); - } - - private void wrapTerminalBranchNodes( - Collection> secondLastNodes, List branchPreviousArguments - ) { - String nodeName = getNodeName(); - - // Wrap terminating nodes to extract flag values - for(CommandNode node : secondLastNodes) { - // Nodes in the `children` and `arguments`/`literals` maps need to be wrapped and substituted - Map> children = CommandAPIHandler.getCommandNodeChildren(node); - Map> literals = CommandAPIHandler.getCommandNodeLiterals(node); - Map> arguments = CommandAPIHandler.getCommandNodeArguments(node); - - for (CommandNode child : node.getChildren()) { - CommandNode finalWrappedNode; - if (child instanceof LiteralCommandNode literalNode) { - LiteralCommandNode wrappedNode = - FlagsArgumentEndingNode.wrapNode(literalNode, nodeName, branchPreviousArguments); - - literals.put(literalNode.getName(), wrappedNode); - finalWrappedNode = wrappedNode; - } else if(child instanceof ArgumentCommandNode argumentNode) { - ArgumentCommandNode wrappedNode = - FlagsArgumentEndingNode.wrapNode(argumentNode, nodeName, branchPreviousArguments); - - arguments.put(argumentNode.getName(), wrappedNode); - finalWrappedNode = wrappedNode; - } else { - throw new IllegalArgumentException("Node must be an argument or literal. Given " + child + " with type " + child.getClass().getName()); + if (loop) { + // Ensure looping flagEnd has same children as root + rootNode.addLoopEndNode(flagEnd); } - children.put(child.getName(), finalWrappedNode); - // These wrapped nodes should always have the same children as the node they are wrapping, so let's share map instances - CommandAPIHandler.setCommandNodeChildren(finalWrappedNode, CommandAPIHandler.getCommandNodeChildren(child)); - CommandAPIHandler.setCommandNodeLiterals(finalWrappedNode, CommandAPIHandler.getCommandNodeLiterals(child)); - CommandAPIHandler.setCommandNodeArguments(finalWrappedNode, CommandAPIHandler.getCommandNodeArguments(child)); + return flagEnd; } + ); + + // Transfer branch nodes to the root node + for (CommandNode child : branchRoot.getChildren()) { + rootNode.addChild(child); } + + // Return the last nodes in the tree + return branchNodeInformation.lastCommandNodes(); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java index a28973b0e6..b7381ec93d 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java @@ -8,6 +8,7 @@ import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.List; @@ -87,14 +88,14 @@ public interface Literal - * Normally, Arguments use thier node name as their help string. However, a Literal uses its literal as the help string. + * Normally, Arguments use their node name as their help string. However, a Literal uses its literal as the help string. */ default NodeInformation linkNode( NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, - Function, Command> terminalExecutorCreator + TerminalNodeModifier terminalNodeModifier ) { // Add rootNode to the previous nodes for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { @@ -108,7 +109,7 @@ default NodeInformation linkNode( children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( new RegisteredCommand.Node<>( getNodeName(), getClass().getSimpleName(), getLiteral(), - getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + rootNode.getCommand() != null, getArgumentPermission(), getRequirements(), children ) @@ -116,6 +117,6 @@ default NodeInformation linkNode( ); // Stack on combined arguments and return last nodes - return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); + return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalNodeModifier); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index a1fdea5c74..0246c363bf 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -8,6 +8,7 @@ import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.ArrayList; @@ -67,9 +68,9 @@ public interface MultiLiteral getCombinedArguments(); /** - * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, Function)}. + * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}. */ - CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, Function, Command> terminalExecutorCreator); + CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, TerminalNodeModifier terminalNodeModifier); //////////////////////////////////////////////////////////////////////////////////////////////////// // OVERRIDING METHODS // @@ -93,7 +94,7 @@ public interface MultiLiteral * Normally, Arguments are only represented by a single node, and so only need to link one node. However, a MultiLiteral * represents multiple literal node paths, which also need to be generated and inserted into the node structure. @@ -101,7 +102,7 @@ public interface MultiLiteral NodeInformation linkNode( NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, - Function, Command> terminalExecutorCreator + TerminalNodeModifier terminalNodeModifier ) { List> newNodes = new ArrayList<>(); // Add root node to previous @@ -122,7 +123,7 @@ default NodeInformation linkNode( LiteralArgumentBuilder.literal(literals.next()); // Finish building node - CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalExecutorCreator); + CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalNodeModifier); // Add node to previous for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { @@ -139,7 +140,7 @@ default NodeInformation linkNode( new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "(" + String.join("|", getLiterals())+ ")", - getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + rootNode.getCommand() != null, getArgumentPermission(), getRequirements(), children ) @@ -147,6 +148,6 @@ nodeName, getClass().getSimpleName(), ); // Stack on combined arguments and return last nodes - return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); + return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalNodeModifier); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java index bb378ff3a3..e8714496d5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java @@ -4,6 +4,7 @@ import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -11,138 +12,204 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.CommandAPIHandler; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; -public abstract class DifferentClientNode extends ArgumentCommandNode { +public interface DifferentClientNode { // Rewrite node trees for the client - public static void rewriteAllChildren(Source client, CommandNode parent) { + static void rewriteAllChildren(Source client, CommandNode parent, boolean onRegister) { // Copy the children, as we do expect them to be modified - List> children = new ArrayList<>(parent.getChildren()); - for (CommandNode child : children) { - rewriteChild(client, parent, child); + Queue> childrenToProcess = new LinkedList<>(parent.getChildren()); + CommandNode child; + while ((child = childrenToProcess.poll()) != null) { + rewriteChild(client, parent, child, onRegister, childrenToProcess); } } - public static void rewriteChild(Source client, CommandNode parent, CommandNode child) { + private static void rewriteChild(Source client, CommandNode parent, CommandNode child, boolean onRegister, Queue> parentChildrenToProcess) { // Get the node creator - DifferentClientNode clientNodeCreator = null; - if (child instanceof DifferentClientNode) { + DifferentClientNode clientNodeCreator = null; + if (child instanceof DifferentClientNode) { // child is directly a DifferentClientNode - clientNodeCreator = (DifferentClientNode) child; - } else if (child instanceof ArgumentCommandNode argument && argument.getType() instanceof Type) { + clientNodeCreator = (DifferentClientNode) child; + } else if (child instanceof ArgumentCommandNode argument && argument.getType() instanceof Argument.Type) { // child was copied from a DifferentClientNode using `createBuilder` (real node hidden in ArgumentType) - Type type = (Type) argument.getType(); + Argument.Type type = (Argument.Type) argument.getType(); clientNodeCreator = type.node; } if (clientNodeCreator != null) { // Get the new client nodes - List> clientNodes = clientNodeCreator.rewriteNodeForClient(client); - - // Inject client node - Map> children = CommandAPIHandler.getCommandNodeChildren(parent); - children.remove(child.getName()); - for (CommandNode clientNode : clientNodes) { - children.put(clientNode.getName(), clientNode); + List> clientNodes = clientNodeCreator.rewriteNodeForClient(child, client, onRegister); + + if (clientNodes != null) { + // Inject client node + Map> children = CommandAPIHandler.getCommandNodeChildren(parent); + children.remove(child.getName()); + for (CommandNode clientNode : clientNodes) { + children.put(clientNode.getName(), clientNode); + } + + // Submit the new client nodes for further processing + // in case they are a client node wrapping a client node. + // This also ensures we rewrite the children of the client + // nodes, rather than the children of the original node. + parentChildrenToProcess.addAll(clientNodes); + return; } } // Modify all children - rewriteAllChildren(client, child); + rewriteAllChildren(client, child, onRegister); } - // Node information - private final ArgumentType type; + // Create the client-side node + /** + * Transforms this node into one the client should see. + * + * @param node The {@link CommandNode} to rewrite. + * @param client The client who the new node is for. If this is null, + * the generated node should just be for any general client. + * @param onRegister {@code true} if the node rewrite is happening when the command is being registered, + * and {@code false} if the node rewrite is happening when the command is being sent to a client. + * @return The version of the given {@link CommandNode} the client should see. This is a list, so one server + * node may become many on the client. If the list is empty, the client will not see any nodes here. If the list + * is null, then no rewriting will occur. + */ + List> rewriteNodeForClient(CommandNode node, Source client, boolean onRegister); + + // Specific implementations for literal and argument Brigadier nodes + abstract class Argument extends ArgumentCommandNode implements DifferentClientNode { + // Node information + private final ArgumentType type; + + public Argument( + String name, ArgumentType type, + Command command, Predicate requirement, + CommandNode redirect, RedirectModifier modifier, boolean forks + ) { + // Platforms often copy this node as a normal ArgumentCommandNode, + // but we can hide a reference to ourselves in the ArgumentType used + // when `ArgumentCommandNode#createBuilder` is called. It would be nice + // to override `createBuilder` to return this class, but that isn't possible. + // https://github.com/Mojang/brigadier/pull/144 :( + super(name, new Type(), command, requirement, redirect, modifier, forks, null); + + ((Type) super.getType()).node = this; + + // This type actually represents this argument when serializing nodes to json + // and also helps this argument blend in with ambiguity checks + this.type = type; + } - public DifferentClientNode( - String name, ArgumentType type, - Command command, Predicate requirement, - CommandNode redirect, RedirectModifier modifier, boolean forks - ) { - // Platforms often copy this node as a normal ArgumentCommandNode, - // but we can hide a reference to ourselves in the ArgumentType used - // when ArgumentCommandNode#createBuilder is called - super(name, new Type(), command, requirement, redirect, modifier, forks, null); + // Handle type nonsense + private static class Type implements ArgumentType { + private Argument node; - ((Type) super.getType()).node = this; + @Override + public T parse(StringReader stringReader) { + throw new IllegalStateException("Not supposed to be called"); + } + } - // This type actually represents this argument when serializing nodes to json - // and also helps this argument blend in with ambiguity checks - this.type = type; - } + @Override + public ArgumentType getType() { + return type; + } - // Handle type nonsense - private static class Type implements ArgumentType { - private DifferentClientNode node; + @Override + public boolean isValidInput(String input) { + try { + StringReader reader = new StringReader(input); + this.type.parse(reader); + return !reader.canRead() || reader.peek() == ' '; + } catch (CommandSyntaxException var3) { + return false; + } + } @Override - public T parse(StringReader stringReader) { - throw new IllegalStateException("Not supposed to be called"); + public Collection getExamples() { + return this.type.getExamples(); } - } - @Override - public ArgumentType getType() { - return type; - } + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Argument other)) return false; - @Override - public boolean isValidInput(String input) { - try { - StringReader reader = new StringReader(input); - this.type.parse(reader); - return !reader.canRead() || reader.peek() == ' '; - } catch (CommandSyntaxException var3) { - return false; + return this.type.equals(other.type) && super.equals(other); } - } - @Override - public Collection getExamples() { - return this.type.getExamples(); - } + @Override + public int hashCode() { + int result = this.type.hashCode(); + result = 31 * result + super.hashCode(); + return result; + } - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof DifferentClientNode other)) return false; + // Require inheritors to redefine these methods from ArgumentCommandNode + @Override + public abstract void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException; - return this.type.equals(other.type) && super.equals(other); - } + @Override + public abstract CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException; - @Override - public int hashCode() { - int result = this.type.hashCode(); - result = 31 * result + super.hashCode(); - return result; + @Override + public abstract String toString(); } - // Special client-side nodes + abstract class Literal extends LiteralCommandNode implements DifferentClientNode { + public Literal( + String literal, + Command command, Predicate requirement, + CommandNode redirect, RedirectModifier modifier, boolean forks + ) { + super(literal, command, requirement, redirect, modifier, forks); + } - /** - * Transforms this node into one the client should see. - * - * @param client The client who the new node is for. If this is null, - * the generated node should just be for any general client. - * @return The version of this {@link CommandNode} the client should see. This is a list, so one server - * node may become many on the client. If the list is empty, the client will not see any nodes. - */ - public abstract List> rewriteNodeForClient(Source client); + // We can actually override `createBuilder` for literal nodes since `LiteralArgumentBuilder` can be extended + // Our builder also includes the client node creator + @Override + public LiteralArgumentBuilder createBuilder() { + return new Builder<>(this); + } - // Require inheritors to redefine these methods from ArgumentCommandNode - @Override - public abstract void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException; + protected static class Builder extends LiteralArgumentBuilder { + protected final DifferentClientNode clientNodeCreator; - @Override - public abstract CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException; + protected Builder(Literal node) { + super(node.getLiteral()); + executes(node.getCommand()); + requires(node.getRequirement()); + forward(node.getRedirect(), node.getRedirectModifier(), node.isFork()); - @Override - public abstract String toString(); + this.clientNodeCreator = node; + } + + @Override + public Literal build() { + Literal result = new Literal<>( + this.getLiteral(), + this.getCommand(), this.getRequirement(), + this.getRedirect(), this.getRedirectModifier(), this.isFork() + ) { + @Override + public List> rewriteNodeForClient(CommandNode node, Source client, boolean onRegister) { + return clientNodeCreator.rewriteNodeForClient(node, client, onRegister); + } + }; + + for (CommandNode argument : this.getArguments()) { + result.addChild(argument); + } + + return result; + } + } + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java index 8d8b177fd8..1c6fcf4415 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java @@ -1,5 +1,6 @@ package dev.jorel.commandapi.commandnodes; +import com.google.gson.JsonArray; import com.mojang.brigadier.Command; import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.StringReader; @@ -19,7 +20,19 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; -public class DynamicMultiLiteralCommandNode extends DifferentClientNode { +public class DynamicMultiLiteralCommandNode extends DifferentClientNode.Argument { + static { + NodeTypeSerializer.registerSerializer(DynamicMultiLiteralCommandNode.class, (target, type) -> { + target.addProperty("type", "dynamicMultiLiteral"); + target.addProperty("isListed", type.isListed); + + LiteralsCreator literalsCreator = type.literalsCreator; + JsonArray literals = new JsonArray(); + literalsCreator.createLiterals(null).forEach(literals::add); + target.add("defaultLiterals", literals); + }); + } + private final boolean isListed; private final LiteralsCreator literalsCreator; @@ -46,7 +59,27 @@ public LiteralsCreator getLiteralsCreator() { // On the client, this node looks like a bunch of literal nodes @Override - public List> rewriteNodeForClient(Source client) { + public List> rewriteNodeForClient(CommandNode node, Source client, boolean onRegister) { + // We only want to rewrite when actually being sent to a client + if (onRegister) { + // However, we do need to ensure the node currently in the tree is a DynamicMultiLiteralCommandNode, + // and not a copied argument, so we can be properly parsed server-side + if (node instanceof DynamicMultiLiteralCommandNode) return null; // No rewrite + + CommandNode result = DynamicMultiLiteralArgumentBuilder + .dynamicMultiLiteral(this.getName(), this.isListed, this.literalsCreator) + .executes(node.getCommand()) + .requires(node.getRequirement()) + .forward(node.getRedirect(), node.getRedirectModifier(), node.isFork()) + .build(); + + for (CommandNode child : node.getChildren()) { + result.addChild(child); + } + + return List.of(result); + } + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); CommandSender sender = commandAPIHandler.getPlatform().getCommandSenderFromCommandSource(client); List literals = literalsCreator.createLiterals(sender); @@ -57,10 +90,10 @@ public List> rewriteNodeForClient(Source client) { for (String literal : literals) { LiteralCommandNode clientNode = new LiteralCommandNode<>( literal, - getCommand(), getRequirement(), - getRedirect(), getRedirectModifier(), isFork() + node.getCommand(), node.getRequirement(), + node.getRedirect(), node.getRedirectModifier(), node.isFork() ); - for (CommandNode child : getChildren()) { + for (CommandNode child : node.getChildren()) { clientNode.addChild(child); } clientNodes.add(clientNode); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java index fce3fe4e94..72f846b3e9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java @@ -1,10 +1,14 @@ package dev.jorel.commandapi.commandnodes; import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.context.ParsedCommandNode; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; @@ -12,32 +16,42 @@ import dev.jorel.commandapi.arguments.FlagsArgumentCommon; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; public interface FlagsArgumentEndingNode /// @endcond -, CommandSender, Source> { +, CommandSender, Source> extends DifferentClientNode { + // Set information static , CommandSender, Source> - LiteralCommandNode wrapNode( - LiteralCommandNode literalNode, String flagsArgumentName, List previousArguments + CommandNode wrapNode( + CommandNode node, String flagsArgumentName, List previousArguments ) { - return new LiteralNode<>(literalNode, flagsArgumentName, previousArguments); + if (node instanceof LiteralCommandNode literalNode) { + return new LiteralNode<>(literalNode, flagsArgumentName, previousArguments); + } else if (node instanceof ArgumentCommandNode argumentNode) { + return new ArgumentNode<>(argumentNode, flagsArgumentName, previousArguments); + } else { + throw new IllegalArgumentException("Node must be an argument or literal. Given " + node + " with type " + node.getClass().getName()); + } } - static , CommandSender, Source> - ArgumentCommandNode wrapNode( - ArgumentCommandNode argumentNode, String flagsArgumentName, List previousArguments - ) { - return new ArgumentNode<>(argumentNode, flagsArgumentName, previousArguments); - } + // Getters + CommandNode getWrappedNode(); String getFlagsArgumentName(); List getPreviousArguments(); + // Use information default void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + // Parse the original node + getWrappedNode().parse(reader, contextBuilder); + // Correctly set parsed node value List> nodes = contextBuilder.getNodes(); int lastNodeIndex = nodes.size() - 1; @@ -51,7 +65,7 @@ default void parse(StringReader reader, CommandContextBuilder contextBui contextBuilder.getArguments().get(name); List> currentInformation = currentValue.getResult(); - // Add new flags + // Add new branch // We do need to copy the information list and the contextBuilder // Otherwise, parsing the argument result references itself, and you get a stack overflow List> newInformation = new ArrayList<>(currentInformation); @@ -59,18 +73,64 @@ default void parse(StringReader reader, CommandContextBuilder contextBui contextBuilder.copy().build(reader.getRead()), getPreviousArguments() )); - // Add new flags back + // Submit new flags ParsedArgument>> newValue = new ParsedArgument<>(currentValue.getRange().getStart(), reader.getCursor(), newInformation); contextBuilder.withArgument(name, newValue); } + @Override + default List> rewriteNodeForClient(CommandNode node, Source client, boolean onRegister) { + // It's important to do this rewrite when the node is registered, because + // command send packet tree copying code tends to throw a StackOverflow if children loop, + // and the events we hook into to rewrite for a specific client aren't called + // until after the tree is copied. + + // Clone wrapped node to remove children + ArgumentBuilder result = getWrappedNode().createBuilder(); + + if (result.getRedirect() == null) { + // If this is a terminating node, we just have to unwrap the + // DifferentClient node so it doesn't get unwrapped again + for (CommandNode child : node.getChildren()) { + result.then(child); + } + return List.of(result.build()); + } + // Fix the redirect in the looped node case. + // Note that on Velocity, this method will be called after the tree is copied, so we + // need to make sure our redirect properly references the copied flag root, + // which is why we hid a reference to it within the tree that got copied. + CommandNode hiddenNode = node.getChild(FlagsArgumentRootNode.HIDDEN_NAME); + CommandNode redirect = hiddenNode.getChildren().iterator().next(); + + result.redirect(redirect); + return List.of(result.build()); + } + + default Collection> filterRelevantNodes(Collection> nodes) { + // Terminal node is fine + if (getWrappedNode().getRedirect() == null) return nodes; + + // Loop node needs the hidden root reference removed to not mess up parsing + List> filteredNodes = new ArrayList<>(nodes); + for (int i = 0; i < filteredNodes.size(); i++) { + if (filteredNodes.get(i).getName().equals(FlagsArgumentRootNode.HIDDEN_NAME)) { + filteredNodes.remove(i); + break; + } + } + return filteredNodes; + } + + // Specific implementations for literal and argument Brigadier nodes class LiteralNode - /// @endcond - , CommandSender, Source> - extends LiteralCommandNode implements FlagsArgumentEndingNode { + /// @cond DOX + extends AbstractArgument + /// @endcond + , CommandSender, Source> + extends DifferentClientNode.Literal implements FlagsArgumentEndingNode { + // Set information private final LiteralCommandNode literalNode; private final String flagsArgumentName; private final List previousArguments; @@ -80,7 +140,9 @@ public LiteralNode( ) { super( literalNode.getName(), literalNode.getCommand(), literalNode.getRequirement(), - literalNode.getRedirect(), literalNode.getRedirectModifier(), literalNode.isFork() + // This node should not redirect because that causes the branch arguments to disappear + // https://github.com/Mojang/brigadier/issues/137 + null, null, false ); this.literalNode = literalNode; @@ -88,6 +150,28 @@ public LiteralNode( this.previousArguments = previousArguments; } + @Override + public void addChild(CommandNode node) { + super.addChild(node); + + if (literalNode.getRedirect() == null) { + // If we are a terminal node (not redirecting back to root), then + // the wrapped node needs our children + literalNode.addChild(node); + } + } + + @Override + public Collection> getRelevantNodes(StringReader input) { + return filterRelevantNodes(super.getRelevantNodes(input)); + } + + // Getters + @Override + public CommandNode getWrappedNode() { + return literalNode; + } + @Override public String getFlagsArgumentName() { return flagsArgumentName; @@ -98,12 +182,28 @@ public List getPreviousArguments() { return previousArguments; } + // Use information @Override public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { - literalNode.parse(reader, contextBuilder); FlagsArgumentEndingNode.super.parse(reader, contextBuilder); } + // We don't reference super for equals and hashCode since those methods reference + // the children of this class, which if this node is part of a loop leads to a StackOverflowException + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof LiteralNode other)) return false; + return Objects.equals(this.literalNode, other.literalNode) + && Objects.equals(this.flagsArgumentName, other.flagsArgumentName) + && Objects.equals(this.previousArguments, other.previousArguments); + } + + @Override + public int hashCode() { + return Objects.hash(this.literalNode, this.flagsArgumentName, this.previousArguments); + } + @Override public String toString() { return ""; @@ -111,11 +211,11 @@ public String toString() { } class ArgumentNode - /// @endcond - , CommandSender, Source, T> - extends ArgumentCommandNode implements FlagsArgumentEndingNode { + /// @cond DOX + extends AbstractArgument + /// @endcond + , CommandSender, Source, T> + extends DifferentClientNode.Argument implements FlagsArgumentEndingNode { private final ArgumentCommandNode argumentNode; private final String flagsArgumentName; private final List previousArguments; @@ -126,8 +226,9 @@ public ArgumentNode( super( argumentNode.getName(), argumentNode.getType(), argumentNode.getCommand(), argumentNode.getRequirement(), - argumentNode.getRedirect(), argumentNode.getRedirectModifier(), argumentNode.isFork(), - argumentNode.getCustomSuggestions() + // This node should not redirect because that causes the branch arguments to disappear + // https://github.com/Mojang/brigadier/issues/137 + null, null, false ); this.argumentNode = argumentNode; @@ -135,6 +236,28 @@ public ArgumentNode( this.previousArguments = previousArguments; } + @Override + public void addChild(CommandNode node) { + super.addChild(node); + + if (argumentNode.getRedirect() == null) { + // If we are a terminal node (not redirecting back to root), then + // the wrapped node needs our children + argumentNode.addChild(node); + } + } + + @Override + public Collection> getRelevantNodes(StringReader input) { + return filterRelevantNodes(super.getRelevantNodes(input)); + } + + // Getters + @Override + public CommandNode getWrappedNode() { + return argumentNode; + } + @Override public String getFlagsArgumentName() { return flagsArgumentName; @@ -145,12 +268,33 @@ public List getPreviousArguments() { return previousArguments; } + // Use information @Override public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { - argumentNode.parse(reader, contextBuilder); FlagsArgumentEndingNode.super.parse(reader, contextBuilder); } + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + return argumentNode.listSuggestions(context, builder); + } + + // We don't reference super for equals and hashCode since those methods reference + // the children of this class, which if this node is part of a loop leads to a StackOverflowException + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ArgumentNode other)) return false; + return Objects.equals(this.argumentNode, other.argumentNode) + && Objects.equals(this.flagsArgumentName, other.flagsArgumentName) + && Objects.equals(this.previousArguments, other.previousArguments); + } + + @Override + public int hashCode() { + return Objects.hash(this.argumentNode, this.flagsArgumentName, this.previousArguments); + } + @Override public String toString() { return ""; diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java index c03bdb7d14..0eb08abc5a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java @@ -1,66 +1,72 @@ package dev.jorel.commandapi.commandnodes; import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; -import dev.jorel.commandapi.CommandAPIHandler; -import dev.jorel.commandapi.arguments.AbstractArgument; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public class FlagsArgumentRootNode extends LiteralCommandNode { + // Ending nodes that loop back should always share the children of this node + private final Set> loopEndNodes = new HashSet<>(); + + // Looping nodes also need a hidden reference to this node to fix their redirect when rewriting for the client + public static final String HIDDEN_NAME = "commandapi:hiddenRootNode"; + private final HiddenRedirect hiddenRedirect; + + public static final class HiddenRedirect extends ArgumentCommandNode { + public HiddenRedirect(CommandNode redirect) { + super( + HIDDEN_NAME, StringArgumentType.word(), + null, source -> true, + null, null, false, + null + ); + this.addChild(redirect); + } + + // Don't interfere with suggestions + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + return builder.buildFuture(); + } + } -public class FlagsArgumentRootNode -/// @endcond -, CommandSender, Source> extends LiteralCommandNode { public FlagsArgumentRootNode(LiteralCommandNode literalNode) { super( literalNode.getName(), literalNode.getCommand(), literalNode.getRequirement(), literalNode.getRedirect(), literalNode.getRedirectModifier(), literalNode.isFork() ); + this.hiddenRedirect = new HiddenRedirect<>(this); } - // Handle setting up the loop back - public void makeChildrenLoopBack(Collection> nodes, List previousArguments) { - for (CommandNode node : nodes) { - // Nodes in the `children` map should redirect to this node, so the client doesn't see a child cycle - // Nodes in the `arguments`/`literals` maps should have all children of this node as children - // and store all the flag values given - Map> children = CommandAPIHandler.getCommandNodeChildren(node); - Map> literals = CommandAPIHandler.getCommandNodeLiterals(node); - Map> arguments = CommandAPIHandler.getCommandNodeArguments(node); - - for (CommandNode child : node.getChildren()) { - // Clone the node, redirect it here, then put the clone into the children map - children.put(child.getName(), child.createBuilder().redirect(this).build()); - - // Wrap nodes in the `arguments`/`literals` map to extract the flag values on loop - CommandNode finalWrappedNode; - if (child instanceof LiteralCommandNode literalNode) { - LiteralCommandNode wrappedNode = - FlagsArgumentEndingNode.wrapNode(literalNode, getName(), previousArguments); + // Handle loops back to here + public void addLoopEndNode(CommandNode node) { + node.addChild(hiddenRedirect); + loopEndNodes.add(node); - literals.put(literalNode.getName(), wrappedNode); - finalWrappedNode = wrappedNode; - } else if(child instanceof ArgumentCommandNode argumentNode) { - ArgumentCommandNode wrappedNode = - FlagsArgumentEndingNode.wrapNode(argumentNode, getName(), previousArguments); + for (CommandNode child : getChildren()) { + node.addChild(child); + } + } - arguments.put(argumentNode.getName(), wrappedNode); - finalWrappedNode = wrappedNode; - } else { - throw new IllegalArgumentException("Node must be an argument or literal. Given " + child + " with type " + child.getClass().getName()); - } + @Override + public void addChild(CommandNode node) { + super.addChild(node); - // These wrapped nodes should always have our children as their children, so let's share map instances - CommandAPIHandler.setCommandNodeChildren(finalWrappedNode, CommandAPIHandler.getCommandNodeChildren(this)); - CommandAPIHandler.setCommandNodeLiterals(finalWrappedNode, CommandAPIHandler.getCommandNodeLiterals(this)); - CommandAPIHandler.setCommandNodeArguments(finalWrappedNode, CommandAPIHandler.getCommandNodeArguments(this)); - } + for (CommandNode loopEndNode : loopEndNodes) { + loopEndNode.addChild(node); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java new file mode 100644 index 0000000000..6bab0c1270 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java @@ -0,0 +1,70 @@ +package dev.jorel.commandapi.commandnodes; + +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.CommandAPIHandler; + +import java.util.HashMap; +import java.util.Map; + +@FunctionalInterface +// Java generics with class literals are annoying, so I can't find a way to parameterize CommandNode here +public interface NodeTypeSerializer { + // Interface for serializing CommandNode subclasses + void extractTypeInformation(JsonObject target, T type); + + // Serializer registry + Map>, NodeTypeSerializer> nodeTypeSerializers = new HashMap<>(); + + static > void registerSerializer(Class clazz, NodeTypeSerializer serializer) { + nodeTypeSerializers.put(clazz, serializer); + } + + // Use serializers + NodeTypeSerializer UNKNOWN = (target, type) -> { + target.addProperty("type", "unknown"); + target.addProperty("typeClassName", type.getClass().getName()); + }; + + static > NodeTypeSerializer getSerializer(T type) { + initialize(); + // Try to find the serializer for the most specific parent class of this node + Class clazz = type.getClass(); + while (!CommandNode.class.equals(clazz)) { + NodeTypeSerializer serializer = (NodeTypeSerializer) nodeTypeSerializers.get(clazz); + if (serializer != null) return serializer; + + clazz = clazz.getSuperclass(); + } + return (NodeTypeSerializer) UNKNOWN; + } + + static > void addTypeInformation(JsonObject target, T type) { + getSerializer(type).extractTypeInformation(target, type); + } + + // Initialize registry - for some reason interfaces can't have static initializers? + static void initialize() { + if (nodeTypeSerializers.containsKey(RootCommandNode.class)) return; + + // BRIGADIER CommandNodes + registerSerializer(RootCommandNode.class, (target, type) -> target.addProperty("type", "root")); + registerSerializer(LiteralCommandNode.class, (target, type) -> target.addProperty("type", "literal")); + + registerSerializer(ArgumentCommandNode.class, ((target, type) -> { + ArgumentType argumentType = type.getType(); + + target.addProperty("type", "argument"); + target.addProperty("argumentType", argumentType.getClass().getName()); + + CommandAPIHandler.getInstance().getPlatform() + .getArgumentTypeProperties(argumentType).ifPresent(properties -> target.add("properties", properties)); + })); + // TODO: Add serializers for custom nodes + // Probably do this within the node classes so they can otherwise be removed by jar minimization + } +} diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index bc84ce7d0e..9ac6deb98c 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -117,8 +117,6 @@ inline fun CommandAPICommand.nbtCompoundArgument(nodeName: String inline fun CommandAPICommand.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(literal, literal), optional, block) inline fun CommandAPICommand.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(nodeName, literal), optional, block) -@Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandAPICommand.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index e4e92522c5..d66517e8e0 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -106,8 +106,6 @@ inline fun CommandTree.nbtCompoundArgument(nodeName: String, bloc inline fun CommandTree.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(literal, literal).apply(block)) inline fun CommandTree.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(nodeName, literal).apply(block)) -@Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, literals).apply(block)) @@ -210,8 +208,6 @@ inline fun Argument<*>.nbtCompoundArgument(nodeName: String, bloc inline fun Argument<*>.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(literal, literal).apply(block)) inline fun Argument<*>.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(nodeName, literal).apply(block)) -@Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, literals).apply(block)) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index bd7865b87c..e155cde02b 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt @@ -48,8 +48,6 @@ inline fun CommandAPICommand.greedyStringArgument(nodeName: String, optional: Bo inline fun CommandAPICommand.literalArgument(literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(literal, literal), optional, block) inline fun CommandAPICommand.literalArgument(nodeName: String, literal: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(LiteralArgument.of(nodeName, literal), optional, block) -@Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandAPICommand.multiLiteralArgument(vararg literals: String, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(literals), optional, block) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) inline fun CommandAPICommand.multiLiteralArgument(nodeName: String, literals: List, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): CommandAPICommand = addArgument(MultiLiteralArgument(nodeName, literals), optional, block) diff --git a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt index 9e8d1a895e..bc5d30005a 100644 --- a/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt +++ b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandTreeDSL.kt @@ -37,8 +37,6 @@ inline fun CommandTree.greedyStringArgument(nodeName: String, block: Argument<*> inline fun CommandTree.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(literal, literal).apply(block)) inline fun CommandTree.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(LiteralArgument.of(nodeName, literal).apply(block)) -@Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun CommandTree.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) inline fun CommandTree.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): CommandTree= then(MultiLiteralArgument(nodeName, literals).apply(block)) @@ -72,8 +70,6 @@ inline fun Argument<*>.greedyStringArgument(nodeName: String, block: Argument<*> inline fun Argument<*>.literalArgument(literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(literal, literal).apply(block)) inline fun Argument<*>.literalArgument(nodeName: String, literal: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(LiteralArgument.of(nodeName, literal).apply(block)) -@Deprecated("This version has been deprecated since version 9.0.2", ReplaceWith("multiLiteralArgument(nodeName, listOf(literals))", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) -inline fun Argument<*>.multiLiteralArgument(vararg literals: String, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(literals).apply(block)) @Deprecated("This method has been deprecated since version 9.1.0", ReplaceWith("multiLiteralArgument(nodeName, literals)", "dev.jorel.commandapi.kotlindsl.*"), DeprecationLevel.WARNING) inline fun Argument<*>.multiLiteralArgument(nodeName: String, literals: List, block: Argument<*>.() -> Unit = {}): Argument<*> = then(MultiLiteralArgument(nodeName, literals).apply(block)) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index 78499e7186..47bea75a37 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -5,8 +5,6 @@ import static dev.jorel.commandapi.preprocessor.Unimplemented.REASON.REQUIRES_MINECRAFT_SERVER; import static dev.jorel.commandapi.preprocessor.Unimplemented.REASON.VERSION_SPECIFIC_IMPLEMENTATION; -import java.io.File; -import java.io.IOException; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; @@ -14,6 +12,9 @@ import java.util.logging.Logger; import net.kyori.adventure.text.ComponentLike; +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import org.bukkit.Bukkit; import org.bukkit.Keyed; import org.bukkit.command.CommandSender; @@ -54,7 +55,7 @@ public abstract class CommandAPIBukkit implements CommandAPIPlatform instance; private static InternalBukkitConfig config; - private PaperImplementations paper; + private PaperImplementations paper; private CommandRegistrationStrategy commandRegistrationStrategy; protected CommandAPIBukkit() { @@ -70,7 +71,7 @@ public static CommandAPIBukkit get() { } } - public PaperImplementations getPaper() { + public PaperImplementations getPaper() { return paper; } @@ -128,7 +129,7 @@ private void checkDependencies() { } } - boolean isPaperPresent = false; + boolean isPaperPresent; try { Class.forName("io.papermc.paper.event.server.ServerResourcesReloadedEvent"); @@ -141,7 +142,7 @@ private void checkDependencies() { } } - boolean isFoliaPresent = false; + boolean isFoliaPresent; try { Class.forName("io.papermc.paper.threadedregions.RegionizedServerInitEvent"); @@ -152,7 +153,7 @@ private void checkDependencies() { isFoliaPresent = false; } - paper = new PaperImplementations(isPaperPresent, isFoliaPresent, this); + paper = new PaperImplementations<>(isPaperPresent, isFoliaPresent, this); commandRegistrationStrategy = createCommandRegistrationStrategy(); } @@ -329,6 +330,7 @@ public void postCommandRegistration(RegisteredCommand registeredC @Override public void registerCommandNode(LiteralCommandNode node, String namespace) { + DifferentClientNode.rewriteAllChildren(null, node, true); commandRegistrationStrategy.registerCommandNode(node, namespace); } @@ -379,8 +381,8 @@ public final CommandDispatcher getBrigadierDispatcher() { @Override @Unimplemented(because = {REQUIRES_MINECRAFT_SERVER, VERSION_SPECIFIC_IMPLEMENTATION}) - public abstract void createDispatcherFile(File file, CommandDispatcher brigadierDispatcher) throws IOException; - + public abstract Optional getArgumentTypeProperties(ArgumentType type); + @Unimplemented(because = REQUIRES_MINECRAFT_SERVER) // What are the odds? public abstract T getMinecraftServer(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java index cf96526ecd..5944b0e29c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java @@ -99,7 +99,7 @@ public void onCommandsSentToPlayer(AsyncPlayerSendCommandsEvent event) { // Rewrite nodes to their client-side version when commands are sent to a client Source source = nmsInstance.getBrigadierSourceFromCommandSender(event.getPlayer()); - DifferentClientNode.rewriteAllChildren(source, (RootCommandNode) event.getCommandNode()); + DifferentClientNode.rewriteAllChildren(source, (RootCommandNode) event.getCommandNode(), false); } /** diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Schedulers.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Schedulers.java index 28e00a54da..b908cfc20b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Schedulers.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/Schedulers.java @@ -4,9 +4,9 @@ public class Schedulers { - private final PaperImplementations paperImplementations; + private final PaperImplementations paperImplementations; - public Schedulers(PaperImplementations paperImplementations) { + public Schedulers(PaperImplementations paperImplementations) { this.paperImplementations = paperImplementations; } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index 3c0c0c898d..f0dadb3caa 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -294,7 +294,7 @@ public void registerCommandNode(LiteralCommandNode node, String namespac // can still produce interesting server-client de-sync behavior that we want to use. CommandSender console = Bukkit.getConsoleSender(); Source source = CommandAPIBukkit.get().getBrigadierSourceFromCommandSender(console); - DifferentClientNode.rewriteAllChildren(source, node); + DifferentClientNode.rewriteAllChildren(source, node, false); } String name = node.getLiteral(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java index 1627cf6631..50faf1c161 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java @@ -20,7 +20,6 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.Message; import com.mojang.brigadier.context.CommandContext; @@ -39,7 +38,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.function.Function; import java.util.function.Predicate; /** @@ -202,17 +200,16 @@ public Argument combineWith(List> combinedArguments) { public NodeInformation addArgumentNodes( NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, - Function>, Command> terminalExecutorCreator + TerminalNodeModifier, CommandSender, Source> terminalNodeModifier ) { // Node structure is determined by the base argument previousNodeInformation = base.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, // Replace the base argument with this argument when executing the command - terminalExecutorCreator == null ? null : - args -> { - List> newArgs = new ArrayList<>(args); - newArgs.set(args.indexOf(base), this); - return terminalExecutorCreator.apply(newArgs); - } + (builder, args) -> { + List> newArgs = new ArrayList<>(args); + newArgs.set(args.indexOf(base), this); + return terminalNodeModifier.finishTerminalNode(builder, newArgs); + } ); // Replace the base argument with this argument when executing the command diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java index 30e8765ca9..6d7d577208 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java @@ -1,6 +1,5 @@ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.jorel.commandapi.executors.CommandArguments; @@ -8,7 +7,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.Function; /** * @apiNote Yields a {@code List<}{@link CommandArguments}{@code >} @@ -73,7 +71,7 @@ public List parseArgument(CommandContext cmdC } @Override - public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return FlagsArgumentCommon.super.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); + public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, TerminalNodeModifier, CommandSender, Source> terminalNodeModifier) { + return FlagsArgumentCommon.super.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, terminalNodeModifier); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index 1e96c986db..a8b8130aa5 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -20,7 +20,6 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -31,7 +30,6 @@ import org.bukkit.command.CommandSender; import java.util.List; -import java.util.function.Function; /** * A pseudo-argument representing a single literal string @@ -157,7 +155,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, TerminalNodeModifier, CommandSender, Source> terminalNodeModifier) { + return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalNodeModifier); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index f3b1b799d9..404017feda 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -20,7 +20,6 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -30,7 +29,6 @@ import org.bukkit.command.CommandSender; import java.util.List; -import java.util.function.Function; /** * An argument that represents multiple LiteralArguments @@ -58,16 +56,6 @@ public MultiLiteralArgument(String nodeName, String... literals) { this.literals = literals; } - /** - * A multiliteral argument. Takes in string literals which cannot be modified - * @param literals the literals that this argument represents - * @deprecated Use {@link MultiLiteralArgument#MultiLiteralArgument(String, String...)} instead - */ - @Deprecated(since = "9.0.2", forRemoval = true) - public MultiLiteralArgument(final String[] literals) { - this(null, literals); - } - /** * A multiliteral argument. Takes in string literals which cannot be modified * @@ -112,7 +100,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, TerminalNodeModifier, CommandSender, Source> terminalNodeModifier) { + return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalNodeModifier); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java index 2368ee4ba7..bce6fa3524 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java @@ -41,7 +41,7 @@ default ExecutionInfo tryForSender(ExecutionInfo sender instanceof RemoteConsoleCommandSender; case FEEDBACK_FORWARDING -> { - PaperImplementations paper = CommandAPIBukkit.get().getPaper(); + PaperImplementations paper = CommandAPIBukkit.get().getPaper(); yield paper.isPaperPresent() && paper.getFeedbackForwardingCommandSender().isInstance(sender); } }) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java index a09ba4df15..08602e0db9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java @@ -20,27 +20,17 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; +import dev.jorel.commandapi.*; import org.bukkit.Axis; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -80,8 +70,6 @@ import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Team; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -90,12 +78,6 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; -import dev.jorel.commandapi.CommandAPI; -import dev.jorel.commandapi.CommandAPIBukkit; -import dev.jorel.commandapi.CommandAPIHandler; -import dev.jorel.commandapi.CommandRegistrationStrategy; -import dev.jorel.commandapi.SafeVarHandle; -import dev.jorel.commandapi.SpigotCommandRegistration; import dev.jorel.commandapi.arguments.ArgumentSubType; import dev.jorel.commandapi.arguments.SuggestionProviders; import dev.jorel.commandapi.preprocessor.Differs; @@ -211,6 +193,7 @@ public class NMS_1_16_R3 extends CommandAPIBukkit { private static final SafeVarHandle itemStackPredicateArgument; private static final Field customFunctionManagerBrigadierDispatcher; private static final SafeVarHandle dataPackResources; + private static final SafeStaticMethodHandle argumentRegistrySerializeToJson; // Compute all var handles all in one go so we don't do this during main server // runtime @@ -225,6 +208,8 @@ public class NMS_1_16_R3 extends CommandAPIBukkit { // For some reason, MethodHandles fails for this field, but Field works okay customFunctionManagerBrigadierDispatcher = CommandAPIHandler.getField(CustomFunctionManager.class, "h", "h"); dataPackResources = SafeVarHandle.ofOrNull(DataPackResources.class, "b", "b", IReloadableResourceManager.class); + + argumentRegistrySerializeToJson = SafeStaticMethodHandle.ofOrNull(ArgumentRegistry.class, "a", "a", void.class, JsonObject.class, ArgumentType.class); } @Differs(from = "1.16.4", by = "Use of non-deprecated NamespacedKey.fromString method") @@ -397,9 +382,10 @@ private SimpleFunctionWrapper convertFunction(CustomFunction customFunction) { } @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentRegistry.a(dispatcher, dispatcher.getRoot())), file, StandardCharsets.UTF_8); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentRegistrySerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java index c5d7aa7109..12dbf0fa9c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java @@ -22,10 +22,7 @@ import static dev.jorel.commandapi.preprocessor.Unimplemented.REASON.NAME_CHANGED; -import java.io.File; -import java.io.IOException; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -35,6 +32,7 @@ import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.*; import net.minecraft.advancements.critereon.MinMaxBounds; @@ -75,9 +73,6 @@ import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Team; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; -import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -148,6 +143,7 @@ public abstract class NMS_1_17_Common extends NMS_Common { private static final SafeVarHandle> helpMapTopics; private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; + private static final SafeStaticMethodHandle argumentTypesSerializeToJson; // Compute all var handles all in one go so we don't do this during main server // runtime @@ -156,6 +152,8 @@ public abstract class NMS_1_17_Common extends NMS_Common { // For some reason, MethodHandles fails for this field, but Field works okay entitySelectorUsesSelector = CommandAPIHandler.getField(EntitySelector.class, "o", "usesSelector"); itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); + + argumentTypesSerializeToJson = SafeStaticMethodHandle.ofOrNull(ArgumentTypes.class, "a", "serializeToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -250,10 +248,10 @@ private SimpleFunctionWrapper convertFunction(CommandFunction commandFunction) { } @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.write( - new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentTypes.serializeNodeToJson(dispatcher, dispatcher.getRoot())), file, StandardCharsets.UTF_8); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentTypesSerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java index c7e316b005..ce1d9571b4 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java @@ -20,12 +20,9 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -38,6 +35,7 @@ import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.*; import net.minecraft.advancements.critereon.MinMaxBounds; @@ -77,8 +75,6 @@ import org.bukkit.inventory.Recipe; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -166,6 +162,7 @@ public class NMS_1_18_R2 extends NMS_Common { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentTypesSerializeToJson; // Compute all var handles all in one go so we don't do this during main server // runtime @@ -176,6 +173,8 @@ public class NMS_1_18_R2 extends NMS_Common { itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "i", "dispatcher"); + + argumentTypesSerializeToJson = SafeStaticMethodHandle.ofOrNull(ArgumentTypes.class, "a", "serializeToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -273,9 +272,10 @@ private SimpleFunctionWrapper convertFunction(CommandFunction commandFunction) { } @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentTypes.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentTypesSerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java index 09bc7395cc..24420cd1bd 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java @@ -20,12 +20,9 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -37,6 +34,7 @@ import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.*; import net.minecraft.advancements.critereon.MinMaxBounds; @@ -75,8 +73,6 @@ import org.bukkit.help.HelpTopic; import org.bukkit.inventory.Recipe; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -153,6 +149,7 @@ public class NMS_1_18_R1 extends NMS_Common { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentTypesSerializeToJson; // Compute all var handles all in one go so we don't do this during main server // runtime @@ -163,6 +160,8 @@ public class NMS_1_18_R1 extends NMS_Common { itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "i", "dispatcher"); + + argumentTypesSerializeToJson = SafeStaticMethodHandle.ofOrNull(ArgumentTypes.class, "a", "serializeToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -257,11 +256,11 @@ private SimpleFunctionWrapper convertFunction(CommandFunction commandFunction) { return new SimpleFunctionWrapper(fromResourceLocation(commandFunction.getId()), appliedObj, result); } - @Differs(from = "1.17", by = "Use of Files.asCharSink() instead of Files.write()") @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentTypes.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentTypesSerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java index 5932a4dd75..70e636b74f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java @@ -21,8 +21,7 @@ package dev.jorel.commandapi.nms; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -129,12 +128,9 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; @@ -157,6 +153,7 @@ public abstract class NMS_1_19_Common extends NMS_CommonWithFunctions { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // From net.minecraft.server.commands.LocateCommand private static final DynamicCommandExceptionType ERROR_BIOME_INVALID; @@ -188,6 +185,8 @@ public abstract class NMS_1_19_Common extends NMS_CommonWithFunctions { // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "i", "dispatcher"); + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); + ERROR_BIOME_INVALID = new DynamicCommandExceptionType( arg -> net.minecraft.network.chat.Component.translatable("commands.locatebiome.invalid", arg)); } @@ -342,10 +341,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction commandFunct } @Override - public final void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java index f649ed71c2..538aee8873 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java @@ -21,8 +21,7 @@ package dev.jorel.commandapi.nms; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -120,12 +119,9 @@ import org.bukkit.help.HelpTopic; import org.bukkit.inventory.Recipe; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; @@ -147,6 +143,7 @@ public class NMS_1_19_3_R2 extends NMS_CommonWithFunctions { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // Derived from net.minecraft.commands.Commands; private static final CommandBuildContext COMMAND_BUILD_CONTEXT; @@ -166,6 +163,8 @@ public class NMS_1_19_3_R2 extends NMS_CommonWithFunctions { itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "g", "dispatcher"); + + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -253,9 +252,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction commandFunct } @Override - public final void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java index 025b2ac88d..4d2edcf2c2 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java @@ -21,8 +21,7 @@ package dev.jorel.commandapi.nms; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -120,12 +119,9 @@ import org.bukkit.help.HelpTopic; import org.bukkit.inventory.Recipe; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; @@ -148,6 +144,7 @@ public class NMS_1_19_4_R3 extends NMS_CommonWithFunctions { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // Derived from net.minecraft.commands.Commands; private static final CommandBuildContext COMMAND_BUILD_CONTEXT; @@ -167,6 +164,8 @@ public class NMS_1_19_4_R3 extends NMS_CommonWithFunctions { itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "g", "dispatcher"); + + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -252,9 +251,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction commandFunct } @Override - public final void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java index bc8bf29521..debd0a44ed 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java @@ -20,23 +20,15 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.*; import net.minecraft.advancements.critereon.MinMaxBounds; @@ -76,8 +68,6 @@ import org.bukkit.inventory.Recipe; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -170,6 +160,7 @@ public class NMS_1_20_R2 extends NMS_CommonWithFunctions { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // Derived from net.minecraft.commands.Commands; private static final CommandBuildContext COMMAND_BUILD_CONTEXT; @@ -189,6 +180,8 @@ public class NMS_1_20_R2 extends NMS_CommonWithFunctions { itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "g", "dispatcher"); + + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -274,9 +267,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction commandFunct } @Override - public final void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java index c001defd08..05f189a7a1 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java @@ -20,12 +20,9 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -39,6 +36,7 @@ import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; import dev.jorel.commandapi.*; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -76,8 +74,6 @@ import org.bukkit.inventory.Recipe; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -195,6 +191,7 @@ public class NMS_1_20_R3 extends NMS_Common { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // Derived from net.minecraft.commands.Commands; private static final CommandBuildContext COMMAND_BUILD_CONTEXT; @@ -215,6 +212,8 @@ public class NMS_1_20_R3 extends NMS_Common { itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "g", "dispatcher"); + + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -342,10 +341,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction dispatcher) - throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java index 74ae982cc7..bc4a8ec971 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java @@ -20,12 +20,9 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -39,6 +36,7 @@ import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; import dev.jorel.commandapi.*; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -76,8 +74,6 @@ import org.bukkit.inventory.Recipe; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -203,6 +199,7 @@ public class NMS_1_20_R4 extends NMS_Common { // private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; private static final boolean vanillaCommandDispatcherFieldExists; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // Derived from net.minecraft.commands.Commands; private static final CommandBuildContext COMMAND_BUILD_CONTEXT; @@ -233,6 +230,8 @@ public class NMS_1_20_R4 extends NMS_Common { fieldExists = false; } vanillaCommandDispatcherFieldExists = fieldExists; + + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -384,10 +383,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction dispatcher) - throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java index dd6d9e3a5f..b946a51932 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java @@ -21,8 +21,7 @@ package dev.jorel.commandapi.nms; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -121,12 +120,9 @@ import org.bukkit.help.HelpTopic; import org.bukkit.inventory.Recipe; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; @@ -147,6 +143,7 @@ public class NMS_1_20_R1 extends NMS_CommonWithFunctions { private static final Field entitySelectorUsesSelector; private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // Derived from net.minecraft.commands.Commands; private static final CommandBuildContext COMMAND_BUILD_CONTEXT; @@ -166,6 +163,8 @@ public class NMS_1_20_R1 extends NMS_CommonWithFunctions { itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); // For some reason, MethodHandles fails for this field, but Field works okay serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "g", "dispatcher"); + + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -251,9 +250,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction commandFunct } @Override - public final void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java index 458f3423a9..e34e4e7f50 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java @@ -20,12 +20,9 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -39,6 +36,7 @@ import java.util.function.Predicate; import java.util.function.ToIntFunction; +import com.google.gson.JsonObject; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Location; @@ -75,8 +73,6 @@ import org.bukkit.inventory.Recipe; import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.ArgumentType; @@ -92,6 +88,7 @@ import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.CommandRegistrationStrategy; import dev.jorel.commandapi.PaperCommandRegistration; +import dev.jorel.commandapi.SafeStaticMethodHandle; import dev.jorel.commandapi.SafeVarHandle; import dev.jorel.commandapi.SpigotCommandRegistration; import dev.jorel.commandapi.arguments.ArgumentSubType; @@ -206,6 +203,7 @@ public class NMS_1_21_R1 extends NMS_Common { // private static final SafeVarHandle itemInput; private static final Field serverFunctionLibraryDispatcher; private static final boolean vanillaCommandDispatcherFieldExists; + private static final SafeStaticMethodHandle argumentUtilsSerializeArgumentToJson; // Derived from net.minecraft.commands.Commands; private static final CommandBuildContext COMMAND_BUILD_CONTEXT; @@ -236,6 +234,8 @@ public class NMS_1_21_R1 extends NMS_Common { fieldExists = false; } vanillaCommandDispatcherFieldExists = fieldExists; + + argumentUtilsSerializeArgumentToJson = SafeStaticMethodHandle.ofOrNull(ArgumentUtils.class, "a", "serializeArgumentToJson", void.class, JsonObject.class, ArgumentType.class); } private static NamespacedKey fromResourceLocation(ResourceLocation key) { @@ -384,10 +384,10 @@ private final SimpleFunctionWrapper convertFunction(CommandFunction dispatcher) - throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot()))); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java index 785c27caf3..b94d16326e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java @@ -20,7 +20,7 @@ *******************************************************************************/ package dev.jorel.commandapi.nms; -import com.mojang.brigadier.CommandDispatcher; +import com.google.gson.JsonObject; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -63,8 +63,6 @@ import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Team; -import java.io.File; -import java.io.IOException; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; @@ -292,8 +290,9 @@ public final String convert(Sound sound) { } @Override - @Unimplemented(because = VERSION_SPECIFIC_IMPLEMENTATION, introducedIn = "1.19") - public abstract void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException; + @Unimplemented(because = NAME_CHANGED, introducedIn = "1.19", + from = "ArgumentTypes#serializeToJson", to = "ArgumentUtils#serializeArgumentToJson") + public abstract Optional getArgumentTypeProperties(ArgumentType type); @Override @Unimplemented(because = VERSION_SPECIFIC_IMPLEMENTATION, introducedIn = "1.20.2") diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index 3697be7065..27a8662df1 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -28,6 +28,8 @@ import java.util.Map.Entry; import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.executors.CommandArguments; +import dev.jorel.commandapi.wrappers.IntegerRange; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; @@ -140,7 +142,7 @@ public void onEnable() { tags.add("hello"); tags.add("world"); - new CommandTree("server") + new CommandTree("servertag") .then( new LiteralArgument("add").then( new StringArgument("tag").executes(info -> { @@ -179,5 +181,77 @@ public void onEnable() { } }) .register(); + + // example from https://github.com/JorelAli/CommandAPI/issues/483 + new CommandAPICommand("serverFlag") + .withArguments( + new FlagsArgument("filters") + .loopingBranch( + new LiteralArgument("filter", "sort").setListed(true), + new MultiLiteralArgument("sortType", "furthest", "nearest", "random") + ) + .loopingBranch( + new LiteralArgument("filter", "limit").setListed(true), + new IntegerArgument("limitAmount", 0) + ) + .loopingBranch( + new LiteralArgument("filter", "distance").setListed(true), + new IntegerRangeArgument("range") + ) + ) + .executes(info -> { + for (CommandArguments branch : info.args().>getUnchecked("filters")) { + String filterType = branch.getUnchecked("filter"); + info.sender().sendMessage(switch (filterType) { + case "sort" -> "Sort " + branch.getUnchecked("sortType"); + case "limit" -> "Limit " + branch.getUnchecked("limitAmount"); + case "distance" -> "Distance " + branch.getUnchecked("range"); + default -> "Unknown branch " + filterType; + }); + } + }) + .register(); + + List keys = new ArrayList<>(); + List values = new ArrayList<>(); + new CommandTree("dynamicMap") + .then( + new LiteralArgument("add").then( + new MultiLiteralArgument("type", "key", "value").then( + new StringArgument("item").executes(info -> { + List choice = (info.args().getUnchecked("type").equals("key")) ? + keys : + values; + + String item = info.args().getUnchecked("item"); + choice.add(item); + }) + ) + ) + ) + .then( + new CustomArgument<>( + new FlagsArgument("map").loopingBranch( + new DynamicMultiLiteralArgument("key", sender -> keys), + new LiteralArgument("->"), // Haha! Multi-character delimiter :P + new DynamicMultiLiteralArgument("value", sender -> values) + ), + info -> { + Map result = new HashMap<>(); + for (CommandArguments args : (List) info.currentInput()) { + String key = args.getUnchecked("key"); + String value = args.getUnchecked("value"); + + if (result.put(key, value) != null) + throw CustomArgument.CustomArgumentException.fromString("Duplicate key \"" + key + "\""); + } + return result; + } + ).executes(info -> { + Map result = info.args().getUnchecked("map"); + info.sender().sendMessage(result.toString()); + }) + ) + .register(); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/pom.xml index b3ed6d2e62..eff9e4b862 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/pom.xml +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-kotlin-test/pom.xml @@ -108,6 +108,14 @@ 1.19.2-R0.1-SNAPSHOT provided + + + + io.papermc.paper + paper-mojangapi + 1.20.6-R0.1-SNAPSHOT + provided + diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 683ebad558..e579c01aeb 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -4,14 +4,14 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import be.seeseemelk.mockbukkit.help.HelpMapMock; +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import org.bukkit.Bukkit; @@ -40,7 +40,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Streams; import com.mojang.authlib.GameProfile; -import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import be.seeseemelk.mockbukkit.ServerMock; @@ -115,7 +114,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // Initialize WorldVersion (game version) SharedConstants.b(); @@ -357,11 +356,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 6280f0ff54..edf99aacfa 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -4,13 +4,13 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import java.io.File; -import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import be.seeseemelk.mockbukkit.help.HelpMapMock; +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; @@ -110,7 +110,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // Initialize WorldVersion (game version) SharedConstants.tryDetectVersion(); @@ -341,11 +341,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 4d97aea53c..c4f23be039 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -4,14 +4,14 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import be.seeseemelk.mockbukkit.help.HelpMapMock; +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; @@ -125,7 +125,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // Initialize WorldVersion (game version) SharedConstants.tryDetectVersion(); @@ -358,11 +358,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 43dbaa37c8..43a09583f8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -4,14 +4,14 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import be.seeseemelk.mockbukkit.help.HelpMapMock; +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; @@ -119,7 +119,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // initializeArgumentsInArgumentTypeInfos(); @@ -354,11 +354,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 67e587e9a5..cf6e120c0d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -4,14 +4,14 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import be.seeseemelk.mockbukkit.help.HelpMapMock; +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.commands.Commands; @@ -120,7 +120,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // initializeArgumentsInArgumentTypeInfos(); @@ -356,11 +356,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index f3535af193..c8a402ccb8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -5,8 +5,10 @@ import be.seeseemelk.mockbukkit.help.HelpMapMock; import be.seeseemelk.mockbukkit.potion.MockPotionEffectType; import com.google.common.collect.Streams; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; @@ -59,8 +61,6 @@ import org.jetbrains.annotations.Nullable; import org.mockito.Mockito; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Collectors; @@ -103,7 +103,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // initializeArgumentsInArgumentTypeInfos(); @@ -351,11 +351,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 4ba17c30b0..3cb2d54f8b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -4,9 +4,11 @@ import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; import be.seeseemelk.mockbukkit.help.HelpMapMock; import com.google.common.collect.Streams; +import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.JsonOps; import dev.jorel.commandapi.*; @@ -63,8 +65,6 @@ import org.jetbrains.annotations.Nullable; import org.mockito.Mockito; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Collectors; @@ -108,7 +108,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // initializeArgumentsInArgumentTypeInfos(); @@ -363,11 +363,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index cff0086202..228bc14b11 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -2,9 +2,11 @@ import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.help.HelpMapMock; +import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.JsonOps; import dev.jorel.commandapi.*; @@ -61,8 +63,6 @@ import org.jetbrains.annotations.Nullable; import org.mockito.Mockito; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Stream; @@ -106,7 +106,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // initializeArgumentsInArgumentTypeInfos(); @@ -382,11 +382,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 91f99bbc86..2490994e47 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -5,8 +5,10 @@ import be.seeseemelk.mockbukkit.help.HelpMapMock; import be.seeseemelk.mockbukkit.potion.MockPotionEffectType; import com.google.common.collect.Streams; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; @@ -60,8 +62,6 @@ import org.jetbrains.annotations.Nullable; import org.mockito.Mockito; -import java.io.File; -import java.io.IOException; import java.security.CodeSource; import java.util.*; import java.util.stream.Collectors; @@ -104,7 +104,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { // Initialize baseNMS's paper field (with paper specific implementations disabled) MockPlatform.setField(CommandAPIBukkit.class, "paper", - super.baseNMS, new PaperImplementations(false, false, super.baseNMS)); + super.baseNMS, new PaperImplementations<>(false, false, super.baseNMS)); // initializeArgumentsInArgumentTypeInfos(); @@ -351,11 +351,9 @@ public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java index 5473666305..cc951579c7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandUnregisterTests.java @@ -32,7 +32,7 @@ static class BRIG_TREE { "children": { "string": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -45,7 +45,7 @@ static class BRIG_TREE { "children": { "string": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -65,7 +65,7 @@ static class BRIG_TREE { "children": { "string": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -85,7 +85,7 @@ static class BRIG_TREE { "children": { "string": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java index 349b573765..54565b613c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java @@ -85,7 +85,7 @@ void testOnEnableRegisterAndUnregisterCommand() { "children": { "argument": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -179,21 +179,34 @@ void testOnEnableRegisterAndUnregisterCommand() { Mockito.verify(updateCommandsPlayer, Mockito.times(2)).updateCommands(); // Make sure main command was removed from tree - // Note: The redirects for alias1 and alias2 are no longer listed. This is expected behavior. - // The redirect entry is supposed to point to where the target node is located in the dispatcher. - // Since the main node "command" doesn't exist anymore, the json serializer can't generate a path - // to the target node, and so it just doesn't add anything. // While the "command" node is no longer in the tree, the alias nodes still have a reference to it - // in their redirect modifier, so they still function perfectly fine as commands. + // as their redirect, so they still function perfectly fine as commands. assertEquals(""" { "type": "root", "children": { "alias1": { - "type": "literal" + "type": "literal", + "redirect": { + "type": "literal", + "children": { + "argument": { + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "properties": { + "type": "word" + }, + "executable": true + } + } + } }, "alias2": { - "type": "literal" + "type": "literal", + "redirect": [ + "alias1", + "redirect command" + ] } } }""", getDispatcherString()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java index 94f702a5bb..0dc2711673 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Set; -import org.bukkit.Color; import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -81,7 +80,7 @@ void commandBuildingTestWithLoopingAndTerminalBranchesAndFollowingArgument() { "children": { "value": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -92,38 +91,36 @@ void commandBuildingTestWithLoopingAndTerminalBranchesAndFollowingArgument() { } } }, - "loop2": { - "type": "literal", - "redirect": [ - "test", - "flags" - ] - }, "end1": { "type": "literal", "children": { "value": { "type": "argument", - "parser": "brigadier:integer", + "argumentType": "com.mojang.brigadier.arguments.IntegerArgumentType", "children": { - "argument": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } + "argument": [ + "test", + "flags", + "end2", + "argument" + ] } } } }, + "loop2": { + "type": "literal", + "redirect": [ + "test", + "flags" + ] + }, "end2": { "type": "literal", "children": { "argument": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -547,9 +544,6 @@ void suggestionsAndExecutionTestWithColorArgument() { new CustomArgument<>( new FlagsArgument("color") .loopingBranch( - // A DynamicMultiLiteral would be perfect here :P - // https://github.com/JorelAli/CommandAPI/issues/513 - // At least, this is how I imagine it would work new StringArgument("channel").replaceSuggestions(ArgumentSuggestions.strings(info -> { Set channelsLeft = new HashSet<>(Set.of("-r", "-g", "-b")); for(CommandArguments previousChannels : info.previousArgs().>getUnchecked("color")) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java index b72f375311..c03226155a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java @@ -109,35 +109,41 @@ void commandBuildingTestWithMultiLiteralArgument() { "option2": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } + "option1": [ + "command1", + "option1", + "option1" + ], + "option2": [ + "command1", + "option1", + "option2" + ], + "option3": [ + "command1", + "option1", + "option3" + ] } }, "option3": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } + "option1": [ + "command1", + "option1", + "option1" + ], + "option2": [ + "command1", + "option1", + "option2" + ], + "option3": [ + "command1", + "option1", + "option3" + ] } } } @@ -165,35 +171,41 @@ void commandBuildingTestWithMultiLiteralArgument() { "option2": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } + "option1": [ + "command2", + "option1", + "option1" + ], + "option2": [ + "command2", + "option1", + "option2" + ], + "option3": [ + "command2", + "option1", + "option3" + ] } }, "option3": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - }, - "option3": { - "type": "literal", - "executable": true - } + "option1": [ + "command2", + "option1", + "option1" + ], + "option2": [ + "command2", + "option1", + "option2" + ], + "option3": [ + "command2", + "option1", + "option3" + ] } } } @@ -220,14 +232,18 @@ void commandBuildingTestWithMultiLiteralArgument() { "option2": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } + "option1": [ + "command3", + "option1", + "option1", + "option1" + ], + "option2": [ + "command3", + "option1", + "option1", + "option2" + ] } } } @@ -235,32 +251,16 @@ void commandBuildingTestWithMultiLiteralArgument() { "option2": { "type": "literal", "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } + "option1": [ + "command3", + "option1", + "option1" + ], + "option2": [ + "command3", + "option1", + "option2" + ] } } } @@ -287,14 +287,18 @@ void commandBuildingTestWithMultiLiteralArgument() { "option2": { "type": "literal", "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } + "option1": [ + "command4", + "option1", + "option1", + "option1" + ], + "option2": [ + "command4", + "option1", + "option1", + "option2" + ] } } } @@ -302,32 +306,16 @@ void commandBuildingTestWithMultiLiteralArgument() { "option2": { "type": "literal", "children": { - "option1": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - }, - "option2": { - "type": "literal", - "children": { - "option1": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } - } - } + "option1": [ + "command4", + "option1", + "option1" + ], + "option2": [ + "command4", + "option1", + "option2" + ] } } } @@ -459,31 +447,6 @@ void executionTestWithSubcommands() { assertEquals(5, results.get()); } - @Test - void executionTestWithArrayConstructor() { - Mut results = Mut.of(); - - new CommandAPICommand("test") - .withArguments(new MultiLiteralArgument(new String[]{"lit1", "lit2", "lit3"})) - .executesPlayer(info -> { - results.set((String) info.args().get(0)); - }) - .register(); - - PlayerMock player = server.addPlayer(); - - server.dispatchCommand(player, "test lit1"); - assertEquals("lit1", results.get()); - - server.dispatchCommand(player, "test lit2"); - assertEquals("lit2", results.get()); - - server.dispatchCommand(player, "test lit3"); - assertEquals("lit3", results.get()); - - assertNoMoreResults(results); - } - /******************** * Suggestion tests * ********************/ diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java index 3fbc66b74f..68b38029c8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentTests.java @@ -92,7 +92,7 @@ void executionTestWithStringArgument() { "children": { "value": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/pom.xml b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/pom.xml index 24e40fd7a0..27d69e7d60 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/pom.xml +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/pom.xml @@ -25,12 +25,6 @@ - - com.mojang - brigadier - 1.0.17 - provided - com.mojang authlib @@ -40,6 +34,7 @@ + com.velocitypowered velocity-api 3.1.1 diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index aadd971b97..344529c1ce 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -1,15 +1,10 @@ package dev.jorel.commandapi; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.tree.ArgumentCommandNode; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.api.command.CommandManager; @@ -17,18 +12,16 @@ import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.proxy.ConsoleCommandSource; +import com.velocitypowered.api.proxy.Player; import dev.jorel.commandapi.arguments.Argument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; +import dev.jorel.commandapi.arguments.serializer.ArgumentTypeSerializer; import dev.jorel.commandapi.commandnodes.DifferentClientNode; import org.apache.logging.log4j.LogManager; -import java.io.File; -import java.io.IOException; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -75,7 +68,7 @@ public void onLoad(CommandAPIConfig config) { // We can't use a SafeVarHandle here because we don't have direct access to the // `com.velocitypowered.proxy.command.VelocityCommandManager` class that holds the field. - // That only exists in the proxy dependency, but we are using velocity-api. + // That only exists in the proxy dependency (which is hard to get), and we are using velocity-api. // However, we can get the class here through `commandManager.getClass()`. Field dispatcherField = CommandAPIHandler.getField(commandManager.getClass(), "dispatcher"); try { @@ -99,7 +92,16 @@ public void onEnable() { @SuppressWarnings("UnstableApiUsage") // This event is marked @Beta public void onCommandsSentToPlayer(PlayerAvailableCommandsEvent event) { // Rewrite nodes to their client-side version when commands are sent to a client - DifferentClientNode.rewriteAllChildren(event.getPlayer(), (RootCommandNode) event.getRootNode()); + RootCommandNode root = (RootCommandNode) event.getRootNode(); + Player client = event.getPlayer(); + + // Velocity's command copying code supports loops, so we don't have to run `onRegister = true` + // during node registration to remove those potential loops. We actually can't do that anyway, + // since Velocity removed the `CommandNode#literals` map, so literal nodes would not keep their + // server-side parsing behavior. I think technically current arguments would work if we only ran + // `onRegister = false`, but it's nice to keep this fact consistent. + DifferentClientNode.rewriteAllChildren(client, root, true); + DifferentClientNode.rewriteAllChildren(client, root, false); } @Override @@ -118,55 +120,8 @@ public CommandDispatcher getBrigadierDispatcher() { } @Override - public void createDispatcherFile(File file, CommandDispatcher brigadierDispatcher) throws IOException { - Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() - .toJson(serializeNodeToJson(dispatcher, dispatcher.getRoot()))); - } - - private static JsonObject serializeNodeToJson(CommandDispatcher dispatcher, CommandNode node) { - JsonObject output = new JsonObject(); - if (node instanceof RootCommandNode) { - output.addProperty("type", "root"); - } else if (node instanceof LiteralCommandNode) { - output.addProperty("type", "literal"); - } else if (node instanceof ArgumentCommandNode) { - ArgumentType type = ((ArgumentCommandNode) node).getType(); - output.addProperty("type", "argument"); - output.addProperty("argumentType", type.getClass().getName()); - // In Bukkit, serializing to json is handled internally - // They have an internal registry that connects ArgumentTypes to serializers that can - // include the specific properties of each argument as well (eg. min/max for an Integer) - // Velocity doesn't seem to have an internal map like this, but we could create our own - // In the meantime, I think it's okay to leave out properties here - } else { - CommandAPI.logError("Could not serialize node %s (%s)!".formatted(node, node.getClass())); - output.addProperty("type", "unknown"); - } - - JsonObject children = new JsonObject(); - - for (CommandNode child : node.getChildren()) { - children.add(child.getName(), serializeNodeToJson(dispatcher, child)); - } - - if (children.size() > 0) { - output.add("children", children); - } - - if (node.getCommand() != null) { - output.addProperty("executable", true); - } - - if (node.getRedirect() != null) { - Collection redirectPath = dispatcher.getPath(node.getRedirect()); - if (!redirectPath.isEmpty()) { - JsonArray redirectInfo = new JsonArray(); - redirectPath.forEach(redirectInfo::add); - output.add("redirect", redirectInfo); - } - } - - return output; + public Optional getArgumentTypeProperties(ArgumentType type) { + return ArgumentTypeSerializer.getProperties(type); } @Override @@ -198,6 +153,10 @@ public SuggestionProvider getSuggestionProvider(SuggestionProvide */ @Override public String validateNamespace(ExecutableCommand command, String namespace) { + if (namespace.isEmpty()) { + // Empty is fine (in fact it's the default), but it won't be matched by the pattern, so we pass it here + return namespace; + } if (!CommandAPIHandler.NAMESPACE_PATTERN.matcher(namespace).matches()) { CommandAPI.logNormal("Registering comand '" + command.getName() + "' using the default namespace because an invalid namespace (" + namespace + ") was given. Only 0-9, a-z, underscores, periods and hyphens are allowed!"); return config.getNamespace(); diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java new file mode 100644 index 0000000000..bbe2f06938 --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java @@ -0,0 +1,77 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.velocitypowered.api.command.CommandSource; +import dev.jorel.commandapi.executors.CommandArguments; + +import java.util.ArrayList; +import java.util.List; + +/** + * @apiNote Yields a {@code List<}{@link CommandArguments}{@code >} + */ +@SuppressWarnings("rawtypes") +public class FlagsArgument extends Argument implements FlagsArgumentCommon, CommandSource> { + // Setup information + private final List>> loopingBranches = new ArrayList<>(); + private final List>> terminalBranches = new ArrayList<>(); + + /** + * Constructs a {@link FlagsArgument}. + * + * @param nodeName the node name for this argument + */ + public FlagsArgument(String nodeName) { + super(nodeName, null); + } + + @Override + public FlagsArgument loopingBranch(Argument... arguments) { + loopingBranches.add(List.of(arguments)); + return this; + } + + @Override + public List>> getLoopingBranches() { + return loopingBranches; + } + + @Override + public FlagsArgument terminalBranch(Argument... arguments) { + terminalBranches.add(List.of(arguments)); + return this; + } + + @Override + public List>> getTerminalBranches() { + return terminalBranches; + } + + // Normal Argument stuff + @Override + public Class getPrimitiveType() { + return List.class; + } + + @Override + public CommandAPIArgumentType getArgumentType() { + return CommandAPIArgumentType.FLAGS_ARGUMENT; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // FlagsArgumentCommon interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the FlagsArgumentCommon interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public List parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { + return FlagsArgumentCommon.super.parseArgument(cmdCtx, key, previousArgs); + } + + @Override + public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, TerminalNodeModifier, CommandSource, Source> terminalNodeModifier) { + return FlagsArgumentCommon.super.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, terminalNodeModifier); + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index 42e40ac687..641a14aa0f 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -20,18 +20,15 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.CommandNode; import com.velocitypowered.api.command.CommandSource; - import dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; import java.util.List; -import java.util.function.Function; /** * A pseudo-argument representing a single literal string @@ -153,7 +150,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames,terminalExecutorCreator); + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, TerminalNodeModifier, CommandSource, Source> terminalNodeModifier) { + return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalNodeModifier); } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index 766e5188ae..4cae04fc01 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -20,7 +20,6 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -30,7 +29,6 @@ import dev.jorel.commandapi.executors.CommandArguments; import java.util.List; -import java.util.function.Function; /** * An argument that represents multiple LiteralArguments @@ -56,16 +54,6 @@ public MultiLiteralArgument(String nodeName, String... literals) { this.literals = literals; } - /** - * A multiliteral argument. Takes in string literals which cannot be modified - * @param literals the literals that this argument represents - * @deprecated Use {@link MultiLiteralArgument#MultiLiteralArgument(String, String...)} instead - */ - @Deprecated(since = "9.0.2", forRemoval = true) - public MultiLiteralArgument(final String[] literals) { - this(null, literals); - } - /** * A multiliteral argument. Takes in string literals which cannot be modified * @@ -110,7 +98,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { - return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, TerminalNodeModifier, CommandSource, Source> terminalNodeModifier) { + return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalNodeModifier); } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/ArgumentTypeSerializer.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/ArgumentTypeSerializer.java new file mode 100644 index 0000000000..673abfd8fd --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/ArgumentTypeSerializer.java @@ -0,0 +1,91 @@ +package dev.jorel.commandapi.arguments.serializer; + + +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + + + +@FunctionalInterface +public interface ArgumentTypeSerializer> { + // Interface for serializing ArgumentType + Optional extractProperties(T type); + + // Serializer registry + Map>, ArgumentTypeSerializer> argumentTypeSerializers = new HashMap<>(); + + static > void registerSerializer(Class clazz, ArgumentTypeSerializer serializer) { + argumentTypeSerializers.put(clazz, serializer); + } + + @FunctionalInterface + interface Properties> extends ArgumentTypeSerializer { + void fillJson(JsonObject result, T type); + + @Override + default Optional extractProperties(T type) { + JsonObject result = new JsonObject(); + fillJson(result, type); + return Optional.of(result); + } + } + + static > void registerPropertiesSerializer(Class clazz, Properties serializer) { + registerSerializer(clazz, serializer); + } + + ArgumentTypeSerializer NO_PROPERTIES = type -> Optional.empty(); + + static > void registerEmptySerializer(Class clazz) { + registerSerializer(clazz, (ArgumentTypeSerializer) NO_PROPERTIES); + } + + // Use serializers + Properties UNKNOWN = (result, type) -> result.addProperty("unknown", type.toString()); + + static > ArgumentTypeSerializer getSerializer(T type) { + initialize(); + return (ArgumentTypeSerializer) argumentTypeSerializers.getOrDefault(type.getClass(), UNKNOWN); + } + + static > Optional getProperties(T type) { + return getSerializer(type).extractProperties(type); + } + + // Initialize registry - for some reason interfaces can't have static initializers? + static void initialize() { + if (argumentTypeSerializers.containsKey(BoolArgumentType.class)) return; + + // BRIGADIER ARGUMENTS + registerEmptySerializer(BoolArgumentType.class); + + registerPropertiesSerializer(DoubleArgumentType.class, new NumberArgumentTypeSerializer<>( + -1.7976931348623157E308, Double.MAX_VALUE, + DoubleArgumentType::getMinimum, DoubleArgumentType::getMaximum + )); + registerPropertiesSerializer(FloatArgumentType.class, new NumberArgumentTypeSerializer<>( + -3.4028235E38F, Float.MAX_VALUE, + FloatArgumentType::getMinimum, FloatArgumentType::getMaximum + )); + registerPropertiesSerializer(IntegerArgumentType.class, new NumberArgumentTypeSerializer<>( + Integer.MIN_VALUE, Integer.MAX_VALUE, + IntegerArgumentType::getMinimum, IntegerArgumentType::getMaximum + )); + registerPropertiesSerializer(LongArgumentType.class, new NumberArgumentTypeSerializer<>( + Long.MIN_VALUE, Long.MAX_VALUE, + LongArgumentType::getMinimum, LongArgumentType::getMaximum + )); + + registerPropertiesSerializer(StringArgumentType.class, (result, type) -> + result.addProperty("type", switch (type.getType()) { + case SINGLE_WORD -> "unquoted"; + case QUOTABLE_PHRASE -> "quotable"; + case GREEDY_PHRASE -> "greedy"; + }) + ); + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/NumberArgumentTypeSerializer.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/NumberArgumentTypeSerializer.java new file mode 100644 index 0000000000..954914ed99 --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/serializer/NumberArgumentTypeSerializer.java @@ -0,0 +1,43 @@ +package dev.jorel.commandapi.arguments.serializer; + +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; + +import java.util.Objects; +import java.util.function.Function; + +public class NumberArgumentTypeSerializer> implements ArgumentTypeSerializer.Properties { + private final N absoluteMinimum; + private final N absoluteMaximum; + + private final Function getMinimum; + private final Function getMaximum; + + public NumberArgumentTypeSerializer( + N absoluteMinimum, N absoluteMaximum, + Function getMinimum, Function getMaximum + ) { + this.absoluteMinimum = absoluteMinimum; + this.absoluteMaximum = absoluteMaximum; + + this.getMinimum = getMinimum; + this.getMaximum = getMaximum; + } + + @Override + public void fillJson(JsonObject result, T type) { + N minimum = getMinimum.apply(type); + if (Objects.equals(minimum, absoluteMinimum)) { + result.addProperty("min", "absolute"); + } else { + result.addProperty("min", minimum); + } + + N maximum = getMaximum.apply(type); + if (Objects.equals(maximum, absoluteMaximum)) { + result.addProperty("max", "absolute"); + } else { + result.addProperty("max", maximum); + } + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index 23da2fe04a..1d9d6e5b6e 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -10,6 +10,7 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.executors.CommandArguments; import net.kyori.adventure.text.Component; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; @@ -83,7 +84,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { tags.add("hello"); tags.add("world"); - new CommandTree("proxy") + new CommandTree("proxytag") .then( new LiteralArgument("add").then( new StringArgument("tag").executes(info -> { @@ -114,6 +115,38 @@ public void onProxyInitialization(ProxyInitializeEvent event) { ) ) .register(); + + // example from https://github.com/JorelAli/CommandAPI/issues/483 + new CommandAPICommand("proxyflag") + .withArguments( + new FlagsArgument("filters") + .loopingBranch( + new LiteralArgument("filter", "sort").setListed(true), + new MultiLiteralArgument("sortType", "furthest", "nearest", "random") + ) + .loopingBranch( + new LiteralArgument("filter", "limit").setListed(true), + new IntegerArgument("limitAmount", 0) + ) + .loopingBranch( + new LiteralArgument("filter", "distance").setListed(true), + new IntegerArgument("low"), + new IntegerArgument("high") + ) + ) + .executes(info -> { + for (CommandArguments branch : info.args().>getUnchecked("filters")) { + String filterType = branch.getUnchecked("filter"); + info.sender().sendMessage(Component.text(switch (filterType) { + case "sort" -> "Sort " + branch.getUnchecked("sortType"); + case "limit" -> "Limit " + branch.getUnchecked("limitAmount"); + case "distance" -> "Distance " + branch.getUnchecked("low") + + " to " + branch.getUnchecked("high"); + default -> "Unknown branch " + filterType; + })); + } + }) + .register(); } @Subscribe From f77ee00b99413d3d6c9cb5a67fef75b8c4cc2847 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Thu, 4 Jul 2024 12:41:57 -0400 Subject: [PATCH 33/42] Update Examples.kt after rebasing https://github.com/JorelAli/CommandAPI/pull/569 into `dev/command-build-rewrite` --- .../kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt index f45f9ad453..3c3575073c 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt @@ -421,7 +421,7 @@ val noSelectorSuggestions = PlayerArgument("target") /* ANCHOR: argumentEntities3 */ CommandAPICommand("warp") .withArguments(noSelectorSuggestions) - .executesPlayer(PlayerCommandExecutor { player, args -> + .executesPlayer(NormalExecutor { player, args -> val target = args["target"] as Player player.teleport(target) }) From dee00f148469e0cd74987044064109203d7aae03 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:16:03 -0400 Subject: [PATCH 34/42] Clean up files (unused imports and spaces when other lines have tabs) Removed subcommand execution test from ArgumentMultiLiteralTests since subcommands are no longer converted to MultiLiterals --- .../commandapi/AbstractCommandAPICommand.java | 2 +- .../jorel/commandapi/CommandAPIPlatform.java | 17 +- .../jorel/commandapi/ExecutableCommand.java | 19 +- .../jorel/commandapi/arguments/Literal.java | 2 - .../commandapi/arguments/MultiLiteral.java | 2 - .../PreviewableArgumentBuilder.java | 12 +- .../commandnodes/PreviewableCommandNode.java | 124 ++++----- .../exceptions/CommandConflictException.java | 36 +-- .../commandapi/executors/NormalExecutor.java | 24 +- .../executors/NormalExecutorInfo.java | 12 +- .../executors/ResultingExecutor.java | 26 +- .../executors/ResultingExecutorInfo.java | 14 +- .../commandapi/help/EditableHelpTopic.java | 242 +++++++++--------- .../help/FullDescriptionGenerator.java | 18 +- .../help/ShortDescriptionGenerator.java | 8 +- .../jorel/commandapi/help/UsageGenerator.java | 24 +- .../jorel/commandapi/BukkitExecutable.java | 1 - .../executors/BukkitNormalTypedExecutor.java | 26 +- .../BukkitResultingTypedExecutor.java | 24 +- .../executors/BukkitTypedExecutor.java | 66 ++--- .../help/BukkitHelpTopicWrapper.java | 43 ++-- .../help/CustomCommandAPIHelpTopic.java | 192 +++++++------- .../test/CommandExecutionTests.java | 2 +- .../test/CommandHelpTestsPlugin.java | 8 +- .../test/RegisteredCommandTestBase.java | 36 +-- .../arguments/ArgumentMultiLiteralTests.java | 36 --- .../VelocityNormalTypedExecutor.java | 26 +- .../VelocityResultingTypedExecutor.java | 24 +- .../executors/VelocityTypedExecutor.java | 38 +-- 29 files changed, 535 insertions(+), 569 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 5d72517542..51c7467ea9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -303,7 +303,7 @@ protected List> createArgumentNod String aliasName = aliasNode.getLiteral(); childrenNodes.add( new RegisteredCommand.Node<>( - aliasName, rootNodeInformation.className(), aliasName, + aliasName, rootNodeInformation.className(), aliasName, rootNodeInformation.executable(), rootNodeInformation.permission(), rootNodeInformation.requirements(), rootNodeInformation.children() ) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index 2096b59855..005ef2860e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -45,6 +45,7 @@ public interface CommandAPIPlatform getSuggestionProvider(SuggestionProviders suggestionProvider); /** - * Ensures the given String is a valid command namespace on this platform. If the namespace + * Ensures the given String is a valid command namespace on this platform. If the namespace * is not valid, this method will return a String that should be used instead. - * + * * @param command The command being registered with the given namespace. * @param namespace The String that wants to be used as a namespace. * @return The String that should be used as the namespace. If the given String is a valid namespace, it will be returned. */ - public abstract String validateNamespace(ExecutableCommand command, String namespace); + public abstract String validateNamespace(ExecutableCommand command, String namespace); /** * Stuff to run before a command is generated. For Bukkit, this involves checking @@ -93,8 +94,8 @@ public interface CommandAPIPlatform registerCommandNode(LiteralArgumentBuilder node, String namespace); @@ -181,7 +182,7 @@ public void severe(String message, Throwable throwable) { /** * Generates a {@link Predicate} that evaluates whether a command sender meets the given permission. - * + * * @param permission The {@link CommandPermission} to check for. * @return A {@link Predicate} that tests if a command sender meets the given permission. */ diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 9b312ecdfd..00eb0ba04a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -139,7 +139,7 @@ public Impl withRequirement(Predicate requirement) { *

  • {@link ExecutableCommand#withHelp(String, String)}
  • * * Further calls to these methods will also be ignored. - * + * * @param helpTopic the help topic to use for this command * @return this command builder */ @@ -209,7 +209,7 @@ public Impl withUsage(String... usage) { /** * Sets the short description of this command to be generated using the given {@link ShortDescriptionGenerator}. * This is the help which is shown in the main /help menu. - * + * * @param description The {@link ShortDescriptionGenerator} to use to generate the short description. * @return this command builder */ @@ -223,7 +223,7 @@ public Impl withShortDescription(ShortDescriptionGenerator description) { /** * Sets the full description of this command to be generated using the given {@link FullDescriptionGenerator}. * This is the help which is shown in the specific /help page for this command (e.g. /help mycommand). - * + * * @param description The {@link FullDescriptionGenerator} to use to generate the full description. * @return this command builder */ @@ -237,7 +237,7 @@ public Impl withFullDescription(FullDescriptionGenerator descript /** * Sets the usage of this command to be generated using the given {@link UsageGenerator}. * This is the usage which is shown in the specific /help page for this command (e.g. /help mycommand). - * + * * @param usage The {@link UsageGenerator} to use to generate the usage. * @return this command builder */ @@ -317,7 +317,7 @@ public void setRequirements(Predicate requirements) { /** * Returns the {@link CommandAPIHelpTopic} for this command - * + * * @return the {@link CommandAPIHelpTopic} for this command */ public CommandAPIHelpTopic getHelpTopic() { @@ -354,6 +354,7 @@ public String[] getUsage() { ////////////////// // Registration // ////////////////// + /** * Overrides a command. Effectively the same as unregistering the command using * CommandAPI.unregister() and then registering the command using .register() @@ -373,7 +374,7 @@ public void register() { /** * Registers the command with the given namespace. * - * @param namespace The namespace for this command. This cannot be null, and each platform may impose additional requirements. + * @param namespace The namespace for this command. This cannot be null, and each platform may impose additional requirements. * See {@link CommandAPIPlatform#validateNamespace(ExecutableCommand, String)}. * @throws NullPointerException if the namespace is null. */ @@ -381,7 +382,11 @@ public void register(String namespace) { ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); } - protected static record CommandInformation(LiteralCommandNode rootNode, List> aliasNodes, RegisteredCommand command) { + protected static record CommandInformation( + LiteralCommandNode rootNode, + List> aliasNodes, + RegisteredCommand command + ) { } protected CommandInformation createCommandInformation(String namespace) { diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java index b7381ec93d..ecc49f1e14 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java @@ -1,6 +1,5 @@ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; @@ -12,7 +11,6 @@ import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.List; -import java.util.function.Function; import java.util.function.Predicate; /** diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index 0246c363bf..35ed8dfb28 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -1,6 +1,5 @@ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; @@ -15,7 +14,6 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; -import java.util.function.Function; import java.util.function.Predicate; /** diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java index 83492f8996..857ec5583e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java @@ -24,15 +24,15 @@ public class PreviewableArgumentBuilder extends ArgumentBuilder type; private SuggestionProvider suggestionsProvider = null; - // `Previewable` information - private final PreviewableFunction previewableFunction; + // `Previewable` information + private final PreviewableFunction previewableFunction; private final boolean legacy; private final boolean isListed; private PreviewableArgumentBuilder(String name, ArgumentType type, PreviewableFunction previewableFunction, boolean legacy, boolean isListed) { this.name = name; this.type = type; - + this.previewableFunction = previewableFunction; this.legacy = legacy; this.isListed = isListed; @@ -66,10 +66,10 @@ public String getName() { public PreviewableCommandNode build() { final PreviewableCommandNode result = new PreviewableCommandNode<>( - previewableFunction, legacy, isListed, - getName(), getType(), + previewableFunction, legacy, isListed, + getName(), getType(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider() - ); + ); for (final CommandNode argument : getArguments()) { result.addChild(argument); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java index c3ad0d05ed..5753709a43 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java @@ -27,82 +27,82 @@ * @param The type returned when this argument is parsed. */ public class PreviewableCommandNode extends ArgumentCommandNode { - private final PreviewableFunction preview; - private final boolean legacy; + private final PreviewableFunction preview; + private final boolean legacy; - // Instead of having a listed and unlisted copy of this class, we can just handle this with this boolean - private final boolean isListed; + // Instead of having a listed and unlisted copy of this class, we can just handle this with this boolean + private final boolean isListed; public PreviewableCommandNode( - PreviewableFunction preview, boolean legacy, boolean isListed, - String name, ArgumentType type, - Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions - ) { + PreviewableFunction preview, boolean legacy, boolean isListed, + String name, ArgumentType type, + Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions + ) { super(name, type, command, requirement, redirect, modifier, forks, customSuggestions); - this.preview = preview; - this.legacy = legacy; - this.isListed = isListed; + this.preview = preview; + this.legacy = legacy; + this.isListed = isListed; } - // Methods needed to generate a preview - public Optional> getPreview() { - return Optional.ofNullable(preview); - } + // Methods needed to generate a preview + public Optional> getPreview() { + return Optional.ofNullable(preview); + } - public boolean isLegacy() { - return legacy; - } + public boolean isLegacy() { + return legacy; + } - // If we are unlisted, then when parsed, don't add the argument result to the CommandContext - public boolean isListed() { - return isListed; - } + // If we are unlisted, then when parsed, don't add the argument result to the CommandContext + public boolean isListed() { + return isListed; + } - @Override - public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { - // Copied from `super#parse`, but with listability added - int start = reader.getCursor(); + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + // Copied from `super#parse`, but with listability added + int start = reader.getCursor(); - T result = this.getType().parse(reader); - ParsedArgument parsed = new ParsedArgument<>(start, reader.getCursor(), result); + T result = this.getType().parse(reader); + ParsedArgument parsed = new ParsedArgument<>(start, reader.getCursor(), result); - if(isListed) contextBuilder.withArgument(this.getName(), parsed); + if (isListed) contextBuilder.withArgument(this.getName(), parsed); - contextBuilder.withNode(this, parsed.getRange()); - } + contextBuilder.withNode(this, parsed.getRange()); + } - // Typical ArgumentCommandNode methods, but make it our classes + // Typical ArgumentCommandNode methods, but make it our classes // Mostly copied and inspired by the implementations for these methods in ArgumentCommandNode - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof PreviewableCommandNode other)) return false; - - if (!Objects.equals(this.preview, other.preview)) return false; - if (this.legacy != other.legacy) return false; - if (this.isListed != other.isListed) return false; - return super.equals(other); - } - - @Override - public int hashCode() { - int result = Objects.hash(this.preview, this.legacy, this.isListed); - result = 31*result + super.hashCode(); - return result; - } - - // TODO: Um, this currently doesn't work since PreviewableArgumentBuilder does not extend RequiredArgumentBuilder + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof PreviewableCommandNode other)) return false; + + if (!Objects.equals(this.preview, other.preview)) return false; + if (this.legacy != other.legacy) return false; + if (this.isListed != other.isListed) return false; + return super.equals(other); + } + + @Override + public int hashCode() { + int result = Objects.hash(this.preview, this.legacy, this.isListed); + result = 31 * result + super.hashCode(); + return result; + } + + // TODO: Um, this currently doesn't work since PreviewableArgumentBuilder does not extend RequiredArgumentBuilder // See PreviewableArgumentBuilder for why // I hope no one tries to use this method! - // @Override - // public PreviewableArgumentBuilder createBuilder() { - // PreviewableArgumentBuilder builder = PreviewableArgumentBuilder.previewableArgument(getName(), getType(), preview, legacy, isListed); - - // builder.requires(getRequirement()); - // builder.forward(getRedirect(), getRedirectModifier(), isFork()); - // if (getCommand() != null) { - // builder.executes(getCommand()); - // } - // return builder; - // } +// @Override +// public PreviewableArgumentBuilder createBuilder() { +// PreviewableArgumentBuilder builder = PreviewableArgumentBuilder.previewableArgument(getName(), getType(), preview, legacy, isListed); +// +// builder.requires(getRequirement()); +// builder.forward(getRedirect(), getRedirectModifier(), isFork()); +// if (getCommand() != null) { +// builder.executes(getCommand()); +// } +// return builder; +// } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java index eac8dfc43a..4aca1c1f2e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java @@ -6,27 +6,27 @@ * An exception caused when a command path that is already executable is registered again. */ public class CommandConflictException extends CommandRegistrationException { - /** - * Creates a new CommandConflictException. - * - * @param path The path of Brigadier node names that was re-registered. - */ - public CommandConflictException(List path) { - super(buildMessage(path)); - } + /** + * Creates a new CommandConflictException. + * + * @param path The path of Brigadier node names that was re-registered. + */ + public CommandConflictException(List path) { + super(buildMessage(path)); + } - private static String buildMessage(List path) { - StringBuilder builder = new StringBuilder(); + private static String buildMessage(List path) { + StringBuilder builder = new StringBuilder(); - builder.append("The command path \"/"); + builder.append("The command path \"/"); - for (String node : path) { - builder.append(node).append(" "); - } - builder.setCharAt(builder.length()-1, '\"'); + for (String node : path) { + builder.append(node).append(" "); + } + builder.setCharAt(builder.length() - 1, '\"'); - builder.append(" could not be registered because it conflicts with a previously registered command."); + builder.append(" could not be registered because it conflicts with a previously registered command."); - return builder.toString(); - } + return builder.toString(); + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java index 0864bd2b3f..654c1c785e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutor.java @@ -12,17 +12,17 @@ */ @FunctionalInterface public interface NormalExecutor extends NormalExecutorInfo { - @Override - default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info.sender(), info.args()); - } + @Override + default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { + this.run(info.sender(), info.args()); + } - /** - * Runs this executor. - * - * @param sender The command sender. - * @param arguments The {@link CommandArguments} for this command. - * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. - */ - void run(Sender sender, CommandArguments arguments) throws WrapperCommandSyntaxException; + /** + * Runs this executor. + * + * @param sender The command sender. + * @param arguments The {@link CommandArguments} for this command. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ + void run(Sender sender, CommandArguments arguments) throws WrapperCommandSyntaxException; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java index 228d894ad7..fab38b1746 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/NormalExecutorInfo.java @@ -12,11 +12,11 @@ */ @FunctionalInterface public interface NormalExecutorInfo { - /** - * Runs this executor. - * - * @param info The {@link ExecutionInfo} for this command. - * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. - */ + /** + * Runs this executor. + * + * @param info The {@link ExecutionInfo} for this command. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ void run(ExecutionInfo info) throws WrapperCommandSyntaxException; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java index b46bbb34a5..a71069003c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutor.java @@ -11,18 +11,18 @@ */ @FunctionalInterface public interface ResultingExecutor extends ResultingExecutorInfo { - @Override - default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info.sender(), info.args()); - } + @Override + default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { + return this.run(info.sender(), info.args()); + } - /** - * Runs this executor. - * - * @param sender The command sender. - * @param arguments The {@link CommandArguments} for this command. - * @return The int result of running this executor. - * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. - */ - int run(Sender sender, CommandArguments arguments) throws WrapperCommandSyntaxException; + /** + * Runs this executor. + * + * @param sender The command sender. + * @param arguments The {@link CommandArguments} for this command. + * @return The int result of running this executor. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ + int run(Sender sender, CommandArguments arguments) throws WrapperCommandSyntaxException; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java index b50aaa8be3..3ba472d842 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/executors/ResultingExecutorInfo.java @@ -11,12 +11,12 @@ */ @FunctionalInterface public interface ResultingExecutorInfo { - /** - * Runs this executor. - * - * @param info The {@link ExecutionInfo} for this command. - * @return The int result of running this executor. - * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. - */ + /** + * Runs this executor. + * + * @param info The {@link ExecutionInfo} for this command. + * @return The int result of running this executor. + * @throws WrapperCommandSyntaxException if something goes wrong while running this executor. + */ int run(ExecutionInfo info) throws WrapperCommandSyntaxException; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java index 367275a966..f0c9f40b4c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java @@ -12,50 +12,51 @@ * An {@link CommandAPIHelpTopic} that can have its short description, full description, and usage edited. */ public class EditableHelpTopic implements CommandAPIHelpTopic { - private ShortDescriptionGenerator shortDescription = Optional::empty; - private FullDescriptionGenerator fullDescription = forWho -> Optional.empty(); - private UsageGenerator usage = (forWho, argumentTree) -> Optional.empty(); - - /** - * Creates a new {@link EditableHelpTopic} that returns empty {@link Optional}s - * by default for its short description, full description, and usage. - */ - public EditableHelpTopic() { - - } - - /** - * Creates a new {@link EditableHelpTopic} that returns the given short description, full description, and usage by default. - * - * @param shortDescription The short description {@link String} for this command help. - * @param fullDescription The full description {@link String} for this command help. - * @param usage The {@link String} array that holds the usage for this command help. - */ - public EditableHelpTopic(@Nullable String shortDescription, @Nullable String fullDescription, @Nullable String[] usage) { - Optional shortDescriptionResult = Optional.ofNullable(shortDescription); - this.shortDescription = () -> shortDescriptionResult; - - Optional fullDescriptionResult = Optional.ofNullable(fullDescription); - this.fullDescription = forWho -> fullDescriptionResult; - - Optional usageResult = Optional.ofNullable(usage); - this.usage = (forWho, argumentTree) -> usageResult; - } - - // Static help results - /** + private ShortDescriptionGenerator shortDescription = Optional::empty; + private FullDescriptionGenerator fullDescription = forWho -> Optional.empty(); + private UsageGenerator usage = (forWho, argumentTree) -> Optional.empty(); + + /** + * Creates a new {@link EditableHelpTopic} that returns empty {@link Optional}s + * by default for its short description, full description, and usage. + */ + public EditableHelpTopic() { + + } + + /** + * Creates a new {@link EditableHelpTopic} that returns the given short description, full description, and usage by default. + * + * @param shortDescription The short description {@link String} for this command help. + * @param fullDescription The full description {@link String} for this command help. + * @param usage The {@link String} array that holds the usage for this command help. + */ + public EditableHelpTopic(@Nullable String shortDescription, @Nullable String fullDescription, @Nullable String[] usage) { + Optional shortDescriptionResult = Optional.ofNullable(shortDescription); + this.shortDescription = () -> shortDescriptionResult; + + Optional fullDescriptionResult = Optional.ofNullable(fullDescription); + this.fullDescription = forWho -> fullDescriptionResult; + + Optional usageResult = Optional.ofNullable(usage); + this.usage = (forWho, argumentTree) -> usageResult; + } + + // Static help results + + /** * Sets the short description for this command help. This is the help which is * shown in the main /help menu. * * @param description the short description for the command help * @return this {@link EditableHelpTopic} */ - public EditableHelpTopic withShortDescription(@Nullable String description) { - Optional result = Optional.ofNullable(description); - this.shortDescription = () -> result; + public EditableHelpTopic withShortDescription(@Nullable String description) { + Optional result = Optional.ofNullable(description); + this.shortDescription = () -> result; - return this; - } + return this; + } /** * Sets the full description for this command help. This is the help which is @@ -64,12 +65,12 @@ public EditableHelpTopic withShortDescription(@Nullable String de * @param description the full description for this command help * @return this {@link EditableHelpTopic} */ - public EditableHelpTopic withFullDescription(@Nullable String description) { - Optional result = Optional.ofNullable(description); - this.fullDescription = forWho -> result; + public EditableHelpTopic withFullDescription(@Nullable String description) { + Optional result = Optional.ofNullable(description); + this.fullDescription = forWho -> result; - return this; - } + return this; + } /** * Sets the short and full description for this command help. This is a short-hand @@ -79,12 +80,12 @@ public EditableHelpTopic withFullDescription(@Nullable String des * @param fullDescription the full description for this command help * @return this {@link EditableHelpTopic} */ - public EditableHelpTopic withHelp(@Nullable String shortDescription, @Nullable String fullDescription) { - this.withShortDescription(shortDescription); - this.withFullDescription(fullDescription); + public EditableHelpTopic withHelp(@Nullable String shortDescription, @Nullable String fullDescription) { + this.withShortDescription(shortDescription); + this.withFullDescription(fullDescription); - return this; - } + return this; + } /** * Sets the usage for this command help. This is the usage which is @@ -93,101 +94,102 @@ public EditableHelpTopic withHelp(@Nullable String shortDescripti * @param usage the full usage for this command * @return this {@link EditableHelpTopic} */ - public EditableHelpTopic withUsage(@Nullable String... usage) { - Optional result = Optional.ofNullable(usage); - this.usage = (forWho, argumentTree) -> result; + public EditableHelpTopic withUsage(@Nullable String... usage) { + Optional result = Optional.ofNullable(usage); + this.usage = (forWho, argumentTree) -> result; - return this; - } + return this; + } + + // Dynamic help results - // Dynamic help results /** * Sets the short description of this command help to be generated using the given {@link ShortDescriptionGenerator}. * This is the help which is shown in the main /help menu. - * + * * @param description The {@link ShortDescriptionGenerator} to use to generate the short description. * @return this {@link EditableHelpTopic} */ - public EditableHelpTopic withShortDescription(ShortDescriptionGenerator description) { - this.shortDescription = description; + public EditableHelpTopic withShortDescription(ShortDescriptionGenerator description) { + this.shortDescription = description; - return this; - } + return this; + } /** * Sets the full description of this command help to be generated using the given {@link FullDescriptionGenerator}. * This is the help which is shown in the specific /help page for the command (e.g. /help mycommand). - * + * * @param description The {@link FullDescriptionGenerator} to use to generate the full description. * @return this {@link EditableHelpTopic} */ - public EditableHelpTopic withFullDescription(FullDescriptionGenerator description) { - this.fullDescription = description; + public EditableHelpTopic withFullDescription(FullDescriptionGenerator description) { + this.fullDescription = description; - return this; - } + return this; + } /** * Sets the usage of this command help to be generated using the given {@link UsageGenerator}. * This is the usage which is shown in the specific /help page for the command (e.g. /help mycommand). - * + * * @param usage The {@link UsageGenerator} to use to generate the usage. * @return this {@link EditableHelpTopic} */ - public EditableHelpTopic withUsage(UsageGenerator usage) { - this.usage = usage; - - return this; - } - - // Implement CommandAPIHelpTopic methods - @Override - public Optional getShortDescription() { - return shortDescription.getShortDescription(); - } - - @Override - public Optional getFullDescription(@Nullable CommandSender forWho) { - return fullDescription.getFullDescription(forWho); - } - - @Override - public Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree) { - return usage.getUsage(forWho, argumentTree); - } - - // equals, hashCode, toString - // Since our fields are functions, they aren't easily compared - // However, the 'default' values returned by passing null parameters tend to make sense - // Just keep in mind that these return Optionals, and in the case of usage, the String[] should use Arrays methods - @Override - public final boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof EditableHelpTopic)) { - return false; - } - EditableHelpTopic other = (EditableHelpTopic) obj; - return Objects.equals(shortDescription.getShortDescription(), other.shortDescription.getShortDescription()) - && Objects.equals(fullDescription.getFullDescription(null), other.fullDescription.getFullDescription(null)) - && Arrays.equals(usage.getUsage(null, null).orElse(null), other.usage.getUsage(null, null).orElse(null)); - } - - @Override - public final int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(usage.getUsage(null, null).orElse(null)); - result = prime * result + Objects.hash(shortDescription.getShortDescription(), fullDescription.getFullDescription(null)); - return result; - } - - @Override - public final String toString() { - return "EditableHelpTopic [" - + "shortDescription=" + shortDescription.getShortDescription() - + ", fullDescription=" + fullDescription.getFullDescription(null) - + ", usage=" + usage.getUsage(null, null).map(Arrays::toString) + "]"; - } + public EditableHelpTopic withUsage(UsageGenerator usage) { + this.usage = usage; + + return this; + } + + // Implement CommandAPIHelpTopic methods + @Override + public Optional getShortDescription() { + return shortDescription.getShortDescription(); + } + + @Override + public Optional getFullDescription(@Nullable CommandSender forWho) { + return fullDescription.getFullDescription(forWho); + } + + @Override + public Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree) { + return usage.getUsage(forWho, argumentTree); + } + + // equals, hashCode, toString + // Since our fields are functions, they aren't easily compared + // However, the 'default' values returned by passing null parameters tend to make sense + // Just keep in mind that these return Optionals, and in the case of usage, the String[] should use Arrays methods + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof EditableHelpTopic)) { + return false; + } + EditableHelpTopic other = (EditableHelpTopic) obj; + return Objects.equals(shortDescription.getShortDescription(), other.shortDescription.getShortDescription()) + && Objects.equals(fullDescription.getFullDescription(null), other.fullDescription.getFullDescription(null)) + && Arrays.equals(usage.getUsage(null, null).orElse(null), other.usage.getUsage(null, null).orElse(null)); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(usage.getUsage(null, null).orElse(null)); + result = prime * result + Objects.hash(shortDescription.getShortDescription(), fullDescription.getFullDescription(null)); + return result; + } + + @Override + public final String toString() { + return "EditableHelpTopic [" + + "shortDescription=" + shortDescription.getShortDescription() + + ", fullDescription=" + fullDescription.getFullDescription(null) + + ", usage=" + usage.getUsage(null, null).map(Arrays::toString) + "]"; + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java index 2c4839fe0c..79f6208d46 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java @@ -12,13 +12,13 @@ */ @FunctionalInterface public interface FullDescriptionGenerator { - /** - * Returns an {@link Optional} containing the {@link String} that is the full description for this command help. - * - * @param forWho The {@code CommandSender} the full description should be generated for. For example, you - * could test if this sender has permission to see a branch in your command. This - * parameter may be null. - * @return An {@link Optional} {@link String} that is the full description for this command help. - */ - public Optional getFullDescription(@Nullable CommandSender forWho); + /** + * Returns an {@link Optional} containing the {@link String} that is the full description for this command help. + * + * @param forWho The {@code CommandSender} the full description should be generated for. For example, you + * could test if this sender has permission to see a branch in your command. This + * parameter may be null. + * @return An {@link Optional} {@link String} that is the full description for this command help. + */ + Optional getFullDescription(@Nullable CommandSender forWho); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java index 25b17c1d3e..051741cb85 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java @@ -8,8 +8,8 @@ */ @FunctionalInterface public interface ShortDescriptionGenerator { - /** - * @return An {@link Optional} {@link String} that is the short description for this command help. - */ - public Optional getShortDescription(); + /** + * @return An {@link Optional} {@link String} that is the short description for this command help. + */ + Optional getShortDescription(); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java index 5f18333828..f3a839f8fe 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java @@ -14,16 +14,16 @@ */ @FunctionalInterface public interface UsageGenerator { - /** - * Returns an {@link Optional} containing a {@code String[]}, where each item in the array - * represents a possible way to use the command. - * - * @param forWho The {@code CommandSender} the usage should be generated for. For example, you - * could test if this sender has permission to see a branch in your command. This - * parameter may be null. - * @param argumentTree The {@link RegisteredCommand.Node} that is the root node of the command the usage - * should be generated for. This parameter may be null. - * @return An {@link Optional} {@link String} array with the usage for this command help. - */ - public Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree); + /** + * Returns an {@link Optional} containing a {@code String[]}, where each item in the array + * represents a possible way to use the command. + * + * @param forWho The {@code CommandSender} the usage should be generated for. For example, you + * could test if this sender has permission to see a branch in your command. This + * parameter may be null. + * @param argumentTree The {@link RegisteredCommand.Node} that is the root node of the command the usage + * should be generated for. This parameter may be null. + * @return An {@link Optional} {@link String} array with the usage for this command help. + */ + Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java index d02ca16783..89c08d365d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java @@ -3,7 +3,6 @@ import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.command.ProxiedCommandSender; import org.bukkit.command.RemoteConsoleCommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java index 8adffe0631..ee90cb95ee 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitNormalTypedExecutor.java @@ -15,19 +15,19 @@ */ public record BukkitNormalTypedExecutor( - /** - * @return The {@link NormalExecutorInfo} to invoke when running this executor. - */ - NormalExecutorInfo executor, + /** + * @return The {@link NormalExecutorInfo} to invoke when running this executor. + */ + NormalExecutorInfo executor, - /** - * @return The {@link ExecutorType}s that this executor accepts. - */ - ExecutorType... types + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types ) implements BukkitTypedExecutor { - @Override - public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - executor.run(info); - return 1; - } + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + executor.run(info); + return 1; + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java index 9deaa1796b..b9697ed98f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitResultingTypedExecutor.java @@ -14,18 +14,18 @@ */ public record BukkitResultingTypedExecutor( - /** - * @return The {@link ResultingExecutorInfo} to invoke when running this executor. - */ - ResultingExecutorInfo executor, + /** + * @return The {@link ResultingExecutorInfo} to invoke when running this executor. + */ + ResultingExecutorInfo executor, - /** - * @return The {@link ExecutorType}s that this executor accepts. - */ - ExecutorType... types + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types ) implements BukkitTypedExecutor { - @Override - public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - return executor.run(info); - } + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + return executor.run(info); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java index bce6fa3524..c7e4ab7319 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java @@ -20,39 +20,39 @@ * @param The class for executing Brigadier commands. */ public interface BukkitTypedExecutor extends TypedExecutor { - @Override - default ExecutionInfo tryForSender(ExecutionInfo info) { - CommandSender sender = info.sender(); + @Override + default ExecutionInfo tryForSender(ExecutionInfo info) { + CommandSender sender = info.sender(); - for (ExecutorType type : types()) { - // Check if we can cast to the defined sender type - if (switch (type) { - case ALL -> true; - case PLAYER -> sender instanceof Player; - case ENTITY -> sender instanceof Entity; - case CONSOLE -> sender instanceof ConsoleCommandSender; - case BLOCK -> sender instanceof BlockCommandSender; - case PROXY -> sender instanceof ProxiedCommandSender; - case NATIVE -> { - // If we're a NATIVE executor, always accept and convert sender to a NativeProxyCommandSender - NativeProxyCommandSender proxyCommandSender = CommandAPIBukkit.get().getNativeProxyCommandSender(info.cmdCtx()); - info = info.copyWithNewSender(proxyCommandSender); - yield true; - } - case REMOTE -> sender instanceof RemoteConsoleCommandSender; - case FEEDBACK_FORWARDING -> { - PaperImplementations paper = CommandAPIBukkit.get().getPaper(); - yield paper.isPaperPresent() && paper.getFeedbackForwardingCommandSender().isInstance(sender); - } - }) { - return (ExecutionInfo) info; - } - } - return null; - } + for (ExecutorType type : types()) { + // Check if we can cast to the defined sender type + if (switch (type) { + case ALL -> true; + case PLAYER -> sender instanceof Player; + case ENTITY -> sender instanceof Entity; + case CONSOLE -> sender instanceof ConsoleCommandSender; + case BLOCK -> sender instanceof BlockCommandSender; + case PROXY -> sender instanceof ProxiedCommandSender; + case NATIVE -> { + // If we're a NATIVE executor, always accept and convert sender to a NativeProxyCommandSender + NativeProxyCommandSender proxyCommandSender = CommandAPIBukkit.get().getNativeProxyCommandSender(info.cmdCtx()); + info = info.copyWithNewSender(proxyCommandSender); + yield true; + } + case REMOTE -> sender instanceof RemoteConsoleCommandSender; + case FEEDBACK_FORWARDING -> { + PaperImplementations paper = CommandAPIBukkit.get().getPaper(); + yield paper.isPaperPresent() && paper.getFeedbackForwardingCommandSender().isInstance(sender); + } + }) { + return (ExecutionInfo) info; + } + } + return null; + } - /** - * @return The {@link ExecutorType}s that this executor accepts. - */ - ExecutorType[] types(); + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType[] types(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java index 6947e4f957..5c0857fff8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java @@ -16,26 +16,25 @@ */ public record BukkitHelpTopicWrapper( - /** - * @return The Bukkit {@link HelpTopic} being wrapped - */ - HelpTopic helpTopic) implements CommandAPIHelpTopic { - - @Override - public Optional getShortDescription() { - return Optional.of(helpTopic.getShortText()); - } - - @Override - public Optional getFullDescription(@Nullable CommandSender forWho) { - if (forWho == null) return Optional.empty(); - - return Optional.of(helpTopic.getFullText(forWho)); - } - - @Override - public Optional getUsage(@Nullable CommandSender forWho, @Nullable Node argumentTree) { - return Optional.empty(); - } - + /** + * @return The Bukkit {@link HelpTopic} being wrapped + */ + HelpTopic helpTopic) implements CommandAPIHelpTopic { + + @Override + public Optional getShortDescription() { + return Optional.of(helpTopic.getShortText()); + } + + @Override + public Optional getFullDescription(@Nullable CommandSender forWho) { + if (forWho == null) return Optional.empty(); + + return Optional.of(helpTopic.getFullText(forWho)); + } + + @Override + public Optional getUsage(@Nullable CommandSender forWho, @Nullable Node argumentTree) { + return Optional.empty(); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java index de4ec4dec1..34d060ef07 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java @@ -14,108 +14,108 @@ import dev.jorel.commandapi.RegisteredCommand; public class CustomCommandAPIHelpTopic extends HelpTopic { - private final String aliasString; - private final CommandAPIHelpTopic helpTopic; - private final RegisteredCommand.Node argumentTree; - - public CustomCommandAPIHelpTopic(String name, String[] aliases, CommandAPIHelpTopic helpTopic, RegisteredCommand.Node argumentTree) { - this.name = name; - - // Pre-generate alias help text since it doesn't depend on sender - if (aliases.length > 0) { - this.aliasString = "\n" + ChatColor.GOLD + "Aliases: " + ChatColor.WHITE + String.join(", ", aliases); - } else { - this.aliasString = ""; - } - - this.helpTopic = helpTopic; - this.argumentTree = argumentTree; - } - - @Override - public boolean canSee(@NotNull CommandSender sender) { - // Check if sender can see root node - return canSeeNode(sender, argumentTree); - } - - private boolean canSeeNode(CommandSender sender, RegisteredCommand.Node node) { - final boolean hasPermission; - - // Evaluate the CommandPermission - CommandPermission permission = node.permission(); - if (permission.equals(CommandPermission.NONE)) { - hasPermission = true; - } else if(permission.equals(CommandPermission.OP)) { - hasPermission = sender.isOp(); - } else { - Optional optionalStringPermission = permission.getPermission(); - if (optionalStringPermission.isPresent()) { - hasPermission = sender.hasPermission(optionalStringPermission.get()); - } else { - hasPermission = true; - } - } - - // If sender doesn't have permission (when negated if needed), they can't see this help - if (!hasPermission ^ permission.isNegated()) return false; - - // Check requirements - return node.requirements().test(sender); - } - - @Override - public @NotNull String getShortText() { - Optional shortDescriptionOptional = helpTopic.getShortDescription(); - if (shortDescriptionOptional.isPresent()) { - return shortDescriptionOptional.get(); - } else { - return helpTopic.getFullDescription(null) - .orElse("A command by the " + CommandAPIBukkit.getConfiguration().getPlugin().getName() + " plugin."); - } - } - - @Override - public @NotNull String getFullText(@NotNull CommandSender forWho) { - // Generate full text for the given sender - StringBuilder sb = new StringBuilder(this.getShortText()); - - // Add fullDescription if present - Optional fullDescriptionOptional = this.helpTopic.getFullDescription(forWho); - if (fullDescriptionOptional.isPresent()) { - sb.append("\n").append(ChatColor.GOLD).append("Description: ").append(ChatColor.WHITE).append(fullDescriptionOptional.get()); - } - - // Add usage if present, and otherwise generate default usage - String[] usages = this.helpTopic.getUsage(forWho, this.argumentTree).orElseGet(() -> generateDefaultUsage(forWho)); - - if (usages.length > 0) { - sb.append("\n").append(ChatColor.GOLD).append("Usage: ").append(ChatColor.WHITE); - // If 1 usage, put it on the same line, otherwise format like a list - if (usages.length == 1) { - sb.append(usages[0]); - } else { - for (String usage : usages) { - sb.append("\n- ").append(usage); - } - } - } - - // Add aliases - sb.append(this.aliasString); - - return sb.toString(); - } - - private String[] generateDefaultUsage(CommandSender forWho) { + private final String aliasString; + private final CommandAPIHelpTopic helpTopic; + private final RegisteredCommand.Node argumentTree; + + public CustomCommandAPIHelpTopic(String name, String[] aliases, CommandAPIHelpTopic helpTopic, RegisteredCommand.Node argumentTree) { + this.name = name; + + // Pre-generate alias help text since it doesn't depend on sender + if (aliases.length > 0) { + this.aliasString = "\n" + ChatColor.GOLD + "Aliases: " + ChatColor.WHITE + String.join(", ", aliases); + } else { + this.aliasString = ""; + } + + this.helpTopic = helpTopic; + this.argumentTree = argumentTree; + } + + @Override + public boolean canSee(@NotNull CommandSender sender) { + // Check if sender can see root node + return canSeeNode(sender, argumentTree); + } + + private boolean canSeeNode(CommandSender sender, RegisteredCommand.Node node) { + final boolean hasPermission; + + // Evaluate the CommandPermission + CommandPermission permission = node.permission(); + if (permission.equals(CommandPermission.NONE)) { + hasPermission = true; + } else if (permission.equals(CommandPermission.OP)) { + hasPermission = sender.isOp(); + } else { + Optional optionalStringPermission = permission.getPermission(); + if (optionalStringPermission.isPresent()) { + hasPermission = sender.hasPermission(optionalStringPermission.get()); + } else { + hasPermission = true; + } + } + + // If sender doesn't have permission (when negated if needed), they can't see this help + if (!hasPermission ^ permission.isNegated()) return false; + + // Check requirements + return node.requirements().test(sender); + } + + @Override + public @NotNull String getShortText() { + Optional shortDescriptionOptional = helpTopic.getShortDescription(); + if (shortDescriptionOptional.isPresent()) { + return shortDescriptionOptional.get(); + } else { + return helpTopic.getFullDescription(null) + .orElse("A command by the " + CommandAPIBukkit.getConfiguration().getPlugin().getName() + " plugin."); + } + } + + @Override + public @NotNull String getFullText(@NotNull CommandSender forWho) { + // Generate full text for the given sender + StringBuilder sb = new StringBuilder(this.getShortText()); + + // Add fullDescription if present + Optional fullDescriptionOptional = this.helpTopic.getFullDescription(forWho); + if (fullDescriptionOptional.isPresent()) { + sb.append("\n").append(ChatColor.GOLD).append("Description: ").append(ChatColor.WHITE).append(fullDescriptionOptional.get()); + } + + // Add usage if present, and otherwise generate default usage + String[] usages = this.helpTopic.getUsage(forWho, this.argumentTree).orElseGet(() -> generateDefaultUsage(forWho)); + + if (usages.length > 0) { + sb.append("\n").append(ChatColor.GOLD).append("Usage: ").append(ChatColor.WHITE); + // If 1 usage, put it on the same line, otherwise format like a list + if (usages.length == 1) { + sb.append(usages[0]); + } else { + for (String usage : usages) { + sb.append("\n- ").append(usage); + } + } + } + + // Add aliases + sb.append(this.aliasString); + + return sb.toString(); + } + + private String[] generateDefaultUsage(CommandSender forWho) { List usages = new ArrayList<>(); StringBuilder usageSoFar = new StringBuilder("/"); addUsageForNode(this.argumentTree, usages, usageSoFar, forWho); return usages.toArray(String[]::new); - } + } private void addUsageForNode(RegisteredCommand.Node node, List usages, StringBuilder usageSoFar, CommandSender forWho) { - // If sender can't see the node, don't include it in the usage - if(!canSeeNode(forWho, node)) return; + // If sender can't see the node, don't include it in the usage + if (!canSeeNode(forWho, node)) return; // Add node to usage usageSoFar.append(node.helpString()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java index f1e988b2c7..fb301a322e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandExecutionTests.java @@ -63,7 +63,7 @@ public void tearDown() { // server itself will never use its own `org.bukkit.craftbukkit.command.ProxiedNativeCommandSender` class. // So, if you can make a class cast exception happen on a server, change this mock to `ProxiedCommandSender` // and fix `executesProxy`, but otherwise we can provide the more specific `NativeProxyCommandSender` class. -// executorTypeToMockSender.put(ExecutorType.PROXY, () -> Mockito.mock(ProxiedCommandSender.class)); +// executorTypeToMockSender.put(ExecutorType.PROXY, () -> Mockito.mock(ProxiedCommandSender.class)); executorTypeToMockSender.put(ExecutorType.PROXY, () -> Mockito.mock(NativeProxyCommandSender.class)); executorTypeToMockSender.put(ExecutorType.REMOTE, () -> Mockito.mock(RemoteConsoleCommandSender.class)); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java index 083a296a97..edc6541850 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java @@ -9,7 +9,7 @@ import org.bukkit.plugin.java.JavaPluginLoader; public class CommandHelpTestsPlugin extends JavaPlugin { - // Additional constructors required for MockBukkit + // Additional constructors required for MockBukkit public CommandHelpTestsPlugin() { super(); } @@ -28,8 +28,8 @@ public static InputStream pluginYaml() { website: https://www.jorel.dev/CommandAPI/ api-version: 1.13 commands: - registeredCommand: - registeredAlias: - """.getBytes()); + registeredCommand: + registeredAlias: + """.getBytes()); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java index aef00c7833..97f7e4769d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java @@ -34,10 +34,10 @@ public void setUp() { public void tearDown() { super.tearDown(); } - - /******************* - * Utility methods * - *******************/ + + /******************* + * Utility methods * + *******************/ @SafeVarargs public final void assertCreatedSimpleRegisteredCommand(String name, NodeBuilder args, List... argsAsStr) { @@ -49,7 +49,7 @@ public final void assertCreatedSimpleRegisteredCommand(String name, NodeBuilder assertEquals(Arrays.asList(argsAsStr), CommandAPI.getRegisteredCommands().get(0).rootNode().argsAsStr()); } - public RegisteredCommand simpleRegisteredCommand(String name, String namespace, NodeBuilder args, String... aliases) { + public RegisteredCommand simpleRegisteredCommand(String name, String namespace, NodeBuilder args, String... aliases) { return new RegisteredCommand<>( name, aliases, namespace, new EditableHelpTopic<>(), @@ -63,12 +63,12 @@ public static NodeBuilder node(String name, Class clazz, boolean executable) } public static List> children(NodeBuilder... children) { - List> result = new ArrayList<>(children.length); - for (NodeBuilder child : children) { - result.add(child.build()); - } - return result; - } + List> result = new ArrayList<>(children.length); + for (NodeBuilder child : children) { + result.add(child.build()); + } + return result; + } private final String nodeName; private final String className; @@ -103,7 +103,7 @@ public NodeBuilder requirements(Predicate requirements) { this.requirements = requirements; return this; } - + public NodeBuilder withChildren(NodeBuilder... children) { for (NodeBuilder child : children) { this.children.add(child.build()); @@ -116,14 +116,14 @@ public final NodeBuilder withChildren(Node... children) { return withChildren(Arrays.asList(children)); } - public NodeBuilder withChildren(List> children) { - this.children.addAll(children); - return this; - } + public NodeBuilder withChildren(List> children) { + this.children.addAll(children); + return this; + } public Node build() { - return new Node(nodeName, className, helpString, executable, permission, requirements, children); - } + return new Node<>(nodeName, className, helpString, executable, permission, requirements, children); + } } @SafeVarargs diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java index c03226155a..567c37f2c5 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java @@ -3,13 +3,9 @@ import be.seeseemelk.mockbukkit.entity.PlayerMock; import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.CommandTree; -import dev.jorel.commandapi.arguments.IntegerArgument; -import dev.jorel.commandapi.arguments.ItemStackArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.test.Mut; import dev.jorel.commandapi.test.TestBase; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -415,38 +411,6 @@ void executionTestWithMultipleMultiLiteralArguments() { assertNoMoreResults(results); } - @Test - void executionTestWithSubcommands() { - // Doing these because subcommands are converted into MultiLiteralArguments - Mut results = Mut.of(); - - new CommandAPICommand("test") - .withSubcommand(new CommandAPICommand("hello") - .withArguments(new ItemStackArgument("hello")) - .executesPlayer(info -> { - results.set(info.args().get("hello")); - }) - ) - .withSubcommand(new CommandAPICommand("bye") - .withArguments(new IntegerArgument("bye")) - .executesPlayer(info -> { - results.set(info.args().get("bye")); - }) - ) - .register(); - - PlayerMock player = server.addPlayer(); - - // /test hello minecraft:stick - ItemStack item = new ItemStack(Material.STICK); - server.dispatchCommand(player, "test hello minecraft:stick"); - assertEquals(item, results.get()); - - // /test bye 5 - server.dispatchCommand(player, "test bye 5"); - assertEquals(5, results.get()); - } - /******************** * Suggestion tests * ********************/ diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java index d0e700c6d0..a3604501b6 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityNormalTypedExecutor.java @@ -14,19 +14,19 @@ */ public record VelocityNormalTypedExecutor( - /** - * @return The {@link NormalExecutorInfo} to invoke when running this executor. - */ - NormalExecutorInfo executor, + /** + * @return The {@link NormalExecutorInfo} to invoke when running this executor. + */ + NormalExecutorInfo executor, - /** - * @return The {@link ExecutorType}s that this executor accepts. - */ - ExecutorType... types + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types ) implements VelocityTypedExecutor { - @Override - public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - executor.run(info); - return 1; - } + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + executor.run(info); + return 1; + } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java index 5d725c0db1..291c70c317 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityResultingTypedExecutor.java @@ -13,18 +13,18 @@ */ public record VelocityResultingTypedExecutor( - /** - * @return The {@link ResultingExecutorInfo} to invoke when running this executor. - */ - ResultingExecutorInfo executor, + /** + * @return The {@link ResultingExecutorInfo} to invoke when running this executor. + */ + ResultingExecutorInfo executor, - /** - * @return The {@link ExecutorType}s that this executor accepts. - */ - ExecutorType... types + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType... types ) implements VelocityTypedExecutor { - @Override - public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - return executor.run(info); - } + @Override + public int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { + return executor.run(info); + } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java index 93fb48897a..006096aa55 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/executors/VelocityTypedExecutor.java @@ -11,25 +11,25 @@ * @param The {@link CommandSource} class that this executor accepts. */ public interface VelocityTypedExecutor extends TypedExecutor { - @Override - default ExecutionInfo tryForSender(ExecutionInfo info) { - CommandSource sender = info.sender(); + @Override + default ExecutionInfo tryForSender(ExecutionInfo info) { + CommandSource sender = info.sender(); - for (ExecutorType type : types()) { - // Check if we can cast to the defined sender type - if (switch (type) { - case ALL -> true; - case PLAYER -> sender instanceof Player; - case CONSOLE -> sender instanceof ConsoleCommandSource; - }) { - return (ExecutionInfo) info; - } - } - return null; - } + for (ExecutorType type : types()) { + // Check if we can cast to the defined sender type + if (switch (type) { + case ALL -> true; + case PLAYER -> sender instanceof Player; + case CONSOLE -> sender instanceof ConsoleCommandSource; + }) { + return (ExecutionInfo) info; + } + } + return null; + } - /** - * @return The {@link ExecutorType}s that this executor accepts. - */ - ExecutorType[] types(); + /** + * @return The {@link ExecutorType}s that this executor accepts. + */ + ExecutorType[] types(); } From 3dfa38fbe6bdd5a603c843c2da6e4a27411fa17a Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:47:01 -0400 Subject: [PATCH 35/42] Address some SonarCloud issues --- .../jorel/commandapi/CommandAPIHandler.java | 24 ++++++------- .../commandnodes/DifferentClientNode.java | 6 ++-- .../DynamicMultiLiteralArgumentBuilder.java | 6 +--- .../DynamicMultiLiteralCommandNode.java | 27 +++++++------- .../commandnodes/FlagsArgumentRootNode.java | 17 +++++++++ .../PreviewableArgumentBuilder.java | 33 ++++++++++------- .../commandnodes/PreviewableCommandNode.java | 35 ++++++++----------- .../jorel/commandapi/kotlindsl/ExecutorDSL.kt | 1 - .../jorel/commandapi/CommandAPIBukkit.java | 18 +++++----- .../arguments/ArgumentListabilityTests.java | 2 +- .../CommandConflictExceptionTests.java | 2 +- .../DuplicateNodeNameExceptionTests.java | 2 +- .../GreedyArgumentExceptionTests.java | 2 +- .../InvalidCommandNameExceptionTests.java | 2 +- .../MissingCommandExecutorExceptionTests.java | 2 +- 15 files changed, 95 insertions(+), 84 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index 603ea35696..e3435b16b4 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -449,7 +449,7 @@ public void writeDispatcherToFile(File file, CommandDispatcher dispatche .toJson(serializeNodeToJson(dispatcher.getRoot()))); } - private record Node(CommandNode commandNode, Consumer resultConsumer, String[] path) { + private record Node(CommandNode commandNode, Consumer resultConsumer, JsonArray path) { } public JsonObject serializeNodeToJson(CommandNode rootNode) { @@ -464,7 +464,7 @@ public JsonObject serializeNodeToJson(CommandNode rootNode) { // Extract serialization of the rootNode as our result JsonObject resultHolder = new JsonObject(); - redirectsToProcess.offer(new Node<>(rootNode, result -> resultHolder.add("result", result), new String[0])); + redirectsToProcess.offer(new Node<>(rootNode, result -> resultHolder.add("result", result), new JsonArray())); Node node; while ((node = redirectsToProcess.poll()) != null) { @@ -476,17 +476,15 @@ public JsonObject serializeNodeToJson(CommandNode rootNode) { // Add information to parent JsonArray path = shortestPath.get(commandNode); if (path != null) { - // Node has already appeared earlier in the traversal + // Represent this node with the shortest path node.resultConsumer.accept(path); continue; } + // This is the first time finding this node - path = new JsonArray(); - for (String step : node.path) { - path.add(step); - } - shortestPath.put(commandNode, path); + shortestPath.put(commandNode, node.path); + // Represent this node with a new object JsonObject output = new JsonObject(); node.resultConsumer.accept(output); @@ -502,9 +500,8 @@ public JsonObject serializeNodeToJson(CommandNode rootNode) { for (CommandNode child : children) { String name = child.getName(); - String[] newPath = new String[node.path.length + 1]; - System.arraycopy(node.path, 0, newPath, 0, node.path.length); - newPath[node.path.length] = name; + JsonArray newPath = node.path.deepCopy(); + newPath.add(name); nodesToProcess.offer(new Node<>(child, result -> childrenHolder.add(name, result), newPath)); } @@ -518,9 +515,8 @@ public JsonObject serializeNodeToJson(CommandNode rootNode) { // Redirect CommandNode redirect = commandNode.getRedirect(); if (redirect != null) { - String[] newPath = new String[node.path.length + 1]; - System.arraycopy(node.path, 0, newPath, 0, node.path.length); - newPath[node.path.length] = "redirect " + redirect.getName(); + JsonArray newPath = node.path.deepCopy(); + newPath.add("redirect " + redirect.getName()); redirectsToProcess.offer(new Node<>(redirect, result -> output.add("redirect", result), newPath)); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java index e8714496d5..344dcbd2a8 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java @@ -87,7 +87,7 @@ abstract class Argument extends ArgumentCommandNode implem // Node information private final ArgumentType type; - public Argument( + protected Argument( String name, ArgumentType type, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks @@ -97,7 +97,7 @@ public Argument( // when `ArgumentCommandNode#createBuilder` is called. It would be nice // to override `createBuilder` to return this class, but that isn't possible. // https://github.com/Mojang/brigadier/pull/144 :( - super(name, new Type(), command, requirement, redirect, modifier, forks, null); + super(name, new Type<>(), command, requirement, redirect, modifier, forks, null); ((Type) super.getType()).node = this; @@ -164,7 +164,7 @@ public int hashCode() { } abstract class Literal extends LiteralCommandNode implements DifferentClientNode { - public Literal( + protected Literal( String literal, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java index 6cb9aa24aa..478e1d5621 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java @@ -43,11 +43,7 @@ public LiteralsCreator getLiteralsCreator() { // Create node @Override public DynamicMultiLiteralCommandNode build() { - final DynamicMultiLiteralCommandNode result = new DynamicMultiLiteralCommandNode<>( - name, isListed, literalsCreator, - getCommand(), getRequirement(), - getRedirect(), getRedirectModifier(), isFork() - ); + final DynamicMultiLiteralCommandNode result = new DynamicMultiLiteralCommandNode<>(this); for (final CommandNode argument : getArguments()) { result.addChild(argument); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java index 1c6fcf4415..f9a1be7c3e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java @@ -1,8 +1,6 @@ package dev.jorel.commandapi.commandnodes; import com.google.gson.JsonArray; -import com.mojang.brigadier.Command; -import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -16,9 +14,10 @@ import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.arguments.DynamicMultiLiteralArgumentCommon.LiteralsCreator; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.function.Predicate; public class DynamicMultiLiteralCommandNode extends DifferentClientNode.Argument { static { @@ -36,16 +35,16 @@ public class DynamicMultiLiteralCommandNode extends Diffe private final boolean isListed; private final LiteralsCreator literalsCreator; - public DynamicMultiLiteralCommandNode( - String name, boolean isListed, LiteralsCreator literalsCreator, - Command command, Predicate requirement, - CommandNode redirect, RedirectModifier modifier, boolean forks - ) { + public DynamicMultiLiteralCommandNode(DynamicMultiLiteralArgumentBuilder builder) { // This mostly acts like a StringArgument - super(name, StringArgumentType.word(), command, requirement, redirect, modifier, forks); - - this.isListed = isListed; - this.literalsCreator = literalsCreator; + super( + builder.getName(), StringArgumentType.word(), + builder.getCommand(), builder.getRequirement(), + builder.getRedirect(), builder.getRedirectModifier(), builder.isFork() + ); + + this.isListed = builder.isListed(); + this.literalsCreator = builder.getLiteralsCreator(); } // Getters @@ -64,7 +63,7 @@ public List> rewriteNodeForClient(CommandNode node, if (onRegister) { // However, we do need to ensure the node currently in the tree is a DynamicMultiLiteralCommandNode, // and not a copied argument, so we can be properly parsed server-side - if (node instanceof DynamicMultiLiteralCommandNode) return null; // No rewrite + if (node instanceof DynamicMultiLiteralCommandNode) return null; // No rewrite CommandNode result = DynamicMultiLiteralArgumentBuilder .dynamicMultiLiteral(this.getName(), this.isListed, this.literalsCreator) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java index 0eb08abc5a..e1c87d8462 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -83,4 +84,20 @@ public void parse(StringReader reader, CommandContextBuilder contextBuil public String toString() { return ""; } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof FlagsArgumentRootNode other)) return false; + + if (!Objects.equals(this.hiddenRedirect, other.hiddenRedirect)) return false; + return super.equals(other); + } + + @Override + public int hashCode() { + int result = Objects.hash(this.hiddenRedirect); + result = 31 * result + super.hashCode(); + return result; + } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java index 857ec5583e..b70e1a13cc 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java @@ -5,7 +5,6 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.CommandNode; - import dev.jorel.commandapi.arguments.Previewable; import dev.jorel.commandapi.wrappers.PreviewableFunction; @@ -47,29 +46,39 @@ public PreviewableArgumentBuilder suggests(final SuggestionProvider getSuggestionsProvider() { - return suggestionsProvider; - } - + // Getters @Override protected PreviewableArgumentBuilder getThis() { return this; } + public String getName() { + return name; + } + public ArgumentType getType() { return type; } - public String getName() { - return name; + public SuggestionProvider getSuggestionsProvider() { + return suggestionsProvider; + } + + public PreviewableFunction getPreviewableFunction() { + return previewableFunction; + } + + public boolean isLegacy() { + return legacy; + } + + public boolean isListed() { + return isListed; } + // Create node public PreviewableCommandNode build() { - final PreviewableCommandNode result = new PreviewableCommandNode<>( - previewableFunction, legacy, isListed, - getName(), getType(), - getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider() - ); + final PreviewableCommandNode result = new PreviewableCommandNode<>(this); for (final CommandNode argument : getArguments()) { result.addChild(argument); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java index 5753709a43..0d6dbd8da3 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java @@ -1,23 +1,16 @@ package dev.jorel.commandapi.commandnodes; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Predicate; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.StringReader; -import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.ArgumentCommandNode; -import com.mojang.brigadier.tree.CommandNode; - import dev.jorel.commandapi.arguments.Previewable; import dev.jorel.commandapi.wrappers.PreviewableFunction; +import java.util.Objects; +import java.util.Optional; + /** * A special type of {@link ArgumentCommandNode} for {@link Previewable} arguments. Compared to the * {@link ArgumentCommandNode}, this class also has the methods {@link #getPreview()} and {@link #isLegacy()}, @@ -30,18 +23,20 @@ public class PreviewableCommandNode extends ArgumentCommandNode preview; private final boolean legacy; - // Instead of having a listed and unlisted copy of this class, we can just handle this with this boolean + // Instead of having a listed and unlisted copy of this class, we can handle both with this boolean private final boolean isListed; - public PreviewableCommandNode( - PreviewableFunction preview, boolean legacy, boolean isListed, - String name, ArgumentType type, - Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions - ) { - super(name, type, command, requirement, redirect, modifier, forks, customSuggestions); - this.preview = preview; - this.legacy = legacy; - this.isListed = isListed; + public PreviewableCommandNode(PreviewableArgumentBuilder builder) { + super( + builder.getName(), builder.getType(), + builder.getCommand(), builder.getRequirement(), + builder.getRedirect(), builder.getRedirectModifier(), builder.isFork(), + builder.getSuggestionsProvider() + ); + + this.preview = builder.getPreviewableFunction(); + this.legacy = builder.isLegacy(); + this.isListed = builder.isListed(); } // Methods needed to generate a preview diff --git a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt index 58115f293e..f03117ca48 100644 --- a/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt +++ b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/ExecutorDSL.kt @@ -12,7 +12,6 @@ import dev.jorel.commandapi.wrappers.NativeProxyCommandSender import org.bukkit.command.BlockCommandSender import org.bukkit.command.CommandSender import org.bukkit.command.ConsoleCommandSender -import org.bukkit.command.ProxiedCommandSender import org.bukkit.command.RemoteConsoleCommandSender import org.bukkit.entity.Entity import org.bukkit.entity.Player diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index 47bea75a37..8f920ecf0c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -64,7 +64,7 @@ protected CommandAPIBukkit() { @SuppressWarnings("unchecked") public static CommandAPIBukkit get() { - if(CommandAPIBukkit.instance != null) { + if (CommandAPIBukkit.instance != null) { return (CommandAPIBukkit) instance; } else { throw new IllegalStateException("Tried to access CommandAPIBukkit instance, but it was null! Are you using CommandAPI features before calling CommandAPI#onLoad?"); @@ -76,7 +76,7 @@ public PaperImplementations getPaper() { } public static InternalBukkitConfig getConfiguration() { - if(config != null) { + if (config != null) { return config; } else { throw new IllegalStateException("Tried to access InternalBukkitConfig, but it was null! Did you load the CommandAPI properly with CommandAPI#onLoad?"); @@ -89,7 +89,7 @@ public CommandRegistrationStrategy getCommandRegistrationStrategy() { @Override public void onLoad(CommandAPIConfig config) { - if(config instanceof CommandAPIBukkitConfig bukkitConfig) { + if (config instanceof CommandAPIBukkitConfig bukkitConfig) { CommandAPIBukkit.setInternalConfig(new InternalBukkitConfig(bukkitConfig)); } else { CommandAPI.logError("CommandAPIBukkit was loaded with non-Bukkit config!"); @@ -98,7 +98,7 @@ public void onLoad(CommandAPIConfig config) { checkDependencies(); } - + private static void setInternalConfig(InternalBukkitConfig internalBukkitConfig) { CommandAPIBukkit.config = internalBukkitConfig; } @@ -198,9 +198,9 @@ void updateHelpForCommands(List> commands) { String namespaceAddon = (command.namespace().isEmpty() ? "" : command.namespace() + ":"); String commandName = namespaceAddon + command.commandName(); CommandAPIHelpTopic commandAPIHelpTopic = command.helpTopic(); - + // Don't override other plugin's help topics - if(Bukkit.getPluginCommand(commandName) == null) { + if (Bukkit.getPluginCommand(commandName) == null) { final HelpTopic helpTopic; if (commandAPIHelpTopic instanceof BukkitHelpTopicWrapper bukkitHelpTopic) { helpTopic = bukkitHelpTopic.helpTopic(); @@ -214,7 +214,7 @@ void updateHelpForCommands(List> commands) { String aliasName = namespaceAddon + alias; // Don't override other plugin's help topics - if(Bukkit.getPluginCommand(aliasName) != null) { + if (Bukkit.getPluginCommand(aliasName) != null) { continue; } @@ -455,7 +455,7 @@ public static WrapperCommandSyntaxException failWithAdventureComponent(Component public static WrapperCommandSyntaxException failWithAdventureComponent(ComponentLike message) { return CommandAPI.failWithMessage(BukkitTooltip.messageFromAdventureComponent(message.asComponent())); } - + /** * Initializes the CommandAPI's implementation of an NBT API. If you are shading * the CommandAPI, you should be using @@ -475,7 +475,7 @@ public static WrapperCommandSyntaxException failWithAdventureComponent(Component public static void initializeNBTAPI(Class nbtContainerClass, Function nbtContainerConstructor) { getConfiguration().lateInitializeNBT(nbtContainerClass, nbtContainerConstructor); } - + protected void registerBukkitRecipesSafely(Iterator recipes) { Recipe recipe; while (recipes.hasNext()) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java index 86b5ae21f3..cccd86a6af 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java @@ -14,7 +14,7 @@ * Tests for the effects of the {@link AbstractArgument#setListed(boolean)} method. * Arguments may use different {@link CommandNode} implementation that implement this with separate code. */ -public class ArgumentListabilityTests extends TestBase { +class ArgumentListabilityTests extends TestBase { /********* * Setup * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java index 26abc83021..d8ba0d406e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/CommandConflictExceptionTests.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class CommandConflictExceptionTests extends TestBase { +class CommandConflictExceptionTests extends TestBase { /********* * Setup * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java index 6aa86b2290..cce719643e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/DuplicateNodeNameExceptionTests.java @@ -16,7 +16,7 @@ /** * Tests for the {@link DuplicateNodeNameException}. */ -public class DuplicateNodeNameExceptionTests extends TestBase { +class DuplicateNodeNameExceptionTests extends TestBase { /********* * Setup * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java index 27d0ada1dd..16ad165ae7 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/GreedyArgumentExceptionTests.java @@ -15,7 +15,7 @@ /** * Tests for the {@link GreedyArgumentException}. */ -public class GreedyArgumentExceptionTests extends TestBase { +class GreedyArgumentExceptionTests extends TestBase { /********* * Setup * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java index c6a0803838..7d05b2282a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/InvalidCommandNameExceptionTests.java @@ -11,7 +11,7 @@ /** * Tests for the {@link InvalidCommandNameException}. */ -public class InvalidCommandNameExceptionTests extends TestBase { +class InvalidCommandNameExceptionTests extends TestBase { /********* * Setup * diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java index 847320b6de..f129522236 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/exceptions/MissingCommandExecutorExceptionTests.java @@ -15,7 +15,7 @@ /** * Tests for the {@link MissingCommandExecutorException}. */ -public class MissingCommandExecutorExceptionTests extends TestBase { +class MissingCommandExecutorExceptionTests extends TestBase { /********* * Setup * From 8c20d4e51a6da69d664bad194fb1e8188e860a58 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:44:35 -0400 Subject: [PATCH 36/42] Add serializers for custom CommandNodes Add NodeTypeSerializerTests --- .../commandnodes/FlagsArgumentEndingNode.java | 38 +- .../commandnodes/FlagsArgumentRootNode.java | 14 + .../commandnodes/NamedLiteralCommandNode.java | 8 + .../commandnodes/NodeTypeSerializer.java | 2 - .../commandnodes/PreviewableCommandNode.java | 19 + .../UnnamedArgumentCommandNode.java | 14 + .../test/arguments/ArgumentFlagsTests.java | 2 +- .../arguments/ArgumentMultiLiteralTests.java | 72 ++-- .../commandnodes/NodeTypeSerializerTests.java | 355 ++++++++++++++++++ .../test/commandnodes/UnknownCommandNode.java | 60 +++ 10 files changed, 554 insertions(+), 30 deletions(-) create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/UnknownCommandNode.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java index 72f846b3e9..497defaf76 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java @@ -1,5 +1,6 @@ package dev.jorel.commandapi.commandnodes; +import com.google.gson.JsonObject; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -33,7 +34,7 @@ CommandNode wrapNode( ) { if (node instanceof LiteralCommandNode literalNode) { return new LiteralNode<>(literalNode, flagsArgumentName, previousArguments); - } else if (node instanceof ArgumentCommandNode argumentNode) { + } else if (node instanceof ArgumentCommandNode argumentNode) { return new ArgumentNode<>(argumentNode, flagsArgumentName, previousArguments); } else { throw new IllegalArgumentException("Node must be an argument or literal. Given " + node + " with type " + node.getClass().getName()); @@ -130,6 +131,21 @@ class LiteralNode extends DifferentClientNode.Literal implements FlagsArgumentEndingNode { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(LiteralNode.class, (target, type) -> { + target.addProperty("type", "flagArgumentEndingLiteral"); + target.addProperty("flagsArgumentName", type.flagsArgumentName); + + LiteralCommandNode wrappedNode = type.literalNode; + + JsonObject subProperties = new JsonObject(); + subProperties.addProperty("name", wrappedNode.getName()); + NodeTypeSerializer.addTypeInformation(subProperties, wrappedNode); + target.add("wrappedNode", subProperties); + }); + } + // Set information private final LiteralCommandNode literalNode; private final String flagsArgumentName; @@ -193,7 +209,7 @@ public void parse(StringReader reader, CommandContextBuilder contextBuil @Override public boolean equals(Object obj) { if (this == obj) return true; - if (!(obj instanceof LiteralNode other)) return false; + if (!(obj instanceof LiteralNode other)) return false; return Objects.equals(this.literalNode, other.literalNode) && Objects.equals(this.flagsArgumentName, other.flagsArgumentName) && Objects.equals(this.previousArguments, other.previousArguments); @@ -216,6 +232,22 @@ class ArgumentNode extends DifferentClientNode.Argument implements FlagsArgumentEndingNode { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(ArgumentNode.class, (target, type) -> { + target.addProperty("type", "flagArgumentEndingArgument"); + target.addProperty("flagsArgumentName", type.flagsArgumentName); + + ArgumentCommandNode wrappedNode = type.argumentNode; + + JsonObject subProperties = new JsonObject(); + subProperties.addProperty("name", wrappedNode.getName()); + NodeTypeSerializer.addTypeInformation(subProperties, wrappedNode); + target.add("wrappedNode", subProperties); + }); + } + + // Set information private final ArgumentCommandNode argumentNode; private final String flagsArgumentName; private final List previousArguments; @@ -284,7 +316,7 @@ public CompletableFuture listSuggestions(CommandContext con @Override public boolean equals(Object obj) { if (this == obj) return true; - if (!(obj instanceof ArgumentNode other)) return false; + if (!(obj instanceof ArgumentNode other)) return false; return Objects.equals(this.argumentNode, other.argumentNode) && Objects.equals(this.flagsArgumentName, other.flagsArgumentName) && Objects.equals(this.previousArguments, other.previousArguments); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java index e1c87d8462..5fa5232bcb 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java @@ -19,6 +19,13 @@ import java.util.concurrent.CompletableFuture; public class FlagsArgumentRootNode extends LiteralCommandNode { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(FlagsArgumentRootNode.class, (target, type) -> + target.addProperty("type", "flagsArgumentRootNode") + ); + } + // Ending nodes that loop back should always share the children of this node private final Set> loopEndNodes = new HashSet<>(); @@ -27,6 +34,13 @@ public class FlagsArgumentRootNode extends LiteralCommandNode { private final HiddenRedirect hiddenRedirect; public static final class HiddenRedirect extends ArgumentCommandNode { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(HiddenRedirect.class, (target, type) -> + target.addProperty("type", "flagArgumentHiddenRedirect") + ); + } + public HiddenRedirect(CommandNode redirect) { super( HIDDEN_NAME, StringArgumentType.word(), diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java index fbb210dad3..adde09de2b 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java @@ -21,6 +21,14 @@ * @param The Brigadier Source object for running commands. */ public class NamedLiteralCommandNode extends LiteralCommandNode { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(NamedLiteralCommandNode.class, (target, type) -> { + target.addProperty("type", "namedLiteral"); + target.addProperty("nodeName", type.nodeName); + }); + } + private final String nodeName; public NamedLiteralCommandNode(String nodeName, String literal, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks) { diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java index 6bab0c1270..f460d0cdfe 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java @@ -64,7 +64,5 @@ static void initialize() { CommandAPIHandler.getInstance().getPlatform() .getArgumentTypeProperties(argumentType).ifPresent(properties -> target.add("properties", properties)); })); - // TODO: Add serializers for custom nodes - // Probably do this within the node classes so they can otherwise be removed by jar minimization } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java index 0d6dbd8da3..dc31efb14a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java @@ -1,10 +1,12 @@ package dev.jorel.commandapi.commandnodes; import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.ArgumentCommandNode; +import dev.jorel.commandapi.CommandAPIHandler; import dev.jorel.commandapi.arguments.Previewable; import dev.jorel.commandapi.wrappers.PreviewableFunction; @@ -20,6 +22,23 @@ * @param The type returned when this argument is parsed. */ public class PreviewableCommandNode extends ArgumentCommandNode { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(PreviewableCommandNode.class, (target, type) -> { + ArgumentType argumentType = type.getType(); + + target.addProperty("type", "previewableArgument"); + target.addProperty("hasPreview", type.preview != null); + target.addProperty("legacy", type.legacy); + target.addProperty("listed", type.isListed); + + target.addProperty("argumentType", argumentType.getClass().getName()); + + CommandAPIHandler.getInstance().getPlatform() + .getArgumentTypeProperties(argumentType).ifPresent(properties -> target.add("properties", properties)); + }); + } + private final PreviewableFunction preview; private final boolean legacy; diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java index c8a5ecac82..0c7fe13d73 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java @@ -10,6 +10,7 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandAPIHandler; import java.util.function.Predicate; @@ -21,6 +22,19 @@ * @param The type returned when this argument is parsed. */ public class UnnamedArgumentCommandNode extends ArgumentCommandNode { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(UnnamedArgumentCommandNode.class, (target, type) -> { + ArgumentType argumentType = type.getType(); + + target.addProperty("type", "unnamedArgument"); + target.addProperty("argumentType", argumentType.getClass().getName()); + + CommandAPIHandler.getInstance().getPlatform() + .getArgumentTypeProperties(argumentType).ifPresent(properties -> target.add("properties", properties)); + }); + } + public UnnamedArgumentCommandNode(String name, ArgumentType type, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions) { super(name, type, command, requirement, redirect, modifier, forks, customSuggestions); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java index 0dc2711673..2615132aef 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentFlagsTests.java @@ -73,7 +73,7 @@ void commandBuildingTestWithLoopingAndTerminalBranchesAndFollowingArgument() { "type": "literal", "children": { "flags": { - "type": "literal", + "type": "flagsArgumentRootNode", "children": { "loop1": { "type": "literal", diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java index 567c37f2c5..bbb335cbc3 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentMultiLiteralTests.java @@ -86,24 +86,29 @@ void commandBuildingTestWithMultiLiteralArgument() { "type": "literal", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "executable": true }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "executable": true }, "option3": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "executable": true } } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": [ "command1", @@ -123,7 +128,8 @@ void commandBuildingTestWithMultiLiteralArgument() { } }, "option3": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": [ "command1", @@ -148,24 +154,29 @@ void commandBuildingTestWithMultiLiteralArgument() { "type": "literal", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "executable": true }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "executable": true }, "option3": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "executable": true } } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": [ "command2", @@ -185,7 +196,8 @@ void commandBuildingTestWithMultiLiteralArgument() { } }, "option3": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": [ "command2", @@ -210,23 +222,28 @@ void commandBuildingTestWithMultiLiteralArgument() { "type": "literal", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal3", "executable": true }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal3", "executable": true } } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "children": { "option1": [ "command3", @@ -245,7 +262,8 @@ void commandBuildingTestWithMultiLiteralArgument() { } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": [ "command3", @@ -265,23 +283,28 @@ void commandBuildingTestWithMultiLiteralArgument() { "type": "literal", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "children": { "option1": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal3", "executable": true }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal3", "executable": true } } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal2", "children": { "option1": [ "command4", @@ -300,7 +323,8 @@ void commandBuildingTestWithMultiLiteralArgument() { } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "children": { "option1": [ "command4", diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java new file mode 100644 index 0000000000..7bf895f469 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java @@ -0,0 +1,355 @@ +package dev.jorel.commandapi.test.commandnodes; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.commandnodes.NodeTypeSerializer; +import dev.jorel.commandapi.test.TestBase; +import org.bukkit.command.CommandSender; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for {@link NodeTypeSerializer#addTypeInformation(JsonObject, CommandNode)} + * via {@link CommandAPIHandler#serializeNodeToJson(CommandNode)} + */ +class NodeTypeSerializerTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + private RootCommandNode getArgumentNodes(Argument argument) { + RootCommandNode root = new RootCommandNode<>(); + NodeInformation previousNodeInformation = new NodeInformation<>( + List.of(root), children -> { + } + ); + argument.addArgumentNodes( + previousNodeInformation, new ArrayList<>(), new ArrayList<>(), (builder, args) -> builder.build() + ); + return root; + } + + private JsonObject serialize(CommandNode node) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + return handler.serializeNodeToJson(node); + } + + private String prettyJsonString(JsonObject object) { + return new GsonBuilder().setPrettyPrinting().create().toJson(object); + } + + /********* + * Tests * + *********/ + @Test + void testUnknownNodeSerialization() { + CommandNode node = new UnknownCommandNode(); + JsonObject actual = serialize(node); + + JsonObject expected = new JsonObject(); + expected.addProperty("type", "unknown"); + expected.addProperty("typeClassName", "dev.jorel.commandapi.test.commandnodes.UnknownCommandNode"); + + assertEquals(expected, actual); + } + + @Test + void testFlagsArgumentSerialization() { + FlagsArgument argument = new FlagsArgument("flags") + .loopingBranch(new LiteralArgument("literalLoop")) + .loopingBranch(new StringArgument("argumentLoop")) + .terminalBranch(new LiteralArgument("literalTerminal")) + .terminalBranch(new StringArgument("argumentTerminal")); + + RootCommandNode node = getArgumentNodes(argument); + JsonObject actual = serialize(node); + + assertEquals(""" + { + "type": "root", + "children": { + "flags": { + "type": "flagsArgumentRootNode", + "children": { + "literalLoop": { + "type": "flagArgumentEndingLiteral", + "flagsArgumentName": "flags", + "wrappedNode": { + "name": "literalLoop", + "type": "literal" + }, + "children": { + "commandapi:hiddenRootNode": { + "type": "flagArgumentHiddenRedirect", + "children": { + "flags": [ + "flags" + ] + } + }, + "literalLoop": [ + "flags", + "literalLoop" + ], + "argumentLoop": [ + "flags", + "argumentLoop" + ], + "literalTerminal": [ + "flags", + "literalTerminal" + ], + "argumentTerminal": [ + "flags", + "argumentTerminal" + ] + } + }, + "argumentLoop": { + "type": "flagArgumentEndingArgument", + "flagsArgumentName": "flags", + "wrappedNode": { + "name": "argumentLoop", + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "properties": { + "type": "word" + } + }, + "children": { + "commandapi:hiddenRootNode": [ + "flags", + "literalLoop", + "commandapi:hiddenRootNode" + ], + "literalLoop": [ + "flags", + "literalLoop" + ], + "argumentLoop": [ + "flags", + "argumentLoop" + ], + "literalTerminal": [ + "flags", + "literalTerminal" + ], + "argumentTerminal": [ + "flags", + "argumentTerminal" + ] + } + }, + "literalTerminal": { + "type": "flagArgumentEndingLiteral", + "flagsArgumentName": "flags", + "wrappedNode": { + "name": "literalTerminal", + "type": "literal" + } + }, + "argumentTerminal": { + "type": "flagArgumentEndingArgument", + "flagsArgumentName": "flags", + "wrappedNode": { + "name": "argumentTerminal", + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "properties": { + "type": "word" + } + } + } + } + } + } + }""", prettyJsonString(actual)); + } + + @Test + void testNamedLiteralSerialization() { + // Unlisted literals use Brigadier literal node + // Listed literals use custom NamedLiteral node + Argument argument = new MultiLiteralArgument("listedMulti", "option1", "option2") + .combineWith( + new MultiLiteralArgument("unlistedMulti", "option1", "option2").setListed(false), + new LiteralArgument("unlistedLiteral", "option3"), + new LiteralArgument("listedLiteral", "option4").setListed(true) + ); + + RootCommandNode node = getArgumentNodes(argument); + JsonObject actual = serialize(node); + + assertEquals(""" + { + "type": "root", + "children": { + "option1": { + "type": "namedLiteral", + "nodeName": "listedMulti", + "children": { + "option1": { + "type": "literal", + "children": { + "option3": { + "type": "literal", + "children": { + "option4": { + "type": "namedLiteral", + "nodeName": "listedLiteral" + } + } + } + } + }, + "option2": { + "type": "literal", + "children": { + "option3": [ + "option1", + "option1", + "option3" + ] + } + } + } + }, + "option2": { + "type": "namedLiteral", + "nodeName": "listedMulti", + "children": { + "option1": [ + "option1", + "option1" + ], + "option2": [ + "option1", + "option2" + ] + } + } + } + }""", prettyJsonString(actual)); + } + + @Test + void testUnnamedArgumentSerialization() { + // Listed arguments use Brigadier argument node + // Unlisted arguments use custom UnnamedArgument + Argument argument = new StringArgument("namedArgument") + .combineWith(new StringArgument("unnamedArgument").setListed(false)); + + RootCommandNode node = getArgumentNodes(argument); + JsonObject actual = serialize(node); + + assertEquals(""" + { + "type": "root", + "children": { + "namedArgument": { + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "properties": { + "type": "word" + }, + "children": { + "unnamedArgument": { + "type": "unnamedArgument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "properties": { + "type": "word" + } + } + } + } + } + }""", prettyJsonString(actual)); + } + + @ParameterizedTest + @ValueSource(bytes = {0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111}) + void testPreviewableSerialization(byte parameter) { + // Test all 8 combinations of the 3 boolean parameters + boolean hasPreview = (parameter & 0b001) == 0; + boolean legacy = (parameter & 0b010) == 0; + boolean listed = (parameter & 0b100) == 0; + + // Create argument according to parameters + Argument argument = (legacy ? new ChatArgument("argument") : new AdventureChatArgument("argument")) + .withPreview(hasPreview ? PreviewInfo::parsedInput : null); + argument.setListed(listed); + + RootCommandNode node = getArgumentNodes(argument); + JsonObject actual = serialize(node); + + assertEquals(String.format(""" + { + "type": "root", + "children": { + "argument": { + "type": "previewableArgument", + "hasPreview": %s, + "legacy": %s, + "listed": %s, + "argumentType": "net.minecraft.commands.arguments.ArgumentChat" + } + } + }""", hasPreview, legacy, listed), prettyJsonString(actual)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testDynamicMultiLiteralSerialization(boolean listed) { + DynamicMultiLiteralArgument argument = new DynamicMultiLiteralArgument("argument", sender -> { + List literals = new ArrayList<>(List.of("first", "second", "third")); + // For serialization, the sender is unknown, so only the default literals should be included + if (sender != null) literals.add(sender.getName()); + + return literals; + }); + argument.setListed(listed); + + RootCommandNode node = getArgumentNodes(argument); + JsonObject actual = serialize(node); + + assertEquals(String.format(""" + { + "type": "root", + "children": { + "argument": { + "type": "dynamicMultiLiteral", + "isListed": %s, + "defaultLiterals": [ + "first", + "second", + "third" + ] + } + } + }""", listed), prettyJsonString(actual)); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/UnknownCommandNode.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/UnknownCommandNode.java new file mode 100644 index 0000000000..0961a74e8e --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/UnknownCommandNode.java @@ -0,0 +1,60 @@ +package dev.jorel.commandapi.test.commandnodes; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.CommandNode; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +class UnknownCommandNode extends CommandNode { + protected UnknownCommandNode() { + super(null, source -> true, null, null, false); + } + + @Override + protected boolean isValidInput(String input) { + return false; + } + + @Override + public String getName() { + return ""; + } + + @Override + public String getUsageText() { + return ""; + } + + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + return null; + } + + @Override + public ArgumentBuilder createBuilder() { + return null; + } + + @Override + protected String getSortedKey() { + return ""; + } + + @Override + public Collection getExamples() { + return List.of(); + } +} From 0e66ac8cc87fff56dc9e6dc64c7ca1a02835d3ee Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:21:29 -0400 Subject: [PATCH 37/42] Make `CommandAPI#unregister` remove commands from `CommandAPIHandler.registeredCommands` Also made unregistering on Velocity respect the `unregisterNamespaces` parameter and update the dispatcher file --- .../java/dev/jorel/commandapi/CommandAPI.java | 8 +- .../jorel/commandapi/CommandAPIHandler.java | 69 +++++- .../jorel/commandapi/CommandAPIBukkit.java | 11 + .../CommandRegistrationStrategy.java | 47 ---- .../commandapi/PaperCommandRegistration.java | 11 +- .../commandapi/SpigotCommandRegistration.java | 18 +- ...mmandAPICommandRegisteredCommandTests.java | 204 +++++++++++++----- .../CommandTreeRegisteredCommandTests.java | 125 ++++++++--- .../jorel/commandapi/CommandAPIVelocity.java | 16 +- 9 files changed, 351 insertions(+), 158 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java index 2d94bc121a..0434fb5aa5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java @@ -6,7 +6,6 @@ import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -265,14 +264,13 @@ public static WrapperCommandSyntaxException failWithMessage(Message message) { } // Command registration and unregistration - /** * Unregisters a command * * @param command the name of the command to unregister */ public static void unregister(String command) { - CommandAPIHandler.getInstance().getPlatform().unregister(command, false); + CommandAPIHandler.getInstance().unregister(command, false); } /** @@ -285,7 +283,7 @@ public static void unregister(String command) { * unregistered. */ public static void unregister(String command, boolean unregisterNamespaces) { - CommandAPIHandler.getInstance().getPlatform().unregister(command, unregisterNamespaces); + CommandAPIHandler.getInstance().unregister(command, unregisterNamespaces); } /** @@ -307,6 +305,6 @@ public static void registerCommand(Class commandClass) { */ public static List> getRegisteredCommands() { CommandAPIHandler handler = CommandAPIHandler.getInstance(); - return Collections.unmodifiableList(new ArrayList<>(handler.registeredCommands.values())); + return List.copyOf(handler.registeredCommands.values()); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index e3435b16b4..4e9603225a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -36,6 +36,7 @@ import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.ArgumentSuggestions; import dev.jorel.commandapi.arguments.CustomProvidedArgument; @@ -257,8 +258,7 @@ private void ensureNoCommandConflict(CommandNode nodeToRegister, Command if (mergeTarget == null) return; // The `nodeToRegister` does not already exist, no conflict possible // Add node to path - List path = new ArrayList<>(); - path.addAll(pathSoFar); + List path = new ArrayList<>(pathSoFar); path.add(nodeToRegister.getName()); if (nodeToRegister.getCommand() != null && mergeTarget.getCommand() != null) { @@ -411,6 +411,71 @@ public Predicate generateBrigadierRequirements(CommandPermission permiss }; } + ///////////////////////////// + // SECTION: Unregistration // + ///////////////////////////// + /** + * Unregisters a command + * + * @param command the name of the command to unregister + * @param unregisterNamespaces whether the unregistration system should attempt to remove versions of the + * command that start with a namespace. E.g. `minecraft:command`, `bukkit:command`, + * or `plugin:command`. If true, these namespaced versions of a command are also + * unregistered. + */ + public void unregister(String command, boolean unregisterNamespaces) { + // Remove command from `registeredCommands` + registeredCommands.remove(command); + if (unregisterNamespaces) removeCommandNamespace(registeredCommands, command, e -> true); + + // Platform-specific unregistration logic + platform.unregister(command, unregisterNamespaces); + + // Update the dispatcher file + writeDispatcherToFile(); + } + + + protected void removeBrigadierCommands(RootCommandNode root, String commandName, + boolean unregisterNamespaces, Predicate> extraCheck) { + Map> children = CommandAPIHandler.getCommandNodeChildren(root); + Map> literals = CommandAPIHandler.getCommandNodeLiterals(root); + Map> arguments = CommandAPIHandler.getCommandNodeArguments(root); + + removeCommandFromMapIfCheckPasses(children, commandName, extraCheck); + if (literals != null) removeCommandFromMapIfCheckPasses(literals, commandName, extraCheck); + // Commands should really only be represented as literals, but it is technically possible + // to put an ArgumentCommandNode in the root, so we'll check + removeCommandFromMapIfCheckPasses(arguments, commandName, extraCheck); + + if (unregisterNamespaces) { + removeCommandNamespace(children, commandName, extraCheck); + if (literals != null) removeCommandNamespace(literals, commandName, extraCheck); + removeCommandNamespace(arguments, commandName, extraCheck); + } + } + + protected static void removeCommandNamespace(Map map, String commandName, Predicate extraCheck) { + for (String key : new HashSet<>(map.keySet())) { + if (!isThisTheCommandButNamespaced(commandName, key)) continue; + + removeCommandFromMapIfCheckPasses(map, key, extraCheck); + } + } + + protected static void removeCommandFromMapIfCheckPasses(Map map, String key, Predicate extraCheck) { + T element = map.get(key); + if (element == null) return; + if (extraCheck.test(element)) map.remove(key); + } + + protected static boolean isThisTheCommandButNamespaced(String commandName, String key) { + if (!key.contains(":")) return false; + String[] split = key.split(":"); + if (split.length < 2) return false; + return split[1].equalsIgnoreCase(commandName); + } + //////////////////////////////// // SECTION: Brigadier Helpers // //////////////////////////////// diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index 8f920ecf0c..4db1b7429e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -354,6 +354,17 @@ public void unregister(String commandName, boolean unregisterNamespaces) { * commands, and commands registered by other plugin using Bukkit API are Bukkit commands. */ public static void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit) { + if (!unregisterBukkit) { + // If we're unregistering vanilla commands (which includes CommandAPI commands), + // attempt to remove their information from the `registeredCommands` map. + // `CommandAPIHandler#unregister` usually does this, but this method bypasses that. + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + Map> registeredCommands = handler.registeredCommands; + + registeredCommands.remove(commandName); + if (unregisterNamespaces) CommandAPIHandler.removeCommandNamespace(registeredCommands, commandName, e -> true); + } + CommandAPIBukkit.get().unregisterInternal(commandName, unregisterNamespaces, unregisterBukkit); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java index 98253f2387..6ac905e1ec 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandRegistrationStrategy.java @@ -1,59 +1,12 @@ package dev.jorel.commandapi; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.tree.ArgumentCommandNode; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; -import com.mojang.brigadier.tree.RootCommandNode; import org.bukkit.command.CommandSender; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.function.Predicate; public abstract class CommandRegistrationStrategy { - // Utility methods - protected void removeBrigadierCommands(RootCommandNode root, String commandName, - boolean unregisterNamespaces, Predicate> extraCheck) { - Map> children = CommandAPIHandler.getCommandNodeChildren(root); - Map> literals = CommandAPIHandler.getCommandNodeLiterals(root); - Map> arguments = CommandAPIHandler.getCommandNodeArguments(root); - - removeCommandFromMapIfCheckPasses(children, commandName, extraCheck); - removeCommandFromMapIfCheckPasses(literals, commandName, extraCheck); - // Commands should really only be represented as literals, but it is technically possible - // to put an ArgumentCommandNode in the root, so we'll check - removeCommandFromMapIfCheckPasses(arguments, commandName, extraCheck); - - if (unregisterNamespaces) { - removeCommandNamespace(children, commandName, extraCheck); - removeCommandNamespace(literals, commandName, extraCheck); - removeCommandNamespace(arguments, commandName, extraCheck); - } - } - - protected static void removeCommandNamespace(Map map, String commandName, Predicate extraCheck) { - for (String key : new HashSet<>(map.keySet())) { - if (!isThisTheCommandButNamespaced(commandName, key)) continue; - - removeCommandFromMapIfCheckPasses(map, key, extraCheck); - } - } - - protected static void removeCommandFromMapIfCheckPasses(Map map, String key, Predicate extraCheck) { - T element = map.get(key); - if (element == null) return; - if (extraCheck.test(element)) map.remove(key); - } - - protected static boolean isThisTheCommandButNamespaced(String commandName, String key) { - if (!key.contains(":")) return false; - String[] split = key.split(":"); - if (split.length < 2) return false; - return split[1].equalsIgnoreCase(commandName); - } - // Behavior methods public abstract CommandDispatcher getBrigadierDispatcher(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java index f6de166be3..728ff25503 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperCommandRegistration.java @@ -72,8 +72,10 @@ public void registerCommandNode(LiteralCommandNode node, String namespac @Override public void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit) { - // Remove nodes from the dispatcher - removeBrigadierCommands(getBrigadierDispatcher.get().getRoot(), commandName, unregisterNamespaces, + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Remove nodes from the dispatcher + handler.removeBrigadierCommands(getBrigadierDispatcher.get().getRoot(), commandName, unregisterNamespaces, // If we are unregistering a Bukkit command, ONLY unregister BukkitCommandNodes // If we are unregistering a Vanilla command, DO NOT unregister BukkitCommandNodes c -> !unregisterBukkit ^ isBukkitCommand.test(c)); @@ -81,11 +83,8 @@ public void unregister(String commandName, boolean unregisterNamespaces, boolean // CommandAPI commands count as non-Bukkit if (!unregisterBukkit) { // Don't add nodes back after a reload - removeBrigadierCommands(registeredNodes, commandName, unregisterNamespaces, c -> true); + handler.removeBrigadierCommands(registeredNodes, commandName, unregisterNamespaces, c -> true); } - - // Update the dispatcher file - CommandAPIHandler.getInstance().writeDispatcherToFile(); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index f0dadb3caa..536c43fb3c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -127,13 +127,14 @@ public void runTasksAfterServerStart() { } private void fixNamespaces() { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); Map knownCommands = commandMapKnownCommands.get(commandMap); RootCommandNode resourcesRootNode = getResourcesDispatcher.get().getRoot(); // Remove namespaces for (String command : namespacesToFix) { knownCommands.remove(command); - removeBrigadierCommands(resourcesRootNode, command, false, c -> true); + handler.removeBrigadierCommands(resourcesRootNode, command, false, c -> true); } // Add back certain minecraft: namespace commands @@ -344,14 +345,13 @@ private void fillNamespacesToFix(String... namespacedCommands) { @Override public void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + if (!unregisterBukkit) { // Remove nodes from the Vanilla dispatcher // This dispatcher doesn't usually have namespaced version of commands (those are created when commands // are transferred to Bukkit's CommandMap), but if they ask, we'll do it - removeBrigadierCommands(brigadierDispatcher.getRoot(), commandName, unregisterNamespaces, c -> true); - - // Update the dispatcher file - CommandAPIHandler.getInstance().writeDispatcherToFile(); + handler.removeBrigadierCommands(brigadierDispatcher.getRoot(), commandName, unregisterNamespaces, c -> true); } if (unregisterBukkit || !CommandAPI.canRegister()) { @@ -366,7 +366,7 @@ public void unregister(String commandName, boolean unregisterNamespaces, boolean if (unregisterBukkit ^ isMainVanilla) knownCommands.remove(commandName); if (unregisterNamespaces) { - removeCommandNamespace(knownCommands, commandName, c -> unregisterBukkit ^ isVanillaCommandWrapper.test(c)); + CommandAPIHandler.removeCommandNamespace(knownCommands, commandName, c -> unregisterBukkit ^ isVanillaCommandWrapper.test(c)); } } @@ -374,9 +374,9 @@ public void unregister(String commandName, boolean unregisterNamespaces, boolean // If the server is enabled, we have extra cleanup to do // Remove commands from the resources dispatcher - // If we are unregistering a Bukkit command, ONLY unregister BukkitCommandWrappers - // If we are unregistering a Vanilla command, DO NOT unregister BukkitCommandWrappers - removeBrigadierCommands(getResourcesDispatcher.get().getRoot(), commandName, unregisterNamespaces, + handler.removeBrigadierCommands(getResourcesDispatcher.get().getRoot(), commandName, unregisterNamespaces, + // If we are unregistering a Bukkit command, ONLY unregister BukkitCommandWrappers + // If we are unregistering a Vanilla command, DO NOT unregister BukkitCommandWrappers c -> !unregisterBukkit ^ isBukkitCommandWrapper.test(c)); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java index 0c4cd060ff..05b8b0904c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java @@ -1,26 +1,25 @@ package dev.jorel.commandapi.test; -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.CommandPermission; -import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.*; import dev.jorel.commandapi.RegisteredCommand.Node; import dev.jorel.commandapi.arguments.IntegerArgument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; import dev.jorel.commandapi.help.EditableHelpTopic; - import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.util.List; import java.util.function.Predicate; -import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.children; +import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; /** * Tests for making sure the {@link RegisteredCommand} information is correct when registering {@link CommandAPICommand}s @@ -56,7 +55,7 @@ void testRegister() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", true), List.of("command:CommandAPICommand") ); @@ -125,8 +124,8 @@ void testRegisterRequirement() { .register(); RegisteredCommand expectedCommand = new RegisteredCommand<>( - "command", new String[0], "minecraft", - new EditableHelpTopic<>(), + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), commandNode("command", true).requirements(requirement).build() ); @@ -140,7 +139,11 @@ void testRegisterOneAlias() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true), + "alias1" + ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -152,18 +155,25 @@ void testRegisterTwoAliases() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true), + "alias1", "alias2" + ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } - @Test + @Test void testRegisterNamespace() { new CommandAPICommand("command") .executesPlayer(P_EXEC) .register("custom"); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "custom", + commandNode("command", true) + ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -176,7 +186,7 @@ void testRegisterOneArgument() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false) .withChildren(node("string", StringArgument.class, true)), List.of("command:CommandAPICommand", "string:StringArgument") @@ -194,7 +204,7 @@ void testRegisterTwoArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false) .withChildren(node("string", StringArgument.class, false) .withChildren(node("integer", IntegerArgument.class, true)) @@ -215,7 +225,7 @@ void testRegisterArgumentPermissions() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( node("noPermission", StringArgument.class, false).withChildren( node("opPermission", StringArgument.class, false).permission(CommandPermission.OP).withChildren( @@ -272,7 +282,7 @@ void testRegisterOneOptionalArgument() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", true) .withChildren(node("string", StringArgument.class, true)), List.of("command:CommandAPICommand"), @@ -291,7 +301,7 @@ void testRegisterTwoOptionalArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", true) .withChildren(node("string", StringArgument.class, true) .withChildren(node("integer", IntegerArgument.class, true)) @@ -313,7 +323,7 @@ void testRegisterCombinedOptionalArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", true) .withChildren(node("1", LiteralArgument.class, false).helpString("1") .withChildren(node("2", LiteralArgument.class, true).helpString("2") @@ -340,30 +350,30 @@ void testRegisterCombinedRequiredAndOptionalArguments() { .executesPlayer(P_EXEC) .register(); - assertCreatedSimpleRegisteredCommand( - "command", - commandNode("command", false) - .withChildren(node("1", LiteralArgument.class, false).helpString("1") - .withChildren(node("2", LiteralArgument.class, false).helpString("2") - .withChildren(node("3", LiteralArgument.class, false).helpString("3") - .withChildren(node("4", LiteralArgument.class, true).helpString("4") - .withChildren(node("5", LiteralArgument.class, false).helpString("5") - .withChildren(node("6", LiteralArgument.class, true).helpString("6") - .withChildren(node("7", LiteralArgument.class, false).helpString("7") - .withChildren(node("8", LiteralArgument.class, true).helpString("8") - )))))))), - List.of("command:CommandAPICommand", - "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" - ), - List.of("command:CommandAPICommand", - "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", - "5:LiteralArgument", "6:LiteralArgument" - ), - List.of("command:CommandAPICommand", - "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", - "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" - ) - ); + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("1", LiteralArgument.class, false).helpString("1") + .withChildren(node("2", LiteralArgument.class, false).helpString("2") + .withChildren(node("3", LiteralArgument.class, false).helpString("3") + .withChildren(node("4", LiteralArgument.class, true).helpString("4") + .withChildren(node("5", LiteralArgument.class, false).helpString("5") + .withChildren(node("6", LiteralArgument.class, true).helpString("6") + .withChildren(node("7", LiteralArgument.class, false).helpString("7") + .withChildren(node("8", LiteralArgument.class, true).helpString("8") + )))))))), + List.of("command:CommandAPICommand", + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" + ), + List.of("command:CommandAPICommand", + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument" + ), + List.of("command:CommandAPICommand", + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" + ) + ); } ////////////////////////////////////// @@ -381,7 +391,7 @@ void testRegisterOneSubcommand() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false) .withChildren(commandNode("subcommand", true)), List.of("command:CommandAPICommand", "subcommand:CommandAPICommand") @@ -400,7 +410,7 @@ void testRegisterTwoSubcommands() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( commandNode("subcommand1", true), commandNode("subcommand2", true) @@ -421,7 +431,7 @@ void testRegisterOneSubcommandAndBaseExecutable() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", true) .withChildren(commandNode("subcommand", true)), List.of("command:CommandAPICommand"), @@ -440,7 +450,7 @@ void testRegisterSubcommandWithAliases() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( commandNode("subcommand", true), commandNode("alias1", true), @@ -472,7 +482,7 @@ void testRegisterSubcommandsWithArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( commandNode("subcommand1", false).withChildren( node("string1", StringArgument.class, false).withChildren( @@ -519,7 +529,7 @@ void testRegisterSubcommandWithAliasesAndMultiLiteralArgument() { ); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren(subcommands), List.of("command:CommandAPICommand", "subcommand:CommandAPICommand", "literal1:MultiLiteralArgument", "literal2:MultiLiteralArgument"), List.of("command:CommandAPICommand", "alias1:CommandAPICommand", "literal1:MultiLiteralArgument", "literal2:MultiLiteralArgument"), @@ -549,7 +559,7 @@ void testRegisterSubcommandsWithOptionalArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( commandNode("subcommand1", true).withChildren( node("a", LiteralArgument.class, true).helpString("a").withChildren( @@ -597,7 +607,7 @@ void testRegisterSubcommandsWithCombinedRequiredAndOptionalArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( commandNode("subcommand1", false) .withChildren(node("1a", LiteralArgument.class, false).helpString("1a") @@ -622,7 +632,7 @@ void testRegisterSubcommandsWithCombinedRequiredAndOptionalArguments() { ///////////////////////// // Information merging // ///////////////////////// - + @Test void testRegisterTwoSeparateCommands() { new CommandAPICommand("command1") @@ -633,8 +643,14 @@ void testRegisterTwoSeparateCommands() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); - RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + RegisteredCommand command1 = simpleRegisteredCommand( + "command1", "minecraft", + commandNode("command1", true) + ); + RegisteredCommand command2 = simpleRegisteredCommand( + "command2", "minecraft", + commandNode("command2", true) + ); assertCreatedRegisteredCommands( command1.copyWithEmptyNamespace(), command1, @@ -655,7 +671,7 @@ void testRegisterMergeArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( node("string", StringArgument.class, true), node("integer", IntegerArgument.class, true) @@ -680,7 +696,7 @@ void testRegisterMergeNamespaces() { .register("second"); RegisteredCommand first = simpleRegisteredCommand( - "command", "first", + "command", "first", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first") ), @@ -688,7 +704,7 @@ void testRegisterMergeNamespaces() { ); RegisteredCommand second = simpleRegisteredCommand( - "command", "second", + "command", "second", commandNode("command", false).withChildren( node("second", LiteralArgument.class, true).helpString("second") ), @@ -696,7 +712,7 @@ void testRegisterMergeNamespaces() { ); RegisteredCommand merged = simpleRegisteredCommand( - "command", "", + "command", "", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first"), node("second", LiteralArgument.class, true).helpString("second") @@ -706,4 +722,78 @@ void testRegisterMergeNamespaces() { assertCreatedRegisteredCommands(merged, first, second); } + + /////////////////// + // Unregistering // + /////////////////// + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testUnregister(boolean useBukkitMethod) { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true) + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + + if (!useBukkitMethod) { + CommandAPI.unregister("command"); + } else { + // CommandAPIHandler usually removes RegisteredCommands, but the extra parameter in the + // CommandAPIBukkit method means it has to be handled separately + CommandAPIBukkit.unregister("command", false, false); + } + + // Only the namespaced version should be left + assertCreatedRegisteredCommands(expectedCommand); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testUnregisterNamespace(boolean useBukkitMethod) { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true) + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + + if (!useBukkitMethod) { + CommandAPI.unregister("command", true); + } else { + // CommandAPIHandler usually removes RegisteredCommands, but the extra parameter in the + // CommandAPIBukkit method means it has to be handled separately + CommandAPIBukkit.unregister("command", true, false); + } + + // Command should be fully removed + assertCreatedRegisteredCommands(); + } + + @Test + void testUnregisterBukkit() { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true) + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + + CommandAPIBukkit.unregister("command", true, true); + + // CommandAPI commands are not Bukkit commands, so they aren't unregistered here + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java index 7a6e85ee99..0659a935b3 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java @@ -1,19 +1,18 @@ package dev.jorel.commandapi.test; -import dev.jorel.commandapi.CommandTree; -import dev.jorel.commandapi.CommandPermission; -import dev.jorel.commandapi.RegisteredCommand; +import dev.jorel.commandapi.*; import dev.jorel.commandapi.arguments.IntegerArgument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; import dev.jorel.commandapi.help.EditableHelpTopic; - import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.util.List; import java.util.function.Predicate; @@ -54,7 +53,7 @@ void testRegister() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", true), List.of("command:CommandTree") ); @@ -123,8 +122,8 @@ void testRegisterRequirement() { .register(); RegisteredCommand expectedCommand = new RegisteredCommand<>( - "command", new String[0], "minecraft", - new EditableHelpTopic<>(), + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), commandNode("command", true).requirements(requirement).build() ); @@ -155,7 +154,7 @@ void testRegisterTwoAliases() { assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } - @Test + @Test void testRegisterNamespace() { new CommandTree("command") .executesPlayer(P_EXEC) @@ -173,7 +172,7 @@ void testRegisterOneBranch() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false) .withChildren(node("string", StringArgument.class, true)), List.of("command:CommandTree", "string:StringArgument") @@ -216,7 +215,7 @@ void testRegisterArgumentPermissions() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( node("noPermission", StringArgument.class, false).withChildren( node("opPermission", StringArgument.class, false).permission(CommandPermission.OP).withChildren( @@ -295,14 +294,14 @@ void testRegisterCombinedArguments() { .withChildren(node("7", LiteralArgument.class, false).helpString("7") .withChildren(node("8", LiteralArgument.class, true).helpString("8") )))))))), - List.of("command:CommandTree", + List.of("command:CommandTree", "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument" ), - List.of("command:CommandTree", + List.of("command:CommandTree", "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", "5:LiteralArgument", "6:LiteralArgument" ), - List.of("command:CommandTree", + List.of("command:CommandTree", "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", "5:LiteralArgument", "6:LiteralArgument", "7:LiteralArgument", "8:LiteralArgument" ) @@ -421,10 +420,6 @@ void testRegisterBranchesWithCombinedArguments() { // Information merging // ///////////////////////// - ///////////////////////// - // Information merging // - ///////////////////////// - @Test void testRegisterTwoSeparateCommands() { new CommandTree("command1") @@ -435,8 +430,14 @@ void testRegisterTwoSeparateCommands() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); - RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + RegisteredCommand command1 = simpleRegisteredCommand( + "command1", "minecraft", + commandNode("command1", true) + ); + RegisteredCommand command2 = simpleRegisteredCommand( + "command2", "minecraft", + commandNode("command2", true) + ); assertCreatedRegisteredCommands( command1.copyWithEmptyNamespace(), command1, @@ -455,7 +456,7 @@ void testRegisterMergeArguments() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( node("string", StringArgument.class, true), node("integer", IntegerArgument.class, true) @@ -490,7 +491,7 @@ void testRegisterMergeDifferentLengthBranches() { .register(); assertCreatedSimpleRegisteredCommand( - "command", + "command", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first").withChildren( node("argument", StringArgument.class, true) @@ -498,7 +499,7 @@ void testRegisterMergeDifferentLengthBranches() { node("second", LiteralArgument.class, true).helpString("second").withChildren( node("argument", IntegerArgument.class, true) ) - ), + ), List.of("command:CommandTree", "first:LiteralArgument"), List.of("command:CommandTree", "first:LiteralArgument", "argument:StringArgument"), List.of("command:CommandTree", "second:LiteralArgument"), @@ -519,7 +520,7 @@ void testRegisterMergeNamespaces() { .register("second"); RegisteredCommand first = simpleRegisteredCommand( - "command", "first", + "command", "first", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first") ), @@ -527,7 +528,7 @@ void testRegisterMergeNamespaces() { ); RegisteredCommand second = simpleRegisteredCommand( - "command", "second", + "command", "second", commandNode("command", false).withChildren( node("second", LiteralArgument.class, true).helpString("second") ), @@ -535,7 +536,7 @@ void testRegisterMergeNamespaces() { ); RegisteredCommand merged = simpleRegisteredCommand( - "command", "", + "command", "", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first"), node("second", LiteralArgument.class, true).helpString("second") @@ -545,4 +546,78 @@ void testRegisterMergeNamespaces() { assertCreatedRegisteredCommands(merged, first, second); } + + /////////////////// + // Unregistering // + /////////////////// + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testUnregister(boolean useBukkitMethod) { + new CommandTree("command") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true) + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + + if (!useBukkitMethod) { + CommandAPI.unregister("command"); + } else { + // CommandAPIHandler usually removes RegisteredCommands, but the extra parameter in the + // CommandAPIBukkit method means it has to be handled separately + CommandAPIBukkit.unregister("command", false, false); + } + + // Only the namespaced version should be left + assertCreatedRegisteredCommands(expectedCommand); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testUnregisterNamespace(boolean useBukkitMethod) { + new CommandTree("command") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true) + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + + if (!useBukkitMethod) { + CommandAPI.unregister("command", true); + } else { + // CommandAPIHandler usually removes RegisteredCommands, but the extra parameter in the + // CommandAPIBukkit method means it has to be handled separately + CommandAPIBukkit.unregister("command", true, false); + } + + // Command should be fully removed + assertCreatedRegisteredCommands(); + } + + @Test + void testUnregisterBukkit() { + new CommandTree("command") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true) + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + + CommandAPIBukkit.unregister("command", true, true); + + // CommandAPI commands are not Bukkit commands, so they aren't unregistered here + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index 344529c1ce..db4bbf3661 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -32,7 +32,6 @@ public class CommandAPIVelocity implements CommandAPIPlatform, Comma private static CommandAPIVelocity instance; private static InternalVelocityConfig config; - private CommandManager commandManager; private CommandDispatcher dispatcher; public CommandAPIVelocity() { @@ -64,7 +63,7 @@ public void onLoad(CommandAPIConfig config) { CommandAPI.logError("Attempts to access Velocity-specific config variables will fail!"); } - commandManager = getConfiguration().getServer().getCommandManager(); + CommandManager commandManager = getConfiguration().getServer().getCommandManager(); // We can't use a SafeVarHandle here because we don't have direct access to the // `com.velocitypowered.proxy.command.VelocityCommandManager` class that holds the field. @@ -111,7 +110,9 @@ public void onDisable() { @Override public void unregister(String commandName, boolean unregisterNamespaces) { - commandManager.unregister(commandName); + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + handler.removeBrigadierCommands(getBrigadierDispatcher().getRoot(), commandName, unregisterNamespaces, c -> true); } @Override @@ -178,14 +179,15 @@ public void postCommandRegistration(RegisteredCommand registeredC @Override public void registerCommandNode(LiteralCommandNode node, String namespace) { + RootCommandNode root = getBrigadierDispatcher().getRoot(); + // Register the main node - getBrigadierDispatcher().getRoot().addChild(node); + root.addChild(node); // Register the namespaced node if it is not empty if (!namespace.isEmpty()) { - getBrigadierDispatcher().getRoot().addChild( - CommandAPIHandler., CommandSource, CommandSource>getInstance().namespaceNode(node, namespace) - ); + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + root.addChild(handler.namespaceNode(node, namespace)); } } From 1bc6e7aca0c76ea39538fe3d59a4ec80f1d456d2 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sun, 1 Sep 2024 13:36:55 -0400 Subject: [PATCH 38/42] Add tests for `DynamicMultiLiteralArgument` --- .../ArgumentDynamicMultiLiteralTests.java | 293 ++++++++++++++++++ .../arguments/ArgumentListabilityTests.java | 25 ++ .../commandnodes/NodeTypeSerializerTests.java | 13 +- 3 files changed, 324 insertions(+), 7 deletions(-) create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentDynamicMultiLiteralTests.java diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentDynamicMultiLiteralTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentDynamicMultiLiteralTests.java new file mode 100644 index 0000000000..3a456fad57 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentDynamicMultiLiteralTests.java @@ -0,0 +1,293 @@ +package dev.jorel.commandapi.test.arguments; + +import com.google.gson.JsonObject; +import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.Brigadier; +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.arguments.Argument; +import dev.jorel.commandapi.arguments.DynamicMultiLiteralArgument; +import dev.jorel.commandapi.arguments.LiteralArgument; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; +import dev.jorel.commandapi.test.Mut; +import dev.jorel.commandapi.test.TestBase; +import dev.jorel.commandapi.test.commandnodes.NodeTypeSerializerTests; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for the {@link DynamicMultiLiteralArgument}. + */ +class ArgumentDynamicMultiLiteralTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + /********* + * Tests * + *********/ + + @ParameterizedTest + @ValueSource(strings = {"PlayerA", "PlayerB"}) + void testClientRewriting(String playerName) { + Argument argument = new DynamicMultiLiteralArgument("option", sender -> + sender == null ? List.of("default") : List.of("default", sender.getName()) + ).combineWith(new LiteralArgument("following")); + + RootCommandNode node = NodeTypeSerializerTests.getArgumentNodes(argument); + Object client = Brigadier.getBrigadierSourceFromCommandSender(server.addPlayer(playerName)); + + // DynamicMultiLiteralCommandNode should be used in structure + JsonObject originalStructure = NodeTypeSerializerTests.serialize(node); + assertEquals(""" + { + "type": "root", + "children": { + "option": { + "type": "dynamicMultiLiteral", + "isListed": true, + "defaultLiterals": [ + "default" + ], + "children": { + "following": { + "type": "literal" + } + } + } + } + }""", NodeTypeSerializerTests.prettyJsonString(originalStructure)); + + // DynamicMultiLiteral should not change when registered + DifferentClientNode.rewriteAllChildren(client, node, true); + + JsonObject registerRewrite = NodeTypeSerializerTests.serialize(node); + assertEquals(""" + { + "type": "root", + "children": { + "option": { + "type": "dynamicMultiLiteral", + "isListed": true, + "defaultLiterals": [ + "default" + ], + "children": { + "following": { + "type": "literal" + } + } + } + } + }""", NodeTypeSerializerTests.prettyJsonString(registerRewrite)); + + // DynamicMultiLiteral should be replaced with appropriate literal nodes when sent to client + DifferentClientNode.rewriteAllChildren(client, node, false); + + JsonObject clientRewrite = NodeTypeSerializerTests.serialize(node); + assertEquals(String.format(""" + { + "type": "root", + "children": { + "default": { + "type": "literal", + "children": { + "following": { + "type": "literal" + } + } + }, + "%s": { + "type": "literal", + "children": { + "following": [ + "default", + "following" + ] + } + } + } + }""", playerName), NodeTypeSerializerTests.prettyJsonString(clientRewrite)); + } + + @Test + void testMultiLiteralBehavior() { + Mut results = Mut.of(); + + new CommandAPICommand("test") + .withArguments( + new DynamicMultiLiteralArgument("option", sender -> List.of("a", "b", "c")) + ) + .executes(info -> { + results.set(info.args().getUnchecked("option")); + }) + .register(); + + Player sender = server.addPlayer(); + + // Defined literals should be accepted + assertStoresResult(sender, "test a", results, "a"); + assertStoresResult(sender, "test b", results, "b"); + assertStoresResult(sender, "test c", results, "c"); + + // Other input should be rejected + assertCommandFailsWith( + sender, "test d", + "Expected literal [a, b, c] at position 5: test <--[HERE]" + ); + assertCommandFailsWith( + sender, "test hello", + "Expected literal [a, b, c] at position 5: test <--[HERE]" + ); + + assertNoMoreResults(results); + } + + @Test + void testDynamicBehavior() { + Mut results = Mut.of(); + + new CommandAPICommand("test") + .withArguments( + new DynamicMultiLiteralArgument("option", sender -> + sender == null ? List.of("default") : List.of("default", sender.getName()) + ) + ) + .executes(info -> { + results.set(info.args().getUnchecked("option")); + }) + .register(); + + Player playerA = server.addPlayer("PlayerA"); + Player playerB = server.addPlayer("PlayerB"); + + CommandSender console = server.getConsoleSender(); + + // Senders should only be able to use their own name option + assertStoresResult(playerA, "test default", results, "default"); + assertStoresResult(playerA, "test PlayerA", results, "PlayerA"); + assertCommandFailsWith( + playerA, "test PlayerB", + "Expected literal [default, PlayerA] at position 5: test <--[HERE]" + ); + assertCommandFailsWith( + playerA, "test CONSOLE", + "Expected literal [default, PlayerA] at position 5: test <--[HERE]" + ); + + assertStoresResult(playerB, "test default", results, "default"); + assertCommandFailsWith( + playerB, "test PlayerA", + "Expected literal [default, PlayerB] at position 5: test <--[HERE]" + ); + assertStoresResult(playerB, "test PlayerB", results, "PlayerB"); + assertCommandFailsWith( + playerB, "test CONSOLE", + "Expected literal [default, PlayerB] at position 5: test <--[HERE]" + ); + + assertStoresResult(console, "test default", results, "default"); + assertCommandFailsWith( + console, "test PlayerA", + "Expected literal [default, CONSOLE] at position 5: test <--[HERE]" + ); + assertCommandFailsWith( + console, "test PlayerB", + "Expected literal [default, CONSOLE] at position 5: test <--[HERE]" + ); + assertStoresResult(console, "test CONSOLE", results, "CONSOLE"); + + assertNoMoreResults(results); + } + + /******************** + * Suggestion tests * + ********************/ + + @Test + void testMultiLiteralSuggestions() { + new CommandAPICommand("test") + .withArguments( + new DynamicMultiLiteralArgument("option", sender -> List.of("alice", "apple", "bob", "banana")) + ) + .executesPlayer(P_EXEC) + .register(); + + Player sender = server.addPlayer(); + + assertCommandSuggests( + sender, "test ", + "alice", "apple", "banana", "bob" + ); + + assertCommandSuggests( + sender, "test a", + "alice", "apple" + ); + assertCommandSuggests( + sender, "test b", + "banana", "bob" + ); + + assertCommandSuggests( + sender, "test al", + "alice" + ); + + assertCommandSuggests( + sender, "test c" + ); + } + + @Test + void testDynamicSuggestions() { + new CommandAPICommand("test") + .withArguments( + new DynamicMultiLiteralArgument("option", sender -> + sender == null ? List.of("default") : List.of("default", sender.getName()) + ) + ) + .executesPlayer(P_EXEC) + .register(); + + Player playerA = server.addPlayer("PlayerA"); + Player playerB = server.addPlayer("PlayerB"); + + CommandSender console = server.getConsoleSender(); + + // Senders should only be suggested their own name option + assertCommandSuggests( + playerA, "test ", + "default", "PlayerA" + ); + + assertCommandSuggests( + playerB, "test ", + "default", "PlayerB" + ); + + assertCommandSuggests( + console, "test ", + "CONSOLE", "default" + ); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java index cccd86a6af..e7b33eae30 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/arguments/ArgumentListabilityTests.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.List; + /** * Tests for the effects of the {@link AbstractArgument#setListed(boolean)} method. * Arguments may use different {@link CommandNode} implementation that implement this with separate code. @@ -131,4 +133,27 @@ void testUnlistedPreviewableArguments() { assertNoMoreResults(results); } + + @Test + void testUnlistedDynamicMultiLiteralArguments() { + Mut results = Mut.of(); + Player sender = server.addPlayer(); + + new CommandAPICommand("test") + .withArguments( + new DynamicMultiLiteralArgument("literal", s -> List.of("a", "b", "c")).setListed(false), + new DynamicMultiLiteralArgument("literal", s -> List.of("d", "e", "f")), + new DynamicMultiLiteralArgument("literal", s -> List.of("g", "h", "i")).setListed(false) + ) + .executes(info -> { + results.set(info.args().getUnchecked("literal")); + }) + .register(); + + assertStoresResult(sender, "test a d g", results, "d"); + assertStoresResult(sender, "test b e h", results, "e"); + assertStoresResult(sender, "test c f i", results, "f"); + + assertNoMoreResults(results); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java index 7bf895f469..d46ba517e1 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/commandnodes/NodeTypeSerializerTests.java @@ -25,7 +25,7 @@ * Tests for {@link NodeTypeSerializer#addTypeInformation(JsonObject, CommandNode)} * via {@link CommandAPIHandler#serializeNodeToJson(CommandNode)} */ -class NodeTypeSerializerTests extends TestBase { +public class NodeTypeSerializerTests extends TestBase { /********* * Setup * @@ -41,11 +41,10 @@ public void tearDown() { super.tearDown(); } - private RootCommandNode getArgumentNodes(Argument argument) { - RootCommandNode root = new RootCommandNode<>(); + public static RootCommandNode getArgumentNodes(Argument argument) { + RootCommandNode root = new RootCommandNode<>(); NodeInformation previousNodeInformation = new NodeInformation<>( - List.of(root), children -> { - } + List.of(root), children -> {} ); argument.addArgumentNodes( previousNodeInformation, new ArrayList<>(), new ArrayList<>(), (builder, args) -> builder.build() @@ -53,12 +52,12 @@ private RootCommandNode getArgumentNodes(Argument argument) { return root; } - private JsonObject serialize(CommandNode node) { + public static JsonObject serialize(CommandNode node) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); return handler.serializeNodeToJson(node); } - private String prettyJsonString(JsonObject object) { + public static String prettyJsonString(JsonObject object) { return new GsonBuilder().setPrettyPrinting().create().toJson(object); } From ad5f4261de7984d80443acb85acaec35a74134b7 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sun, 1 Sep 2024 13:39:22 -0400 Subject: [PATCH 39/42] Remove test commands --- .../dev/jorel/commandapi/CommandAPIMain.java | 121 ------------------ .../dev/jorel/commandapi/CommandAPIMain.java | 74 ----------- 2 files changed, 195 deletions(-) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index 27a8662df1..cb5a44eb15 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -21,18 +21,13 @@ package dev.jorel.commandapi; import java.io.File; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import dev.jorel.commandapi.arguments.*; -import dev.jorel.commandapi.executors.CommandArguments; -import dev.jorel.commandapi.wrappers.IntegerRange; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; import org.bukkit.plugin.InvalidPluginException; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -137,121 +132,5 @@ private JavaPlugin getAndValidatePlugin(String pluginName) { @Override public void onEnable() { CommandAPI.onEnable(); - - List tags = new ArrayList<>(); - tags.add("hello"); - tags.add("world"); - - new CommandTree("servertag") - .then( - new LiteralArgument("add").then( - new StringArgument("tag").executes(info -> { - String tag = info.args().getUnchecked("tag"); - - tags.add(tag); - }) - ) - ) - .then( - new LiteralArgument("tag").then( - new DynamicMultiLiteralArgument("tag", sender -> { - if (sender instanceof Player player) { - List addPlayer = new ArrayList<>(tags); - addPlayer.add(player.getName()); - return addPlayer; - } else { - return tags; - } - }).then(new IntegerArgument("extra").replaceSafeSuggestions(SafeSuggestions.suggest(1, 2, 3)) - .executes(info -> { - String tag = info.args().getUnchecked("tag"); - int extra = info.args().getUnchecked("extra"); - - info.sender().sendMessage(tag + " " + extra); - } - ) - )) - ) - .register(); - - new CommandAPICommand("updateCommands") - .executes(info -> { - for (Player player : Bukkit.getOnlinePlayers()) { - CommandAPI.updateRequirements(player); - } - }) - .register(); - - // example from https://github.com/JorelAli/CommandAPI/issues/483 - new CommandAPICommand("serverFlag") - .withArguments( - new FlagsArgument("filters") - .loopingBranch( - new LiteralArgument("filter", "sort").setListed(true), - new MultiLiteralArgument("sortType", "furthest", "nearest", "random") - ) - .loopingBranch( - new LiteralArgument("filter", "limit").setListed(true), - new IntegerArgument("limitAmount", 0) - ) - .loopingBranch( - new LiteralArgument("filter", "distance").setListed(true), - new IntegerRangeArgument("range") - ) - ) - .executes(info -> { - for (CommandArguments branch : info.args().>getUnchecked("filters")) { - String filterType = branch.getUnchecked("filter"); - info.sender().sendMessage(switch (filterType) { - case "sort" -> "Sort " + branch.getUnchecked("sortType"); - case "limit" -> "Limit " + branch.getUnchecked("limitAmount"); - case "distance" -> "Distance " + branch.getUnchecked("range"); - default -> "Unknown branch " + filterType; - }); - } - }) - .register(); - - List keys = new ArrayList<>(); - List values = new ArrayList<>(); - new CommandTree("dynamicMap") - .then( - new LiteralArgument("add").then( - new MultiLiteralArgument("type", "key", "value").then( - new StringArgument("item").executes(info -> { - List choice = (info.args().getUnchecked("type").equals("key")) ? - keys : - values; - - String item = info.args().getUnchecked("item"); - choice.add(item); - }) - ) - ) - ) - .then( - new CustomArgument<>( - new FlagsArgument("map").loopingBranch( - new DynamicMultiLiteralArgument("key", sender -> keys), - new LiteralArgument("->"), // Haha! Multi-character delimiter :P - new DynamicMultiLiteralArgument("value", sender -> values) - ), - info -> { - Map result = new HashMap<>(); - for (CommandArguments args : (List) info.currentInput()) { - String key = args.getUnchecked("key"); - String value = args.getUnchecked("value"); - - if (result.put(key, value) != null) - throw CustomArgument.CustomArgumentException.fromString("Duplicate key \"" + key + "\""); - } - return result; - } - ).executes(info -> { - Map result = info.args().getUnchecked("map"); - info.sender().sendMessage(result.toString()); - }) - ) - .register(); } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index 1d9d6e5b6e..b6458cdb61 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -7,11 +7,7 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.annotation.DataDirectory; -import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import dev.jorel.commandapi.arguments.*; -import dev.jorel.commandapi.executors.CommandArguments; -import net.kyori.adventure.text.Component; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; @@ -20,8 +16,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import java.util.logging.Logger; /** @@ -79,74 +73,6 @@ public CommandAPIMain(ProxyServer server, Logger logger, @DataDirectory Path dat public void onProxyInitialization(ProxyInitializeEvent event) { // Enable CommandAPI.onEnable(); - - List tags = new ArrayList<>(); - tags.add("hello"); - tags.add("world"); - - new CommandTree("proxytag") - .then( - new LiteralArgument("add").then( - new StringArgument("tag").executes(info -> { - String tag = info.args().getUnchecked("tag"); - - tags.add(tag); - }) - ) - ) - .then( - new LiteralArgument("tag").then( - new DynamicMultiLiteralArgument("tag", sender -> { - if (sender instanceof Player player) { - List addPlayer = new ArrayList<>(tags); - addPlayer.add(player.getUsername()); - return addPlayer; - } else { - return tags; - } - }).then(new IntegerArgument("extra").replaceSafeSuggestions(SafeSuggestions.suggest(1, 2, 3)) - .executes(info -> { - String tag = info.args().getUnchecked("tag"); - int extra = info.args().getUnchecked("extra"); - - info.sender().sendMessage(Component.text(tag + " " + extra)); - }) - ) - ) - ) - .register(); - - // example from https://github.com/JorelAli/CommandAPI/issues/483 - new CommandAPICommand("proxyflag") - .withArguments( - new FlagsArgument("filters") - .loopingBranch( - new LiteralArgument("filter", "sort").setListed(true), - new MultiLiteralArgument("sortType", "furthest", "nearest", "random") - ) - .loopingBranch( - new LiteralArgument("filter", "limit").setListed(true), - new IntegerArgument("limitAmount", 0) - ) - .loopingBranch( - new LiteralArgument("filter", "distance").setListed(true), - new IntegerArgument("low"), - new IntegerArgument("high") - ) - ) - .executes(info -> { - for (CommandArguments branch : info.args().>getUnchecked("filters")) { - String filterType = branch.getUnchecked("filter"); - info.sender().sendMessage(Component.text(switch (filterType) { - case "sort" -> "Sort " + branch.getUnchecked("sortType"); - case "limit" -> "Limit " + branch.getUnchecked("limitAmount"); - case "distance" -> "Distance " + branch.getUnchecked("low") - + " to " + branch.getUnchecked("high"); - default -> "Unknown branch " + filterType; - })); - } - }) - .register(); } @Subscribe From 2d7ec3552ffe335c6d2f071ae6f18a92b03c59dc Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Sun, 1 Sep 2024 19:10:13 -0400 Subject: [PATCH 40/42] Fix proxy senders The logic for detecting when CommandSourceStacks have a target Entity different from the sender (e.g. when using `execute as`) was incorrectly removed, causing `executesProxy` executors to never be taken on a real server --- .../executors/BukkitTypedExecutor.java | 2 +- .../java/dev/jorel/commandapi/nms/NMS.java | 3 +- .../dev/jorel/commandapi/nms/NMS_1_16_R3.java | 31 ++++++++++++------- .../jorel/commandapi/nms/NMS_1_17_Common.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_1_18_R2.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_1_18_R1.java | 13 ++++---- .../jorel/commandapi/nms/NMS_1_19_Common.java | 13 ++++---- .../jorel/commandapi/nms/NMS_1_19_3_R2.java | 13 ++++---- .../jorel/commandapi/nms/NMS_1_19_4_R3.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_1_20_R2.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_1_20_R3.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_1_20_R4.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_1_20_R1.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_1_21_R1.java | 13 ++++---- .../dev/jorel/commandapi/nms/NMS_Common.java | 28 +++++++++++++---- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- .../dev/jorel/commandapi/test/MockNMS.java | 5 ++- 24 files changed, 140 insertions(+), 112 deletions(-) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java index c7e4ab7319..5445f4b0b2 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/executors/BukkitTypedExecutor.java @@ -35,7 +35,7 @@ default ExecutionInfo tryForSender(ExecutionInfo sender instanceof ProxiedCommandSender; case NATIVE -> { // If we're a NATIVE executor, always accept and convert sender to a NativeProxyCommandSender - NativeProxyCommandSender proxyCommandSender = CommandAPIBukkit.get().getNativeProxyCommandSender(info.cmdCtx()); + NativeProxyCommandSender proxyCommandSender = CommandAPIBukkit.get().getNativeProxyCommandSender(sender, info.cmdCtx().getSource()); info = info.copyWithNewSender(proxyCommandSender); yield true; } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java index b0bb9e6ec0..d9d7eee773 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java @@ -39,6 +39,7 @@ import org.bukkit.advancement.Advancement; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; +import org.bukkit.command.CommandSender; import org.bukkit.command.SimpleCommandMap; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; @@ -394,7 +395,7 @@ String getScoreHolderSingle(CommandContext cmdCtx, Strin Object getSound(CommandContext cmdCtx, String key, ArgumentSubType subType); - public abstract NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx); + NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandListenerWrapper css); /** * Retrieve a specific NMS implemented SuggestionProvider diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java index 08602e0db9..102e10faa1 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R3.java @@ -488,15 +488,29 @@ public CommandListenerWrapper getBrigadierSourceFromCommandSender(CommandSender @Override public CommandSender getCommandSenderFromCommandSource(CommandListenerWrapper clw) { + CommandSender sender; try { - CommandSender sender = clw.getBukkitSender(); - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - return sender == null ? Bukkit.getConsoleSender() : sender; + sender = clw.getBukkitSender(); } catch (UnsupportedOperationException e) { + // We expect this to happen when the source is `CommandSource.NULL`, + // which is used when parsing data pack functions return null; } + + // Sender CANNOT be null. This can occur when using a remote console + // sender. You can access it directly using this.getMinecraftServer().remoteConsole + // however this may also be null, so delegate to the next most-meaningful sender. + if (sender == null) { + sender = Bukkit.getConsoleSender(); + } + + // Check for a proxy entity + Entity proxyEntity = clw.getEntity(); + if (proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity())) { + return getNativeProxyCommandSender(sender, clw); + } + + return sender; } @Override @@ -797,12 +811,7 @@ public String getScoreHolderSingle(CommandContext cmdCtx } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandListenerWrapper clw = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(clw); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandListenerWrapper clw) { // Get position Vec3D pos = clw.getPosition(); Vec2F rot = clw.i(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java index 12dbf0fa9c..25e5106c21 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java @@ -304,6 +304,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public Enchantment getEnchantment(CommandContext cmdCtx, String key) throws CommandSyntaxException { return Enchantment.getByKey(fromResourceLocation(Registry.ENCHANTMENT.getKey(ItemEnchantmentArgument.getEnchantment(cmdCtx, key)))); @@ -582,12 +588,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java index ce1d9571b4..ba1ab39221 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java @@ -366,6 +366,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public Enchantment getEnchantment(CommandContext cmdCtx, String key) throws CommandSyntaxException { return Enchantment.getByKey(fromResourceLocation(Registry.ENCHANTMENT.getKey(ItemEnchantmentArgument.getEnchantment(cmdCtx, key)))); @@ -634,12 +640,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java index 24420cd1bd..5bae4f0eff 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java @@ -312,6 +312,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public Enchantment getEnchantment(CommandContext cmdCtx, String key) throws CommandSyntaxException { return Enchantment.getByKey(fromResourceLocation(Registry.ENCHANTMENT.getKey(ItemEnchantmentArgument.getEnchantment(cmdCtx, key)))); @@ -583,12 +589,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java index 70e636b74f..2408a95346 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java @@ -434,6 +434,12 @@ public final CommandSourceStack getBrigadierSourceFromCommandSender(CommandSende return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public Enchantment getEnchantment(CommandContext cmdCtx, String key) throws CommandSyntaxException { return Enchantment.getByKey(fromResourceLocation(Registry.ENCHANTMENT.getKey(ItemEnchantmentArgument.getEnchantment(cmdCtx, key)))); @@ -688,12 +694,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java index 538aee8873..f501714c9a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java @@ -310,6 +310,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public final World getDimension(CommandContext cmdCtx, String key) throws CommandSyntaxException { return DimensionArgument.getDimension(cmdCtx, key).getWorld(); @@ -568,12 +574,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java index 4d2edcf2c2..e785308c8f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java @@ -308,6 +308,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public final World getDimension(CommandContext cmdCtx, String key) throws CommandSyntaxException { return DimensionArgument.getDimension(cmdCtx, key).getWorld(); @@ -562,12 +568,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java index debd0a44ed..ddefacb877 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java @@ -324,6 +324,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public final World getDimension(CommandContext cmdCtx, String key) throws CommandSyntaxException { return DimensionArgument.getDimension(cmdCtx, key).getWorld(); @@ -576,12 +582,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java index 05f189a7a1..cf8732989e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java @@ -403,6 +403,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public final World getDimension(CommandContext cmdCtx, String key) throws CommandSyntaxException { @@ -707,12 +713,7 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java index bc4a8ec971..ec59c0de6a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java @@ -452,6 +452,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Differs(from = "1.20.4", by = "Serializer.toJson now needs a Provider") @Override public final BaseComponent[] getChat(CommandContext cmdCtx, String key) throws CommandSyntaxException { @@ -779,12 +785,7 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java index b946a51932..3393e7b874 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java @@ -307,6 +307,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public final World getDimension(CommandContext cmdCtx, String key) throws CommandSyntaxException { return DimensionArgument.getDimension(cmdCtx, key).getWorld(); @@ -562,12 +568,7 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java index e34e4e7f50..b5b01c31f8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.21/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R1.java @@ -452,6 +452,12 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send return VanillaCommandWrapper.getListener(sender); } + @Override + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); + } + @Override public final BaseComponent[] getChat(CommandContext cmdCtx, String key) throws CommandSyntaxException { return ComponentSerializer.parse(Serializer.toJson(MessageArgument.getMessage(cmdCtx, key), COMMAND_BUILD_CONTEXT)); @@ -777,12 +783,7 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - CommandSourceStack css = cmdCtx.getSource(); - - // Get original sender - CommandSender sender = getCommandSenderFromCommandSource(css); - + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { // Get position Vec3 pos = css.getPosition(); Vec2 rot = css.getRotation(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java index b94d16326e..eb03f32990 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java @@ -359,17 +359,33 @@ public final BaseComponent[] getChatComponent(CommandContext @Override public final CommandSender getCommandSenderFromCommandSource(CommandSourceStack css) { + CommandSender sender; try { - CommandSender sender = css.getBukkitSender(); - // Sender CANNOT be null. This can occur when using a remote console - // sender. You can access it directly using this.getMinecraftServer().remoteConsole - // however this may also be null, so delegate to the next most-meaningful sender. - return sender == null ? Bukkit.getConsoleSender() : sender; + sender = css.getBukkitSender(); } catch (UnsupportedOperationException e) { + // We expect this to happen when the source is `CommandSource.NULL`, + // which is used when parsing data pack functions return null; } + + // Sender CANNOT be null. This can occur when using a remote console + // sender. You can access it directly using this.getMinecraftServer().remoteConsole + // however this may also be null, so delegate to the next most-meaningful sender. + if (sender == null) { + sender = Bukkit.getConsoleSender(); + } + + // Check for a proxy entity + if (isProxyEntity(sender, css)) { + return getNativeProxyCommandSender(sender, css); + } + + return sender; } + @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CraftEntity") + protected abstract boolean isProxyEntity(CommandSender sender, CommandSourceStack css); + @Override @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CraftWorld", info = "CraftWorld is implicitly referenced by ServerLevel#getWorld, due to package renaming, it can't resolve at runtime") public abstract World getDimension(CommandContext cmdCtx, String key) throws CommandSyntaxException; @@ -528,7 +544,7 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St @Unimplemented(because = NAME_CHANGED, info = "i (1.17) -> getRotation (1.18) -> l (1.19)") @Unimplemented(because = NAME_CHANGED, info = "getEntity (1.17) -> getEntity (1.18) -> g (1.19)") @Unimplemented(because = NAME_CHANGED, info = "getWorld (1.17) -> getLevel (1.18) -> f (1.19)") - public abstract NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx); + public abstract NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css); @Override @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CraftServer") diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index e579c01aeb..675b87d395 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -40,7 +40,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Streams; import com.mojang.authlib.GameProfile; -import com.mojang.brigadier.context.CommandContext; import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; @@ -352,8 +351,8 @@ public CommandListenerWrapper getBrigadierSourceFromCommandSender(CommandSender } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandListenerWrapper css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java index edf99aacfa..fcd55f8275 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -39,7 +39,6 @@ import com.google.common.collect.Streams; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.context.CommandContext; import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; @@ -337,8 +336,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java index c4f23be039..44974bf73c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -43,7 +43,6 @@ import com.google.common.collect.Streams; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.context.CommandContext; import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; @@ -354,8 +353,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 43a09583f8..76d0810c74 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -43,7 +43,6 @@ import com.google.common.collect.Streams; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.context.CommandContext; import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; @@ -350,8 +349,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java index cf6e120c0d..7693a38886 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -43,7 +43,6 @@ import com.google.common.collect.Streams; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.context.CommandContext; import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.enchantments.EnchantmentMock; @@ -352,8 +351,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index c8a402ccb8..53d2fef8e6 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -9,7 +9,6 @@ import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.context.CommandContext; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.SharedConstants; @@ -347,8 +346,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 3cb2d54f8b..3f241c6132 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -9,7 +9,6 @@ import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.JsonOps; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; @@ -359,8 +358,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 228bc14b11..d92f8c85ce 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -7,7 +7,6 @@ import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.JsonOps; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; @@ -378,8 +377,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 2490994e47..2d2994a9fa 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -9,7 +9,6 @@ import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.context.CommandContext; import dev.jorel.commandapi.*; import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; import net.minecraft.SharedConstants; @@ -347,8 +346,8 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(CommandSender send } @Override - public NativeProxyCommandSender getNativeProxyCommandSender(CommandContext cmdCtx) { - return baseNMS.getNativeProxyCommandSender(cmdCtx); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); } @Override From c02421f726c6b29b4fd2881c8f0e64629c16c151 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:52:50 -0400 Subject: [PATCH 41/42] Fix Brigadier node serialization on Bukkit 1.16.5 and 1.17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some reason, on those specific versions, `JsonArray#deepCopy` has protected visibility ¯\_(ツ)_/¯. So, just don't use that method ig. --- .../main/java/dev/jorel/commandapi/CommandAPIHandler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index 4e9603225a..ef0a726fd0 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -565,7 +565,8 @@ public JsonObject serializeNodeToJson(CommandNode rootNode) { for (CommandNode child : children) { String name = child.getName(); - JsonArray newPath = node.path.deepCopy(); + JsonArray newPath = new JsonArray(); + newPath.addAll(node.path); newPath.add(name); nodesToProcess.offer(new Node<>(child, result -> childrenHolder.add(name, result), newPath)); @@ -580,7 +581,8 @@ public JsonObject serializeNodeToJson(CommandNode rootNode) { // Redirect CommandNode redirect = commandNode.getRedirect(); if (redirect != null) { - JsonArray newPath = node.path.deepCopy(); + JsonArray newPath = new JsonArray(); + newPath.addAll(node.path); newPath.add("redirect " + redirect.getName()); redirectsToProcess.offer(new Node<>(redirect, result -> output.add("redirect", result), newPath)); From 4844a0e2eea572952baa9115ec5a62a75e5d1f00 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:42:15 -0400 Subject: [PATCH 42/42] Resolve https://github.com/JorelAli/CommandAPI/issues/310 --- .../jorel/commandapi/CommandAPIHandler.java | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index ef0a726fd0..a49d230166 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -641,31 +641,12 @@ public static Map> getCommandNodeLit public static String getRawArgumentInput(CommandContext cmdCtx, String key) { final ParsedArgument parsedArgument = commandContextArguments.get(cmdCtx).get(key); - // TODO: Issue #310: Parsing this argument via /execute run doesn't have the value in - // the arguments for this command context (most likely because it's a redirected command). - // We need to figure out how to handle this case. - - // TODO: What is this talking about? https://github.com/JorelAli/CommandAPI/issues/310 - - // TODO: Oh, I might have figured out what's wrong - // https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/CommandDispatcher.java#L239 - // Redirects work by adding children onto a context builder - // Seen in that line, the source of the command is copied onto the context, but the arguments are not - // The child context is the one used to run the commands, so the argument doesn't exist when the command is being run - // This is currently also affecting MultiLiteralArguments since they use redirects now - // I feel like this is a bug in Brigadier, but maybe there is a reason for this? - // I hope there is at least a work around - // https://github.com/Mojang/brigadier/issues/137 - if (parsedArgument != null) { - // Sanity check: See https://github.com/JorelAli/CommandAPI/wiki/Implementation-details#chatcomponentargument-raw-arguments - StringRange range = parsedArgument.getRange(); - if (range.getEnd() > cmdCtx.getInput().length()) { - range = StringRange.between(range.getStart(), cmdCtx.getInput().length()); - } - return range.get(cmdCtx.getInput()); - } else { - return ""; + // Sanity check: See https://github.com/JorelAli/CommandAPI/wiki/Implementation-details#chatcomponentargument-raw-arguments + StringRange range = parsedArgument.getRange(); + if (range.getEnd() > cmdCtx.getInput().length()) { + range = StringRange.between(range.getStart(), cmdCtx.getInput().length()); } + return range.get(cmdCtx.getInput()); } /** @@ -677,6 +658,13 @@ public static String getRawArgumentInput(CommandContext cmdCtx, List args) throws CommandSyntaxException { + // https://github.com/JorelAli/CommandAPI/issues/310 & https://github.com/Mojang/brigadier/issues/137 + // If a command goes through a redirect, Brigadier puts all context after the redirect into the child + // CommandContext. When executing a command, Brigadier will pass us the child context. However, when + // suggesting a command, Brigadier passes us the root context for some reason. Assuming the behavior + // when executing a command is correct, when suggesting commands we should use the last child. + cmdCtx = cmdCtx.getLastChild(); + // Array for arguments for executor List argList = new ArrayList<>();