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;
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 <player> <message>");
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<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}"));
}
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"};
}
}

View File

@ -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<DeclareCommandsPacket.Node> 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;

View File

@ -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<CommandSyntax> 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;
}
}

View File

@ -13,9 +13,9 @@ public class CommandDispatcher {
private Set<Command> 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;
}

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);
}
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) {

View File

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

View File

@ -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);
}
}