Added dynamic arguments for Command (allow for server tab completion)

This commit is contained in:
Felix Cravic 2020-08-04 06:14:42 +02:00
parent baccc36ed7
commit aecf0f427a
9 changed files with 180 additions and 80 deletions

View File

@ -1,6 +1,5 @@
package fr.themode.demo.commands; package fr.themode.demo.commands;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.command.CommandSender; import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.Arguments; import net.minestom.server.command.builder.Arguments;
import net.minestom.server.command.builder.Command; 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.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import java.util.Optional;
public class TestCommand extends Command { public class TestCommand extends Command {
public TestCommand() { public TestCommand() {
super("msg"); super("testcmd");
setDefaultExecutor(this::usage); setDefaultExecutor(this::usage);
Argument player = ArgumentType.Word("player"); Argument dynamicWord = ArgumentType.DynamicWord("test");
Argument message = ArgumentType.StringArray("array");
addSyntax(this::execute, player, message); addSyntax(this::execute, dynamicWord);
} }
private void usage(CommandSender sender, Arguments arguments) { private void usage(CommandSender sender, Arguments arguments) {
sender.sendMessage("Usage: /whisper <player> <message>"); sender.sendMessage("Incorrect usage");
} }
private void execute(CommandSender sender, Arguments arguments) { private void execute(CommandSender sender, Arguments arguments) {
Player player = (Player) sender; final String word = arguments.getWord("test");
String targetName = arguments.getWord("player"); sender.sendMessage("word: " + word);
String[] Message = arguments.getStringArray("array");
Optional<Player> 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}"));
}
} }
private boolean isAllowed(Player player) { private boolean isAllowed(Player player) {
return true; // TODO: permissions return true; // TODO: permissions
} }
@Override
public String[] onDynamicWrite(String text) {
return new String[]{"test1", "test2"};
}
} }

View File

@ -73,6 +73,16 @@ public class CommandManager {
this.dispatcher.register(command); 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 * Register a simple command without auto-completion
* *
@ -353,12 +363,12 @@ public class CommandManager {
}*/ }*/
if (argument instanceof ArgumentBoolean) { if (argument instanceof ArgumentBoolean) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "brigadier:bool"; argumentNode.parser = "brigadier:bool";
argumentNode.properties = packetWriter -> packetWriter.writeByte((byte) 0); argumentNode.properties = packetWriter -> packetWriter.writeByte((byte) 0);
} else if (argument instanceof ArgumentDouble) { } else if (argument instanceof ArgumentDouble) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
ArgumentDouble argumentDouble = (ArgumentDouble) argument; ArgumentDouble argumentDouble = (ArgumentDouble) argument;
argumentNode.parser = "brigadier:double"; argumentNode.parser = "brigadier:double";
@ -370,7 +380,7 @@ public class CommandManager {
packetWriter.writeDouble(argumentDouble.getMax()); packetWriter.writeDouble(argumentDouble.getMax());
}; };
} else if (argument instanceof ArgumentFloat) { } else if (argument instanceof ArgumentFloat) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
ArgumentFloat argumentFloat = (ArgumentFloat) argument; ArgumentFloat argumentFloat = (ArgumentFloat) argument;
argumentNode.parser = "brigadier:float"; argumentNode.parser = "brigadier:float";
@ -382,7 +392,7 @@ public class CommandManager {
packetWriter.writeFloat(argumentFloat.getMax()); packetWriter.writeFloat(argumentFloat.getMax());
}; };
} else if (argument instanceof ArgumentInteger) { } else if (argument instanceof ArgumentInteger) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
ArgumentInteger argumentInteger = (ArgumentInteger) argument; ArgumentInteger argumentInteger = (ArgumentInteger) argument;
argumentNode.parser = "brigadier:integer"; argumentNode.parser = "brigadier:integer";
@ -418,50 +428,67 @@ public class CommandManager {
} }
} else { } else {
// Can be any word, add only one argument node // 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); 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) { } 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.parser = "brigadier:string";
argumentNode.properties = packetWriter -> { argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(1); // Quotable phrase packetWriter.writeVarInt(1); // Quotable phrase
}; };
} else if (argument instanceof ArgumentStringArray) { } 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.parser = "brigadier:string";
argumentNode.properties = packetWriter -> { argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(2); // Greedy phrase 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) { } else if (argument instanceof ArgumentColor) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:color"; argumentNode.parser = "minecraft:color";
} else if (argument instanceof ArgumentTime) { } else if (argument instanceof ArgumentTime) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:time"; argumentNode.parser = "minecraft:time";
} else if (argument instanceof ArgumentEnchantment) { } 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"; argumentNode.parser = "minecraft:item_enchantment";
} else if (argument instanceof ArgumentParticle) { } else if (argument instanceof ArgumentParticle) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:particle"; argumentNode.parser = "minecraft:particle";
} else if (argument instanceof ArgumentPotion) { } 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"; argumentNode.parser = "minecraft:mob_effect";
} else if (argument instanceof ArgumentEntityType) { } 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"; argumentNode.parser = "minecraft:entity_summon";
} else if (argument instanceof ArgumentIntRange) { } 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"; argumentNode.parser = "minecraft:int_range";
} else if (argument instanceof ArgumentFloatRange) { } 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"; argumentNode.parser = "minecraft:float_range";
} else if (argument instanceof ArgumentEntities) { } else if (argument instanceof ArgumentEntities) {
ArgumentEntities argumentEntities = (ArgumentEntities) argument; ArgumentEntities argumentEntities = (ArgumentEntities) argument;
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable); DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:entity"; argumentNode.parser = "minecraft:entity";
argumentNode.properties = packetWriter -> { argumentNode.properties = packetWriter -> {
byte mask = 0; byte mask = 0;
@ -496,11 +523,11 @@ public class CommandManager {
* @return the created {@link DeclareCommandsPacket.Node} * @return the created {@link DeclareCommandsPacket.Node}
*/ */
private DeclareCommandsPacket.Node simpleArgumentNode(List<DeclareCommandsPacket.Node> nodes, private DeclareCommandsPacket.Node simpleArgumentNode(List<DeclareCommandsPacket.Node> nodes,
Argument<?> argument, boolean executable) { Argument<?> argument, boolean executable, boolean suggestion) {
DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node(); DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node();
nodes.add(argumentNode); nodes.add(argumentNode);
argumentNode.flags = getFlag(NodeType.ARGUMENT, executable, false, false); argumentNode.flags = getFlag(NodeType.ARGUMENT, executable, false, suggestion);
argumentNode.name = argument.getId(); argumentNode.name = argument.getId();
return argumentNode; return argumentNode;

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder; package net.minestom.server.command.builder;
import net.minestom.server.command.builder.arguments.Argument; 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 net.minestom.server.command.builder.condition.CommandCondition;
import java.util.ArrayList; import java.util.ArrayList;
@ -123,4 +125,16 @@ public class Command {
public Collection<CommandSyntax> getSyntaxes() { public Collection<CommandSyntax> getSyntaxes() {
return syntaxes; 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;
}
} }

View File

@ -13,9 +13,9 @@ public class CommandDispatcher {
private Set<Command> commands = new HashSet<>(); private Set<Command> commands = new HashSet<>();
public void register(Command command) { public void register(Command command) {
this.commandMap.put(command.getName(), command); this.commandMap.put(command.getName().toLowerCase(), command);
for (String alias : command.getAliases()) { for (String alias : command.getAliases()) {
this.commandMap.put(alias, command); this.commandMap.put(alias.toLowerCase(), command);
} }
this.commands.add(command); this.commands.add(command);
} }
@ -54,7 +54,14 @@ public class CommandDispatcher {
return Collections.unmodifiableSet(commands); 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; return commandMap.containsKey(commandName) ? commandMap.get(commandName) : null;
} }

View File

@ -0,0 +1,25 @@
package net.minestom.server.command.builder.arguments;
import java.util.regex.Pattern;
public class ArgumentDynamicStringArray extends Argument<String[]> {
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;
}
}

View File

@ -0,0 +1,23 @@
package net.minestom.server.command.builder.arguments;
public class ArgumentDynamicWord extends Argument<String> {
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;
}
}

View File

@ -43,10 +43,18 @@ public class ArgumentType {
return new ArgumentWord(id); return new ArgumentWord(id);
} }
public static ArgumentDynamicWord DynamicWord(String id) {
return new ArgumentDynamicWord(id);
}
public static ArgumentStringArray StringArray(String id) { public static ArgumentStringArray StringArray(String id) {
return new ArgumentStringArray(id); return new ArgumentStringArray(id);
} }
public static ArgumentDynamicStringArray DynamicStringArray(String id) {
return new ArgumentDynamicStringArray(id);
}
// Minecraft specific // Minecraft specific
public static ArgumentColor Color(String id) { public static ArgumentColor Color(String id) {

View File

@ -21,7 +21,7 @@ public class ArgumentWord extends Argument<String> {
if (value.contains(" ")) if (value.contains(" "))
return SPACE_ERROR; return SPACE_ERROR;
return Argument.SUCCESS; return SUCCESS;
} }
@Override @Override
@ -37,7 +37,7 @@ public class ArgumentWord extends Argument<String> {
if (restrictions != null && restrictions.length > 0) { if (restrictions != null && restrictions.length > 0) {
for (String r : restrictions) { for (String r : restrictions) {
if (value.equalsIgnoreCase(r)) if (value.equalsIgnoreCase(r))
return Argument.SUCCESS; return SUCCESS;
} }
if (!findRestriction) if (!findRestriction)
return RESTRICTION_ERROR; return RESTRICTION_ERROR;

View File

@ -3,6 +3,7 @@ package net.minestom.server.listener;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandManager; import net.minestom.server.command.CommandManager;
import net.minestom.server.command.CommandProcessor; import net.minestom.server.command.CommandProcessor;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.client.play.ClientTabCompletePacket; import net.minestom.server.network.packet.client.play.ClientTabCompletePacket;
import net.minestom.server.network.packet.server.play.TabCompletePacket; import net.minestom.server.network.packet.server.play.TabCompletePacket;
@ -23,22 +24,41 @@ public class TabCompleteListener {
// Tab complete for CommandProcessor // Tab complete for CommandProcessor
final CommandProcessor commandProcessor = COMMAND_MANAGER.getCommandProcessor(commandName); final CommandProcessor commandProcessor = COMMAND_MANAGER.getCommandProcessor(commandName);
if (commandProcessor != null) { if (commandProcessor != null) {
final int start = findStart(text, split);
final String[] matches = commandProcessor.onWrite(text);
if (matches != null && matches.length > 0) {
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);
}
}
}
}
private static int findStart(String text, String[] split) {
final boolean endSpace = text.endsWith(" "); final boolean endSpace = text.endsWith(" ");
int start; int start;
if (endSpace) { if (endSpace) {
start = text.length(); start = text.length();
} else { } else {
final String lastArg = split[split.length - 1]; final String lastArg = split[split.length - 1];
start = text.indexOf(lastArg); start = text.lastIndexOf(lastArg);
}
return start;
} }
final String[] matches = commandProcessor.onWrite(text); private static void sendTabCompletePacket(int transactionId, int start, String[] matches, Player player) {
if (matches != null && matches.length > 0) {
TabCompletePacket tabCompletePacket = new TabCompletePacket(); TabCompletePacket tabCompletePacket = new TabCompletePacket();
tabCompletePacket.transactionId = packet.transactionId; tabCompletePacket.transactionId = transactionId;
tabCompletePacket.start = start; tabCompletePacket.start = start;
tabCompletePacket.length = 20; tabCompletePacket.length = 20;
@ -53,12 +73,6 @@ public class TabCompleteListener {
player.getPlayerConnection().sendPacket(tabCompletePacket); player.getPlayerConnection().sendPacket(tabCompletePacket);
} }
}
// TODO tab complete for Command (TabArgument)
}
} }