Initial command suggestion commit

This commit is contained in:
themode 2021-03-09 01:44:01 +01:00
parent 7241dbdcf7
commit 441cb5a1db
12 changed files with 195 additions and 18 deletions

View File

@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
import net.minestom.server.MinecraftServer;
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.entity.Player;
import net.minestom.server.event.player.PlayerCommandEvent;
@ -497,6 +498,14 @@ public final class CommandManager {
nodeMaker.getNodeIdsMap().put(argumentNode, childId);
argChildren.add(childId);
// Enable ASK_SERVER suggestion if required
{
if (argument.hasSuggestion()) {
argumentNode.flags |= 0x10; // Suggestion flag
argumentNode.suggestionsType = SuggestionType.ASK_SERVER.getIdentifier();
}
}
// Append to the last node
{
final int[] children = ArrayUtils.toArray(argChildren);

View File

@ -5,6 +5,7 @@ import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandExecutor;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -28,6 +29,8 @@ public abstract class Argument<T> {
private T defaultValue;
public SuggestionCallback suggestionCallback;
/**
* Creates a new argument.
*
@ -148,6 +151,15 @@ public abstract class Argument<T> {
this.callback = callback;
}
/**
* Gets if the argument has any error callback.
*
* @return true if the argument has an error callback, false otherwise
*/
public boolean hasErrorCallback() {
return callback != null;
}
/**
* Gets if this argument is 'optional'.
* <p>
@ -185,13 +197,18 @@ public abstract class Argument<T> {
return this;
}
/**
* Gets if the argument has any error callback.
*
* @return true if the argument has an error callback, false otherwise
*/
public boolean hasErrorCallback() {
return callback != null;
@Nullable
public SuggestionCallback getSuggestionCallback() {
return suggestionCallback;
}
public Argument<T> setSuggestionCallback(@NotNull SuggestionCallback suggestionCallback) {
this.suggestionCallback = suggestionCallback;
return this;
}
public boolean hasSuggestion() {
return suggestionCallback != null;
}
@Override

View File

@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
* Same as {@link ArgumentStringArray} with the exception
* that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(CommandSender, String)}.
*/
@Deprecated
public class ArgumentDynamicStringArray extends Argument<String[]> {
public static final int RESTRICTION_ERROR = 1;

View File

@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
* that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(CommandSender, String)}
* when the suggestion type is {@link SuggestionType#ASK_SERVER}, or any other suggestions available in the enum.
*/
@Deprecated
public class ArgumentDynamicWord extends Argument<String> {
public static final int SPACE_ERROR = 1;

View File

@ -23,7 +23,7 @@ public class ArgumentEnum<E extends Enum> extends Argument<E> {
this.values = enumClass.getEnumConstants();
}
public ArgumentEnum setFormat(@NotNull Format format) {
public ArgumentEnum<E> setFormat(@NotNull Format format) {
this.format = format;
return this;
}

View File

@ -45,7 +45,8 @@ public class ArgumentString extends Argument<String> {
// Check if value start and end with quote
final char first = input.charAt(0);
final char last = input.charAt(input.length() - 1);
final boolean quote = first == StringUtil.DOUBLE_QUOTE && last == StringUtil.DOUBLE_QUOTE;
final boolean quote = input.length() >= 2 &&
first == StringUtil.DOUBLE_QUOTE && last == StringUtil.DOUBLE_QUOTE;
if (!quote)
throw new ArgumentSyntaxException("String argument needs to start and end with quotes", input, QUOTE_ERROR);

View File

@ -0,0 +1,39 @@
package net.minestom.server.command.builder.suggestion;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class Suggestion {
private int start;
private int length;
private final List<SuggestionEntry> suggestionEntries = new ArrayList<>();
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@NotNull
public List<SuggestionEntry> getEntries() {
return suggestionEntries;
}
public void addEntry(@NotNull SuggestionEntry entry) {
this.suggestionEntries.add(entry);
}
}

View File

@ -0,0 +1,8 @@
package net.minestom.server.command.builder.suggestion;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface SuggestionCallback {
void apply(@NotNull Suggestion suggestion);
}

View File

@ -0,0 +1,30 @@
package net.minestom.server.command.builder.suggestion;
import net.minestom.server.chat.JsonMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SuggestionEntry {
private final String entry;
private final JsonMessage tooltip;
public SuggestionEntry(@NotNull String entry, @Nullable JsonMessage tooltip) {
this.entry = entry;
this.tooltip = tooltip;
}
public SuggestionEntry(@NotNull String entry) {
this(entry, null);
}
@NotNull
public String getEntry() {
return entry;
}
@Nullable
public JsonMessage getTooltip() {
return tooltip;
}
}

View File

@ -1,14 +1,27 @@
package net.minestom.server.listener;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
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.command.builder.CommandDispatcher;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.parser.CommandParser;
import net.minestom.server.command.builder.parser.CommandSuggestionHolder;
import net.minestom.server.command.builder.parser.ValidSyntaxHolder;
import net.minestom.server.command.builder.suggestion.Suggestion;
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.client.play.ClientTabCompletePacket;
import net.minestom.server.network.packet.server.play.TabCompletePacket;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
public class TabCompleteListener {
@ -18,6 +31,62 @@ public class TabCompleteListener {
public static void listener(ClientTabCompletePacket packet, Player player) {
final String text = packet.text;
{
String commandString = packet.text.replaceFirst("/", "");
String[] split = commandString.split(StringUtils.SPACE);
String commandName = split[0];
final CommandDispatcher commandDispatcher = MinecraftServer.getCommandManager().getDispatcher();
final Command command = commandDispatcher.findCommand(commandName);
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(StringUtils.SPACE);
if (args.length == 1 && args[0].length() == 0) {
args = new String[0];
}
for (CommandSyntax syntax : syntaxes) {
CommandParser.parse(syntax, syntax.getArguments(), args, validSyntaxes, syntaxesSuggestions);
}
if (!syntaxesSuggestions.isEmpty()) {
final int max = syntaxesSuggestions.firstIntKey();
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
final CommandSyntax syntax = suggestionHolder.syntax;
final int argIndex = suggestionHolder.argIndex;
final Argument<?> argument = syntax.getArguments()[argIndex];
final SuggestionCallback suggestionCallback = argument.suggestionCallback;
if (suggestionCallback != null) {
Suggestion suggestion = new Suggestion();
suggestionCallback.apply(suggestion);
TabCompletePacket tabCompletePacket = new TabCompletePacket();
tabCompletePacket.transactionId = packet.transactionId;
tabCompletePacket.start = suggestion.getStart();
tabCompletePacket.length = suggestion.getLength();
tabCompletePacket.matches = suggestion.getEntries()
.stream()
.map(suggestionEntry -> {
TabCompletePacket.Match match = new TabCompletePacket.Match();
match.match = suggestionEntry.getEntry();
match.hasTooltip = suggestionEntry.getTooltip() != null;
match.tooltip = suggestionEntry.getTooltip();
return match;
}).toArray(TabCompletePacket.Match[]::new);
player.getPlayerConnection().sendPacket(tabCompletePacket);
}
System.out.println("arg: " + argument.getClass());
}
System.out.println("test " + syntaxesSuggestions.size() + " " + validSyntaxes.size());
if (true)
return;
}
final String[] split = packet.text.split(Pattern.quote(StringUtils.SPACE));
final String commandName = split[0].replaceFirst(CommandManager.COMMAND_PREFIX, "");

View File

@ -31,7 +31,7 @@ public class Main {
CommandManager commandManager = MinecraftServer.getCommandManager();
commandManager.register(new TestCommand());
commandManager.register(new GamemodeCommand());
/*commandManager.register(new GamemodeCommand());
commandManager.register(new EntitySelectorCommand());
commandManager.register(new HealthCommand());
commandManager.register(new SimpleCommand());
@ -43,7 +43,7 @@ public class Main {
commandManager.register(new TitleCommand());
commandManager.register(new BookCommand());
commandManager.register(new ShootCommand());
commandManager.register(new HorseCommand());
commandManager.register(new HorseCommand());*/
commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage("unknown command"));

View File

@ -1,12 +1,13 @@
package demo.commands;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.chat.ChatColor;
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;
import net.minestom.server.command.builder.suggestion.SuggestionEntry;
import static net.minestom.server.command.builder.arguments.ArgumentType.Component;
import static net.minestom.server.command.builder.arguments.ArgumentType.Integer;
import static net.minestom.server.command.builder.arguments.ArgumentType.String;
public class TestCommand extends Command {
@ -14,11 +15,12 @@ public class TestCommand extends Command {
super("testcmd");
setDefaultExecutor(this::usage);
var number = Integer("number2");
addSyntax((sender, args) -> {
sender.sendMessage((JsonMessage) args.get("msg"));
}, Component("msg"));
System.out.println("test: " + args.get("msg"));
}, String("msg").setSuggestionCallback(suggestion -> {
suggestion.setLength(999);
suggestion.addEntry(new SuggestionEntry("Match", ColoredText.of(ChatColor.RED, "Hover")));
}));
}
private void usage(CommandSender sender, Arguments arguments) {