From aecf0f427ab4f56bab8d190a2278de7d53f432c2 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Tue, 4 Aug 2020 06:14:42 +0200 Subject: [PATCH] Added dynamic arguments for Command (allow for server tab completion) --- .../fr/themode/demo/commands/TestCommand.java | 40 +++-------- .../server/command/CommandManager.java | 63 ++++++++++++----- .../server/command/builder/Command.java | 14 ++++ .../command/builder/CommandDispatcher.java | 13 +++- .../arguments/ArgumentDynamicStringArray.java | 25 +++++++ .../arguments/ArgumentDynamicWord.java | 23 ++++++ .../builder/arguments/ArgumentType.java | 8 +++ .../builder/arguments/ArgumentWord.java | 4 +- .../server/listener/TabCompleteListener.java | 70 +++++++++++-------- 9 files changed, 180 insertions(+), 80 deletions(-) create mode 100644 src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java create mode 100644 src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java diff --git a/src/main/java/fr/themode/demo/commands/TestCommand.java b/src/main/java/fr/themode/demo/commands/TestCommand.java index 4f7082727..94abd3741 100644 --- a/src/main/java/fr/themode/demo/commands/TestCommand.java +++ b/src/main/java/fr/themode/demo/commands/TestCommand.java @@ -1,6 +1,5 @@ package fr.themode.demo.commands; -import net.minestom.server.chat.ColoredText; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Arguments; import net.minestom.server.command.builder.Command; @@ -8,49 +7,32 @@ import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.arguments.ArgumentType; import net.minestom.server.entity.Player; -import java.util.Optional; - public class TestCommand extends Command { public TestCommand() { - super("msg"); + super("testcmd"); setDefaultExecutor(this::usage); - Argument player = ArgumentType.Word("player"); - Argument message = ArgumentType.StringArray("array"); + Argument dynamicWord = ArgumentType.DynamicWord("test"); - addSyntax(this::execute, player, message); + addSyntax(this::execute, dynamicWord); } private void usage(CommandSender sender, Arguments arguments) { - sender.sendMessage("Usage: /whisper "); + sender.sendMessage("Incorrect usage"); } private void execute(CommandSender sender, Arguments arguments) { - Player player = (Player) sender; - String targetName = arguments.getWord("player"); - String[] Message = arguments.getStringArray("array"); - Optional target = player.getInstance().getPlayers().stream().filter(p -> p.getUsername().equalsIgnoreCase(targetName)).findFirst(); - if (target.isPresent()) { - if (target.get() == player) { - player.sendMessage("You cannot message yourself"); - } else { - String message = ""; - for (int i = 0; i < Message.length; i++) { - if (i != 0) { - message = message + " "; - } - message = message + Message[i]; - } - player.sendMessage("You -> " + targetName + ": " + message); - target.get().sendMessage(player.getUsername() + " -> You: " + message); - } - } else { - player.sendMessage(ColoredText.ofFormat("{@argument.player.unknown}")); - } + final String word = arguments.getWord("test"); + sender.sendMessage("word: " + word); } private boolean isAllowed(Player player) { return true; // TODO: permissions } + + @Override + public String[] onDynamicWrite(String text) { + return new String[]{"test1", "test2"}; + } } diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 91386df48..b2bd6b56f 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -73,6 +73,16 @@ public class CommandManager { this.dispatcher.register(command); } + /** + * Get the command register by {@link #register(Command)} + * + * @param commandName the command name + * @return the command associated with the name, null if not any + */ + public Command getCommand(String commandName) { + return dispatcher.findCommand(commandName); + } + /** * Register a simple command without auto-completion * @@ -353,12 +363,12 @@ public class CommandManager { }*/ if (argument instanceof ArgumentBoolean) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "brigadier:bool"; argumentNode.properties = packetWriter -> packetWriter.writeByte((byte) 0); } else if (argument instanceof ArgumentDouble) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); ArgumentDouble argumentDouble = (ArgumentDouble) argument; argumentNode.parser = "brigadier:double"; @@ -370,7 +380,7 @@ public class CommandManager { packetWriter.writeDouble(argumentDouble.getMax()); }; } else if (argument instanceof ArgumentFloat) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); ArgumentFloat argumentFloat = (ArgumentFloat) argument; argumentNode.parser = "brigadier:float"; @@ -382,7 +392,7 @@ public class CommandManager { packetWriter.writeFloat(argumentFloat.getMax()); }; } else if (argument instanceof ArgumentInteger) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); ArgumentInteger argumentInteger = (ArgumentInteger) argument; argumentNode.parser = "brigadier:integer"; @@ -418,50 +428,67 @@ public class CommandManager { } } else { // Can be any word, add only one argument node - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); wordConsumer.accept(argumentNode); } + } else if (argument instanceof ArgumentDynamicWord) { + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, true); + + argumentNode.parser = "brigadier:string"; + argumentNode.properties = packetWriter -> { + packetWriter.writeVarInt(0); // Single word + }; + argumentNode.suggestionsType = "minecraft:ask_server"; + } else if (argument instanceof ArgumentString) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "brigadier:string"; argumentNode.properties = packetWriter -> { packetWriter.writeVarInt(1); // Quotable phrase }; } else if (argument instanceof ArgumentStringArray) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "brigadier:string"; argumentNode.properties = packetWriter -> { packetWriter.writeVarInt(2); // Greedy phrase }; + } else if (argument instanceof ArgumentDynamicStringArray) { + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, true); + + argumentNode.parser = "brigadier:string"; + argumentNode.properties = packetWriter -> { + packetWriter.writeVarInt(2); // Greedy phrase + }; + argumentNode.suggestionsType = "minecraft:ask_server"; } else if (argument instanceof ArgumentColor) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:color"; } else if (argument instanceof ArgumentTime) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:time"; } else if (argument instanceof ArgumentEnchantment) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:item_enchantment"; } else if (argument instanceof ArgumentParticle) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:particle"; } else if (argument instanceof ArgumentPotion) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:mob_effect"; } else if (argument instanceof ArgumentEntityType) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:entity_summon"; } else if (argument instanceof ArgumentIntRange) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:int_range"; } else if (argument instanceof ArgumentFloatRange) { - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:float_range"; } else if (argument instanceof ArgumentEntities) { ArgumentEntities argumentEntities = (ArgumentEntities) argument; - DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); + DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); argumentNode.parser = "minecraft:entity"; argumentNode.properties = packetWriter -> { byte mask = 0; @@ -496,11 +523,11 @@ public class CommandManager { * @return the created {@link DeclareCommandsPacket.Node} */ private DeclareCommandsPacket.Node simpleArgumentNode(List nodes, - Argument argument, boolean executable) { + Argument argument, boolean executable, boolean suggestion) { DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node(); nodes.add(argumentNode); - argumentNode.flags = getFlag(NodeType.ARGUMENT, executable, false, false); + argumentNode.flags = getFlag(NodeType.ARGUMENT, executable, false, suggestion); argumentNode.name = argument.getId(); return argumentNode; diff --git a/src/main/java/net/minestom/server/command/builder/Command.java b/src/main/java/net/minestom/server/command/builder/Command.java index dd06dc76d..06967333f 100644 --- a/src/main/java/net/minestom/server/command/builder/Command.java +++ b/src/main/java/net/minestom/server/command/builder/Command.java @@ -1,6 +1,8 @@ package net.minestom.server.command.builder; import net.minestom.server.command.builder.arguments.Argument; +import net.minestom.server.command.builder.arguments.ArgumentDynamicStringArray; +import net.minestom.server.command.builder.arguments.ArgumentDynamicWord; import net.minestom.server.command.builder.condition.CommandCondition; import java.util.ArrayList; @@ -123,4 +125,16 @@ public class Command { public Collection getSyntaxes() { return syntaxes; } + + /** + * Allow for tab auto completion, this is called everytime the player press a key in the chat + * when in a dynamic argument ({@link ArgumentDynamicWord} & {@link ArgumentDynamicStringArray}) + * + * @param text the whole player text + * @return the array containing all the suggestion for the current arg (split " ") + */ + public String[] onDynamicWrite(String text) { + return null; + } + } 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 903a59e8a..47d6a2f2f 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -13,9 +13,9 @@ public class CommandDispatcher { private Set commands = new HashSet<>(); public void register(Command command) { - this.commandMap.put(command.getName(), command); + this.commandMap.put(command.getName().toLowerCase(), command); for (String alias : command.getAliases()) { - this.commandMap.put(alias, command); + this.commandMap.put(alias.toLowerCase(), command); } this.commands.add(command); } @@ -54,7 +54,14 @@ public class CommandDispatcher { return Collections.unmodifiableSet(commands); } - private Command findCommand(String commandName) { + /** + * Get the command class associated with its name + * + * @param commandName the command name + * @return the {@link Command} associated with the name, null if not any + */ + public Command findCommand(String commandName) { + commandName = commandName.toLowerCase(); return commandMap.containsKey(commandName) ? commandMap.get(commandName) : null; } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java new file mode 100644 index 000000000..8942dacf4 --- /dev/null +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java @@ -0,0 +1,25 @@ +package net.minestom.server.command.builder.arguments; + +import java.util.regex.Pattern; + +public class ArgumentDynamicStringArray extends Argument { + + public ArgumentDynamicStringArray(String id) { + super(id, true, true); + } + + @Override + public int getCorrectionResult(String value) { + return SUCCESS; + } + + @Override + public String[] parse(String value) { + return value.split(Pattern.quote(" ")); + } + + @Override + public int getConditionResult(String[] value) { + return SUCCESS; + } +} diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java new file mode 100644 index 000000000..12f148ffd --- /dev/null +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java @@ -0,0 +1,23 @@ +package net.minestom.server.command.builder.arguments; + +public class ArgumentDynamicWord extends Argument { + + public ArgumentDynamicWord(String id) { + super(id); + } + + @Override + public int getCorrectionResult(String value) { + return SUCCESS; + } + + @Override + public String parse(String value) { + return value; + } + + @Override + public int getConditionResult(String value) { + return SUCCESS; + } +} diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java index e102079ea..cb6840d11 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java @@ -43,10 +43,18 @@ public class ArgumentType { return new ArgumentWord(id); } + public static ArgumentDynamicWord DynamicWord(String id) { + return new ArgumentDynamicWord(id); + } + public static ArgumentStringArray StringArray(String id) { return new ArgumentStringArray(id); } + public static ArgumentDynamicStringArray DynamicStringArray(String id) { + return new ArgumentDynamicStringArray(id); + } + // Minecraft specific public static ArgumentColor Color(String id) { diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java index 39a26b0b7..b3392b4b3 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java @@ -21,7 +21,7 @@ public class ArgumentWord extends Argument { if (value.contains(" ")) return SPACE_ERROR; - return Argument.SUCCESS; + return SUCCESS; } @Override @@ -37,7 +37,7 @@ public class ArgumentWord extends Argument { if (restrictions != null && restrictions.length > 0) { for (String r : restrictions) { if (value.equalsIgnoreCase(r)) - return Argument.SUCCESS; + return SUCCESS; } if (!findRestriction) return RESTRICTION_ERROR; diff --git a/src/main/java/net/minestom/server/listener/TabCompleteListener.java b/src/main/java/net/minestom/server/listener/TabCompleteListener.java index 46856580c..52040000e 100644 --- a/src/main/java/net/minestom/server/listener/TabCompleteListener.java +++ b/src/main/java/net/minestom/server/listener/TabCompleteListener.java @@ -3,6 +3,7 @@ package net.minestom.server.listener; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; import net.minestom.server.command.CommandProcessor; +import net.minestom.server.command.builder.Command; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.client.play.ClientTabCompletePacket; import net.minestom.server.network.packet.server.play.TabCompletePacket; @@ -23,42 +24,55 @@ public class TabCompleteListener { // Tab complete for CommandProcessor final CommandProcessor commandProcessor = COMMAND_MANAGER.getCommandProcessor(commandName); if (commandProcessor != null) { - final boolean endSpace = text.endsWith(" "); - - int start; - - if (endSpace) { - start = text.length(); - } else { - final String lastArg = split[split.length - 1]; - start = text.indexOf(lastArg); - } - + final int start = findStart(text, split); final String[] matches = commandProcessor.onWrite(text); - if (matches != null && matches.length > 0) { - TabCompletePacket tabCompletePacket = new TabCompletePacket(); - tabCompletePacket.transactionId = packet.transactionId; - tabCompletePacket.start = start; - tabCompletePacket.length = 20; - - TabCompletePacket.Match[] matchesArray = new TabCompletePacket.Match[matches.length]; - for (int i = 0; i < matchesArray.length; i++) { - TabCompletePacket.Match match = new TabCompletePacket.Match(); - match.match = matches[i]; - matchesArray[i] = match; + sendTabCompletePacket(packet.transactionId, start, matches, player); + } + } else { + // Tab complete for Command + final Command command = COMMAND_MANAGER.getCommand(commandName); + if (command != null) { + final int start = findStart(text, split); + final String[] matches = command.onDynamicWrite(text); + if (matches != null && matches.length > 0) { + sendTabCompletePacket(packet.transactionId, start, matches, player); } - - tabCompletePacket.matches = matchesArray; - - player.getPlayerConnection().sendPacket(tabCompletePacket); } } - // TODO tab complete for Command (TabArgument) - } + private static int findStart(String text, String[] split) { + final boolean endSpace = text.endsWith(" "); + int start; + if (endSpace) { + start = text.length(); + } else { + final String lastArg = split[split.length - 1]; + start = text.lastIndexOf(lastArg); + } + return start; + } + + private static void sendTabCompletePacket(int transactionId, int start, String[] matches, Player player) { + TabCompletePacket tabCompletePacket = new TabCompletePacket(); + tabCompletePacket.transactionId = transactionId; + tabCompletePacket.start = start; + tabCompletePacket.length = 20; + + TabCompletePacket.Match[] matchesArray = new TabCompletePacket.Match[matches.length]; + for (int i = 0; i < matchesArray.length; i++) { + TabCompletePacket.Match match = new TabCompletePacket.Match(); + match.match = matches[i]; + matchesArray[i] = match; + } + + tabCompletePacket.matches = matchesArray; + + player.getPlayerConnection().sendPacket(tabCompletePacket); + } + }