diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index 810b23b218..2c58117cb9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -3,6 +3,7 @@ import com.mojang.brigadier.tree.CommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import java.util.ArrayList; @@ -110,9 +111,21 @@ public void buildBrigadierNode( throw new MissingCommandExecutorException(previousArguments, argument); } + // Create executor, if it exists + TerminalNodeModifier terminalNodeModifier = (builder, args) -> { + if (executor.hasAnyExecutors()) { + builder.executes(handler.generateBrigadierCommand(args, executor)); + } + + return builder.build(); + }; + // Create node for this argument - previousNodeInformation = argument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, - executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null); + previousNodeInformation = argument.addArgumentNodes( + previousNodeInformation, + previousArguments, previousArgumentNames, + terminalNodeModifier + ); // Collect children into our own list List> childrenNodeInformation = new ArrayList<>(); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 8f2cfb12fd..5d72517542 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -20,17 +20,16 @@ *******************************************************************************/ package dev.jorel.commandapi; -import com.mojang.brigadier.Command; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; +import dev.jorel.commandapi.arguments.AbstractArgument.TerminalNodeModifier; import dev.jorel.commandapi.exceptions.MissingCommandExecutorException; import dev.jorel.commandapi.exceptions.OptionalArgumentException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.function.Function; /** * A builder used to create commands to be registered by the CommandAPI. @@ -261,16 +260,25 @@ protected List> createArgumentNod // Note that `executor#hasAnyExecutors` must be true here // If not, then `checkPreconditions` would have thrown a `MissingCommandExecutorException` - Function, Command> executorCreator = args -> handler.generateBrigadierCommand(args, executor); + TerminalNodeModifier executorCreator = (builder, args) -> + builder.executes(handler.generateBrigadierCommand(args, executor)).build(); // Stack required arguments // Only the last required argument is executable - previousNodeInformation = AbstractArgument.stackArguments(requiredArguments, previousNodeInformation, previousArguments, previousArgumentNames, executorCreator); + previousNodeInformation = AbstractArgument.stackArguments( + requiredArguments, previousNodeInformation, + previousArguments, previousArgumentNames, + executorCreator + ); // Add optional arguments for (Argument argument : optionalArguments) { // All optional arguments are executable - previousNodeInformation = argument.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, executorCreator); + previousNodeInformation = argument.addArgumentNodes( + previousNodeInformation, + previousArguments, previousArgumentNames, + executorCreator + ); } // Create registered nodes now that all children are generated diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index abc1544737..603ea35696 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -20,36 +20,41 @@ *******************************************************************************/ package dev.jorel.commandapi; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.regex.Pattern; - +import com.google.common.io.Files; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ParsedArgument; import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; -import dev.jorel.commandapi.arguments.*; +import dev.jorel.commandapi.arguments.AbstractArgument; +import dev.jorel.commandapi.arguments.ArgumentSuggestions; +import dev.jorel.commandapi.arguments.CustomProvidedArgument; +import dev.jorel.commandapi.commandnodes.NodeTypeSerializer; import dev.jorel.commandapi.exceptions.CommandConflictException; import dev.jorel.commandapi.executors.CommandArguments; import dev.jorel.commandapi.executors.ExecutionInfo; import dev.jorel.commandapi.preprocessor.RequireField; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.regex.Pattern; /** * The "brains" behind the CommandAPI. @@ -74,19 +79,27 @@ public class CommandAPIHandler FIELDS; private static final SafeVarHandle, Map>> commandContextArguments; - // VarHandle seems incapable of setting final fields, so we have to use Field here - private static final Field commandNodeChildren; - private static final Field commandNodeLiterals; - private static final Field commandNodeArguments; + // I think these maps need to be raw since the parameter Source is inaccessible, but we want to cast to that + private static final SafeVarHandle, Map> commandNodeChildren; + private static final SafeVarHandle, Map> commandNodeLiterals; + private static final SafeVarHandle, Map> commandNodeArguments; // Compute all var handles all in one go so we don't do this during main server runtime static { FIELDS = new HashMap<>(); - commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", "arguments", Map.class); - commandNodeChildren = CommandAPIHandler.getField(CommandNode.class, "children"); - commandNodeLiterals = CommandAPIHandler.getField(CommandNode.class, "literals"); - commandNodeArguments = CommandAPIHandler.getField(CommandNode.class, "arguments"); + commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", Map.class); + commandNodeChildren = SafeVarHandle.ofOrNull(CommandNode.class, "children", Map.class); + commandNodeArguments = SafeVarHandle.ofOrNull(CommandNode.class, "arguments", Map.class); + + SafeVarHandle, Map> literals; + try { + literals = SafeVarHandle.of(CommandNode.class, "literals", "literals", Map.class); + } catch (ReflectiveOperationException ignored) { + // CommandNode.literals does not exist on Velocity, so we expect this to happen then + literals = null; + } + commandNodeLiterals = literals; } final CommandAPIPlatform platform; @@ -416,13 +429,107 @@ public void writeDispatcherToFile() { try { // Write the dispatcher json - platform.createDispatcherFile(file, platform.getBrigadierDispatcher()); + writeDispatcherToFile(file, platform.getBrigadierDispatcher()); } catch (IOException e) { CommandAPI.logError("Failed to write command registration info to " + file.getName() + ": " + e.getMessage()); } } } + /** + * Creates a JSON file that describes the hierarchical structure of the commands + * that have been registered by the server. + * + * @param file The JSON file to write to + * @param dispatcher The Brigadier CommandDispatcher + * @throws IOException When the file fails to be written to + */ + public void writeDispatcherToFile(File file, CommandDispatcher dispatcher) throws IOException { + Files.asCharSink(file, StandardCharsets.UTF_8).write(new GsonBuilder().setPrettyPrinting().create() + .toJson(serializeNodeToJson(dispatcher.getRoot()))); + } + + private record Node(CommandNode commandNode, Consumer resultConsumer, String[] path) { + } + + public JsonObject serializeNodeToJson(CommandNode rootNode) { + // We preform a breadth-first traversal of the node tree to find the shortest path to each node. + // We prefer that the first path found would not go through a redirect, so nodes found from redirects + // are put in their own lower priority queue. It may be necessary to fully process these nodes though + // in case a node is only accessible via redirects, which may happen for example when the main alias + // of a command is removed. + Queue> nodesToProcess = new LinkedList<>(); + Queue> redirectsToProcess = new LinkedList<>(); + Map, JsonArray> shortestPath = new IdentityHashMap<>(); + + // Extract serialization of the rootNode as our result + JsonObject resultHolder = new JsonObject(); + redirectsToProcess.offer(new Node<>(rootNode, result -> resultHolder.add("result", result), new String[0])); + + Node node; + while ((node = redirectsToProcess.poll()) != null) { + nodesToProcess.offer(node); + + while ((node = nodesToProcess.poll()) != null) { + CommandNode commandNode = node.commandNode; + + // Add information to parent + JsonArray path = shortestPath.get(commandNode); + if (path != null) { + // Node has already appeared earlier in the traversal + node.resultConsumer.accept(path); + continue; + } + // This is the first time finding this node + path = new JsonArray(); + for (String step : node.path) { + path.add(step); + } + shortestPath.put(commandNode, path); + + JsonObject output = new JsonObject(); + node.resultConsumer.accept(output); + + // Node type + NodeTypeSerializer.addTypeInformation(output, commandNode); + + // Children + Collection> children = commandNode.getChildren(); + if (!children.isEmpty()) { + JsonObject childrenHolder = new JsonObject(); + output.add("children", childrenHolder); + + for (CommandNode child : children) { + String name = child.getName(); + + String[] newPath = new String[node.path.length + 1]; + System.arraycopy(node.path, 0, newPath, 0, node.path.length); + newPath[node.path.length] = name; + + nodesToProcess.offer(new Node<>(child, result -> childrenHolder.add(name, result), newPath)); + } + } + + // Command + if (commandNode.getCommand() != null) { + output.addProperty("executable", true); + } + + // Redirect + CommandNode redirect = commandNode.getRedirect(); + if (redirect != null) { + String[] newPath = new String[node.path.length + 1]; + System.arraycopy(node.path, 0, newPath, 0, node.path.length); + newPath[node.path.length] = "redirect " + redirect.getName(); + + redirectsToProcess.offer(new Node<>(redirect, result -> output.add("redirect", result), newPath)); + } + } + } + + return resultHolder.getAsJsonObject("result"); + } + public LiteralCommandNode namespaceNode(LiteralCommandNode original, String namespace) { // Adapted from a section of `CraftServer#syncCommands` LiteralCommandNode clone = new LiteralCommandNode<>( @@ -441,51 +548,16 @@ public LiteralCommandNode namespaceNode(LiteralCommandNode origi } public static Map> getCommandNodeChildren(CommandNode target) { - try { - return (Map>) commandNodeChildren.get(target); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } - } - - public static void setCommandNodeChildren(CommandNode target, Map> children) { - try { - commandNodeChildren.set(target, children); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } + return (Map>) commandNodeChildren.get(target); } public static Map> getCommandNodeLiterals(CommandNode target) { - try { - return (Map>) commandNodeLiterals.get(target); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } - } - - public static void setCommandNodeLiterals(CommandNode target, Map> literals) { - try { - commandNodeLiterals.set(target, literals); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } + if (commandNodeLiterals == null) return null; + return (Map>) commandNodeLiterals.get(target); } public static Map> getCommandNodeArguments(CommandNode target) { - try { - return (Map>) commandNodeArguments.get(target); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } - } - - public static void setCommandNodeArguments(CommandNode target, Map> arguments) { - try { - commandNodeArguments.set(target, arguments); - } catch (IllegalAccessException e) { - throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e); - } + return (Map>) commandNodeArguments.get(target); } //////////////////////////////// diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index 7b4e4704d2..2096b59855 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -1,15 +1,16 @@ package dev.jorel.commandapi; +import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.LiteralCommandNode; import dev.jorel.commandapi.arguments.AbstractArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; -import java.io.File; -import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.function.Predicate; /** @@ -127,15 +128,7 @@ default LiteralCommandNode registerCommandNode(LiteralArgumentBuilder getBrigadierDispatcher(); - /** - * Creates a JSON file that describes the hierarchical structure of the commands - * that have been registered by the server. - * - * @param file The JSON file to write to - * @param dispatcher The Brigadier CommandDispatcher - * @throws IOException When the file fails to be written to - */ - public abstract void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException; + public abstract Optional getArgumentTypeProperties(ArgumentType type); /** * @return A new default Logger meant for the CommandAPI to use diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/SafeStaticMethodHandle.java b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeStaticMethodHandle.java new file mode 100644 index 0000000000..01eb1d0ee8 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeStaticMethodHandle.java @@ -0,0 +1,55 @@ +package dev.jorel.commandapi; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +public class SafeStaticMethodHandle { + private MethodHandle handle; + + private SafeStaticMethodHandle(MethodHandle handle) { + this.handle = handle; + } + + private static SafeStaticMethodHandle of( + Class classType, + String methodName, String mojangMappedMethodName, + Class returnType, + Class... parameterTypes + ) throws ReflectiveOperationException { + return new SafeStaticMethodHandle<>( + MethodHandles.privateLookupIn(classType, MethodHandles.lookup()) + .findStatic( + classType, SafeVarHandle.USING_MOJANG_MAPPINGS ? mojangMappedMethodName : methodName, + MethodType.methodType(returnType, parameterTypes) + ) + ); + } + + public static SafeStaticMethodHandle ofOrNull( + Class classType, + String methodName, String mojangMappedMethodName, + Class returnType, + Class... parameterTypes + ) { + try { + return of(classType, methodName, mojangMappedMethodName, returnType, parameterTypes); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + return null; + } + } + + public ReturnType invoke(Object... args) throws Throwable { + return (ReturnType) handle.invokeWithArguments(args); + } + + public ReturnType invokeOrNull(Object... args) { + try { + return invoke(args); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java index 3362dfe6a7..c9ce56639c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/SafeVarHandle.java @@ -21,7 +21,7 @@ private SafeVarHandle(VarHandle handle) { this.handle = handle; } - private static SafeVarHandle of(Class classType, String fieldName, String mojangMappedFieldName, Class fieldType) + public static SafeVarHandle of(Class classType, String fieldName, String mojangMappedFieldName, Class fieldType) throws ReflectiveOperationException { return new SafeVarHandle<>( MethodHandles.privateLookupIn(classType, MethodHandles.lookup()).findVarHandle(classType, USING_MOJANG_MAPPINGS ? mojangMappedFieldName : fieldName, fieldType)); @@ -36,6 +36,10 @@ public static SafeVarHandle ofOrNull(Class SafeVarHandle ofOrNull(Class classType, String fieldName, Class fieldType) { + return ofOrNull(classType, fieldName, fieldName, fieldType); + } + public FieldType get(Type instance) { return (FieldType) handle.get(instance); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 88e46d1c80..61e6c00cd3 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -20,7 +20,6 @@ *******************************************************************************/ package dev.jorel.commandapi.arguments; -import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; @@ -42,7 +41,6 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.function.Function; import java.util.function.Predicate; /** @@ -333,45 +331,71 @@ public String toString() { * A record of the information needed to stack one argument onto another. * * @param lastCommandNodes A {@link List} of {@link CommandNode}s that were the last nodes in the structure of the previous argument. - * @param childrenConsumer A callback that accepts a {@link List} of all the {@link RegisteredCommand.Node}s that represent the nodes - * added as children to the previous argument. + * @param childrenConsumer See {@link ChildrenConsumer#createNodeWithChildren(List)}. */ public static record NodeInformation(List> lastCommandNodes, ChildrenConsumer childrenConsumer) { } + /** + * A callback function used by {@link NodeInformation}. See {@link #createNodeWithChildren(List)}. + */ @FunctionalInterface public static interface ChildrenConsumer { + /** + * Accepts a {@link List} of all the {@link RegisteredCommand.Node}s that + * represent the {@link CommandNode}s added as children to the previous argument. + * @param children The children {@link RegisteredCommand.Node}s + */ public void createNodeWithChildren(List> children); } + /** + * A callback function used by {@link AbstractArgument#finishBuildingNode(ArgumentBuilder, List, TerminalNodeModifier)}. + * See {@link #finishTerminalNode(ArgumentBuilder, List)}. + */ + @FunctionalInterface + public static interface TerminalNodeModifier + /// @endcond + , CommandSender, Source> { + /** + * Applys any necessary changes to the argument builder of a terminal node. + * This can simply build the builder if no modifications are necessary. + * + * @param builder The {@link ArgumentBuilder} to finish building. + * @param previousArguments A List of CommandAPI arguments that came before this argument. + * @return The built {@link CommandNode}. + */ + public CommandNode finishTerminalNode(ArgumentBuilder builder, List previousArguments); + } + /** * Builds the Brigadier {@link CommandNode} structure for this argument. Note that the Brigadier node structure may * contain multiple nodes, for example if {@link #combineWith(AbstractArgument[])} was called for this argument to * merge it with other arguments. *

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

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

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