diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 6c69dbece..64bb6da91 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -1,5 +1,6 @@ package net.minestom.server.command; +import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; @@ -9,6 +10,9 @@ import net.minestom.server.command.builder.*; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.arguments.minecraft.SuggestionType; import net.minestom.server.command.builder.condition.CommandCondition; +import net.minestom.server.command.builder.parser.ArgumentQueryResult; +import net.minestom.server.command.builder.parser.CommandParser; +import net.minestom.server.command.builder.parser.CommandQueryResult; import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerCommandEvent; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; @@ -310,9 +314,46 @@ public final class CommandManager { rootNode.flags = 0; nodes.add(rootNode); + Map commandIdentityMap = new IdentityHashMap<>(); + Map, DeclareCommandsPacket.Node[]> argumentIdentityMap = new IdentityHashMap<>(); + + List> nodeRequests = new ArrayList<>(); + // Brigadier-like commands for (Command command : dispatcher.getCommands()) { - serializeCommand(player, command, nodes, rootChildren); + final int commandNodeIndex = serializeCommand(player, command, nodes, rootChildren, commandIdentityMap, argumentIdentityMap, nodeRequests); + commandIdentityMap.put(command, commandNodeIndex); + } + + // Answer to all node requests + for (Pair pair : nodeRequests) { + String input = pair.left(); + NodeMaker.Request request = pair.right(); + + final CommandQueryResult commandQueryResult = CommandParser.findCommand(input); + if (commandQueryResult == null) { + // Invalid command, return root node + request.retrieve(0); + continue; + } + + final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command, + commandQueryResult.args, input, false, argument -> true); + if (queryResult == null) { + // Invalid argument, return command node + int commandNode = commandIdentityMap.get(commandQueryResult.command); + request.retrieve(commandNode); + continue; + } + + // Retrieve argument node + Argument argument = queryResult.argument; + DeclareCommandsPacket.Node[] argNodes = argumentIdentityMap.get(argument); + for (DeclareCommandsPacket.Node argNode : argNodes) { + int node = argNode.children[0]; + request.retrieve(node); + break; + } } // Pair @@ -371,7 +412,10 @@ public final class CommandManager { private int serializeCommand(CommandSender sender, Command command, List nodes, - IntList rootChildren) { + IntList rootChildren, + Map commandIdentityMap, + Map, DeclareCommandsPacket.Node[]> argumentIdentityMap, + List> nodeRequests) { // Check if player should see this command final CommandCondition commandCondition = command.getCondition(); if (commandCondition != null) { @@ -386,15 +430,16 @@ public final class CommandManager { final Collection syntaxes = command.getSyntaxes(); // Create command for main name - final DeclareCommandsPacket.Node mainNode = createCommand(sender, nodes, cmdChildren, - command.getName(), syntaxes, rootChildren); + final DeclareCommandsPacket.Node mainNode = createCommandNodes(sender, nodes, cmdChildren, + command.getName(), syntaxes, rootChildren, argumentIdentityMap, nodeRequests); final int mainNodeIndex = nodes.indexOf(mainNode); // Serialize all the subcommands for (Command subcommand : command.getSubcommands()) { - final int subNodeIndex = serializeCommand(sender, subcommand, nodes, cmdChildren); + final int subNodeIndex = serializeCommand(sender, subcommand, nodes, cmdChildren, commandIdentityMap, argumentIdentityMap, nodeRequests); if (subNodeIndex != -1) { mainNode.children = ArrayUtils.concatenateIntArrays(mainNode.children, new int[]{subNodeIndex}); + commandIdentityMap.put(subcommand, subNodeIndex); } } @@ -426,12 +471,14 @@ public final class CommandManager { * @param rootChildren the children of the main node (all commands name) * @return The index of the main node for alias redirection */ - private DeclareCommandsPacket.Node createCommand(@NotNull CommandSender sender, - @NotNull List nodes, - @NotNull IntList cmdChildren, - @NotNull String name, - @NotNull Collection syntaxes, - @NotNull IntList rootChildren) { + private DeclareCommandsPacket.Node createCommandNodes(@NotNull CommandSender sender, + @NotNull List nodes, + @NotNull IntList cmdChildren, + @NotNull String name, + @NotNull Collection syntaxes, + @NotNull IntList rootChildren, + @NotNull Map, DeclareCommandsPacket.Node[]> argumentIdentityMap, + @NotNull List> nodeRequests) { DeclareCommandsPacket.Node literalNode = createMainNode(name, syntaxes.isEmpty()); @@ -465,7 +512,7 @@ public final class CommandManager { final Argument argument = arguments[i]; final boolean isLast = i == arguments.length - 1; - // Search previously parsed syntaxes to find identical part in order to create a node between those + // Search previously parsed syntaxes to find identical part in order to create a link between those { // Find shared part boolean foundSharedPart = false; @@ -536,9 +583,14 @@ public final class CommandManager { lastArgumentNodeIndex = nodesLayer.size(); } } + + nodeRequests.addAll(nodeMaker.getNodeRequests()); + syntaxesArguments.add(arguments); } + storedArgumentsNodes.forEach((argument, nodes1) -> argumentIdentityMap.put(argument, nodes1.get(0))); + literalNode.children = ArrayUtils.toArray(cmdChildren); return literalNode; diff --git a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java index f0c35350b..a60560d2e 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -118,21 +118,17 @@ public class CommandDispatcher { final String[] parts = commandString.split(StringUtils.SPACE); final String commandName = parts[0]; - String[] args = new String[parts.length - 1]; - System.arraycopy(parts, 1, args, 0, args.length); - - final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandName, args); + final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString); // Check if the command exists if (commandQueryResult == null) { return CommandResult.of(CommandResult.Type.UNKNOWN, commandName); } final Command command = commandQueryResult.command; - args = commandQueryResult.args; CommandResult result = new CommandResult(); result.input = commandString; // Find the used syntax and fill CommandResult#type and CommandResult#parsedCommand - findParsedCommand(command, commandName, args, commandString, result); + findParsedCommand(command, commandName, commandQueryResult.args, commandString, result); // Cache result { diff --git a/src/main/java/net/minestom/server/command/builder/NodeMaker.java b/src/main/java/net/minestom/server/command/builder/NodeMaker.java index 9493575b0..bafe9e467 100644 --- a/src/main/java/net/minestom/server/command/builder/NodeMaker.java +++ b/src/main/java/net/minestom/server/command/builder/NodeMaker.java @@ -1,10 +1,10 @@ package net.minestom.server.command.builder; +import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -15,8 +15,7 @@ public class NodeMaker { private final List nodes = new ArrayList<>(2); private final Object2IntMap nodeIdsMap = new Object2IntOpenHashMap<>(); - private Rule rule; - private int ruleCount; + private final List> nodeRequests = new ArrayList<>(); public NodeMaker(@NotNull DeclareCommandsPacket.Node[] commandNodes, int id) { addNodes(commandNodes); @@ -41,26 +40,11 @@ public class NodeMaker { } public void addNodes(@NotNull DeclareCommandsPacket.Node[] nodes) { - Options options = null; - if (rule != null) { - options = rule.listen(nodes, ruleCount++); - } - if (options == null) { - options = new Options(); - } + Options options = new Options(); this.configuredNodes.add(ConfiguredNodes.of(nodes, options)); this.nodes.add(nodes); } - public void setRule(@NotNull Rule rule) { - this.rule = rule; - } - - public void resetRule() { - this.rule = null; - this.ruleCount = 0; - } - @NotNull public List getConfiguredNodes() { return configuredNodes; @@ -75,6 +59,14 @@ public class NodeMaker { return nodeIdsMap; } + public void request(String input, Request request) { + this.nodeRequests.add(Pair.of(input, request)); + } + + public List> getNodeRequests() { + return nodeRequests; + } + public static class ConfiguredNodes { private DeclareCommandsPacket.Node[] nodes; private Options options; @@ -95,11 +87,6 @@ public class NodeMaker { } } - public interface Rule { - @Nullable - Options listen(DeclareCommandsPacket.Node[] nodes, int count); - } - public static class Options { private boolean updateLastNode = true; @@ -128,4 +115,9 @@ public class NodeMaker { } } + @FunctionalInterface + public interface Request { + void retrieve(int id); + } + } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java index 4bfa95e3c..a4c4d4c99 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java @@ -6,12 +6,15 @@ import net.minestom.server.command.builder.CommandResult; import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; public class ArgumentCommand extends Argument { public static final int INVALID_COMMAND_ERROR = 1; + private String shortcut = ""; + public ArgumentCommand(@NotNull String id) { super(id, true, true); } @@ -19,8 +22,11 @@ public class ArgumentCommand extends Argument { @NotNull @Override public CommandResult parse(@NotNull String input) throws ArgumentSyntaxException { + final String commandString = !shortcut.isEmpty() ? + shortcut + StringUtils.SPACE + input + : input; CommandDispatcher dispatcher = MinecraftServer.getCommandManager().getDispatcher(); - CommandResult result = dispatcher.parse(input); + CommandResult result = dispatcher.parse(commandString); if (result.getType() != CommandResult.Type.SUCCESS) throw new ArgumentSyntaxException("Invalid command", input, INVALID_COMMAND_ERROR); @@ -32,10 +38,28 @@ public class ArgumentCommand extends Argument { public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) { final DeclareCommandsPacket.Node[] lastNodes = nodeMaker.getLatestNodes(); - // FIXME check if lastNodes is null - for (DeclareCommandsPacket.Node node : lastNodes) { - node.flags |= 0x08; // Redirection mask - node.redirectedNode = 0; // Redirect to root + if (!shortcut.isEmpty()) { + nodeMaker.request(shortcut, (id) -> { + for (DeclareCommandsPacket.Node node : lastNodes) { + node.flags |= 0x08; // Redirection mask + node.redirectedNode = id; + } + }); + } else { + for (DeclareCommandsPacket.Node node : lastNodes) { + node.flags |= 0x08; // Redirection mask + node.redirectedNode = 0; // Redirect to root + } } } + + @NotNull + public String getShortcut() { + return shortcut; + } + + public ArgumentCommand setShortcut(@NotNull String shortcut) { + this.shortcut = shortcut; + return this; + } } diff --git a/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java b/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java index 5bdca3206..8f1fc12ab 100644 --- a/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java +++ b/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java @@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Function; public class CommandParser { @@ -49,6 +50,16 @@ public class CommandParser { return commandQueryResult; } + @Nullable + public static CommandQueryResult findCommand(@NotNull String input) { + final String[] parts = input.split(StringUtils.SPACE); + final String commandName = parts[0]; + + String[] args = new String[parts.length - 1]; + System.arraycopy(parts, 1, args, 0, args.length); + return CommandParser.findCommand(commandName, args); + } + public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument[] commandArguments, @NotNull String[] inputArguments, @NotNull String commandString, @Nullable List validSyntaxes, @@ -153,8 +164,8 @@ public class CommandParser { } @Nullable - public static ArgumentQueryResult findSuggestibleArgument(@NotNull Command command, String[] args, String commandString, - boolean trailingSpace) { + public static ArgumentQueryResult findEligibleArgument(@NotNull Command command, String[] args, String commandString, + boolean trailingSpace, Function, Boolean> eligibilityFunction) { final Collection syntaxes = command.getSyntaxes(); Int2ObjectRBTreeMap suggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder()); @@ -190,7 +201,8 @@ public class CommandParser { context.setArg(argument.getId(), argumentResult.parsedValue, argumentResult.rawArg); } - if (argument.hasSuggestion()) { + // Save result + if (eligibilityFunction.apply(argument)) { ArgumentQueryResult queryResult = new ArgumentQueryResult(); queryResult.syntax = syntax; queryResult.argument = argument; diff --git a/src/main/java/net/minestom/server/listener/TabCompleteListener.java b/src/main/java/net/minestom/server/listener/TabCompleteListener.java index d9c52f5b1..4a9db88b1 100644 --- a/src/main/java/net/minestom/server/listener/TabCompleteListener.java +++ b/src/main/java/net/minestom/server/listener/TabCompleteListener.java @@ -24,17 +24,15 @@ public class TabCompleteListener { String commandName = split[0]; String args = commandString.replaceFirst(commandName, ""); - String[] argsSplit = new String[split.length - 1]; - System.arraycopy(split, 1, argsSplit, 0, argsSplit.length); - final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandName, argsSplit); + final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString); if (commandQueryResult == null) { // Command not found return; } - final ArgumentQueryResult queryResult = CommandParser.findSuggestibleArgument(commandQueryResult.command, - commandQueryResult.args, commandString, text.endsWith(StringUtils.SPACE)); + final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command, + commandQueryResult.args, commandString, text.endsWith(StringUtils.SPACE), Argument::hasSuggestion); if (queryResult == null) { // Suggestible argument not found return; diff --git a/src/test/java/demo/commands/TestCommand.java b/src/test/java/demo/commands/TestCommand.java index f754e883e..ac869e693 100644 --- a/src/test/java/demo/commands/TestCommand.java +++ b/src/test/java/demo/commands/TestCommand.java @@ -6,7 +6,7 @@ import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.suggestion.SuggestionEntry; import static net.minestom.server.command.builder.arguments.ArgumentType.Integer; -import static net.minestom.server.command.builder.arguments.ArgumentType.Word; +import static net.minestom.server.command.builder.arguments.ArgumentType.*; public class TestCommand extends Command { @@ -24,7 +24,11 @@ public class TestCommand extends Command { addSyntax((sender, context) -> { System.out.println("executed"); - }, test1, test2); + }, Literal("test"), test1, test2); + + addSyntax((sender, context) -> { + System.out.println("cmd syntax"); + }, Literal("debug"), Command("cmd")); }