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..2c58117cb9 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.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; +import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; import java.util.List; @@ -21,8 +25,8 @@ public abstract class AbstractArgumentTree extends Executable { - final List> arguments = new ArrayList<>(); - final Argument argument; + private final Argument argument; + private List> arguments = new ArrayList<>(); /** * Instantiates an {@link AbstractArgumentTree}. This can only be called if the class @@ -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"); } } @@ -49,6 +53,10 @@ protected AbstractArgumentTree(final Argument argument) { this.executor = argument.executor; } + ///////////////////// + // Builder methods // + ///////////////////// + /** * Create a child branch on this node * @@ -60,19 +68,84 @@ 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)); + ///////////////////////// + // Getters and setters // + ///////////////////////// + + /** + * @return The child branches added to this tree by {@link #then(AbstractArgumentTree)}. + */ + public List> getArguments() { + return arguments; + } + + /** + * Sets the child branches that this node has + * + * @param arguments A new list of branches for this node + */ + public void setArguments(List> arguments) { + this.arguments = arguments; + } + + ////////////////// + // Registration // + ////////////////// + /** + * Builds the Brigadier {@link CommandNode} structure for this argument tree. + * + * @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( + NodeInformation previousNodeInformation, + List previousArguments, List previousArgumentNames + ) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Check preconditions + 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); } - // 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)); + + // 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, + terminalNodeModifier + ); + + // Collect children into our own list + 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<>( + previousNodeInformation.lastCommandNodes(), + childrenNodeInformation::addAll + ); + + // We need a new list so each branch acts independently + List newPreviousArguments = new ArrayList<>(previousArguments); + List newPreviousArgumentNames = new ArrayList<>(previousArgumentNames); + + child.buildBrigadierNode(newPreviousNodeInformation, newPreviousArguments, newPreviousArgumentNames); } - return executions; + + // 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 2bf2a9b701..51c7467ea9 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,17 @@ *******************************************************************************/ 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.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; -import dev.jorel.commandapi.arguments.GreedyArgument; -import dev.jorel.commandapi.exceptions.GreedyArgumentException; +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; + /** * A builder used to create commands to be registered by the CommandAPI. * @@ -49,9 +48,9 @@ public abstract class AbstractCommandAPICommand extends ExecutableCommand { - protected List arguments = new ArrayList<>(); - protected List subcommands = new ArrayList<>(); - protected boolean isConverted; + private List requiredArguments = new ArrayList<>(); + private List optionalArguments = new ArrayList<>(); + private List subcommands = new ArrayList<>(); /** * Creates a new command builder @@ -60,18 +59,11 @@ public abstract class AbstractCommandAPICommand metaData) { - super(metaData); - this.isConverted = false; - } + ///////////////////// + // Builder methods // + ///////////////////// /** * Appends the arguments to the current command builder @@ -79,9 +71,17 @@ protected AbstractCommandAPICommand(CommandMetaData metaData) { * @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(); } @@ -90,45 +90,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)); } /** @@ -148,18 +137,33 @@ 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) { - this.subcommands.addAll(Arrays.asList(subcommands)); + public Impl withSubcommands(List subcommands) { + this.subcommands.addAll(subcommands); return instance(); } + /** + * Adds subcommands to this command builder + * + * @param subcommands the subcommands to add as children of this command + * @return this command builder + */ + @SafeVarargs + public final Impl withSubcommands(Impl... subcommands) { + 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; } /** @@ -168,218 +172,146 @@ 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 subcommands that this command has + * Returns the list of optional arguments that this command has * - * @return the list of subcommands that this command has + * @return the list of optional arguments that this command has */ - public List getSubcommands() { - return subcommands; + public List getOptionalArguments() { + return optionalArguments; } /** - * Sets the list of subcommands that this command has + * Sets the optional arguments that this command has * - * @param subcommands the list of subcommands that this command has + * @param args the optional arguments that this command has */ - public void setSubcommands(List subcommands) { - this.subcommands = subcommands; + public void setOptionalArguments(List args) { + this.optionalArguments = args; } /** - * Returns whether this command is an automatically converted command - * - * @return whether this command is an automatically converted command + * @return True if this command has any required or optional arguments. */ - public boolean isConverted() { - return isConverted; + public boolean hasAnyArguments() { + return !requiredArguments.isEmpty() || !optionalArguments.isEmpty(); } /** - * 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 + * Returns the list of subcommands that this command has * - * @param isConverted whether this command is converted or not - * @return this command builder + * @return the list of subcommands that this command has */ - 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); - } + public List getSubcommands() { + return subcommands; } /** - * Registers the command with a given namespace + * Sets the list of subcommands that this command has * - * @param namespace The namespace of this command. This cannot be null - * @throws NullPointerException if the namespace is null + * @param subcommands the list of subcommands that this command has */ - @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 + "!"); - } - @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); - } - } - - 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); - } - } - - // Convert subcommands into multiliteral arguments - for (Impl subcommand : this.subcommands) { - flatten(this.copy(), new ArrayList<>(), subcommand, namespace); - } + public void setSubcommands(List subcommands) { + this.subcommands = subcommands; } - // 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); - } + ////////////////// + // Registration // + ////////////////// + @Override + 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); } } - 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; + @Override + protected boolean isRootExecutable() { + return executor.hasAnyExecutors() && requiredArguments.isEmpty(); } - protected abstract Impl newConcreteCommandAPICommand(CommandMetaData metaData); - - private List getArgumentsToRegister(Argument[] argumentsArray) { - List argumentsToRegister = new ArrayList<>(); - List currentCommand = new ArrayList<>(); - - Iterator argumentIterator = List.of(argumentsArray).iterator(); - - // 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; + @Override + protected List> createArgumentNodes(LiteralCommandNode rootNode) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + List> childrenNodes = new ArrayList<>(); + + // Create arguments + if (hasAnyArguments()) { + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), childrenNodes::addAll); + 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); + + // Note that `executor#hasAnyExecutors` must be true here + // If not, then `checkPreconditions` would have thrown a `MissingCommandExecutorException` + 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 + ); + + // Add optional arguments + for (Argument argument : optionalArguments) { + // All optional arguments are executable + previousNodeInformation = argument.addArgumentNodes( + previousNodeInformation, + previousArguments, previousArgumentNames, + executorCreator + ); } - currentCommand.addAll(unpackCombinedArguments(next)); + + // Create registered nodes now that all children are generated + previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of()); } - // 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) { + 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.permission(), rootNodeInformation.requirements(), + rootNodeInformation.children() + ) + ); } - 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; - } - - 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)); - } - return combinedArguments; + // 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 b8d010759b..70f53dae99 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.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; import java.util.List; @@ -21,7 +24,7 @@ public abstract class AbstractCommandTree extends ExecutableCommand { - private final List> arguments = new ArrayList<>(); + private List> arguments = new ArrayList<>(); /** * Creates a main root node for a command tree with a given command name @@ -32,6 +35,10 @@ protected AbstractCommandTree(final String commandName) { super(commandName); } + ///////////////////// + // Builder methods // + ///////////////////// + /** * Create a child branch on the tree * @@ -43,27 +50,69 @@ public Impl then(final AbstractArgumentTree tree) { return instance(); } + ///////////////////////// + // Getters and setters // + ///////////////////////// + + /** + * @return The child branches added to this tree by {@link #then(AbstractArgumentTree)}. + */ + public List> getArguments() { + return arguments; + } + /** - * Registers the command with a given namespace + * Sets the child branches that this command has * - * @param namespace The namespace of this command. This cannot be null - * @throws NullPointerException if the namespace is null + * @param arguments A new list of branches for this command */ + public void setArguments(List> arguments) { + this.arguments = arguments; + } + + ////////////////// + // Registration // + ////////////////// @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 + "!"); - } - List> executions = new ArrayList<>(); - if (this.executor.hasAnyExecutors()) { - executions.add(new Execution<>(List.of(), this.executor)); + protected void checkPreconditions() { + 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); } - for (AbstractArgumentTree tree : arguments) { - executions.addAll(tree.getExecutions()); - } - for (Execution execution : executions) { - execution.register(this.meta, namespace); + } + + @Override + protected boolean isRootExecutable() { + return executor.hasAnyExecutors(); + } + + @Override + 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]; + literals[0] = name; + System.arraycopy(aliases, 0, literals, 1, aliases.length); + Argument commandNames = handler.getPlatform().newConcreteMultiLiteralArgument(name, literals); + commandNames.setListed(false); + + // 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), childrenNodes::addAll); + List previousArguments = new ArrayList<>(); + List previousArgumentNames = new ArrayList<>(); + + previousArguments.add(commandNames); + + argument.buildBrigadierNode(previousNodeInformation, previousArguments, previousArgumentNames); } + + // Return children + return childrenNodes; } } 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..5b137c0e87 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java @@ -30,8 +30,8 @@ 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; import java.util.Collections; import java.util.List; @@ -80,10 +80,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 +125,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 +139,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 +159,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 +185,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,24 +201,21 @@ 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)); + 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()); } -} \ No newline at end of file +} 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..0434fb5aa5 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java @@ -3,11 +3,9 @@ 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; -import java.util.Collections; import java.util.List; /** @@ -232,14 +230,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 @@ -267,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); } /** @@ -287,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,7 +303,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(CommandAPIHandler.getInstance().registeredCommands); + public static List> getRegisteredCommands() { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + return List.copyOf(handler.registeredCommands.values()); } } 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 c8f34841c8..a49d230166 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -20,119 +20,102 @@ *******************************************************************************/ 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.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.builder.ArgumentBuilder; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; +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.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 com.mojang.brigadier.tree.RootCommandNode; 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; +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 dev.jorel.commandapi.wrappers.PreviewableFunction; + +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. * 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) +@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; + // 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 + // Compute all var handles all in one go so we don't do this during main server runtime static { - commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", "arguments", Map.class); - } + FIELDS = new HashMap<>(); - /** - * 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 - */ - 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 ""; + 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; } - // 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 + 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; + //////////////////// + // SECTION: Setup // + //////////////////// + protected CommandAPIHandler(CommandAPIPlatform platform) { this.platform = platform; - this.registeredCommands = new ArrayList<>(); - this.previewableArguments = new HashMap<>(); + this.registeredCommands = new LinkedHashMap<>(); // This should be a LinkedHashMap to preserve insertion order CommandAPIHandler.instance = this; } @@ -160,14 +143,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,580 +161,441 @@ public CommandAPIPlatform getPlatform() { return this.platform; } + //////////////////////////////// + // SECTION: Creating commands // + //////////////////////////////// + /** - * 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 + * 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. */ - Command generateCommand(Argument[] args, CommandAPIExecutor> executor, boolean converted) { + 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); - // Generate our command from executor - return cmdCtx -> { - AbstractCommandSender sender = platform.getSenderForCommand(cmdCtx, executor.isForceNative()); - CommandArguments commandArguments = argsToCommandArgs(cmdCtx, args); - ExecutionInfo> executionInfo = new ExecutionInfo<>() { - @Override - public CommandSender sender() { - return sender.getSource(); - } + // Do plaform-specific pre-registration tasks + platform.preCommandRegistration(command.getName()); - @Override - public AbstractCommandSender senderWrapper() { - return sender; - } + // Generate command information + ExecutableCommand.CommandInformation commandInformation = command.createCommandInformation(namespace); - @Override - 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(); - } + LiteralCommandNode resultantNode = commandInformation.rootNode(); + List> aliasNodes = commandInformation.aliasNodes(); + RegisteredCommand registeredCommand = commandInformation.command(); - @Override - public AbstractCommandSender senderWrapper() { - return sender; - } + // Log the commands being registered + for (List argsAsStr : registeredCommand.rootNode().argsAsStr()) { + CommandAPI.logInfo("Registering command /" + String.join(" ", argsAsStr)); + } - @Override - public CommandArguments args() { - return new CommandArguments(result, new LinkedHashMap<>(), result, new LinkedHashMap<>(), "/" + cmdCtx.getInput()); - } - }; + // Handle command conflicts + ensureNoCommandConflict(resultantNode); - System.arraycopy(argsAndCmd, 1, result, 0, argsAndCmd.length - 1); + // Register rootNode + platform.registerCommandNode(resultantNode, namespace); - // 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); - } + // Register aliasNodes + for (LiteralCommandNode aliasNode : aliasNodes) { + platform.registerCommandNode(aliasNode, namespace); + } - return resultValue; - } else { - return executor.execute(executionInfo); - } - }; +// 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(); + + // Merge RegisteredCommand into map + BiFunction, RegisteredCommand> 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(registeredCommand, resultantNode, aliasNodes); } /** - * Converts the List<Argument> into a {@link CommandArguments} for command execution + * 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 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 + * @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. */ - CommandArguments argsToCommandArgs(CommandContext cmdCtx, Argument[] args) - throws CommandSyntaxException { - // Array for arguments for executor - List argList = new ArrayList<>(); + 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()); - // LinkedHashMap for arguments for executor - Map argsMap = new LinkedHashMap<>(); + if (mergeTarget == null) return; // The `nodeToRegister` does not already exist, no conflict possible - // List for raw arguments - List rawArguments = new ArrayList<>(); + // Add node to path + List path = new ArrayList<>(pathSoFar); + path.add(nodeToRegister.getName()); - // LinkedHashMap for raw arguments - Map rawArgumentsMap = new LinkedHashMap<>(); + 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); + } - // 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())); + // Ensure children do not conflict + for (CommandNode child : nodeToRegister.getChildren()) { + ensureNoCommandConflict(child, mergeTarget, path); + } + } - // Add the parsed argument - argList.add(parsedArgument); - argsMap.put(argument.getNodeName(), parsedArgument); + /** + * 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. + */ + 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 + CommandSender sender = platform.getCommandSenderFromCommandSource(cmdCtx.getSource()); + CommandArguments commandArguments = argsToCommandArgs(cmdCtx, immutableArguments); - // Add the raw argument - String rawArgumentString = getRawArgumentInput(cmdCtx, argument.getNodeName()); + ExecutionInfo executionInfo = new ExecutionInfo<>(sender, commandArguments, cmdCtx); - rawArguments.add(rawArgumentString); - rawArgumentsMap.put(argument.getNodeName(), rawArgumentString); + // Apply the executor + return executor.execute(executionInfo); + }; + } + + /** + * 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; + }; } - return new CommandArguments(argList.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + cmdCtx.getInput()); + // 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; } /** - * 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()), + 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 + * after the permissions check + * @return A Predicate that makes sure a Brigadier source object satisfies the given permission and arbitrary requirements. */ - 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); - } + public Predicate generateBrigadierRequirements(CommandPermission permission, Predicate requirements) { + // If requirements are always false, result is always false + if (requirements == CommandPermission.FALSE()) return CommandPermission.FALSE(); - // Register permission to the platform's registry, if both exist - permission.getPermission().ifPresent(platform::registerPermission); + // Find the intial check for the given CommandPermission + Predicate senderCheck = platform.getPermissionCheck(permission); - // Generate predicate for the permission and requirement check - CommandPermission finalPermission = permission; - return (Source css) -> permissionCheck(platform.getCommandSenderFromCommandSource(css), finalPermission, - requirements); + // Merge with requirements (unless its always true, which wouldn't add anything) + if (requirements != CommandPermission.TRUE()) senderCheck = senderCheck.and(requirements); + + // 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(); + + // Otherwise, convert Brigadier Source to CommandSender and run the check + final Predicate finalSenderCheck = senderCheck; + return source -> { + CommandSender sender = platform.getCommandSenderFromCommandSource(source); + return finalSenderCheck.test(sender); + }; } + ///////////////////////////// + // SECTION: Unregistration // + ///////////////////////////// /** - * 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 + * 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. */ - static boolean permissionCheck(AbstractCommandSender sender, CommandPermission permission, Predicate requirements) { - boolean satisfiesPermissions; - if (sender == null) { - satisfiesPermissions = true; - } else { - if (permission.equals(CommandPermission.NONE)) { - // No permission set - satisfiesPermissions = true; - } else if (permission.equals(CommandPermission.OP)) { - // Op permission set - satisfiesPermissions = sender.isOp(); - } 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()); - } + public void unregister(String command, boolean unregisterNamespaces) { + // Remove command from `registeredCommands` + registeredCommands.remove(command); + if (unregisterNamespaces) removeCommandNamespace(registeredCommands, command, e -> true); - ////////////////////////////////////////////////////////////////////////////////////////////////////// - // SECTION: Registration // - ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Platform-specific unregistration logic + platform.unregister(command, unregisterNamespaces); - /* - * 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; + // Update the dispatcher file + writeDispatcherToFile(); } - // 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 + 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); - Because it conflicts with this previously registered command: + 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); - %s %s - """.formatted(commandName, argumentsAsString, commandName, builder2.toString())); - return true; - } + if (unregisterNamespaces) { + removeCommandNamespace(children, commandName, extraCheck); + if (literals != null) removeCommandNamespace(literals, commandName, extraCheck); + removeCommandNamespace(arguments, commandName, extraCheck); } - return false; } - // Links arg -> Executor - private ArgumentBuilder generateInnerArgument(Command command, Argument[] args) { - Argument innerArg = args[args.length - 1]; + protected static void removeCommandNamespace(Map map, String commandName, Predicate extraCheck) { + for (String key : new HashSet<>(map.keySet())) { + if (!isThisTheCommandButNamespaced(commandName, key)) continue; - // Handle Literal arguments - if (innerArg instanceof Literal) { - @SuppressWarnings("unchecked") - Literal literalArgument = (Literal) innerArg; - return getLiteralArgumentBuilderArgument(literalArgument.getLiteral(), innerArg.getArgumentPermission(), - innerArg.getRequirements()).executes(command); + removeCommandFromMapIfCheckPasses(map, key, extraCheck); } + } - // 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); - } + 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); + } - // Handle every other type of argument - else { - return getRequiredArgumentBuilderDynamic(args, innerArg).executes(command); - } + 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); } - // 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); - } + //////////////////////////////// + // SECTION: Brigadier Helpers // + //////////////////////////////// - // 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); + public void writeDispatcherToFile() { + File file = CommandAPI.getConfiguration().getDispatcherFile(); + if (file != null) { + try { + // Make sure the file exists + file.getParentFile().mkdirs(); + file.createNewFile(); + } catch (IOException e) { + CommandAPI.logError("Failed to create the required directories for " + file.getName() + ": " + e.getMessage()); + return; } - // Handle every other type of argument - else { - outer = getRequiredArgumentBuilderDynamic(args, outerArg).then(outer); + try { + // Write the dispatcher json + writeDispatcherToFile(file, platform.getBrigadierDispatcher()); + } catch (IOException e) { + CommandAPI.logError("Failed to write command registration info to " + file.getName() + ": " + e.getMessage()); } } - return outer; } /** - * Handles previewable arguments. This stores the path to previewable arguments - * in {@link CommandAPIHandler#previewableArguments} for runtime resolving + * Creates a JSON file that describes the hierarchical structure of the commands + * that have been registered by the server. * - * @param commandName the name of the command - * @param args the declared arguments - * @param aliases the command's aliases + * @param file The JSON file to write to + * @param dispatcher The Brigadier CommandDispatcher + * @throws IOException When the file fails to be written to */ - 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); + public void writeDispatcherToFile(File file, CommandDispatcher dispatcher) throws IOException { + Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() + .toJson(serializeNodeToJson(dispatcher.getRoot()))); + } - // And aliases - for (String alias : aliases) { - path.set(0, alias); - previewableArguments.put(List.copyOf(path), previewable); - } - } + private record Node(CommandNode commandNode, Consumer resultConsumer, JsonArray path) { } - // 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; - } + 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<>(); - // 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(); - } + // Extract serialization of the rootNode as our result + JsonObject resultHolder = new JsonObject(); + redirectsToProcess.offer(new Node<>(rootNode, result -> resultHolder.add("result", result), new JsonArray())); - // #312 Safeguard against duplicate node names. This only applies to - // required arguments (i.e. not literal arguments) - if(!checkForDuplicateArgumentNodeNames(args, humanReadableCommandArgSyntax, meta.commandName)) { - return; - } + Node node; + while ((node = redirectsToProcess.poll()) != null) { + nodesToProcess.offer(node); - // 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; + while ((node = nodesToProcess.poll()) != null) { + CommandNode commandNode = node.commandNode; - // Handle command conflicts - boolean hasRegisteredCommand = false; - for (int i = 0, size = registeredCommands.size(); i < size && !hasRegisteredCommand; i++) { - hasRegisteredCommand |= registeredCommands.get(i).commandName().equals(commandName); - } + // Add information to parent + JsonArray path = shortestPath.get(commandNode); + if (path != null) { + // Represent this node with the shortest path + node.resultConsumer.accept(path); + continue; + } - if (hasRegisteredCommand && hasCommandConflict(commandName, args, humanReadableCommandArgSyntax)) { - return; - } + // This is the first time finding this node + shortestPath.put(commandNode, node.path); - 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 { + // Represent this node with a new object + JsonObject output = new JsonObject(); + node.resultConsumer.accept(output); - // Generate all of the arguments, following each other and finally linking to - // the executor - ArgumentBuilder commandArguments = generateOuterArguments( - generateInnerArgument(command, args), args); + // Node type + NodeTypeSerializer.addTypeInformation(output, commandNode); - // Link command name to first argument and register - resultantNode = platform.registerCommandNode(getLiteralArgumentBuilder(commandName) - .requires(generatePermissions(commandName, permission, requirements, namespace)).then(commandArguments), namespace); + // Children + Collection> children = commandNode.getChildren(); + if (!children.isEmpty()) { + JsonObject childrenHolder = new JsonObject(); + output.add("children", childrenHolder); - // Register aliases - for (String alias : aliases) { - if (CommandAPI.getConfiguration().hasVerboseOutput()) { - CommandAPI.logInfo("Registering alias /" + alias + " -> " + resultantNode.getName()); - } + for (CommandNode child : children) { + String name = child.getName(); - aliasNodes.add(platform.registerCommandNode(getLiteralArgumentBuilder(alias) - .requires(generatePermissions(alias, permission, requirements, namespace)).then(commandArguments), namespace)); - } - } + JsonArray newPath = new JsonArray(); + newPath.addAll(node.path); + newPath.add(name); -// 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(); + nodesToProcess.offer(new Node<>(child, result -> childrenHolder.add(name, result), newPath)); + } + } - platform.postCommandRegistration(registeredCommandInformation, resultantNode, aliasNodes); - } + // Command + if (commandNode.getCommand() != null) { + output.addProperty("executable", true); + } - /** - * 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()); + // Redirect + CommandNode redirect = commandNode.getRedirect(); + if (redirect != null) { + JsonArray newPath = new JsonArray(); + newPath.addAll(node.path); + newPath.add("redirect " + redirect.getName()); + + redirectsToProcess.offer(new Node<>(redirect, result -> output.add("redirect", result), newPath)); } } } - return true; - } - - public void writeDispatcherToFile() { - File file = CommandAPI.getConfiguration().getDispatcherFile(); - if (file != null) { - try { - file.getParentFile().mkdirs(); - if (file.createNewFile()) { - // Cool, we've created the file - assert true; - } - } catch (IOException e) { - CommandAPI.logError("Failed to create the required directories for " + file.getName() + ": " + e.getMessage()); - return; - } - try { - platform.createDispatcherFile(file, platform.getBrigadierDispatcher()); - } catch (IOException e) { - CommandAPI.logError("Failed to write command registration info to " + file.getName() + ": " + e.getMessage()); - } - } + return resultHolder.getAsJsonObject("result"); } - LiteralCommandNode namespaceNode(LiteralCommandNode original, String namespace) { + public 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 +604,71 @@ 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); + public static Map> getCommandNodeChildren(CommandNode target) { + return (Map>) commandNodeChildren.get(target); } - /** - * 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 - */ - LiteralArgumentBuilder getLiteralArgumentBuilderArgument(String commandName, CommandPermission permission, Predicate requirements) { - LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal(commandName); - return builder.requires((Source css) -> permissionCheck(platform.getCommandSenderFromCommandSource(css), - permission, requirements)); + public static Map> getCommandNodeLiterals(CommandNode target) { + if (commandNodeLiterals == null) return null; + return (Map>) commandNodeLiterals.get(target); } - // Gets a RequiredArgumentBuilder for a DynamicSuggestedStringArgument - RequiredArgumentBuilder getRequiredArgumentBuilderDynamic(final Argument[] args, Argument argument) { - - final SuggestionProvider suggestions; - - 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); - } else { - suggestions = null; - } - - return getRequiredArgumentBuilderWithProvider(argument, args, suggestions); + public static Map> getCommandNodeArguments(CommandNode target) { + return (Map>) commandNodeArguments.get(target); } - // 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); + //////////////////////////////// + // SECTION: Parsing arguments // + //////////////////////////////// - newSuggestionsProvider = (cmdCtx, builder) -> { - // Heavily inspired by CommandDispatcher#listSuggestions, with combining - // multiple CompletableFuture into one. + /** + * 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 + */ + public static String getRawArgumentInput(CommandContext cmdCtx, String key) { + final ParsedArgument parsedArgument = commandContextArguments.get(cmdCtx).get(key); - 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; - }; + // 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()); } - - RequiredArgumentBuilder requiredArgumentBuilder = RequiredArgumentBuilder - .argument(argument.getNodeName(), argument.getRawType()); - - return requiredArgumentBuilder.requires(css -> permissionCheck(platform.getCommandSenderFromCommandSource(css), - argument.getArgumentPermission(), argument.getRequirements())).suggests(newSuggestionsProvider); + return range.get(cmdCtx.getInput()); } - 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 + */ + public CommandArguments argsToCommandArgs(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(); - // LinkedHashMap for arguments + // Array for arguments for executor + List argList = new ArrayList<>(); + + // LinkedHashMap for arguments for executor Map argsMap = new LinkedHashMap<>(); // List for raw arguments @@ -856,89 +677,40 @@ 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()); - } - - 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); - }; - } - /** - * 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 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. - */ - @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(); - } + return new CommandArguments(argList.toArray(), argsMap, rawArguments.toArray(new String[0]), rawArgumentsMap, "/" + cmdCtx.getInput()); } /** - * - * @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 + * 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 */ - public boolean lookupPreviewableLegacyStatus(List path) { - final Previewable previewable = previewableArguments.get(path); - if (previewable != null && previewable.getPreview().isPresent()) { - return previewable.isLegacy(); + public Object parseArgument(CommandContext cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException { + if (value.isListed()) { + return value.parseArgument(cmdCtx, key, previousArgs); } else { - return true; + return null; } } @@ -949,7 +721,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 +733,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 +757,6 @@ public static Field getField(Class clazz, String name, String mojangMappedNam } } - ////////////////////////////// - // SECTION: Private classes // - ////////////////////////////// - /** * Class to store cached methods and fields *

@@ -997,56 +765,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..005ef2860e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -1,18 +1,17 @@ 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.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.Optional; +import java.util.function.Predicate; /** * @param The implementation of AbstractArgument used for the platform @@ -45,47 +44,36 @@ 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); + public abstract Source getBrigadierSourceFromCommandSender(CommandSender sender); + + // Some commands have existing suggestion providers + public abstract SuggestionProvider getSuggestionProvider(SuggestionProviders suggestionProvider); /** - * Wraps a CommandSender in an AbstractCommandSender class, the inverse operation to {@link AbstractCommandSender#getSource()} + * 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 sender The CommandSender to wrap - * @return An AbstractCommandSender with a class appropriate to the underlying class of the CommandSender + * @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 AbstractCommandSender 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); + public abstract String validateNamespace(ExecutableCommand command, String namespace); /** * Stuff to run before a command is generated. For Bukkit, this involves checking @@ -102,21 +90,37 @@ public interface CommandAPIPlatform resultantNode, List> aliasNodes); + public abstract void postCommandRegistration(RegisteredCommand registeredCommand, 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. + */ + 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); @@ -125,15 +129,7 @@ public interface CommandAPIPlatform 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 @@ -174,16 +170,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); - - // Create the concrete instances of objects implemented by the platform - public abstract AbstractCommandAPICommand newConcreteCommandAPICommand(CommandMetaData meta); + 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/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/CommandPermission.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java index adcdcb884c..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 * @@ -147,9 +219,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/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 3f1b41e4a8..00eb0ba04a 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -1,8 +1,19 @@ package dev.jorel.commandapi; -import java.util.Optional; +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.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 * @@ -14,65 +25,93 @@ 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 the comamand sender must pass in order to execute the command + */ + protected Predicate requirements = CommandPermission.TRUE(); - ExecutableCommand(final String commandName) { - this.meta = new CommandMetaData<>(commandName); - } + // Command help + protected CommandAPIHelpTopic helpTopic = new EditableHelpTopic<>(); + + 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 +124,243 @@ 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 {@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 withAliases(String... aliases) { - this.meta.aliases = aliases; + 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. + * + * @param description the short description for this command + * @return this command builder + */ + public Impl withShortDescription(String description) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withShortDescription(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) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withFullDescription(description); + } + return instance(); + } /** - * Returns the permission associated with this command - * @return the permission associated with this command + * 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 CommandPermission getPermission() { - return this.meta.permission; + public Impl withHelp(String shortDescription, String fullDescription) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withHelp(shortDescription, fullDescription); + } + return instance(); } /** - * Sets the permission required to run this command - * @param permission the permission required to run 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 void setPermission(CommandPermission permission) { - this.meta.permission = permission; + public Impl withUsage(String... usage) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withUsage(usage); + } + return instance(); } /** - * 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 + * 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 String[] getAliases() { - return meta.aliases; + 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(); + } + + ///////////////////////// + // Getters and setters // + ///////////////////////// + + /** + * Returns the name of this command + * + * @return the name of this command + */ + public String getName() { + return name; } /** * Sets the aliases for this command + * * @param aliases the aliases for this command */ public void setAliases(String[] aliases) { - this.meta.aliases = 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 aliases; + } + + /** + * Returns the permission associated with this command + * + * @return the permission associated with this command + */ + 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 + * Returns the {@link CommandAPIHelpTopic} for this command + * + * @return the {@link CommandAPIHelpTopic} for this command */ - public String getShortDescription() { - return this.meta.shortDescription.orElse(null); + public CommandAPIHelpTopic getHelpTopic() { + return helpTopic; } /** - * 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 + * Returns the short description for this command + * + * @return the short description for this command */ - public Impl withShortDescription(String description) { - this.meta.shortDescription = Optional.ofNullable(description); - return instance(); + public String getShortDescription() { + return this.helpTopic.getShortDescription().orElse(null); } - + /** * 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.helpTopic.getFullDescription(null).orElse(null); } /** * Returns the usage for this command + * * @return the usage for this command */ public String[] getUsage() { - return this.meta.usageDescription.orElse(null); + return this.helpTopic.getUsage(null, null).orElse(null); } - /** - * 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.meta.shortDescription = Optional.ofNullable(shortDescription); - this.meta.fullDescription = Optional.ofNullable(fullDescription); - return instance(); - } + ////////////////// + // Registration // + ////////////////// /** * 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 +372,79 @@ public void register() { } /** - * Registers this command with a custom {@link String} namespace + * 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. + * See {@link CommandAPIPlatform#validateNamespace(ExecutableCommand, String)}. + * @throws NullPointerException if the namespace is null. */ - public abstract void register(String namespace); + public void register(String namespace) { + ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); + } + + protected static record CommandInformation( + LiteralCommandNode rootNode, + List> aliasNodes, + RegisteredCommand command + ) { + } + + protected CommandInformation createCommandInformation(String namespace) { + checkPreconditions(); + + // Create rootNode + LiteralCommandNode rootNode = this.createCommandNodeBuilder(name).build(); + + List> children = createArgumentNodes(rootNode); + + // Create aliaseNodes + List> aliasNodes = createAliasNodes(rootNode); + + // Create command information + 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); + } + + 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 List> createArgumentNodes(LiteralCommandNode rootNode); } 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/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/RegisteredCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java index ebaa9adea1..b130d295b2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java @@ -1,20 +1,27 @@ 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; +import java.util.function.Predicate; + +import dev.jorel.commandapi.help.CommandAPIHelpTopic; /** - * 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 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 @@ -22,64 +29,181 @@ 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 */ - List argsAsStr, + String[] aliases, /** - * @return An unmodifiable list of arguments for this command + * @return The namespace for this command */ - List> arguments, + String namespace, /** - * @return An {@link Optional} containing this command's help's short - * descriptions + * @return The {@link CommandAPIHelpTopic} that stores the help information for this command */ - Optional shortDescription, + CommandAPIHelpTopic helpTopic, /** - * @return An {@link Optional} containing this command's help's full - * descriptions + * @return The root {@link Node} in the tree structure that holds the arguments of this command */ - Optional fullDescription, + Node rootNode) { /** - * @return An {@link Optional} containing this command's help's - * usage - */ - Optional usageDescription, - - /** - * @return An {@link Optional} containing this command's help topic (for Bukkit) + * 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 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 */ - Optional helpTopic, + public 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 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) { + + /** + * @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; + } - /** - * @return a {@link String}{@code []} of aliases for this command - */ - String[] aliases, + 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, 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); + } + } /** - * @return The {@link CommandPermission} required to run 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. */ - CommandPermission permission, + 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); + + // Other information defaults to the command that was registered first (this) + return new RegisteredCommand<>(commandName, mergedAliases, namespace, helpTopic, mergedRootNode); + } /** - * @return The namespace for this command + * @return A copy of this {@link RegisteredCommand}, but with {@link RegisteredCommand#namespace()} as {@code ""}. */ - String namespace) { - // 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 + 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` @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(argsAsStr, commandName, fullDescription, permission, shortDescription, helpTopic, namespace); + result = prime * result + Objects.hash(commandName, namespace, helpTopic, rootNode); return result; } @@ -91,18 +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(argsAsStr, other.argsAsStr) && 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 + ", argsAsStr=" + argsAsStr + ", shortDescription=" + shortDescription + ", fullDescription=" + fullDescription - + ", usageDescription=" + (usageDescription.isPresent() ? "Optional[" + Arrays.toString(usageDescription.get()) + "]" : "Optional.empty") - + ", aliases=" + Arrays.toString(aliases) + ", permission=" + permission + ", namespace=" + namespace + ", helpTopic=" + helpTopic + "]"; + return "RegisteredCommand [commandName=" + commandName + ", aliases=" + Arrays.toString(aliases) + ", namespace=" + namespace + + ", helpTopic=" + helpTopic + ", rootNode=" + rootNode + "]"; } - -} \ No newline at end of file +} 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 decd3ed6c2..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 @@ -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.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; 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.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(); } @@ -166,8 +175,8 @@ public Impl replaceSuggestions(ArgumentSuggestions suggestions) { * @return a function that provides suggestions, or Optional.empty() if there * are no overridden suggestions. */ - public final Optional> getOverriddenSuggestions() { - return suggestions; + public Optional> getOverriddenSuggestions() { + return Optional.ofNullable(replaceSuggestions); } ///////////////// @@ -182,7 +191,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(); } @@ -193,7 +202,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(); } @@ -203,7 +212,7 @@ public final Impl withPermission(String permission) { * * @return the permission required to run this command */ - public final CommandPermission getArgumentPermission() { + public CommandPermission getArgumentPermission() { return permission; } @@ -211,14 +220,14 @@ public final CommandPermission getArgumentPermission() { // Requirements // ////////////////// - private Predicate requirements = s -> true; + private Predicate requirements = CommandPermission.TRUE(); /** * Returns the requirements required to run this command * * @return the requirements required to run this command */ - public final Predicate getRequirements() { + public Predicate getRequirements() { return this.requirements; } @@ -230,18 +239,11 @@ 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(); } - /** - * Resets the requirements for this command - */ - void resetRequirements() { - this.requirements = s -> true; - } - ///////////////// // Listability // ///////////////// @@ -271,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. * @@ -314,59 +294,296 @@ 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. + * 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. * - * 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. + * @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 */ @SafeVarargs public final Impl combineWith(Argument... combinedArguments) { - for (Argument argument : combinedArguments) { - this.combinedArguments.add(argument); - } - return instance(); + return this.combineWith(Arrays.asList(combinedArguments)); + } + + ////////////////////// + // Command Building // + ////////////////////// + @Override + public String toString() { + return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">"; + } + + /** + * 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 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); } - /////////// - // Other // - /////////// + /** + * 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); + } /** - * 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)} + * 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(NodeInformation, List, List)}
  • + *
  • {@link #createArgumentBuilder(List, List)}
  • + *
  • {@link #finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}
  • + *
  • {@link #linkNode(NodeInformation, CommandNode, List, List, TerminalNodeModifier)}
  • + *
* - * @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 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 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 List getEntityNames(Object argument) { - return Collections.singletonList(null); + public NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, + List previousArguments, List previousArgumentNames, + TerminalNodeModifier terminalNodeModifier + ) { + // Check preconditions + checkPreconditions(previousNodeInformation, previousArguments, previousArgumentNames); + + // Create node + ArgumentBuilder rootBuilder = createArgumentBuilder(previousArguments, previousArgumentNames); + + // Finish building node + CommandNode rootNode = finishBuildingNode(rootBuilder, previousArguments, terminalNodeModifier); + + // Link node to previous + previousNodeInformation = linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalNodeModifier); + + // Return last nodes + return previousNodeInformation; } /** - * Copies permissions and requirements from the provided argument to this argument - * This also resets additional permissions and requirements. + * Checks for any conditions that mean this argument cannot be built properly. * - * @param argument The argument to copy permissions and requirements from + * @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 copyPermissionsAndRequirements(Argument argument) { - this.resetRequirements(); - this.withRequirement(argument.getRequirements()); - this.withPermission(argument.getArgumentPermission()); + public void checkPreconditions( + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames + ) { + if(previousNodeInformation.lastCommandNodes().isEmpty()) { + throw new GreedyArgumentException(previousArguments, (Argument) this); + } + if (isListed && previousArgumentNames.contains(nodeName)) { + throw new DuplicateNodeNameException(previousArguments, (Argument) this); + } } - public String getHelpString() { - return "<" + this.getNodeName() + ">"; + /** + * 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 previousArgumentNames} + * lists. + * + * @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 previousArgumentNames) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // 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. + SuggestionProvider suggestions = handler.generateBrigadierSuggestions(previousArguments, (Argument) this); + ArgumentBuilder rootBuilder; + 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(suggestions); + + rootBuilder = builder; + } else { + UnnamedRequiredArgumentBuilder builder = UnnamedRequiredArgumentBuilder.unnamedArgument(nodeName, rawType); + builder.suggests(suggestions); + + 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); + if(isListed) previousArgumentNames.add(nodeName); + + return rootBuilder; } - @Override - public String toString() { - return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">"; + /** + * 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 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, + TerminalNodeModifier terminalNodeModifier + ) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + // Add permission and requirements + rootBuilder.requires(handler.generateBrigadierRequirements(permission, requirements)); + + // Apply the terminal modifier if we are the last node + if (combinedArguments.isEmpty()) { + return terminalNodeModifier.finishTerminalNode(rootBuilder, previousArguments); + } + + return rootBuilder.build(); + } + + /** + * Links this argument into the Brigadier {@link CommandNode} structure. + * + * @param previousNodeInformation The {@link NodeInformation} of the argument this argument is being added to. + * @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 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 {@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 NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, + List previousArguments, List previousArgumentNames, + TerminalNodeModifier terminalNodeModifier + ) { + // Add rootNode to the previous nodes + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { + previousNode.addChild(rootNode); + } + + // 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 + ">", + rootNode.getCommand() != null, + permission, requirements, + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + return stackArguments(combinedArguments, nodeInformation, previousArguments, previousArgumentNames, terminalNodeModifier); + } + + /** + * 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 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 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. + * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing the built argument stack. + */ + public static , CommandSender, Source> NodeInformation stackArguments( + List argumentsToStack, NodeInformation previousNodeInformation, + List previousArguments, List previousArgumentNames, + 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 terminal modifier to the last argument + i == lastIndex ? terminalNodeModifier : (builder, args) -> builder.build() + ); + } + + // Return information + return previousNodeInformation; } -} \ No newline at end of file +} 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..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 * @@ -123,6 +128,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/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/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java new file mode 100644 index 0000000000..2963f3f0a6 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -0,0 +1,226 @@ +package dev.jorel.commandapi.arguments; + +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.CommandNode; +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.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +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#getArgumentPermission()}. + */ + CommandPermission getArgumentPermission(); + + /** + * Links to {@link AbstractArgument#getRequirements()}. + */ + Predicate getRequirements(); + + /** + * Links to {@link AbstractArgument#getCombinedArguments()}. + */ + List getCombinedArguments(); + + /** + * Links to {@link AbstractArgument#checkPreconditions(NodeInformation, List, List)}. + */ + void checkPreconditions(NodeInformation previousNodes, List previousArguments, List previousArgumentNames); + + /** + * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}. + */ + CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, TerminalNodeModifier terminalNodeModifier); + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDING METHODS // + // 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)} + */ + 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; + } + + /** + * 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. + */ + default NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, + List previousArguments, List previousArgumentNames, + TerminalNodeModifier terminalNodeModifier + ) { + // Typical preconditions still apply + checkPreconditions(previousNodeInformation, 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, (builder, args) -> builder.build()); + FlagsArgumentRootNode rootNode = new FlagsArgumentRootNode<>(rootBuilder.build()); + + for (CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { + previousNode.addChild(rootNode); + } + + // Setup looping branches + 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 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, + terminalBranchModifier, false + )); + } + + // 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 + ">", + loopingBranchesTerminal || terminalBranchesTerminal, + getArgumentPermission(), getRequirements(), + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + return AbstractArgument.stackArguments( + getCombinedArguments(), nodeInformation, + previousArguments, previousArgumentNames, + terminalNodeModifier + ); + } + + private List> setupBranch( + List branchArguments, FlagsArgumentRootNode rootNode, + List previousArguments, List previousArgumentNames, + TerminalNodeModifier terminalNodeModifier, boolean loop + ) { + RootCommandNode branchRoot = new RootCommandNode<>(); + NodeInformation branchNodeInformation = new NodeInformation<>(List.of(branchRoot), null); + + // Stack branch nodes + 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); + } + + // Finish building the regular node + CommandNode rawEnd = terminalNodeModifier.finishTerminalNode(builder, branchPreviousArguments); + + // Wrap node + CommandNode flagEnd = FlagsArgumentEndingNode.wrapNode(rawEnd, nodeName, branchPreviousArguments); + + if (loop) { + // Ensure looping flagEnd has same children as root + rootNode.addLoopEndNode(flagEnd); + } + + 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 9953538269..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,15 +1,26 @@ package dev.jorel.commandapi.arguments; -import dev.jorel.commandapi.ChainableBuilder; +import com.mojang.brigadier.builder.ArgumentBuilder; +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.arguments.AbstractArgument.TerminalNodeModifier; +import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; + +import java.util.List; +import java.util.function.Predicate; /** * An interface representing literal-based arguments */ -public interface Literal +extends AbstractArgument /// @endcond -> extends ChainableBuilder { +, CommandSender> { // Literal specific information /** @@ -18,4 +29,92 @@ public interface Literal getRequirements(); + + /** + * Links to {@link AbstractArgument#isListed()}. + */ + boolean isListed(); + + /** + * Links to {@link AbstractArgument#getCombinedArguments()}. + */ + List getCombinedArguments(); + + /////////////////////////////////////////////////////////////////////////////////////////////// + // 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 previousArgumentNames} list here, but Literals only do + * this if they are listed. + */ + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousArgumentNames) { + String nodeName = getNodeName(); + String literal = getLiteral(); + + previousArguments.add((Argument) this); + if(isListed()) previousArgumentNames.add(nodeName); + + // If we are listed, use a NamedLiteralArgumentBuilder to put our literal into the CommandContext + return isListed() ? + NamedLiteralArgumentBuilder.namedLiteral(nodeName, literal) : + LiteralArgumentBuilder.literal(literal); + } + + /** + * Overrides {@link AbstractArgument#linkNode(NodeInformation, CommandNode, List, List, TerminalNodeModifier)}. + *

+ * 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, + TerminalNodeModifier terminalNodeModifier + ) { + // 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(), + rootNode.getCommand() != null, + getArgumentPermission(), getRequirements(), + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + 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 91d9ba5744..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,21 +1,151 @@ package dev.jorel.commandapi.arguments; -import dev.jorel.commandapi.ChainableBuilder; +import com.mojang.brigadier.builder.ArgumentBuilder; +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.arguments.AbstractArgument.TerminalNodeModifier; +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.Predicate; /** * 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#getArgumentPermission()}. + */ + CommandPermission getArgumentPermission(); + + /** + * Links to {@link AbstractArgument#getRequirements()}. + */ + Predicate getRequirements(); + + /** + * Links to {@link AbstractArgument#isListed()}. + */ + boolean isListed(); + + /** + * Links to {@link AbstractArgument#getCombinedArguments()}. + */ + List getCombinedArguments(); + + /** + * Links to {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}. + */ + CommandNode finishBuildingNode(ArgumentBuilder rootBuilder, List previousArguments, TerminalNodeModifier terminalNodeModifier); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDING METHODS // + // A MultiLiteral has special logic that should override the implementations in AbstractArgument // + //////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. + *

+ * Normally, Arguments will use Brigadier's RequiredArgumentBuilder. However, MultiLiterals use LiteralArgumentBuilders. + */ + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousArgumentNames) { + String nodeName = getNodeName(); + String literal = getLiterals()[0]; + + previousArguments.add((Argument) this); + if(isListed()) previousArgumentNames.add(nodeName); + + return isListed() ? + NamedLiteralArgumentBuilder.namedLiteral(nodeName, literal) : + LiteralArgumentBuilder.literal(literal); + } + + /** + * Overrides {@link AbstractArgument#linkNode(NodeInformation, CommandNode, List, List, TerminalNodeModifier)}. + *

+ * 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 NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, + List previousArguments, List previousArgumentNames, + TerminalNodeModifier terminalNodeModifier + ) { + List> newNodes = new ArrayList<>(); + // Add root node to previous + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { + previousNode.addChild(rootNode); + } + 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 + LiteralArgumentBuilder literalBuilder = isListed ? + NamedLiteralArgumentBuilder.namedLiteral(nodeName, literals.next()) : + LiteralArgumentBuilder.literal(literals.next()); + + // Finish building node + CommandNode literalNode = finishBuildingNode(literalBuilder, previousArguments, terminalNodeModifier); + + // Add node to previous + for(CommandNode previousNode : previousNodeInformation.lastCommandNodes()) { + previousNode.addChild(literalNode); + } + newNodes.add(literalNode); + } + + // 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())+ ")", + rootNode.getCommand() != null, + getArgumentPermission(), getRequirements(), + children + ) + )) + ); + + // Stack on combined arguments and return last nodes + return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalNodeModifier); + } } 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 { + // Rewrite node trees for the client + static void rewriteAllChildren(Source client, CommandNode parent, boolean onRegister) { + // Copy the children, as we do expect them to be modified + Queue> childrenToProcess = new LinkedList<>(parent.getChildren()); + CommandNode child; + while ((child = childrenToProcess.poll()) != null) { + rewriteChild(client, parent, child, onRegister, childrenToProcess); + } + } + + 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) { + // child is directly a DifferentClientNode + 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) + Argument.Type type = (Argument.Type) argument.getType(); + clientNodeCreator = type.node; + } + + if (clientNodeCreator != null) { + // Get the new client nodes + 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, onRegister); + } + + // 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; + + protected 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; + } + + // Handle type nonsense + private static class Type implements ArgumentType { + private Argument 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 Argument 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; + } + + // 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(); + } + + abstract class Literal extends LiteralCommandNode implements DifferentClientNode { + protected Literal( + String literal, + Command command, Predicate requirement, + CommandNode redirect, RedirectModifier modifier, boolean forks + ) { + super(literal, command, requirement, redirect, modifier, forks); + } + + // 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); + } + + protected static class Builder extends LiteralArgumentBuilder { + protected final DifferentClientNode clientNodeCreator; + + protected Builder(Literal node) { + super(node.getLiteral()); + executes(node.getCommand()); + requires(node.getRequirement()); + forward(node.getRedirect(), node.getRedirectModifier(), node.isFork()); + + 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/DynamicMultiLiteralArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java new file mode 100644 index 0000000000..478e1d5621 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java @@ -0,0 +1,54 @@ +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<>(this); + + 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..f9a1be7c3e --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java @@ -0,0 +1,168 @@ +package dev.jorel.commandapi.commandnodes; + +import com.google.gson.JsonArray; +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.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +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; + + public DynamicMultiLiteralCommandNode(DynamicMultiLiteralArgumentBuilder builder) { + // This mostly acts like a StringArgument + super( + builder.getName(), StringArgumentType.word(), + builder.getCommand(), builder.getRequirement(), + builder.getRedirect(), builder.getRedirectModifier(), builder.isFork() + ); + + this.isListed = builder.isListed(); + this.literalsCreator = builder.getLiteralsCreator(); + } + + // 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(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); + + if (literals.isEmpty()) return List.of(); + + List> clientNodes = new ArrayList<>(literals.size()); + for (String literal : literals) { + LiteralCommandNode clientNode = new LiteralCommandNode<>( + literal, + node.getCommand(), node.getRequirement(), + node.getRedirect(), node.getRedirectModifier(), node.isFork() + ); + for (CommandNode child : node.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-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..497defaf76 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentEndingNode.java @@ -0,0 +1,335 @@ +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; +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; +import dev.jorel.commandapi.arguments.AbstractArgument; +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> extends DifferentClientNode { + // Set information + static , CommandSender, Source> + CommandNode wrapNode( + CommandNode node, String flagsArgumentName, List 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()); + } + } + + // 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; + 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 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); + newInformation.add(new FlagsArgumentCommon.ParseInformation<>( + contextBuilder.copy().build(reader.getRead()), getPreviousArguments() + )); + + // 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 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; + private final List previousArguments; + + public LiteralNode( + LiteralCommandNode literalNode, String flagsArgumentName, List previousArguments + ) { + super( + literalNode.getName(), literalNode.getCommand(), literalNode.getRequirement(), + // 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; + this.flagsArgumentName = flagsArgumentName; + 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; + } + + @Override + public List getPreviousArguments() { + return previousArguments; + } + + // Use information + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + 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 ""; + } + } + + class ArgumentNode + /// @endcond + , CommandSender, Source, T> + 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; + + public ArgumentNode( + ArgumentCommandNode argumentNode, String flagsArgumentName, List previousArguments + ) { + super( + argumentNode.getName(), argumentNode.getType(), + argumentNode.getCommand(), argumentNode.getRequirement(), + // 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; + this.flagsArgumentName = flagsArgumentName; + 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; + } + + @Override + public List getPreviousArguments() { + return previousArguments; + } + + // Use information + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + 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 new file mode 100644 index 0000000000..5fa5232bcb --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/FlagsArgumentRootNode.java @@ -0,0 +1,117 @@ +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 java.util.ArrayList; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +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<>(); + + // 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 { + // Serialization logic + static { + NodeTypeSerializer.registerSerializer(HiddenRedirect.class, (target, type) -> + target.addProperty("type", "flagArgumentHiddenRedirect") + ); + } + + 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 FlagsArgumentRootNode(LiteralCommandNode literalNode) { + super( + literalNode.getName(), literalNode.getCommand(), literalNode.getRequirement(), + literalNode.getRedirect(), literalNode.getRedirectModifier(), literalNode.isFork() + ); + this.hiddenRedirect = new HiddenRedirect<>(this); + } + + // Handle loops back to here + public void addLoopEndNode(CommandNode node) { + node.addChild(hiddenRedirect); + loopEndNodes.add(node); + + for (CommandNode child : getChildren()) { + node.addChild(child); + } + } + + @Override + public void addChild(CommandNode node) { + super.addChild(node); + + for (CommandNode loopEndNode : loopEndNodes) { + loopEndNode.addChild(node); + } + } + + // 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 ""; + } + + @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/NamedLiteralArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralArgumentBuilder.java new file mode 100644 index 0000000000..6317d3639b --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralArgumentBuilder.java @@ -0,0 +1,57 @@ +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 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 NamedLiteralArgumentBuilder extends LiteralArgumentBuilder { + private final String nodeName; + + /** + * 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 NamedLiteralArgumentBuilder(String nodeName, String literal) { + super(literal); + this.nodeName = nodeName; + } + + /** + * 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 constructed {@link NamedLiteralArgumentBuilder}. + */ + public static NamedLiteralArgumentBuilder namedLiteral(String nodeName, String literal) { + return new NamedLiteralArgumentBuilder<>(nodeName, literal); + } + + /** + * @return the node name that the built command node will be identified by. + */ + public String getNodeName() { + return nodeName; + } + + @Override + public NamedLiteralCommandNode build() { + NamedLiteralCommandNode result = new NamedLiteralCommandNode<>(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/commandnodes/NamedLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java new file mode 100644 index 0000000000..adde09de2b --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NamedLiteralCommandNode.java @@ -0,0 +1,90 @@ +package dev.jorel.commandapi.commandnodes; + +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 dev.jorel.commandapi.arguments.Literal; +import dev.jorel.commandapi.arguments.MultiLiteral; + +import java.util.function.Predicate; + +/** + * 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 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) { + super(literal, command, requirement, redirect, modifier, forks); + this.nodeName = nodeName; + } + + /** + * @return the node name this command represents. + */ + public String getNodeName() { + return 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(); + 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 NamedLiteralCommandNode 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 NamedLiteralArgumentBuilder createBuilder() { + NamedLiteralArgumentBuilder builder = NamedLiteralArgumentBuilder.namedLiteral(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/commandnodes/NodeTypeSerializer.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java new file mode 100644 index 0000000000..f460d0cdfe --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/NodeTypeSerializer.java @@ -0,0 +1,68 @@ +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)); + })); + } +} 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..b70e1a13cc --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableArgumentBuilder.java @@ -0,0 +1,89 @@ +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(); + } + + // Getters + @Override + protected PreviewableArgumentBuilder getThis() { + return this; + } + + public String getName() { + return name; + } + + public ArgumentType getType() { + return type; + } + + 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<>(this); + + 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..dc31efb14a --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/PreviewableCommandNode.java @@ -0,0 +1,122 @@ +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; + +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()}, + * 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 { + // 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; + + // Instead of having a listed and unlisted copy of this class, we can handle both with this boolean + private final boolean 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 + 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 new file mode 100644 index 0000000000..0c7fe13d73 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java @@ -0,0 +1,74 @@ +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 dev.jorel.commandapi.CommandAPIHandler; + +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 { + // 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); + } + + // A UnnamedArgumentCommandNode is mostly identical to a ArgumentCommandNode + // The only difference is that when an UnnamedArgument is parsed, it does not add its argument 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/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/exceptions/CommandConflictException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/CommandConflictException.java new file mode 100644 index 0000000000..4aca1c1f2e --- /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 registered 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 new file mode 100644 index 0000000000..694a2e2ef9 --- /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 abstract class CommandRegistrationException extends RuntimeException { + protected CommandRegistrationException(String message) { + super(message); + } + + protected static > void addArgumentList(StringBuilder builder, List arguments) { + builder.append("["); + for (Argument 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..f185858b19 --- /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 unlisted arguments are + * allowed to share node names with other 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 listed arguments are not allowed! Going down the "); + addArgumentList(builder, previousArguments); + builder.append(" branch, found "); + addArgument(builder, argument); + 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 c48a3d7f27..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 @@ -22,28 +22,36 @@ 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 and including the greedy argument + * @param argument The argument that invalidly came after a greedy argument + * @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) { + super(buildMessage(previousArguments, argument)); } - private static String buildArgsStr(AbstractArgument[] arguments) { + private static > String buildMessage( + List previousArguments, Argument argument) { StringBuilder builder = new StringBuilder(); - for (AbstractArgument arg : arguments) { - builder.append(arg.getNodeName()).append("<").append(arg.getClass().getSimpleName()).append("> "); - } + 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.subList(0, greedyArgumentIndex)); + builder.append(" branch, found the greedy argument "); + addArgument(builder, previousArguments.get(greedyArgumentIndex)); + builder.append(" followed by "); + addArgument(builder, argument); + 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-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..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 @@ -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 - */ +@FunctionalInterface +public interface NormalExecutor extends NormalExecutorInfo { @Override - default int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - this.run(info); - return 1; + default void run(ExecutionInfo info) throws WrapperCommandSyntaxException { + this.run(info.sender(), info.args()); } /** - * Executes the command. - * @param info The ExecutionInfo for this command - * @throws WrapperCommandSyntaxException if an error occurs during the execution of this command + * 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(ExecutionInfo info) throws WrapperCommandSyntaxException; - + 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..fab38b1746 --- /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..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 @@ -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 { - - /** - * 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 - */ +@FunctionalInterface +public interface ResultingExecutor extends ResultingExecutorInfo { @Override - default int executeWith(ExecutionInfo info) throws WrapperCommandSyntaxException { - return this.run(info); + default int run(ExecutionInfo info) throws WrapperCommandSyntaxException { + return this.run(info.sender(), info.args()); } /** - * 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 + * 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(ExecutionInfo info) throws WrapperCommandSyntaxException; + 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..3ba472d842 --- /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/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..f0c9f40b4c --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java @@ -0,0 +1,195 @@ +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..79f6208d46 --- /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. + */ + 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..051741cb85 --- /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. + */ + 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..f3a839f8fe --- /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. + */ + Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree); +} 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..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 @@ -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 @@ -420,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) }) @@ -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-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..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) } } } @@ -1138,7 +1142,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? 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/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-bukkit-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index 773ac92063..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 @@ -24,107 +24,107 @@ 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)) @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..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 @@ -15,214 +15,206 @@ 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)) - // 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, 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 = 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, 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 = 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, 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 = 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, 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 = then(BooleanArgument(nodeName).setOptional(optional).apply(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 = 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, 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 = 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, 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 = 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, 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 = 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, 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 = 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, 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 = 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, 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) then(BiomeArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(BiomeArgument(nodeName).setOptional(optional).apply(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 = 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, 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 = 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.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)) +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, 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)) -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)) +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 = 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, 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 = 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, 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 = then(NBTCompoundArgument(nodeName).setOptional(optional).apply(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= 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, 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= then(MultiLiteralArgument(literals).setOptional(optional).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= then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(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= then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(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 = then(FunctionArgument(nodeName).setOptional(optional).apply(block)) +inline fun CommandTree.functionArgument(nodeName: String, block: Argument<*>.() -> Unit = {}): CommandTree = then(FunctionArgument(nodeName).apply(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<*>.optionalArgument(base: Argument<*>, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.setOptional(true).setOptional(optional).apply(block)) +inline fun Argument<*>.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.apply(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, 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<*> = 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, 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<*> = 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, 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<*> = 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, 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<*> = then(BooleanArgument(nodeName).setOptional(optional).apply(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<*> = 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, 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<*> = 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, 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<*> = 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, 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<*> = 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, 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<*> = 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, 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<*> = 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, 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) then(BiomeArgument.NamespacedKey(nodeName).setOptional(optional).apply(block)) else then(BiomeArgument(nodeName).setOptional(optional).apply(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<*> = 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, 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<*> = 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<*>.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)) +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, 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)) -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)) +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<*> = 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, 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<*> = 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, 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<*> = then(NBTCompoundArgument(nodeName).setOptional(optional).apply(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<*> = 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, 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<*> = then(MultiLiteralArgument(literals).setOptional(optional).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<*> = then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(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<*> = then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(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<*> = then(FunctionArgument(nodeName).setOptional(optional).apply(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-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..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 @@ -1,160 +1,125 @@ 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 -import org.bukkit.command.ProxiedCommandSender import org.bukkit.command.RemoteConsoleCommandSender 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/CommandAPICommandDSL.kt b/commandapi-kotlin/commandapi-velocity-kotlin/src/main/kotlin/dev/jorel/commandapi/kotlindsl/CommandAPICommandDSL.kt index fcd82a56e7..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 @@ -22,37 +22,37 @@ 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)) @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..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 @@ -13,75 +13,67 @@ 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)) - // 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, 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 = 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, 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 = 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, 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 = 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, 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 = then(BooleanArgument(nodeName).setOptional(optional).apply(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 = 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, 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= 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, 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= then(MultiLiteralArgument(literals).setOptional(optional).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= then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(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= then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(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<*>, optional: Boolean = false, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.setOptional(optional).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<*>.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): Argument<*> = then(base.apply(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, 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<*> = 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, 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<*> = 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, 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<*> = 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, 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<*> = then(BooleanArgument(nodeName).setOptional(optional).apply(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<*> = 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, 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<*> = 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, 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<*> = then(MultiLiteralArgument(literals).setOptional(optional).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<*> = then(MultiLiteralArgument(nodeName, literals).setOptional(optional).apply(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<*> = then(MultiLiteralArgument(nodeName, *literals).setOptional(optional).apply(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-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/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/BukkitExecutable.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/BukkitExecutable.java index 08d26ffa64..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 @@ -1,180 +1,127 @@ 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.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 +129,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 +173,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 +217,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 +261,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 +305,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 +349,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 e18a59a98a..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 @@ -5,31 +5,20 @@ 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.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.*; 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 com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.ArgumentType; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; 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; @@ -41,32 +30,20 @@ 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; 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; 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; @@ -78,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() { @@ -87,19 +64,19 @@ 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?"); } } - public PaperImplementations getPaper() { + public PaperImplementations getPaper() { return paper; } 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?"); @@ -112,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!"); @@ -121,7 +98,7 @@ public void onLoad(CommandAPIConfig config) { checkDependencies(); } - + private static void setInternalConfig(InternalBukkitConfig internalBukkitConfig) { CommandAPIBukkit.config = internalBukkitConfig; } @@ -152,7 +129,7 @@ private void checkDependencies() { } } - boolean isPaperPresent = false; + boolean isPaperPresent; try { Class.forName("io.papermc.paper.event.server.ServerResourcesReloadedEvent"); @@ -165,7 +142,7 @@ private void checkDependencies() { } } - boolean isFoliaPresent = false; + boolean isFoliaPresent; try { Class.forName("io.papermc.paper.threadedregions.RegionizedServerInitEvent"); @@ -176,7 +153,7 @@ private void checkDependencies() { isFoliaPresent = false; } - paper = new PaperImplementations(isPaperPresent, isFoliaPresent, this); + paper = new PaperImplementations<>(isPaperPresent, isFoliaPresent, this); commandRegistrationStrategy = createCommandRegistrationStrategy(); } @@ -206,162 +183,59 @@ public void onEnable() { public void onServerLoad(ServerLoadEvent event) { CommandAPI.stopCommandRegistration(); } - }, getConfiguration().getPlugin()); + }, plugin); - paper.registerReloadHandler(plugin); + paper.registerEvents(plugin); } /* * 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) { - List commandsWithIdenticalNames = new ArrayList<>(); + void updateHelpForCommands(List> commands) { + Map helpTopicsToAdd = new HashMap<>(); - // Collect every command with the same name - for (RegisteredCommand registeredCommand : CommandAPIHandler.getInstance().registeredCommands) { - if (registeredCommand.commandName().equals(currentCommand.commandName())) { - commandsWithIdenticalNames.add(registeredCommand); - } - } + for (RegisteredCommand command : commands) { + String namespaceAddon = (command.namespace().isEmpty() ? "" : command.namespace() + ":"); + String commandName = namespaceAddon + command.commandName(); + CommandAPIHelpTopic commandAPIHelpTopic = command.helpTopic(); - // 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(); - } - } - return usages; - } - - 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; - - // 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"); + helpTopic = new CustomCommandAPIHelpTopic(commandName, command.aliases(), commandAPIHelpTopic, command.rootNode()); } - - 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 = 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 @@ -371,61 +245,18 @@ 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 abstract Source getBrigadierSourceFromCommandSender(CommandSender 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"); - } - - @Override public void registerPermission(String string) { try { Bukkit.getPluginManager().addPermission(new Permission(string)); - } catch (IllegalArgumentException e) { - assert true; // nop, not an error. + } 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 } } @@ -433,6 +264,25 @@ public void registerPermission(String string) { @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 @@ -454,12 +304,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.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()) { @@ -469,8 +329,9 @@ 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) { + DifferentClientNode.rewriteAllChildren(null, node, true); + commandRegistrationStrategy.registerCommandNode(node, namespace); } @Override @@ -493,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); } @@ -520,8 +392,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(); @@ -548,8 +420,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 @@ -562,11 +434,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 * @@ -599,7 +466,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 @@ -619,7 +486,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()) { @@ -641,18 +508,28 @@ 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; + @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(); + } } - return false; + + // 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/CommandAPICommand.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index 3fb95c73c8..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,28 +1,23 @@ 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; 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; @@ -31,32 +26,32 @@ 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.meta.helpTopic = Optional.of(helpTopic); + this.helpTopic = new BukkitHelpTopicWrapper(helpTopic); return instance(); } /** - * 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.meta.commandName, namespace)) { - super.register(); - return; - } super.register(namespace); } 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..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,84 +1,20 @@ 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; -import dev.jorel.commandapi.preprocessor.RequireField; +import org.bukkit.command.CommandSender; -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); - - 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(); 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 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..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,7 +1,11 @@ package dev.jorel.commandapi; import dev.jorel.commandapi.arguments.Argument; +import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; + 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,17 +22,36 @@ 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 and similar overloads: + *
    + *
  • {@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 = new BukkitHelpTopicWrapper(helpTopic); + return instance(); + } /** - * 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.meta.commandName, namespace)) { - super.register(); - return; - } super.register(namespace); } 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..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 @@ -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.ResultingExecutor; +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,20 @@ 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 +185,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) -> { + + ResultingExecutor 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.toArray(String[]::new))); } else { - command.execute(proxiedSender, commandName, new String[0]); + return command.execute(proxiedSender, commandName, new String[0]) ? 1 : 0; } }; @@ -226,10 +220,57 @@ 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, 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, new ArrayList<>(), 0); + } + + 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(flattened) ? 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) { + // 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 { + // Just split the raw argument into individual pieces + flattened.addAll(Arrays.asList(rawArguments[argumentIndex].split(" "))); + return flattenArguments( + argumentInfo, commandAPIArguments, + argumentConsumer, + rawArguments, flattened, argumentIndex + 1 + ); + } + } + /* * https://www.jorel.dev/blog/posts/Simplifying-Bukkit-CommandSenders/ */ @@ -272,5 +313,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..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 @@ -1,10 +1,10 @@ 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; +import org.bukkit.command.CommandSender; import java.util.List; import java.util.function.Predicate; @@ -50,29 +50,32 @@ 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 } @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); + + // Register nodes + RootCommandNode dispatcherRoot = getBrigadierDispatcher.get().getRoot(); + dispatcherRoot.addChild(node); + dispatcherRoot.addChild(namespacedCommandNode); - return commandNode; + // Track registered command nodes for reloads + registeredNodes.addChild(node); + registeredNodes.addChild(namespacedCommandNode); } @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)); @@ -80,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/PaperImplementations.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java index c9f04d1b31..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 @@ -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,13 +18,12 @@ 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; - private final Class nullCommandSender; /** * Constructs a PaperImplementations object @@ -32,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; @@ -45,52 +46,60 @@ 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; } /** - * 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(), false); } /** @@ -125,13 +134,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/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 cef9833985..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 @@ -1,12 +1,14 @@ 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; +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; import java.util.*; @@ -29,9 +31,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 +107,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!"); } } @@ -122,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 @@ -152,9 +158,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 +187,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, @@ -199,7 +202,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,10 +227,31 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal } minecraftCommandNamespaces = new RootCommandNode<>(); } + } else { + 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 + // default to the first permission associated with this command. + 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 = registeredCommand.namespace().toLowerCase(); + permissionsToFix.putIfAbsent(namespace + ":" + commandName, permission); + + // Do the same for the aliases + for (String alias : registeredCommand.aliases()) { + alias = alias.toLowerCase(); + permissionsToFix.putIfAbsent(alias, permission); + permissionsToFix.putIfAbsent(namespace + ":" + alias, 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 +264,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 +283,21 @@ 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(); + + 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, false); + } + String name = node.getLiteral(); if (namespace.equals("minecraft")) { if (namespacesToFix.contains("minecraft:" + name)) { @@ -270,7 +305,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,21 +338,20 @@ 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")); } } } @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()) { @@ -333,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)); } } @@ -341,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-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 330d82117d..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,22 +20,25 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import java.io.Serializable; - -import org.bukkit.command.CommandSender; - 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 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.Predicate; /** * An argument that represents any custom object @@ -103,7 +106,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); @@ -115,6 +118,106 @@ 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 NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, + List> previousArguments, List previousArgumentNames, + 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 + (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 + previousArguments.set(previousArguments.indexOf(base), this); + + return previousNodeInformation; + } + /** * 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/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-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/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..6d7d577208 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-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 dev.jorel.commandapi.executors.CommandArguments; +import org.bukkit.command.CommandSender; + +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, 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 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/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/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/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index b08c4301e2..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,17 +20,23 @@ *******************************************************************************/ 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.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 +128,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; @@ -138,12 +139,23 @@ public CommandAPIArgumentType getArgumentType() { } @Override - 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 String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { - return literal; + 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, 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 eeea5b5ec1..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,10 +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 dev.jorel.commandapi.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; +import org.bukkit.command.CommandSender; import java.util.List; @@ -32,7 +35,7 @@ * * @since 4.1 */ -public class MultiLiteralArgument extends Argument implements MultiLiteral> { +public class MultiLiteralArgument extends Argument implements MultiLiteral, CommandSender> { private final String[] literals; @@ -53,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 * @@ -80,10 +73,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 +85,22 @@ 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 ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return MultiLiteral.super.createArgumentBuilder(previousArguments, previousArgumentNames); + } + + @Override + 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/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..ee90cb95ee --- /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..b9697ed98f --- /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..5445f4b0b2 --- /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(sender, info.cmdCtx().getSource()); + 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/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..5c0857fff8 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java @@ -0,0 +1,40 @@ +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..34d060ef07 --- /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..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; @@ -64,6 +65,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 +395,8 @@ String getScoreHolderSingle(CommandContext cmdCtx, Strin Object getSound(CommandContext cmdCtx, String key, ArgumentSubType subType); + NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandListenerWrapper css); + /** * Retrieve a specific NMS implemented SuggestionProvider * @@ -410,8 +414,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-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 b19d588e30..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 @@ -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; @@ -66,7 +56,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; @@ -81,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; @@ -91,17 +78,8 @@ 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.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; @@ -215,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 @@ -229,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") @@ -401,14 +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); - } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentRegistrySerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -505,17 +482,35 @@ 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) { + CommandSender sender; try { - return wrapCommandSender(clw.getBukkitSender()); + 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 @@ -816,31 +811,18 @@ public String getScoreHolderSingle(CommandContext cmdCtx } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandListenerWrapper 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 7e4c688686..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 @@ -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; @@ -64,7 +62,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; @@ -76,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; @@ -88,9 +82,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; @@ -152,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 @@ -160,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) { @@ -254,15 +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); - } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentTypesSerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -311,8 +300,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -593,31 +588,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 5ec15656cb..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 @@ -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; @@ -67,7 +65,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; @@ -78,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; @@ -92,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; @@ -170,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 @@ -180,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) { @@ -277,14 +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()))); - } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentTypesSerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -371,8 +362,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -643,31 +640,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 edc2c0b9cb..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 @@ -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; @@ -66,7 +64,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; @@ -76,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; @@ -88,9 +83,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; @@ -157,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 @@ -167,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) { @@ -261,16 +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()))); - } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentTypesSerializeToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -318,8 +308,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -593,31 +589,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 a44a8503fc..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 @@ -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; @@ -35,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.RequireField; import dev.jorel.commandapi.preprocessor.Unimplemented; @@ -117,7 +113,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; @@ -133,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; @@ -161,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; @@ -192,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)); } @@ -346,15 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -440,8 +430,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -698,22 +694,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - CommandSourceStack css = cmdCtx.getSource(); - - CommandSender sender = css.getBukkitSender(); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 ec7402c1af..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 @@ -3,11 +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.commandsenders.BukkitPlayer; +import dev.jorel.commandapi.commandnodes.PreviewableCommandNode; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; import dev.jorel.commandapi.wrappers.PreviewableFunction; import io.netty.channel.ChannelDuplexHandler; @@ -26,7 +24,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 +48,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,40 +62,44 @@ 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); } - previewInfo = new PreviewInfo<>(new BukkitPlayer(player), input, chatPreviewQuery, parsedInput); + previewInfo = new PreviewInfo<>(player, input, chatPreviewQuery, parsedInput); } 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); } - previewInfo = new PreviewInfo<>(new BukkitPlayer(player), input, chatPreviewQuery, parsedInput); + previewInfo = new PreviewInfo<>(player, input, chatPreviewQuery, parsedInput); } component = preview.get().generatePreview(previewInfo); @@ -125,7 +126,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 / @@ -134,16 +135,17 @@ 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)); - // 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; } } 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..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 @@ -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; @@ -34,9 +33,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; @@ -114,7 +110,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; @@ -124,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; @@ -151,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; @@ -170,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) { @@ -257,14 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -315,8 +306,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -577,31 +574,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 288a5d7f6d..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 @@ -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; @@ -34,9 +33,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; @@ -114,7 +110,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; @@ -124,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; @@ -152,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; @@ -171,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) { @@ -256,14 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -313,8 +304,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -571,32 +568,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 ab5c435600..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 @@ -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; @@ -66,7 +58,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; @@ -77,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; @@ -90,9 +79,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; @@ -174,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; @@ -193,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) { @@ -278,14 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -335,8 +320,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -591,32 +582,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 ba9df06193..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 @@ -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; @@ -66,7 +64,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; @@ -77,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; @@ -90,9 +85,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; @@ -199,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; @@ -219,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) { @@ -346,16 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, - String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -410,9 +399,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -719,35 +713,18 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, - boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 9be7ede63f..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 @@ -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; @@ -66,7 +64,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; @@ -77,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; @@ -92,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; @@ -207,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; @@ -237,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) { @@ -388,16 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, - String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -459,9 +448,14 @@ 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 + 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") @@ -791,35 +785,18 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, - boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 4b30a7136b..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 @@ -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; @@ -34,9 +33,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; @@ -115,7 +111,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; @@ -125,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; @@ -151,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; @@ -170,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) { @@ -255,14 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -312,8 +303,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -571,32 +568,18 @@ public ScoreboardSlot getScoreboardSlot(CommandContext cmdCt } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 40382f44ca..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 @@ -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; @@ -65,7 +63,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; @@ -76,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; @@ -93,13 +88,11 @@ 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; 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; @@ -210,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; @@ -240,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) { @@ -388,16 +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()))); - } - - @Override - public final HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, - String permission) { - return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + public Optional getArgumentTypeProperties(ArgumentType type) { + JsonObject result = new JsonObject(); + argumentUtilsSerializeArgumentToJson.invokeOrNull(result, type); + return Optional.ofNullable((JsonObject) result.get("properties")); } @Override @@ -458,9 +448,14 @@ 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 + protected boolean isProxyEntity(CommandSender sender, CommandSourceStack css) { + Entity proxyEntity = css.getEntity(); + return proxyEntity != null && !sender.equals(proxyEntity.getBukkitEntity()); } @Override @@ -788,35 +783,18 @@ public String getScoreHolderSingle(CommandContext cmdCtx, St } @Override - public BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, - boolean isNative) { - 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(); - } + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack 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 f08e7ecf5b..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 @@ -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; @@ -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; @@ -65,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; @@ -294,13 +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; - - @Override - @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CustomHelpTopic") - public abstract HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, - String permission); + @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") @@ -362,17 +354,38 @@ 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) { + CommandSender sender; try { - return wrapCommandSender(css.getBukkitSender()); + 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; @@ -531,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 BukkitCommandSender getSenderForCommand(CommandContext cmdCtx, boolean isNative); + 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-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-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 48b653a304..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 ¯\_(ツ)_/¯ @@ -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/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..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 @@ -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 @@ -32,6 +32,7 @@ abstract class TestBase { plugin.onDisable() } MockBukkit.unmock() + MockPlatform.unload() } fun assertStoresResult(sender: CommandSender, command: String, queue: Mut, expected: T) { @@ -87,6 +88,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-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..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 ¯\_(ツ)_/¯ @@ -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-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..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 @@ -71,35 +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-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..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 @@ -4,15 +4,16 @@ 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; import org.bukkit.Color; import org.bukkit.Location; @@ -39,15 +40,10 @@ 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; 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; @@ -58,6 +54,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; @@ -116,7 +113,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(); @@ -152,6 +149,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -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); @@ -297,26 +294,16 @@ public CommandListenerWrapper getBrigadierSourceFromCommandSender(AbstractComman // 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<>(); @@ -363,11 +350,14 @@ public CommandListenerWrapper getBrigadierSourceFromCommandSender(AbstractComman return clw; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandListenerWrapper css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -478,9 +468,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); @@ -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) { @@ -581,33 +583,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(wrapCommandSender(player)); + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getName()).thenReturn(name); + + IChatBaseComponent nameComponent = new ChatComponentText(name); + Mockito.when(serverPlayerMock.getDisplayName()).thenReturn(nameComponent); + Mockito.when(serverPlayerMock.getScoreboardDisplayName()).thenReturn(nameComponent); - EntityPlayer handle = Mockito.mock(EntityPlayer.class); - Mockito.when(handle.getCommandListener()).thenReturn(css); + // UUID + UUID uuid = playerMock.getUniqueId(); + Mockito.when(craftPlayerMock.getUniqueId()).thenReturn(uuid); - Mockito.when(player.getHandle()).thenReturn(handle); + // 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); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.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); + // Provide proper handle as VanillaCommandWrapper expects + CommandListenerWrapper css = getBrigadierSourceFromCommandSender(craftPlayerMock); + Mockito.when(serverPlayerMock.getCommandListener()).thenReturn(css); - return player; + // Add to player list + players.add(serverPlayerMock); + + return craftPlayerMock; } @Override @@ -630,22 +645,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; - } - } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); + 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 c706d469ca..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 @@ -4,14 +4,15 @@ 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; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -38,14 +39,10 @@ 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; 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; @@ -74,6 +71,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; @@ -111,7 +109,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(); @@ -147,6 +145,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -261,8 +260,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); @@ -281,25 +279,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -346,11 +335,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -461,9 +453,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); @@ -541,7 +545,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 +560,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) { @@ -566,33 +570,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(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + 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); - - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - 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 @@ -614,22 +636,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; - } - } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); + 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 64f8f229eb..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 @@ -4,15 +4,16 @@ 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; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -42,14 +43,10 @@ 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; 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; @@ -78,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; @@ -126,7 +124,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(); @@ -162,6 +160,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -279,8 +278,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); @@ -299,25 +297,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -363,11 +352,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -476,9 +468,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); @@ -556,7 +560,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 +575,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) { @@ -581,33 +585,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(); + + Mockito.when(craftPlayerMock.getName()).thenReturn(name); + Mockito.when(serverPlayerMock.getScoreboardName()).thenReturn(name); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + 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); - - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(player)); + Mockito.when(craftPlayerMock.getLocation()).thenReturn(new Location(world, 0, 0, 0)); + Mockito.when(craftPlayerMock.getWorld()).thenReturn(world); - 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 @@ -649,22 +671,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; - } - } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); + 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 e499d26c86..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 @@ -4,15 +4,16 @@ 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; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -42,14 +43,10 @@ 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; 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; @@ -76,6 +73,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; @@ -120,7 +118,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(); @@ -157,6 +155,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -275,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); @@ -295,25 +293,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -359,11 +348,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -478,9 +470,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); @@ -549,7 +553,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 +568,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) { @@ -574,33 +578,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); + + 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); - - // Provide proper handle as VanillaCommandWrapper expects - CommandSourceStack css = getBrigadierSourceFromCommandSender(wrapCommandSender(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 @@ -647,17 +669,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 @@ -842,11 +855,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..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 @@ -4,15 +4,16 @@ 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; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -42,14 +43,10 @@ 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; 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; @@ -77,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; @@ -121,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(); @@ -158,6 +156,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -276,8 +275,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); @@ -296,26 +294,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -362,11 +350,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -488,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); @@ -559,7 +562,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 +577,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) { @@ -584,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); + + // 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); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // 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(wrapCommandSender(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 @@ -657,17 +678,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 @@ -853,11 +865,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..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 @@ -5,13 +5,12 @@ 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.context.CommandContext; +import com.mojang.brigadier.arguments.ArgumentType; 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; @@ -34,6 +33,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; @@ -60,8 +60,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 +102,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(); @@ -141,6 +139,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -260,8 +259,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); @@ -291,26 +289,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -357,11 +345,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -485,9 +476,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); @@ -556,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() @@ -571,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) { @@ -581,33 +584,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(); + + 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); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // 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(wrapCommandSender(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 @@ -657,17 +678,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 @@ -853,11 +865,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..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 @@ -4,15 +4,14 @@ 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.context.CommandContext; +import com.mojang.brigadier.arguments.ArgumentType; 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; @@ -29,6 +28,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; @@ -64,8 +64,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; @@ -109,7 +107,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(); @@ -146,6 +144,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -269,8 +268,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); @@ -300,27 +298,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -370,11 +357,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -498,9 +488,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); @@ -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) { @@ -594,33 +596,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(); + + 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); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // 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(wrapCommandSender(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 @@ -670,17 +690,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 @@ -871,11 +882,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..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 @@ -2,15 +2,14 @@ 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.context.CommandContext; +import com.mojang.brigadier.arguments.ArgumentType; 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; @@ -38,6 +37,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; @@ -62,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.Stream; @@ -107,7 +105,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(); @@ -144,6 +142,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 @@ -288,8 +287,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); @@ -319,27 +317,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -389,11 +376,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -627,9 +617,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); @@ -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) { @@ -723,33 +725,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(); + + 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); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // 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(wrapCommandSender(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 @@ -799,17 +819,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 @@ -1000,11 +1011,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..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 @@ -5,13 +5,12 @@ 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.context.CommandContext; +import com.mojang.brigadier.arguments.ArgumentType; 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; @@ -34,6 +33,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; @@ -61,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; @@ -105,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(); @@ -142,6 +140,7 @@ public MockNMS(CommandAPIBukkit baseNMS) { } return null; }); + Mockito.when(playerListMock.getPlayers()).thenAnswer(i -> players); } /************************* @@ -260,8 +259,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); @@ -291,26 +289,16 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen 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<>(); @@ -357,11 +345,14 @@ public CommandSourceStack getBrigadierSourceFromCommandSender(AbstractCommandSen return css; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void createDispatcherFile(File file, CommandDispatcher dispatcher) - throws IOException { - baseNMS.createDispatcherFile(file, dispatcher); + public NativeProxyCommandSender getNativeProxyCommandSender(CommandSender sender, CommandSourceStack css) { + return baseNMS.getNativeProxyCommandSender(sender, css); + } + + @Override + public Optional getArgumentTypeProperties(ArgumentType type) { + return baseNMS.getArgumentTypeProperties(type); } @Override @@ -486,9 +477,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); @@ -557,7 +560,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 +575,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) { @@ -582,33 +585,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(); + + 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); - // getLocation and getWorld is used when creating the CommandSourceStack in MockNMS + // 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(wrapCommandSender(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 @@ -655,17 +676,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 @@ -851,11 +863,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/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/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-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..05b8b0904c --- /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,799 @@ +package dev.jorel.commandapi.test; + +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.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 + */ +class CommandAPICommandRegisteredCommandTests extends RegisteredCommandTestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + private NodeBuilder commandNode(String nodeName, boolean executable) { + return node(nodeName, CommandAPICommand.class, executable).helpString(nodeName); + } + + /********* + * Tests * + *********/ + + @Test + void testRegister() { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true), + List.of("command:CommandAPICommand") + ); + } + + @Test + void testRegisterHelpInformation() { + new CommandAPICommand("command") + .withHelp("short description", "full description") + .withUsage( + "usage 1", + "usage 2", + "usage 3" + ) + .executesPlayer(P_EXEC) + .register(); + + 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() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterOpPermission() { + new CommandAPICommand("command") + .withPermission(CommandPermission.OP) + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.OP).build() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterStringPermission() { + new CommandAPICommand("command") + .withPermission("permission") + .executesPlayer(P_EXEC) + .register(); + + 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); + } + + @Test + void testRegisterOneAlias() { + new CommandAPICommand("command") + .withAliases("alias1") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand( + "command", "minecraft", + commandNode("command", true), + "alias1" + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterTwoAliases() { + new CommandAPICommand("command") + .withAliases("alias1", "alias2") + .executesPlayer(P_EXEC) + .register(); + + 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 + void testRegisterOneArgument() { + new CommandAPICommand("command") + .withArguments(new StringArgument("string")) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("string", StringArgument.class, true)), + List.of("command:CommandAPICommand", "string:StringArgument") + ); + } + + @Test + void testRegisterTwoArguments() { + new CommandAPICommand("command") + .withArguments( + new StringArgument("string"), + new IntegerArgument("integer") + ) + .executesPlayer(P_EXEC) + .register(); + + 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 + 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") + .withArguments( + new MultiLiteralArgument("literal1", "a", "b", "c"), + new MultiLiteralArgument("literal2", "d", "e", "f") + ) + .executesPlayer(P_EXEC) + .register(); + + 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") + ); + } + + @Test + void testRegisterOneOptionalArgument() { + new CommandAPICommand("command") + .withOptionalArguments(new StringArgument("string")) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(node("string", StringArgument.class, true)), + List.of("command:CommandAPICommand"), + List.of("command:CommandAPICommand", "string:StringArgument") + ); + } + + @Test + void testRegisterTwoOptionalArguments() { + new CommandAPICommand("command") + .withOptionalArguments( + new StringArgument("string"), + new IntegerArgument("integer") + ) + .executesPlayer(P_EXEC) + .register(); + + 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") + ); + } + + @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(); + + 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") + ); + } + + @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(); + + 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" + ) + ); + } + + ////////////////////////////////////// + // SUBCOMMANDS // + // The same as commands, but deeper // + ////////////////////////////////////// + + @Test + void testRegisterOneSubcommand() { + new CommandAPICommand("command") + .withSubcommand( + new CommandAPICommand("subcommand") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(commandNode("subcommand", true)), + List.of("command:CommandAPICommand", "subcommand:CommandAPICommand") + ); + } + + @Test + void testRegisterTwoSubcommands() { + new CommandAPICommand("command") + .withSubcommands( + new CommandAPICommand("subcommand1") + .executesPlayer(P_EXEC), + new CommandAPICommand("subcommand2") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + commandNode("subcommand1", true), + commandNode("subcommand2", true) + ), + List.of("command:CommandAPICommand", "subcommand1:CommandAPICommand"), + List.of("command:CommandAPICommand", "subcommand2:CommandAPICommand") + ); + } + + @Test + void testRegisterOneSubcommandAndBaseExecutable() { + new CommandAPICommand("command") + .executesPlayer(P_EXEC) + .withSubcommand( + new CommandAPICommand("subcommand") + .executesPlayer(P_EXEC) + ) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(commandNode("subcommand", true)), + List.of("command:CommandAPICommand"), + List.of("command:CommandAPICommand", "subcommand:CommandAPICommand") + ); + } + + @Test + void testRegisterSubcommandWithAliases() { + new CommandAPICommand("command") + .withSubcommand( + new CommandAPICommand("subcommand") + .withAliases("alias1", "alias2") + .executesPlayer(P_EXEC) + ) + .register(); + + 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") + ); + } + + @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(); + + 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") + ); + } + + @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(); + + 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") + ); + } + + @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(); + + 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") + ); + } + + @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(); + + 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( + 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) + .withAliases("first") + .register("first"); + + new CommandAPICommand("command") + .withArguments(new LiteralArgument("second")) + .executesPlayer(P_EXEC) + .withAliases("second") + .register("second"); + + RegisteredCommand first = simpleRegisteredCommand( + "command", "first", + commandNode("command", false).withChildren( + node("first", LiteralArgument.class, true).helpString("first") + ), + "first" + ); + + RegisteredCommand second = simpleRegisteredCommand( + "command", "second", + commandNode("command", false).withChildren( + node("second", LiteralArgument.class, true).helpString("second") + ), + "second" + ); + + 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); + } + + /////////////////// + // 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/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 c4d5c144dd..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,16 +1,18 @@ 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; -import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; /** * Tests for converted commands @@ -21,9 +23,12 @@ class CommandConvertedTests extends TestBase { * Setup * *********/ + private CommandConvertedTestsPlugin plugin; + @BeforeEach public void setUp() { super.setUp(); + plugin = CommandConvertedTestsPlugin.load(); } @AfterEach @@ -34,17 +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(); - NativeProxyCommandSender nativeProxyMockedPlayer = new NativeProxyCommandSender(player, player, player.getLocation(), player.getWorld()); - server.dispatchBrigadierCommand(nativeProxyMockedPlayer, "mycommand"); + 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 77026a694b..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 @@ -53,18 +53,17 @@ 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)); // 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`. -// executorTypeToMockSender.put(ExecutorType.PROXY, () -> Mockito.mock(ProxiedCommandSender.class)); + // 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); 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..edc6541850 --- /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/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/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 c995cb105d..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,710 +0,0 @@ -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.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 - */ -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, - "Only one GreedyStringArgument or ChatArgument can be declared, at the end of a List. Found arguments: arg1 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, - "Only one GreedyStringArgument or ChatArgument can be declared, at the end of a List. Found arguments: arg1 arg2 ", - invalidGreedyCommand::register - ); - } - - @Test - void testCommandAPICommandInvalidCommandNameException() { - assertThrowsWithMessage( - InvalidCommandNameException.class, - "Invalid command with name 'null' cannot be registered!", - () -> new CommandAPICommand((String) 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, - "/test does not declare any executors or executable subcommands!", - 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, - "/test does not declare any executors or executable subcommands!", - 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()); - } - - // 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 - CommandTree commandWithNoExecutors = new CommandTree("test"); - - assertThrowsWithMessage( - MissingCommandExecutorException.class, - "", - 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, - "", - 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, - "", - 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( - new StringArgument("alice"), - new StringArgument("bob"), - new StringArgument("alice") - ) - .executesPlayer(P_EXEC); - - commandWithDuplicateArgumentNames.register(); - // No commands in tree - assertEquals(""" - { - "type": "root" - }""", getDispatcherString()); - - // This command is okay because LiteralArguments are exempt from the duplicate name rule - CommandAPICommand commandWithDuplicateLiteralArgumentNames = new CommandAPICommand("test") - .withArguments( - new LiteralArgument("alice"), - new LiteralArgument("bob"), - new LiteralArgument("alice") - ) - .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() - ); - - // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule - CommandAPICommand commandWithDuplicateMultiLiteralArgumentNames = new CommandAPICommand("test") - .withArguments( - new MultiLiteralArgument("alice", "option1", "option2"), - new MultiLiteralArgument("bob", "option1", "option2"), - new MultiLiteralArgument("alice", "option1", "option2") - ) - .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() - ); - } - - @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( - new StringArgument("alice").then( - new StringArgument("bob").then( - new StringArgument("alice") - .executesPlayer(P_EXEC) - ) - ) - ); - - commandWithDuplicateArgumentNames.register(); - // No commands in tree - assertEquals(""" - { - "type": "root" - }""", getDispatcherString()); - - // This command is okay because LiteralArguments are exempt from the duplicate name rule - CommandTree commandWithDuplicateLiteralArgumentNames = new CommandTree("test") - .then( - new LiteralArgument("alice").then( - new LiteralArgument("bob").then( - new LiteralArgument("alice") - .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() - ); - - // This command is okay because MultiLiteralArguments are exempt from the duplicate name rule - CommandTree commandWithDuplicateMultiLiteralArgumentNames = new CommandTree("test") - .then( - new MultiLiteralArgument("alice", "option1", "option2").then( - new MultiLiteralArgument("bob", "option1", "option2").then( - new MultiLiteralArgument("alice", "option1", "option2") - .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() - ); - - // 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))); - - 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() - ); - } -} 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..0659a935b3 --- /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,623 @@ +package dev.jorel.commandapi.test; + +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; + +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 RegisteredCommandTestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + private NodeBuilder commandNode(String nodeName, boolean executable) { + return node(nodeName, CommandTree.class, executable).helpString(nodeName); + } + + /********* + * Tests * + *********/ + + @Test + void testRegister() { + new CommandTree("command") + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true), + List.of("command:CommandTree") + ); + } + + @Test + void testRegisterHelpInformation() { + new CommandTree("command") + .withHelp("short description", "full description") + .withUsage( + "usage 1", + "usage 2", + "usage 3" + ) + .executesPlayer(P_EXEC) + .register(); + + 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() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterOpPermission() { + new CommandTree("command") + .withPermission(CommandPermission.OP) + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.OP).build() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterStringPermission() { + new CommandTree("command") + .withPermission("permission") + .executesPlayer(P_EXEC) + .register(); + + 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); + } + + @Test + void testRegisterOneAlias() { + new CommandTree("command") + .withAliases("alias1") + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterTwoAliases() { + new CommandTree("command") + .withAliases("alias1", "alias2") + .executesPlayer(P_EXEC) + .register(); + + 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 + void testRegisterOneBranch() { + new CommandTree("command") + .then(new StringArgument("string").executesPlayer(P_EXEC)) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false) + .withChildren(node("string", StringArgument.class, true)), + List.of("command:CommandTree", "string:StringArgument") + ); + } + + @Test + void testRegisterTwoBranches() { + new CommandTree("command") + .then(new StringArgument("string").executesPlayer(P_EXEC)) + .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 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") + .then( + new MultiLiteralArgument("literal1", "a", "b", "c") + .then(new MultiLiteralArgument("literal2", "d", "e", "f").executesPlayer(P_EXEC)) + ) + .register(); + + 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") + ); + } + + @Test + void testRegisterCombinedArguments() { + new CommandTree("command") + .then( + new LiteralArgument("1").combineWith(new LiteralArgument("2")) + .then( + new LiteralArgument("3").combineWith(new LiteralArgument("4")) + .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(); + + 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" + ), + List.of("command:CommandTree", + "1:LiteralArgument", "2:LiteralArgument", "3:LiteralArgument", "4:LiteralArgument", + "5:LiteralArgument", "6:LiteralArgument" + ), + List.of("command:CommandTree", + "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(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", true) + .withChildren(node("subcommand", LiteralArgument.class, true).helpString("subcommand")), + List.of("command:CommandTree"), + List.of("command:CommandTree", "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(); + + 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") + ); + } + + @Test + void testRegisterBranchesWithCombinedArguments() { + new CommandTree("command") + .then( + new LiteralArgument("subcommand1") + .then( + new LiteralArgument("1a").combineWith(new LiteralArgument("1b")) + .executesPlayer(P_EXEC) + .then( + new LiteralArgument("1c").combineWith(new LiteralArgument("1d")) + .executesPlayer(P_EXEC) + ) + ) + ) + .then( + new LiteralArgument("subcommand2") + .then( + new LiteralArgument("2a").combineWith(new LiteralArgument("2b")) + .executesPlayer(P_EXEC) + .then( + new LiteralArgument("2c").combineWith(new LiteralArgument("2d")) + .executesPlayer(P_EXEC) + ) + ) + ) + .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 // + ///////////////////////// + + @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( + 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)) + .withAliases("first") + .register("first"); + + new CommandTree("command") + .then(new LiteralArgument("second").executesPlayer(P_EXEC)) + .withAliases("second") + .register("second"); + + RegisteredCommand first = simpleRegisteredCommand( + "command", "first", + commandNode("command", false).withChildren( + node("first", LiteralArgument.class, true).helpString("first") + ), + "first" + ); + + RegisteredCommand second = simpleRegisteredCommand( + "command", "second", + commandNode("command", false).withChildren( + node("second", LiteralArgument.class, true).helpString("second") + ), + "second" + ); + + 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); + } + + /////////////////// + // 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-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..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" }, @@ -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..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 @@ -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); @@ -87,7 +85,7 @@ void testOnEnableRegisterAndUnregisterCommand() { "children": { "argument": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -97,29 +95,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()); @@ -176,7 +160,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 @@ -195,35 +179,34 @@ void testOnEnableRegisterAndUnregisterCommand() { Mockito.verify(updateCommandsPlayer, Mockito.times(2)).updateCommands(); // Make sure main command was removed from tree + // While the "command" node is no longer in the tree, the alias nodes still have a reference to it + // as their redirect, 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 + "redirect": { + "type": "literal", + "children": { + "argument": { + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "properties": { + "type": "word" + }, + "executable": true + } } } }, "alias2": { "type": "literal", - "children": { - "argument": { - "type": "argument", - "parser": "brigadier:string", - "properties": { - "type": "word" - }, - "executable": true - } - } + "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/OptionalArgumentTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OptionalArgumentTests.java similarity index 66% 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/OptionalArgumentTests.java index a418b2502e..972e799c9e 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/OptionalArgumentTests.java @@ -1,24 +1,20 @@ -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 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 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 { @@ -111,30 +107,38 @@ 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") + 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((player, args) -> { - type.set((String) args.get("string1")); - type.set((String) args.get("string2")); - }) - .register(); - }); + .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() + ); // 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/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..97f7e4769d --- /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,181 @@ +package dev.jorel.commandapi.test; + +import static org.junit.jupiter.api.Assertions.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.function.Predicate; + +import org.bukkit.command.CommandSender; +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; +import dev.jorel.commandapi.help.EditableHelpTopic; + +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, + new EditableHelpTopic<>(), + 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 CommandPermission permission = CommandPermission.NONE; + private Predicate requirements = CommandPermission.TRUE(); + + 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 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()); + } + return this; + } + + @SafeVarargs + public final 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, permission, requirements, children); + } + } + + @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(); + 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-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..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 @@ -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; @@ -20,10 +13,14 @@ 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; import org.bukkit.potion.PotionEffectType; @@ -37,12 +34,9 @@ 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.PlayerCommandExecutor; +import dev.jorel.commandapi.executors.NormalExecutor; + +import static org.junit.jupiter.api.Assertions.*; public abstract class TestBase { @@ -88,15 +82,25 @@ 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()); 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); @@ -124,6 +128,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. 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/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); + } } 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..2615132aef --- /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,611 @@ +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.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": "flagsArgumentRootNode", + "children": { + "loop1": { + "type": "literal", + "children": { + "value": { + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "properties": { + "type": "word" + }, + "redirect": [ + "test", + "flags" + ] + } + } + }, + "end1": { + "type": "literal", + "children": { + "value": { + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.IntegerArgumentType", + "children": { + "argument": [ + "test", + "flags", + "end2", + "argument" + ] + } + } + } + }, + "loop2": { + "type": "literal", + "redirect": [ + "test", + "flags" + ] + }, + "end2": { + "type": "literal", + "children": { + "argument": { + "type": "argument", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", + "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( + 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); + } +} 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..e7b33eae30 --- /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,159 @@ +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; + +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. + */ +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); + } + + @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/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..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 @@ -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; @@ -21,7 +17,7 @@ /** * Tests for the {@link MultiLiteralArgument} */ -public class ArgumentMultiLiteralTests extends TestBase { +class ArgumentMultiLiteralTests extends TestBase { /********* * Setup * @@ -90,54 +86,66 @@ 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": { - "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", + "type": "namedLiteral", + "nodeName": "literal1", "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" + ] } } } @@ -146,54 +154,66 @@ 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": { - "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", + "type": "namedLiteral", + "nodeName": "literal1", "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" + ] } } } @@ -202,65 +222,59 @@ 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": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } + "option1": [ + "command3", + "option1", + "option1", + "option1" + ], + "option2": [ + "command3", + "option1", + "option1", + "option2" + ] } } } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "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" + ] } } } @@ -269,65 +283,59 @@ 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": { - "type": "literal", - "executable": true - }, - "option2": { - "type": "literal", - "executable": true - } + "option1": [ + "command4", + "option1", + "option1", + "option1" + ], + "option2": [ + "command4", + "option1", + "option1", + "option2" + ] } } } }, "option2": { - "type": "literal", + "type": "namedLiteral", + "nodeName": "literal1", "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" + ] } } } @@ -427,63 +435,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()); - } - - @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 c476b14705..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 @@ -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; @@ -91,7 +92,7 @@ void executionTestWithStringArgument() { "children": { "value": { "type": "argument", - "parser": "brigadier:string", + "argumentType": "com.mojang.brigadier.arguments.StringArgumentType", "properties": { "type": "word" }, @@ -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-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..d46ba517e1 --- /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,354 @@ +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)} + */ +public class NodeTypeSerializerTests extends TestBase { + + /********* + * Setup * + *********/ + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @AfterEach + public void tearDown() { + super.tearDown(); + } + + public static 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; + } + + public static JsonObject serialize(CommandNode node) { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + return handler.serializeNodeToJson(node); + } + + public static 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(); + } +} 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..d8ba0d406e --- /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; + +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..cce719643e --- /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,196 @@ +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.MultiLiteralArgument; +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}. + */ +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 + ); + + 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 + 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..16ad165ae7 --- /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}. + */ +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..7d05b2282a --- /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}. + */ +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..f129522236 --- /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}. + */ +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 + ); + } +} 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/CommandAPICommand.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index 75b8d61691..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 @@ -17,31 +17,13 @@ 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 * - * @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); } @@ -52,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 f07e074483..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 @@ -1,36 +1,30 @@ 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.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; 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; 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 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 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; public class CommandAPIVelocity implements CommandAPIPlatform, CommandSource, CommandSource> { @@ -38,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() { @@ -70,11 +63,11 @@ 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. - // 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 { @@ -90,22 +83,36 @@ private static void setInternalConfig(InternalVelocityConfig internalVelocityCon @Override public void onEnable() { - // Nothing to do + // Register events + config.getServer().getEventManager().register(config.getPlugin(), this); } - @Override - public void onDisable() { - // Nothing to do + @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 + 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 - public void registerPermission(String string) { - // Unsurprisingly, Velocity doesn't have a dumb permission system! + public void onDisable() { + // Nothing to do } @Override public void unregister(String commandName, boolean unregisterNamespaces) { - commandManager.unregister(commandName); + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + + handler.removeBrigadierCommands(getBrigadierDispatcher().getRoot(), commandName, unregisterNamespaces, c -> true); } @Override @@ -114,55 +121,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 @@ -170,31 +130,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()); - } - - @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); + public CommandSource getCommandSenderFromCommandSource(CommandSource commandSource) { + return commandSource; } @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 @@ -204,24 +148,47 @@ 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 (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(); + } + + // Namespace is good, return it + return namespace; + } + @Override public void preCommandRegistration(String commandName) { // Nothing to do } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, 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) { + RootCommandNode root = getBrigadierDispatcher().getRoot(); + + // Register the main node + root.addChild(node); + + // Register the namespaced node if it is not empty if (!namespace.isEmpty()) { - getBrigadierDispatcher().getRoot().addChild(CommandAPIHandler.getInstance().namespaceNode(builtNode, namespace)); + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + root.addChild(handler.namespaceNode(node, namespace)); } - // We're done. The command already is registered - return builtNode; } @Override @@ -230,7 +197,7 @@ public void reloadDataPacks() { } @Override - public void updateRequirements(AbstractPlayer player) { + public void updateRequirements(CommandSource player) { // TODO Auto-generated method stub } @@ -245,7 +212,28 @@ public Argument newConcreteLiteralArgument(String nodeName, String liter } @Override - public AbstractCommandAPICommand, CommandSource> newConcreteCommandAPICommand(CommandMetaData meta) { - return new CommandAPICommand(meta); + 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/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); 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/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-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 ae8e470016..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,15 +20,20 @@ *******************************************************************************/ 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.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 +123,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; @@ -134,12 +134,23 @@ public CommandAPIArgumentType getArgumentType() { } @Override - 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 String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { - return literal; + 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, 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 ee7fca3572..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,8 +20,11 @@ *******************************************************************************/ 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.exceptions.BadLiteralException; import dev.jorel.commandapi.executors.CommandArguments; @@ -30,7 +33,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; @@ -51,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 * @@ -78,10 +71,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 +83,22 @@ 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 ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return MultiLiteral.super.createArgumentBuilder(previousArguments, previousArgumentNames); + } + + @Override + 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-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..a3604501b6 --- /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..291c70c317 --- /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..006096aa55 --- /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(); +} 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`: