Merge pull request #137 from LeoDog896/command

Enhance Command System
This commit is contained in:
TheMode 2021-02-13 06:28:54 +01:00 committed by GitHub
commit 6c4454e9fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1459 additions and 742 deletions

3
.gitignore vendored
View File

@ -55,4 +55,5 @@ gradle-app.setting
/extensions/
# When compiling we get a docs folder
/docs
/docs
.mixin.out/

@ -1 +1 @@
Subproject commit 843fc32877802b9b86ae291a2b2fa3d633c24183
Subproject commit 7151ce11dcef265ae87c7d279f2a237d1158a05c

View File

@ -2,21 +2,11 @@ package net.minestom.server.command;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandData;
import net.minestom.server.command.builder.CommandDispatcher;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.*;
import net.minestom.server.command.builder.arguments.minecraft.*;
import net.minestom.server.command.builder.arguments.minecraft.registry.*;
import net.minestom.server.command.builder.arguments.number.ArgumentDouble;
import net.minestom.server.command.builder.arguments.number.ArgumentFloat;
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
import net.minestom.server.command.builder.arguments.number.ArgumentNumber;
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeBlockPosition;
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec2;
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec3;
import net.minestom.server.command.builder.*;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerCommandEvent;
@ -25,7 +15,6 @@ import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.callback.CommandCallback;
import net.minestom.server.utils.validate.Check;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -33,7 +22,6 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.function.Consumer;
/**
* Manager used to register {@link Command} and {@link CommandProcessor}.
@ -75,9 +63,11 @@ public final class CommandManager {
public synchronized void register(@NotNull Command command) {
Check.stateCondition(commandExists(command.getName()),
"A command with the name " + command.getName() + " is already registered!");
for (String alias : command.getAliases()) {
Check.stateCondition(commandExists(alias),
"A command with the name " + alias + " is already registered!");
if (command.getAliases() != null) {
for (String alias : command.getAliases()) {
Check.stateCondition(commandExists(alias),
"A command with the name " + alias + " is already registered!");
}
}
this.dispatcher.register(command);
}
@ -154,10 +144,10 @@ public final class CommandManager {
*
* @param sender the sender of the command
* @param command the raw command string (without the command prefix)
* @return true if the command hadn't been cancelled and has been successful
* @return the execution result
*/
@Nullable
public CommandData execute(@NotNull CommandSender sender, @NotNull String command) {
@NotNull
public CommandResult execute(@NotNull CommandSender sender, @NotNull String command) {
// Command event
if (sender instanceof Player) {
@ -167,7 +157,7 @@ public final class CommandManager {
player.callEvent(PlayerCommandEvent.class, playerCommandEvent);
if (playerCommandEvent.isCancelled())
return null;
return CommandResult.of(CommandResult.Type.CANCELLED, command);
command = playerCommandEvent.getCommand();
}
@ -176,9 +166,9 @@ public final class CommandManager {
{
// Check for rich-command
final CommandData commandData = this.dispatcher.execute(sender, command);
if (commandData != null) {
return commandData;
final CommandResult result = this.dispatcher.execute(sender, command);
if (result.getType() != CommandResult.Type.UNKNOWN) {
return result;
} else {
// Check for legacy-command
final String[] splitCommand = command.split(StringUtils.SPACE);
@ -188,13 +178,13 @@ public final class CommandManager {
if (unknownCommandCallback != null) {
this.unknownCommandCallback.apply(sender, command);
}
return null;
return CommandResult.of(CommandResult.Type.CANCELLED, command);
}
// Execute the legacy-command
final String[] args = command.substring(command.indexOf(StringUtils.SPACE) + 1).split(StringUtils.SPACE);
commandProcessor.process(sender, commandName, args);
return null;
return CommandResult.of(CommandResult.Type.SUCCESS, command);
}
}
}
@ -205,11 +195,16 @@ public final class CommandManager {
*
* @see #execute(CommandSender, String)
*/
@Nullable
public CommandData executeServerCommand(@NotNull String command) {
@NotNull
public CommandResult executeServerCommand(@NotNull String command) {
return execute(serverSender, command);
}
@NotNull
public CommandDispatcher getDispatcher() {
return dispatcher;
}
/**
* Gets the callback executed once an unknown command is run.
*
@ -304,6 +299,11 @@ public final class CommandManager {
// Contains the children of the main node (all commands name)
IntList rootChildren = new IntArrayList();
// Root node
DeclareCommandsPacket.Node rootNode = new DeclareCommandsPacket.Node();
rootNode.flags = 0;
nodes.add(rootNode);
// Brigadier-like commands
for (Command command : dispatcher.getCommands()) {
// Check if player should see this command
@ -319,39 +319,52 @@ public final class CommandManager {
IntList cmdChildren = new IntArrayList();
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
List<String> names = new ArrayList<>();
names.add(command.getName());
names.addAll(Arrays.asList(command.getAliases()));
for (String name : names) {
createCommand(player, nodes, cmdChildren, name, syntaxes, rootChildren);
// Create command for main name
final int mainNodeIndex = createCommand(player, nodes, cmdChildren,
command.getName(), syntaxes, rootChildren);
// Use redirection to hook aliases with the command
final String[] aliases = command.getAliases();
if (aliases == null)
continue;
for (String alias : aliases) {
DeclareCommandsPacket.Node aliasNode = new DeclareCommandsPacket.Node();
aliasNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL,
false, true, false);
aliasNode.name = alias;
aliasNode.redirectedNode = mainNodeIndex;
addCommandNameNode(aliasNode, rootChildren, nodes);
}
}
// Pair<CommandName,EnabledTracking>
final List<Pair<String, Boolean>> commandsPair = new ArrayList<>();
final Object2BooleanMap<String> commandsPair = new Object2BooleanOpenHashMap<>();
for (CommandProcessor commandProcessor : commandProcessorMap.values()) {
final boolean enableTracking = commandProcessor.enableWritingTracking();
// Do not show command if return false
if (!commandProcessor.hasAccess(player))
continue;
commandsPair.add(Pair.of(commandProcessor.getCommandName(), enableTracking));
commandsPair.put(commandProcessor.getCommandName(), enableTracking);
final String[] aliases = commandProcessor.getAliases();
if (aliases == null || aliases.length == 0)
continue;
for (String alias : aliases) {
commandsPair.add(Pair.of(alias, enableTracking));
commandsPair.put(alias, enableTracking);
}
}
for (Pair<String, Boolean> pair : commandsPair) {
final String name = pair.getLeft();
final boolean tracking = pair.getRight();
for (Object2BooleanMap.Entry<String> entry : commandsPair.object2BooleanEntrySet()) {
final String name = entry.getKey();
final boolean tracking = entry.getBooleanValue();
// Server suggestion (ask_server)
{
DeclareCommandsPacket.Node tabNode = new DeclareCommandsPacket.Node();
tabNode.flags = getFlag(NodeType.ARGUMENT, true, false, tracking);
tabNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.ARGUMENT,
true, false, tracking);
tabNode.name = tracking ? "tab_completion" : "args";
tabNode.parser = "brigadier:string";
tabNode.properties = packetWriter -> packetWriter.writeVarInt(2); // Greedy phrase
@ -364,22 +377,19 @@ public final class CommandManager {
}
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
literalNode.flags = getFlag(NodeType.LITERAL, true, false, false);
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL,
true, false, false);
literalNode.name = name;
literalNode.children = new int[]{nodes.size() - 1};
rootChildren.add(nodes.size());
nodes.add(literalNode);
addCommandNameNode(literalNode, rootChildren, nodes);
}
DeclareCommandsPacket.Node rootNode = new DeclareCommandsPacket.Node();
rootNode.flags = 0;
// Add root node children
rootNode.children = ArrayUtils.toArray(rootChildren);
nodes.add(rootNode);
declareCommandsPacket.nodes = nodes.toArray(new DeclareCommandsPacket.Node[0]);
declareCommandsPacket.rootIndex = nodes.size() - 1;
declareCommandsPacket.rootIndex = 0;
return declareCommandsPacket;
}
@ -393,23 +403,23 @@ public final class CommandManager {
* @param name the name of the command (or the alias)
* @param syntaxes the syntaxes of the command
* @param rootChildren the children of the main node (all commands name)
* @return The index of the main node for alias redirection
*/
private void createCommand(@NotNull CommandSender sender,
@NotNull List<DeclareCommandsPacket.Node> nodes,
@NotNull IntList cmdChildren,
@NotNull String name,
@NotNull Collection<CommandSyntax> syntaxes,
@NotNull IntList rootChildren) {
private int createCommand(@NotNull CommandSender sender,
@NotNull List<DeclareCommandsPacket.Node> nodes,
@NotNull IntList cmdChildren,
@NotNull String name,
@NotNull Collection<CommandSyntax> syntaxes,
@NotNull IntList rootChildren) {
DeclareCommandsPacket.Node literalNode = createMainNode(name, syntaxes.isEmpty());
rootChildren.add(nodes.size());
nodes.add(literalNode);
addCommandNameNode(literalNode, rootChildren, nodes);
// Contains the arguments of the already-parsed syntaxes
List<Argument<?>[]> syntaxesArguments = new ArrayList<>();
// Contains the nodes of an argument
Map<Argument<?>, List<DeclareCommandsPacket.Node>> storedArgumentsNodes = new HashMap<>();
Map<Argument<?>, List<DeclareCommandsPacket.Node[]>> storedArgumentsNodes = new HashMap<>();
for (CommandSyntax syntax : syntaxes) {
final CommandCondition commandCondition = syntax.getCommandCondition();
@ -418,331 +428,107 @@ public final class CommandManager {
continue;
}
// Represent the last nodes computed in the last iteration
List<DeclareCommandsPacket.Node> lastNodes = null;
DeclareCommandsPacket.Node[] lastNodes = new DeclareCommandsPacket.Node[]{literalNode};
// Represent the children of the last node
IntList argChildren = null;
IntList argChildren = cmdChildren;
NodeMaker nodeMaker = new NodeMaker(lastNodes);
int lastArgumentNodeIndex = nodeMaker.getNodesCount();
final Argument<?>[] arguments = syntax.getArguments();
for (int i = 0; i < arguments.length; i++) {
final Argument<?> argument = arguments[i];
final boolean isFirst = i == 0;
final boolean isLast = i == arguments.length - 1;
// Find shared part
boolean foundSharedPart = false;
for (Argument<?>[] parsedArguments : syntaxesArguments) {
if (ArrayUtils.sameStart(arguments, parsedArguments, i + 1)) {
final Argument<?> sharedArgument = parsedArguments[i];
// Search previously parsed syntaxes to find identical part in order to create a node between those
{
// Find shared part
boolean foundSharedPart = false;
for (Argument<?>[] parsedArguments : syntaxesArguments) {
final int index = i + 1;
if (ArrayUtils.sameStart(arguments, parsedArguments, index)) {
final Argument<?> sharedArgument = parsedArguments[i];
final List<DeclareCommandsPacket.Node[]> storedNodes = storedArgumentsNodes.get(sharedArgument);
argChildren = new IntArrayList();
lastNodes = storedArgumentsNodes.get(sharedArgument);
foundSharedPart = true;
argChildren = new IntArrayList();
lastNodes = storedNodes.get(index);
foundSharedPart = true;
}
}
if (foundSharedPart) {
continue;
}
}
if (foundSharedPart) {
continue;
}
// Process the nodes for the argument
{
argument.processNodes(nodeMaker, isLast);
final List<DeclareCommandsPacket.Node> argumentNodes = toNodes(argument, isLast);
storedArgumentsNodes.put(argument, argumentNodes);
for (DeclareCommandsPacket.Node node : argumentNodes) {
final int childId = nodes.size();
// Each node array represent a layer
final List<DeclareCommandsPacket.Node[]> nodesLayer = nodeMaker.getNodes();
storedArgumentsNodes.put(argument, nodesLayer);
for (int nodeIndex = lastArgumentNodeIndex; nodeIndex < nodesLayer.size(); nodeIndex++) {
final NodeMaker.ConfiguredNodes configuredNodes = nodeMaker.getConfiguredNodes().get(nodeIndex);
final NodeMaker.Options options = configuredNodes.getOptions();
final DeclareCommandsPacket.Node[] argumentNodes = nodesLayer.get(nodeIndex);
if (isFirst) {
// Add to main command child
cmdChildren.add(childId);
} else {
// Add to previous argument children
argChildren.add(childId);
for (DeclareCommandsPacket.Node argumentNode : argumentNodes) {
final int childId = nodes.size();
nodeMaker.getNodeIdsMap().put(argumentNode, childId);
argChildren.add(childId);
// Append to the last node
{
final int[] children = ArrayUtils.toArray(argChildren);
for (DeclareCommandsPacket.Node lastNode : lastNodes) {
lastNode.children = lastNode.children == null ?
children :
ArrayUtils.concatenateIntArrays(lastNode.children, children);
}
}
nodes.add(argumentNode);
}
if (options.shouldUpdateLastNode()) {
// 'previousNodes' used if the nodes options require to overwrite the parent
final DeclareCommandsPacket.Node[] previousNodes = options.getPreviousNodes();
lastNodes = previousNodes != null ? previousNodes : argumentNodes;
argChildren = new IntArrayList();
}
}
if (lastNodes != null) {
final int[] children = ArrayUtils.toArray(argChildren);
lastNodes.forEach(n -> n.children = n.children == null ?
children :
ArrayUtils.concatenateIntArrays(n.children, children));
}
nodes.add(node);
}
//System.out.println("debug: " + argument.getId() + " : " + isFirst + " : " + isLast);
//System.out.println("debug2: " + i);
//System.out.println("size: " + (argChildren != null ? argChildren.size() : "NULL"));
if (isLast) {
// Last argument doesn't have children
final int[] children = new int[0];
argumentNodes.forEach(node -> node.children = children);
} else {
// Create children list which will be filled during next iteration
argChildren = new IntArrayList();
lastNodes = argumentNodes;
// Used to do not re-compute the previous arguments
lastArgumentNodeIndex = nodesLayer.size();
}
}
syntaxesArguments.add(arguments);
}
final int[] children = ArrayUtils.toArray(cmdChildren);
//System.out.println("test " + children.length + " : " + children[0]);
literalNode.children = children;
if (children.length > 0) {
literalNode.redirectedNode = children[0];
}
literalNode.children = ArrayUtils.toArray(cmdChildren);
return nodes.indexOf(literalNode);
}
@NotNull
private DeclareCommandsPacket.Node createMainNode(@NotNull String name, boolean executable) {
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
literalNode.flags = getFlag(NodeType.LITERAL, executable, false, false);
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL, executable, false, false);
literalNode.name = name;
return literalNode;
}
/**
* Converts an argument to a node with the correct brigadier parser.
*
* @param argument the argument to convert
* @param executable true if this is the last argument, false otherwise
* @return the list of nodes that the argument require
*/
@NotNull
private List<DeclareCommandsPacket.Node> toNodes(@NotNull Argument<?> argument, boolean executable) {
List<DeclareCommandsPacket.Node> nodes = new ArrayList<>();
// You can uncomment this to test any brigadier parser on the client
/*DeclareCommandsPacket.Node testNode = simpleArgumentNode(nodes, argument, executable, false);
testNode.parser = "minecraft:block_state";
if (true) {
return nodes;
}*/
if (argument instanceof ArgumentBoolean) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "brigadier:bool";
} else if (argument instanceof ArgumentDouble) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
ArgumentDouble argumentDouble = (ArgumentDouble) argument;
argumentNode.parser = "brigadier:double";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte(getNumberProperties(argumentDouble));
if (argumentDouble.hasMin())
packetWriter.writeDouble(argumentDouble.getMin());
if (argumentDouble.hasMax())
packetWriter.writeDouble(argumentDouble.getMax());
};
} else if (argument instanceof ArgumentFloat) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
ArgumentFloat argumentFloat = (ArgumentFloat) argument;
argumentNode.parser = "brigadier:float";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte(getNumberProperties(argumentFloat));
if (argumentFloat.hasMin())
packetWriter.writeFloat(argumentFloat.getMin());
if (argumentFloat.hasMax())
packetWriter.writeFloat(argumentFloat.getMax());
};
} else if (argument instanceof ArgumentInteger) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
ArgumentInteger argumentInteger = (ArgumentInteger) argument;
argumentNode.parser = "brigadier:integer";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte(getNumberProperties(argumentInteger));
if (argumentInteger.hasMin())
packetWriter.writeInt(argumentInteger.getMin());
if (argumentInteger.hasMax())
packetWriter.writeInt(argumentInteger.getMax());
};
} else if (argument instanceof ArgumentWord) {
ArgumentWord argumentWord = (ArgumentWord) argument;
// Add the single word properties + parser
final Consumer<DeclareCommandsPacket.Node> wordConsumer = node -> {
node.parser = "brigadier:string";
node.properties = packetWriter -> {
packetWriter.writeVarInt(0); // Single word
};
};
final boolean hasRestriction = argumentWord.hasRestrictions();
if (hasRestriction) {
// Create a node for each restrictions as literal
for (String restrictionWord : argumentWord.getRestrictions()) {
DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node();
nodes.add(argumentNode);
argumentNode.flags = getFlag(NodeType.LITERAL, executable, false, false);
argumentNode.name = restrictionWord;
wordConsumer.accept(argumentNode);
}
} else {
// Can be any word, add only one argument node
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
wordConsumer.accept(argumentNode);
}
} else if (argument instanceof ArgumentDynamicWord) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, true);
final SuggestionType suggestionType = ((ArgumentDynamicWord) argument).getSuggestionType();
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(0); // Single word
};
argumentNode.suggestionsType = suggestionType.getIdentifier();
} else if (argument instanceof ArgumentString) {
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, 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, false);
argumentNode.parser = "minecraft:color";
} else if (argument instanceof ArgumentTime) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:time";
} else if (argument instanceof ArgumentEnchantment) {
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, false);
argumentNode.parser = "minecraft:particle";
} else if (argument instanceof ArgumentPotionEffect) {
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, false);
argumentNode.parser = "minecraft:entity_summon";
} else if (argument instanceof ArgumentBlockState) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:block_state";
} else if (argument instanceof ArgumentIntRange) {
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, false);
argumentNode.parser = "minecraft:float_range";
} else if (argument instanceof ArgumentEntity) {
ArgumentEntity argumentEntity = (ArgumentEntity) argument;
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:entity";
argumentNode.properties = packetWriter -> {
byte mask = 0;
if (argumentEntity.isOnlySingleEntity()) {
mask += 1;
}
if (argumentEntity.isOnlyPlayers()) {
mask += 2;
}
packetWriter.writeByte(mask);
};
} else if (argument instanceof ArgumentItemStack) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:item_stack";
} else if (argument instanceof ArgumentNbtCompoundTag) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:nbt_compound_tag";
} else if (argument instanceof ArgumentNbtTag) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:nbt_tag";
} else if (argument instanceof ArgumentRelativeBlockPosition) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:block_pos";
} else if (argument instanceof ArgumentRelativeVec3) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:vec3";
} else if (argument instanceof ArgumentRelativeVec2) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
argumentNode.parser = "minecraft:vec2";
}
return nodes;
}
private byte getNumberProperties(@NotNull ArgumentNumber<? extends Number> argumentNumber) {
byte result = 0;
if (argumentNumber.hasMin())
result += 1;
if (argumentNumber.hasMax())
result += 2;
return result;
}
/**
* Builds an argument nod and add it to the nodes list.
*
* @param nodes the current nodes list
* @param argument the argument
* @param executable true if this will be the last argument, false otherwise
* @return the created {@link DeclareCommandsPacket.Node}
*/
@NotNull
private DeclareCommandsPacket.Node simpleArgumentNode(@NotNull List<DeclareCommandsPacket.Node> nodes,
@NotNull Argument<?> argument, boolean executable, boolean suggestion) {
DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node();
nodes.add(argumentNode);
argumentNode.flags = getFlag(NodeType.ARGUMENT, executable, false, suggestion);
argumentNode.name = argument.getId();
return argumentNode;
}
private byte getFlag(@NotNull NodeType type, boolean executable, boolean redirect, boolean suggestionType) {
byte result = (byte) type.mask;
if (executable) {
result |= 0x04;
}
if (redirect) {
result |= 0x08;
}
if (suggestionType) {
result |= 0x10;
}
return result;
}
private enum NodeType {
ROOT(0), LITERAL(0b1), ARGUMENT(0b10), NONE(0x11);
private final int mask;
NodeType(int mask) {
this.mask = mask;
}
private void addCommandNameNode(@NotNull DeclareCommandsPacket.Node commandNode,
@NotNull IntList rootChildren,
@NotNull List<DeclareCommandsPacket.Node> nodes) {
rootChildren.add(nodes.size());
nodes.add(commandNode);
}
}

View File

@ -1,6 +1,5 @@
package net.minestom.server.command;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.entity.Player;
import net.minestom.server.permission.PermissionHandler;

View File

@ -2,6 +2,8 @@ package net.minestom.server.command;
import net.minestom.server.permission.Permission;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
@ -11,11 +13,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
*/
public class ConsoleSender implements CommandSender {
private final static Logger LOGGER = LoggerFactory.getLogger(ConsoleSender.class);
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
@Override
public void sendMessage(@NotNull String message) {
System.out.println(message);
LOGGER.info(message);
}
@NotNull

View File

@ -42,6 +42,13 @@ public final class Arguments {
return (T) getObject(argument.getId());
}
public <T> T get(@NotNull String identifier) {
return (T) args.computeIfAbsent(identifier, s -> {
throw new NullPointerException(
"The argument with the id '" + identifier + "' has no value assigned, be sure to check your arguments id, your syntax, and that you do not change the argument id dynamically.");
});
}
/**
* @deprecated use {@link #get(Argument)}.
*/
@ -244,6 +251,10 @@ public final class Arguments {
return (RelativeVec) getObject(id);
}
/**
* @deprecated use {@link #get(String)}.
*/
@Deprecated
@NotNull
public Object getObject(@NotNull String id) {
return args.computeIfAbsent(id, s -> {
@ -261,11 +272,16 @@ public final class Arguments {
this.returnData = returnData;
}
protected void setArg(@NotNull String id, Object value) {
@NotNull
public Map<String, Object> getMap() {
return args;
}
public void setArg(@NotNull String id, Object value) {
this.args.put(id, value);
}
protected void copy(@NotNull Arguments arguments) {
public void copy(@NotNull Arguments arguments) {
this.args = arguments.args;
}

View File

@ -10,8 +10,9 @@ public class CommandData {
private final Map<String, Object> dataMap = new ConcurrentHashMap<>();
public void set(@NotNull String key, Object value) {
public CommandData set(@NotNull String key, Object value) {
this.dataMap.put(key, value);
return this;
}
@Nullable
@ -19,6 +20,10 @@ public class CommandData {
return (T) dataMap.get(key);
}
public boolean has(@NotNull String key) {
return dataMap.containsKey(key);
}
@NotNull
public Map<String, Object> getDataMap() {
return dataMap;

View File

@ -3,8 +3,10 @@ package net.minestom.server.command.builder;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
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 org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -32,7 +34,7 @@ public class CommandDispatcher {
// Register aliases
final String[] aliases = command.getAliases();
if (aliases != null) {
for (String alias : command.getAliases()) {
for (String alias : aliases) {
this.commandMap.put(alias.toLowerCase(), command);
}
}
@ -53,54 +55,13 @@ public class CommandDispatcher {
this.commands.remove(command);
}
/**
* Parses the given command.
*
* @param commandString the command (containing the command name and the args if any)
* @return the result of the parsing, null if the command doesn't exist
*/
@Nullable
public CommandResult parse(@NotNull String commandString) {
commandString = commandString.trim();
// Split space
final String[] parts = commandString.split(StringUtils.SPACE);
final String commandName = parts[0];
final String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(StringUtils.SPACE);
final Command command = findCommand(commandName);
// Check if the command exists
if (command == null)
return null;
// Find the used syntax, or check which argument is wrong
return findCommandResult(command, args);
}
/**
* Checks if the command exists, and execute it.
*
* @param source the command source
* @param commandString the command with the argument(s)
* @return the command data, null if none
*/
@Nullable
public CommandData execute(@NotNull CommandSender source, @NotNull String commandString) {
CommandResult result = parse(commandString);
if (result != null) {
return result.execute(source, commandString);
}
return null;
}
@NotNull
public Set<Command> getCommands() {
return Collections.unmodifiableSet(commands);
}
/**
* GetS the command class associated with the name;
* Gets the command class associated with the name.
*
* @param commandName the command name
* @return the {@link Command} associated with the name, null if not any
@ -111,18 +72,78 @@ public class CommandDispatcher {
return commandMap.getOrDefault(commandName, null);
}
/**
* Checks if the command exists, and execute it.
*
* @param source the command source
* @param commandString the command with the argument(s)
* @return the command result
*/
@NotNull
private CommandResult findCommandResult(@NotNull Command command, @NotNull String[] args) {
CommandResult result = new CommandResult();
result.command = command;
public CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
CommandResult commandResult = parse(commandString);
ParsedCommand parsedCommand = commandResult.parsedCommand;
if (parsedCommand != null) {
commandResult.commandData = parsedCommand.execute(source, commandString);
}
return commandResult;
}
Arguments executorArgs = new Arguments();
/**
* Parses the given command.
*
* @param commandString the command (containing the command name and the args if any)
* @return the parsing result
*/
@NotNull
public CommandResult parse(@NotNull String commandString) {
commandString = commandString.trim();
// Split space
final String[] parts = commandString.split(StringUtils.SPACE);
final String commandName = parts[0];
final Command command = findCommand(commandName);
// Check if the command exists
if (command == null) {
return CommandResult.of(CommandResult.Type.UNKNOWN, commandName);
}
// Removes the command's name + the space after
final String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(StringUtils.SPACE);
// Find the used syntax
ParsedCommand parsedCommand = findParsedCommand(command, args);
CommandResult result = new CommandResult();
result.input = commandString;
result.parsedCommand = parsedCommand;
if (parsedCommand != null && parsedCommand.executor != null) {
// Syntax found
result.type = CommandResult.Type.SUCCESS;
} else {
// Syntax not found, use the default executor (if any)
result.type = CommandResult.Type.INVALID_SYNTAX;
if (parsedCommand == null) { // Prevent overriding argument callback
result.parsedCommand = ParsedCommand.withDefaultExecutor(command);
}
}
return result;
}
@Nullable
private ParsedCommand findParsedCommand(@NotNull Command command, @NotNull String[] args) {
ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.command = command;
// The default executor should be used if no argument is provided
if (args[0].length() == 0) {
result.executor = command.getDefaultExecutor();
result.arguments = executorArgs;
return result;
{
final CommandExecutor defaultExecutor = command.getDefaultExecutor();
if (defaultExecutor != null && args[0].length() == 0) {
parsedCommand.executor = defaultExecutor;
parsedCommand.arguments = new Arguments();
return parsedCommand;
}
}
// SYNTAXES PARSING
@ -130,128 +151,29 @@ public class CommandDispatcher {
// All the registered syntaxes of the command
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
// Contains all the fully validated syntaxes (we later find the one with the most amount of arguments)
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>();
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
// Contains all the syntaxes that are not fully correct, used to later, retrieve the "most correct syntax"
// Number of correct argument - The data about the failing argument
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
for (CommandSyntax syntax : syntaxes) {
final Argument<?>[] arguments = syntax.getArguments();
final List<Object> argsValues = new ArrayList<>(arguments.length);
boolean syntaxCorrect = true;
// The current index in the raw command string arguments
int splitIndex = 0;
boolean useRemaining = false;
// Check the validity of the arguments...
for (int argCount = 0; argCount < arguments.length; argCount++) {
final boolean lastArgumentIteration = argCount + 1 == arguments.length;
final Argument<?> argument = arguments[argCount];
useRemaining = argument.useRemaining();
// the parsed argument value, null if incorrect
Object parsedValue;
// the argument exception, null if the input is correct
ArgumentSyntaxException argumentSyntaxException = null;
// true if the arg is valid, false otherwise
boolean correct = false;
// the raw string representing the correct argument syntax
StringBuilder argValue = new StringBuilder();
if (useRemaining) {
final boolean hasArgs = args.length > splitIndex;
// Verify if there is any string part available
if (hasArgs) {
// Argument is supposed to take the rest of the command input
for (int i = splitIndex; i < args.length; i++) {
final String arg = args[i];
if (argValue.length() > 0)
argValue.append(StringUtils.SPACE);
argValue.append(arg);
}
final String argValueString = argValue.toString();
try {
parsedValue = argument.parse(argValueString);
correct = true;
argsValues.add(parsedValue);
} catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception;
}
}
} else {
// Argument is either single-word or can accept optional delimited space(s)
for (int i = splitIndex; i < args.length; i++) {
final String rawArg = args[i];
argValue.append(rawArg);
final String argValueString = argValue.toString();
try {
parsedValue = argument.parse(argValueString);
// Prevent quitting the parsing too soon if the argument
// does not allow space
if (lastArgumentIteration && i + 1 < args.length) {
if (!argument.allowSpace())
break;
argValue.append(StringUtils.SPACE);
continue;
}
correct = true;
argsValues.add(parsedValue);
splitIndex = i + 1;
break;
} catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception;
if (!argument.allowSpace())
break;
argValue.append(StringUtils.SPACE);
}
}
}
if (!correct) {
// Argument is not correct, add it to the syntax suggestion with the number
// of correct argument(s) and do not check the next syntax argument
syntaxCorrect = false;
CommandSuggestionHolder suggestionHolder = new CommandSuggestionHolder();
suggestionHolder.syntax = syntax;
suggestionHolder.argumentSyntaxException = argumentSyntaxException;
suggestionHolder.argIndex = argCount;
syntaxesSuggestions.put(argCount, suggestionHolder);
break;
}
}
// Add the syntax to the list of valid syntaxes if correct
if (syntaxCorrect) {
if (arguments.length == argsValues.size() || useRemaining) {
ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder();
validSyntaxHolder.syntax = syntax;
validSyntaxHolder.argumentsValue = argsValues;
validSyntaxes.add(validSyntaxHolder);
}
}
CommandParser.parse(syntax, syntax.getArguments(), args, validSyntaxes, syntaxesSuggestions);
}
// Check if there is at least one correct syntax
if (!validSyntaxes.isEmpty()) {
Arguments executorArgs = new Arguments();
// Search the syntax with all perfect args
final CommandSyntax finalSyntax = findMostCorrectSyntax(validSyntaxes, executorArgs);
if (finalSyntax != null) {
final ValidSyntaxHolder finalValidSyntax = CommandParser.findMostCorrectSyntax(validSyntaxes, executorArgs);
if (finalValidSyntax != null) {
// A fully correct syntax has been found, use it
result.syntax = finalSyntax;
result.executor = finalSyntax.getExecutor();
result.arguments = executorArgs;
return result;
final CommandSyntax syntax = finalValidSyntax.syntax;
parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor();
parsedCommand.arguments = executorArgs;
return parsedCommand;
}
}
@ -269,151 +191,15 @@ public class CommandDispatcher {
// Found the closest syntax with at least 1 correct argument
final Argument<?> argument = syntax.getArguments()[argIndex];
if (argument.hasErrorCallback()) {
result.callback = argument.getCallback();
result.argumentSyntaxException = argumentSyntaxException;
parsedCommand.callback = argument.getCallback();
parsedCommand.argumentSyntaxException = argumentSyntaxException;
return result;
return parsedCommand;
}
}
}
// Use the default executor at last resort
result.executor = command.getDefaultExecutor();
result.arguments = executorArgs;
return result;
}
/**
* Retrieves from the valid syntax map the arguments condition result and get the one with the most
* valid arguments.
*
* @param validSyntaxes the list containing all the valid syntaxes
* @param executorArgs the recipient of the argument parsed values
* @return the command syntax with all of its arguments correct and with the most arguments count, null if not any
*/
@Nullable
private CommandSyntax findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes,
@NotNull Arguments executorArgs) {
CommandSyntax finalSyntax = null;
int maxArguments = 0;
Arguments finalArguments = null;
for (ValidSyntaxHolder validSyntaxHolder : validSyntaxes) {
final CommandSyntax syntax = validSyntaxHolder.syntax;
final Argument<?>[] arguments = syntax.getArguments();
final int argumentsCount = arguments.length;
final List<Object> argsValues = validSyntaxHolder.argumentsValue;
final int argsSize = argsValues.size();
if (argsSize > maxArguments) {
finalSyntax = syntax;
maxArguments = argsSize;
// Fill arguments map
Arguments syntaxValues = new Arguments();
for (int i = 0; i < argumentsCount; i++) {
final Argument<?> argument = arguments[i];
final Object argumentValue = argsValues.get(i);
syntaxValues.setArg(argument.getId(), argumentValue);
}
finalArguments = syntaxValues;
}
}
// Get the arguments values
if (finalSyntax != null) {
executorArgs.copy(finalArguments);
}
return finalSyntax;
}
/**
* Holds the data of a validated syntax.
*/
private static class ValidSyntaxHolder {
private CommandSyntax syntax;
/**
* (Argument index/Argument parsed object)
*/
private List<Object> argumentsValue;
}
/**
* Holds the data of an invalidated syntax.
*/
private static class CommandSuggestionHolder {
private CommandSyntax syntax;
private ArgumentSyntaxException argumentSyntaxException;
private int argIndex;
}
/**
* Represents a {@link Command} ready to be executed (already parsed).
*/
private static class CommandResult {
// Command
private Command command;
// Command Executor
private CommandSyntax syntax;
private CommandExecutor executor;
private Arguments arguments;
// Argument Callback
private ArgumentCallback callback;
private ArgumentSyntaxException argumentSyntaxException;
/**
* Executes the command for the given source.
* <p>
* The command will not be executed if {@link Command#getCondition()}
* is not validated.
*
* @param source the command source
* @param commandString the command string
* @return the command data, null if none
*/
@Nullable
public CommandData execute(@NotNull CommandSender source, @NotNull String commandString) {
// Global listener
command.globalListener(source, arguments, commandString);
// Command condition check
final CommandCondition condition = command.getCondition();
if (condition != null) {
final boolean result = condition.canUse(source, commandString);
if (!result)
return null;
}
// Condition is respected
if (executor != null) {
// An executor has been found
if (syntax != null) {
// The executor is from a syntax
final CommandCondition commandCondition = syntax.getCommandCondition();
if (commandCondition == null || commandCondition.canUse(source, commandString)) {
arguments.retrieveDefaultValues(syntax.getDefaultValuesMap());
executor.apply(source, arguments);
}
} else {
// The executor is probably the default one
executor.apply(source, arguments);
}
} else if (callback != null && argumentSyntaxException != null) {
// No syntax has been validated but the faulty argument with a callback has been found
// Execute the faulty argument callback
callback.apply(source, argumentSyntaxException);
}
return arguments.getReturnData();
}
// No syntax found
return null;
}
}

View File

@ -0,0 +1,64 @@
package net.minestom.server.command.builder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class CommandResult {
protected Type type = Type.UNKNOWN;
protected String input;
protected ParsedCommand parsedCommand;
protected CommandData commandData;
@NotNull
public Type getType() {
return type;
}
@NotNull
public String getInput() {
return input;
}
@Nullable
public ParsedCommand getParsedCommand() {
return parsedCommand;
}
@Nullable
public CommandData getCommandData() {
return commandData;
}
public enum Type {
/**
* Command and syntax successfully found.
*/
SUCCESS,
/**
* Command found, but the syntax is invalid.
* Executor sets to {@link Command#getDefaultExecutor()}.
*/
INVALID_SYNTAX,
/**
* Command cancelled by an event listener.
*/
CANCELLED,
/**
* Command is not registered, it is also the default result type.
*/
UNKNOWN
}
@NotNull
public static CommandResult of(@NotNull Type type, @NotNull String input) {
CommandResult result = new CommandResult();
result.type = type;
result.input = input;
return result;
}
}

View File

@ -0,0 +1,128 @@
package net.minestom.server.command.builder;
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;
public class NodeMaker {
private final List<ConfiguredNodes> configuredNodes = new ArrayList<>(2);
private final List<DeclareCommandsPacket.Node[]> nodes = new ArrayList<>(2);
private final Object2IntMap<DeclareCommandsPacket.Node> nodeIdsMap = new Object2IntOpenHashMap<>();
private Rule rule;
private int ruleCount;
public NodeMaker(@NotNull DeclareCommandsPacket.Node[] commandNodes){
addNodes(commandNodes);
}
public ConfiguredNodes getLatestConfiguredNodes() {
if (configuredNodes.isEmpty())
return null;
return configuredNodes.get(configuredNodes.size() - 1);
}
public DeclareCommandsPacket.Node[] getLatestNodes() {
ConfiguredNodes configuredNodes = getLatestConfiguredNodes();
return configuredNodes != null ? configuredNodes.nodes : null;
}
public int getNodesCount() {
return nodes.size();
}
public void addNodes(@NotNull DeclareCommandsPacket.Node[] nodes) {
Options options = null;
if (rule != null) {
options = rule.listen(nodes, ruleCount++);
}
if (options == null) {
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<ConfiguredNodes> getConfiguredNodes() {
return configuredNodes;
}
public List<DeclareCommandsPacket.Node[]> getNodes() {
return nodes;
}
@NotNull
public Object2IntMap<DeclareCommandsPacket.Node> getNodeIdsMap() {
return nodeIdsMap;
}
public static class ConfiguredNodes {
private DeclareCommandsPacket.Node[] nodes;
private Options options;
private static ConfiguredNodes of(DeclareCommandsPacket.Node[] nodes, Options options) {
ConfiguredNodes configuredNodes = new ConfiguredNodes();
configuredNodes.nodes = nodes;
configuredNodes.options = options;
return configuredNodes;
}
public DeclareCommandsPacket.Node[] getNodes() {
return nodes;
}
public Options getOptions() {
return options;
}
}
public interface Rule {
@Nullable
Options listen(DeclareCommandsPacket.Node[] nodes, int count);
}
public static class Options {
private boolean updateLastNode = true;
private DeclareCommandsPacket.Node[] previousNodes;
public static Options init() {
return new Options();
}
public boolean shouldUpdateLastNode() {
return updateLastNode;
}
public Options updateLastNode(boolean updateLastNode) {
this.updateLastNode = updateLastNode;
return this;
}
public DeclareCommandsPacket.Node[] getPreviousNodes() {
return previousNodes;
}
public Options setPreviousNodes(DeclareCommandsPacket.Node[] previousNodes) {
this.previousNodes = previousNodes;
return this;
}
}
}

View File

@ -0,0 +1,86 @@
package net.minestom.server.command.builder;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a {@link Command} ready to be executed (already parsed).
*/
public class ParsedCommand {
// Command
protected Command command;
// Command Executor
protected CommandSyntax syntax;
protected CommandExecutor executor;
protected Arguments arguments;
// Argument Callback
protected ArgumentCallback callback;
protected ArgumentSyntaxException argumentSyntaxException;
/**
* Executes the command for the given source.
* <p>
* The command will not be executed if {@link Command#getCondition()}
* is not validated.
*
* @param source the command source
* @param commandString the command string
* @return the command data, null if none
*/
@Nullable
public CommandData execute(@NotNull CommandSender source, @NotNull String commandString) {
// Global listener
command.globalListener(source, arguments, commandString);
// Command condition check
final CommandCondition condition = command.getCondition();
if (condition != null) {
final boolean result = condition.canUse(source, commandString);
if (!result)
return null;
}
// Condition is respected
if (executor != null) {
// An executor has been found
if (syntax != null) {
// The executor is from a syntax
final CommandCondition commandCondition = syntax.getCommandCondition();
if (commandCondition == null || commandCondition.canUse(source, commandString)) {
arguments.retrieveDefaultValues(syntax.getDefaultValuesMap());
executor.apply(source, arguments);
}
} else {
// The executor is probably the default one
executor.apply(source, arguments);
}
} else if (callback != null && argumentSyntaxException != null) {
// No syntax has been validated but the faulty argument with a callback has been found
// Execute the faulty argument callback
callback.apply(source, argumentSyntaxException);
}
if (arguments == null) {
// Argument callbacks cannot return data
return null;
}
return arguments.getReturnData();
}
@NotNull
public static ParsedCommand withDefaultExecutor(@NotNull Command command) {
ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.command = command;
parsedCommand.executor = command.getDefaultExecutor();
parsedCommand.arguments = new Arguments();
return parsedCommand;
}
}

View File

@ -3,7 +3,9 @@ package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.builder.ArgumentCallback;
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.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -69,6 +71,32 @@ public abstract class Argument<T> {
@NotNull
public abstract T parse(@NotNull String input) throws ArgumentSyntaxException;
/**
* Turns the argument into a list of nodes for command dispatching. Make sure to set the Node's parser.
*
* @param nodeMaker helper object used to create and modify nodes
* @param executable true if this will be the last argument, false otherwise
*/
public abstract void processNodes(@NotNull NodeMaker nodeMaker, boolean executable);
/**
* Builds an argument node.
*
* @param argument the argument
* @param executable true if this will be the last argument, false otherwise
* @return the created {@link DeclareCommandsPacket.Node}
*/
@NotNull
protected static DeclareCommandsPacket.Node simpleArgumentNode(@NotNull Argument<?> argument,
boolean executable, boolean redirect, boolean suggestion) {
DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node();
argumentNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.ARGUMENT, executable, redirect, suggestion);
argumentNode.name = argument.getId();
return argumentNode;
}
/**
* Gets the ID of the argument, showed in-game above the chat bar
* and used to retrieve the data when the command is parsed in {@link net.minestom.server.command.builder.Arguments}.
@ -166,4 +194,18 @@ public abstract class Argument<T> {
return callback != null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Argument<?> argument = (Argument<?>) o;
return id.equals(argument.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments;
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.jetbrains.annotations.NotNull;
/**
@ -27,4 +29,12 @@ public class ArgumentBoolean extends Argument<Boolean> {
throw new ArgumentSyntaxException("Not a boolean", input, NOT_BOOLEAN_ERROR);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:bool";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -0,0 +1,41 @@
package net.minestom.server.command.builder.arguments;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.CommandDispatcher;
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.jetbrains.annotations.NotNull;
public class ArgumentCommand extends Argument<CommandResult> {
public static final int INVALID_COMMAND_ERROR = 1;
public ArgumentCommand(@NotNull String id) {
super(id, true, true);
}
@NotNull
@Override
public CommandResult parse(@NotNull String input) throws ArgumentSyntaxException {
CommandDispatcher dispatcher = MinecraftServer.getCommandManager().getDispatcher();
CommandResult result = dispatcher.parse(input);
if (result.getType() != CommandResult.Type.SUCCESS)
throw new ArgumentSyntaxException("Invalid command", input, INVALID_COMMAND_ERROR);
return result;
}
@Override
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
}
}
}

View File

@ -1,7 +1,9 @@
package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.CommandSender;
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 net.minestom.server.utils.callback.validator.StringArrayValidator;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -36,6 +38,19 @@ public class ArgumentDynamicStringArray extends Argument<String[]> {
return value;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, true);
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(2); // Greedy phrase
};
argumentNode.suggestionsType = "minecraft:ask_server";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
/**
* Sets the dynamic restriction of this dynamic argument.
* <p>

View File

@ -1,8 +1,10 @@
package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.minecraft.SuggestionType;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.callback.validator.StringValidator;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -43,6 +45,21 @@ public class ArgumentDynamicWord extends Argument<String> {
return input;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, true);
final SuggestionType suggestionType = this.getSuggestionType();
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(0); // Single word
};
argumentNode.suggestionsType = suggestionType.getIdentifier();
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
/**
* Gets the suggestion type of this argument.
* <p>

View File

@ -0,0 +1,47 @@
package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.builder.Arguments;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.parser.CommandParser;
import net.minestom.server.command.builder.parser.ValidSyntaxHolder;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class ArgumentGroup extends Argument<Arguments> {
public static final int INVALID_ARGUMENTS_ERROR = 1;
private final Argument<?>[] group;
public ArgumentGroup(@NotNull String id, @NotNull Argument<?>... group) {
super(id, true, false);
this.group = group;
}
@NotNull
@Override
public Arguments parse(@NotNull String input) throws ArgumentSyntaxException {
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>();
CommandParser.parse(null, group, input.split(StringUtils.SPACE), validSyntaxes, null);
Arguments arguments = new Arguments();
CommandParser.findMostCorrectSyntax(validSyntaxes, arguments);
if (validSyntaxes.isEmpty()) {
throw new ArgumentSyntaxException("Invalid arguments", input, INVALID_ARGUMENTS_ERROR);
}
return arguments;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
for (int i = 0; i < group.length; i++) {
final boolean isLast = i == group.length - 1;
group[i].processNodes(nodeMaker, executable && isLast);
}
}
}

View File

@ -0,0 +1,34 @@
package net.minestom.server.command.builder.arguments;
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.jetbrains.annotations.NotNull;
public class ArgumentLiteral extends Argument<String> {
public static final int INVALID_VALUE_ERROR = 1;
public ArgumentLiteral(@NotNull String id) {
super(id);
}
@NotNull
@Override
public String parse(@NotNull String input) throws ArgumentSyntaxException {
if (!input.equals(getId()))
throw new ArgumentSyntaxException("Invalid literal value", input, INVALID_VALUE_ERROR);
return input;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL,
executable, false, false);
literalNode.name = getId();
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{literalNode});
}
}

View File

@ -0,0 +1,82 @@
package net.minestom.server.command.builder.arguments;
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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ArgumentLoop<T> extends Argument<List<T>> {
public static final int INVALID_INPUT_ERROR = 1;
private final List<Argument<T>> arguments = new ArrayList<>();
@SafeVarargs
public ArgumentLoop(@NotNull String id, @NotNull Argument<T>... arguments) {
super(id, true, true);
this.arguments.addAll(Arrays.asList(arguments));
}
@NotNull
@Override
public List<T> parse(@NotNull String input) throws ArgumentSyntaxException {
List<T> result = new ArrayList<>();
final String[] split = input.split(StringUtils.SPACE);
final StringBuilder builder = new StringBuilder();
boolean success = false;
for (String s : split) {
builder.append(s);
for (Argument<T> argument : arguments) {
try {
final String inputString = builder.toString();
final T value = argument.parse(inputString);
success = true;
result.add(value);
break;
} catch (ArgumentSyntaxException ignored) {
success = false;
}
}
if (success) {
builder.setLength(0); // Clear
} else {
builder.append(StringUtils.SPACE);
}
}
if (result.isEmpty() || !success) {
throw new ArgumentSyntaxException("Invalid loop, there is no valid argument found", input, INVALID_INPUT_ERROR);
}
return result;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node[] latestNodes = nodeMaker.getLatestNodes();
for (DeclareCommandsPacket.Node latestNode : latestNodes) {
final int id = nodeMaker.getNodeIdsMap().getInt(latestNode);
for (Argument<T> argument : arguments) {
DeclareCommandsPacket.Node[] latestCache = nodeMaker.getLatestNodes();
argument.processNodes(nodeMaker, executable);
NodeMaker.ConfiguredNodes configuredNodes = nodeMaker.getLatestConfiguredNodes();
// For the next loop argument to start at the same place
configuredNodes.getOptions().setPreviousNodes(latestCache);
for (DeclareCommandsPacket.Node lastArgumentNode : configuredNodes.getNodes()) {
lastArgumentNode.flags |= 0x08;
lastArgumentNode.redirectedNode = id;
}
}
}
}
}

View File

@ -1,6 +1,9 @@
package net.minestom.server.command.builder.arguments;
import io.netty.util.internal.StringUtil;
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.text.StringEscapeUtils;
import org.jetbrains.annotations.NotNull;
@ -11,6 +14,8 @@ import org.jetbrains.annotations.NotNull;
*/
public class ArgumentString extends Argument<String> {
public static final char BACKSLASH = '\\';
public static final int QUOTE_ERROR = 1;
public ArgumentString(String id) {
@ -23,12 +28,24 @@ public class ArgumentString extends Argument<String> {
return staticParse(input);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(1); // Quotable phrase
};
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
@NotNull
public static String staticParse(@NotNull String input) throws ArgumentSyntaxException {
// 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 == '\"' && last == '\"';
final boolean quote = 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);
@ -38,9 +55,9 @@ public class ArgumentString extends Argument<String> {
// Verify backslashes
for (int i = 1; i < input.length(); i++) {
final char c = input.charAt(i);
if (c == '\"') {
if (c == StringUtil.DOUBLE_QUOTE) {
final char lastChar = input.charAt(i - 1);
if (lastChar != '\\') {
if (lastChar != BACKSLASH) {
throw new ArgumentSyntaxException("Non-escaped quote", input, QUOTE_ERROR);
}
}

View File

@ -1,6 +1,7 @@
package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -19,7 +20,19 @@ public class ArgumentStringArray extends Argument<String[]> {
@NotNull
@Override
public String[] parse(@NotNull String input) throws ArgumentSyntaxException {
public String[] parse(@NotNull String input) {
return input.split(Pattern.quote(StringUtils.SPACE));
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(2); // Greedy phrase
};
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -18,6 +18,19 @@ import org.jetbrains.annotations.NotNull;
*/
public class ArgumentType {
public static ArgumentLiteral Literal(@NotNull String id) {
return new ArgumentLiteral(id);
}
public static ArgumentGroup Group(@NotNull String id, @NotNull Argument<?>... arguments) {
return new ArgumentGroup(id, arguments);
}
@SafeVarargs
public static <T> ArgumentLoop<T> Loop(@NotNull String id, @NotNull Argument<T>... arguments) {
return new ArgumentLoop<>(id, arguments);
}
public static ArgumentBoolean Boolean(@NotNull String id) {
return new ArgumentBoolean(id);
}
@ -62,6 +75,10 @@ public class ArgumentType {
return new ArgumentDynamicStringArray(id);
}
public static ArgumentCommand Command(@NotNull String id) {
return new ArgumentCommand(id);
}
// Minecraft specific arguments
public static ArgumentColor Color(@NotNull String id) {

View File

@ -1,12 +1,14 @@
package net.minestom.server.command.builder.arguments;
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 net.minestom.server.utils.validate.Check;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.function.Consumer;
/**
* Represents a single word in the command.
@ -68,6 +70,43 @@ public class ArgumentWord extends Argument<String> {
return input;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
// Add the single word properties + parser
final Consumer<DeclareCommandsPacket.Node> wordConsumer = node -> {
node.parser = "brigadier:string";
node.properties = packetWriter -> {
packetWriter.writeVarInt(0); // Single word
};
};
final boolean hasRestriction = this.hasRestrictions();
if (hasRestriction) {
// Create a primitive array for mapping
DeclareCommandsPacket.Node[] nodes = new DeclareCommandsPacket.Node[this.getRestrictions().length];
// Create a node for each restrictions as literal
for (int i = 0; i < nodes.length; i++) {
DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node();
argumentNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL,
executable, false, false);
argumentNode.name = this.getRestrictions()[i];
wordConsumer.accept(argumentNode);
nodes[i] = argumentNode;
}
nodeMaker.addNodes(nodes);
} else {
// Can be any word, add only one argument node
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
wordConsumer.accept(argumentNode);
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}
/**
* Gets if this argument allow complete freedom in the word choice or if a list has been defined.
*
@ -86,17 +125,4 @@ public class ArgumentWord extends Argument<String> {
public String[] getRestrictions() {
return restrictions;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ArgumentWord that = (ArgumentWord) o;
return Arrays.equals(restrictions, that.restrictions);
}
@Override
public int hashCode() {
return Arrays.hashCode(restrictions);
}
}

View File

@ -1,8 +1,10 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.chat.ChatColor;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
/**
@ -27,4 +29,12 @@ public class ArgumentColor extends Argument<ChatColor> {
return color;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:color";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,11 +1,11 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.entity.EntityFinder;
import net.minestom.server.utils.math.IntRange;
@ -14,8 +14,6 @@ import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
// TODO
/**
* Represents the target selector argument.
* https://minecraft.gamepedia.com/Commands#Target_selectors
@ -27,23 +25,25 @@ public class ArgumentEntity extends Argument<EntityFinder> {
public static final int ONLY_PLAYERS_ERROR = -4;
public static final int INVALID_ARGUMENT_NAME = -5;
public static final int INVALID_ARGUMENT_VALUE = -6;
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
private static final List<String> selectorVariables = Arrays.asList("@p", "@r", "@a", "@e", "@s");
private static final List<String> playersOnlySelector = Arrays.asList("@p", "@r", "@a", "@s");
private static final List<String> singleOnlySelector = Arrays.asList("@p", "@r", "@s");
private static final String SELECTOR_PREFIX = "@";
private static final List<String> SELECTOR_VARIABLES = Arrays.asList("@p", "@r", "@a", "@e", "@s");
private static final List<String> PLAYERS_ONLY_SELECTOR = Arrays.asList("@p", "@r", "@a", "@s");
private static final List<String> SINGLE_ONLY_SELECTOR = Arrays.asList("@p", "@r", "@s");
// List with all the valid arguments
private static final List<String> validArguments = Arrays.asList(
private static final List<String> VALID_ARGUMENTS = Arrays.asList(
"x", "y", "z",
"distance", "dx", "dy", "dz",
"scores", "tag", "team", "limit", "sort", "level", "gamemode", "name",
"x_rotation", "y_rotation", "type", "nbt", "advancements", "predicate");
// List with all the easily parsable arguments which only require reading until a specific character (comma)
private static final List<String> simpleArguments = Arrays.asList(
private static final List<String> SIMPLE_ARGUMENTS = Arrays.asList(
"x", "y", "z",
"distance", "dx", "dy", "dz",
"scores", "tag", "team", "limit", "sort", "level", "gamemode",
"x_rotation", "y_rotation", "type", "advancements", "predicate");
private boolean onlySingleEntity;
private boolean onlyPlayers;
@ -67,38 +67,54 @@ public class ArgumentEntity extends Argument<EntityFinder> {
return staticParse(input, onlySingleEntity, onlyPlayers);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:entity";
argumentNode.properties = packetWriter -> {
byte mask = 0;
if (this.isOnlySingleEntity()) {
mask += 1;
}
if (this.isOnlyPlayers()) {
mask += 2;
}
packetWriter.writeByte(mask);
};
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
@NotNull
public static EntityFinder staticParse(@NotNull String input,
boolean onlySingleEntity, boolean onlyPlayers) throws ArgumentSyntaxException {
// Check for raw player name
if (input.length() <= 16) {
if (CONNECTION_MANAGER.getPlayer(input) != null) {
return new EntityFinder()
.setTargetSelector(EntityFinder.TargetSelector.ALL_PLAYERS)
.setName(input, EntityFinder.ToggleableType.INCLUDE);
}
if (input.length() <= 16 && !input.contains(SELECTOR_PREFIX)) {
return new EntityFinder()
.setTargetSelector(EntityFinder.TargetSelector.ALL_PLAYERS)
.setName(input, EntityFinder.ToggleableType.INCLUDE);
}
// The minimum size is always 0 (for the selector variable, ex: @p)
// The minimum size is always 2 (for the selector variable, ex: @p)
if (input.length() < 2)
throw new ArgumentSyntaxException("Length needs to be > 1", input, INVALID_SYNTAX);
// The target selector variable always start by '@'
if (!input.startsWith("@"))
if (!input.startsWith(SELECTOR_PREFIX))
throw new ArgumentSyntaxException("Target selector needs to start with @", input, INVALID_SYNTAX);
final String selectorVariable = input.substring(0, 2);
// Check if the selector variable used exists
if (!selectorVariables.contains(selectorVariable))
if (!SELECTOR_VARIABLES.contains(selectorVariable))
throw new ArgumentSyntaxException("Invalid selector variable", input, INVALID_SYNTAX);
// Check if it should only select single entity and if the selector variable valid the condition
if (onlySingleEntity && !singleOnlySelector.contains(selectorVariable))
if (onlySingleEntity && !SINGLE_ONLY_SELECTOR.contains(selectorVariable))
throw new ArgumentSyntaxException("Argument requires only a single entity", input, ONLY_SINGLE_ENTITY_ERROR);
// Check if it should only select players and if the selector variable valid the condition
if (onlyPlayers && !playersOnlySelector.contains(selectorVariable))
if (onlyPlayers && !PLAYERS_ONLY_SELECTOR.contains(selectorVariable))
throw new ArgumentSyntaxException("Argument requires only players", input, ONLY_PLAYERS_ERROR);
// Create the EntityFinder which will be used for the rest of the parsing
@ -134,7 +150,7 @@ public class ArgumentEntity extends Argument<EntityFinder> {
// Replace all unnecessary spaces
currentArgument = currentArgument.trim();
if (!validArguments.contains(currentArgument))
if (!VALID_ARGUMENTS.contains(currentArgument))
throw new ArgumentSyntaxException("Argument name '" + currentArgument + "' does not exist", input, INVALID_ARGUMENT_NAME);
i = parseArgument(entityFinder, currentArgument, input, structureData, i);
@ -152,7 +168,7 @@ public class ArgumentEntity extends Argument<EntityFinder> {
@NotNull String input,
@NotNull String structureData, int beginIndex) throws ArgumentSyntaxException {
final char comma = ',';
final boolean isSimple = simpleArguments.contains(argumentName);
final boolean isSimple = SIMPLE_ARGUMENTS.contains(argumentName);
int finalIndex = beginIndex + 1;
StringBuilder valueBuilder = new StringBuilder();

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.minecraft;
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 net.minestom.server.utils.math.FloatRange;
import org.jetbrains.annotations.NotNull;
@ -52,4 +54,12 @@ public class ArgumentFloatRange extends ArgumentRange<FloatRange> {
throw new ArgumentSyntaxException("Invalid number", input, FORMAT_ERROR);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:float_range";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.minecraft;
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 net.minestom.server.utils.math.IntRange;
import org.jetbrains.annotations.NotNull;
@ -57,4 +59,12 @@ public class ArgumentIntRange extends ArgumentRange<IntRange> {
throw new ArgumentSyntaxException("Invalid number", input, FORMAT_ERROR);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:int_range";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,9 +1,11 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NBTUtils;
import org.jetbrains.annotations.NotNull;
@ -61,4 +63,12 @@ public class ArgumentItemStack extends Argument<ItemStack> {
return itemStack;
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:item_stack";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,7 +1,9 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
@ -37,4 +39,12 @@ public class ArgumentNbtCompoundTag extends Argument<NBTCompound> {
throw new ArgumentSyntaxException("NBTCompound is invalid", input, INVALID_NBT);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:nbt_compound_tag";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,7 +1,9 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTException;
@ -33,4 +35,12 @@ public class ArgumentNbtTag extends Argument<NBT> {
throw new ArgumentSyntaxException("Invalid NBT", input, INVALID_NBT);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:nbt_tag";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -2,8 +2,10 @@ package net.minestom.server.command.builder.arguments.minecraft;
import it.unimi.dsi.fastutil.chars.CharArrayList;
import it.unimi.dsi.fastutil.chars.CharList;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.time.UpdateOption;
import org.jetbrains.annotations.NotNull;
@ -28,27 +30,40 @@ public class ArgumentTime extends Argument<UpdateOption> {
@Override
public UpdateOption parse(@NotNull String input) throws ArgumentSyntaxException {
final char lastChar = input.charAt(input.length() - 1);
if (!SUFFIXES.contains(lastChar))
throw new ArgumentSyntaxException("Time format is invalid", input, INVALID_TIME_FORMAT);
// Remove last char
input = input.substring(0, input.length() - 1);
try {
// Check if value is a number
final int time = Integer.parseInt(input);
TimeUnit timeUnit;
if (Character.isDigit(lastChar))
timeUnit = TimeUnit.TICK;
else if (SUFFIXES.contains(lastChar)) {
input = input.substring(0, input.length() - 1);
TimeUnit timeUnit = null;
if (lastChar == 'd') {
timeUnit = TimeUnit.DAY;
} else if (lastChar == 's') {
timeUnit = TimeUnit.SECOND;
} else if (lastChar == 't') {
timeUnit = TimeUnit.TICK;
} else {
throw new ArgumentSyntaxException("Time needs to have the unit d, s, t, or none", input, NO_NUMBER);
}
} else
throw new ArgumentSyntaxException("Time needs to have a unit", input, NO_NUMBER);
try {
// Check if value is a number
final int time = Integer.parseInt(input);
return new UpdateOption(time, timeUnit);
} catch (NumberFormatException e) {
throw new ArgumentSyntaxException("Time needs to be a number", input, NO_NUMBER);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:time";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -9,7 +9,7 @@ public enum SuggestionType {
AVAILABLE_SOUNDS("minecraft:available_sounds"),
SUMMONABLE_ENTITIES("minecraft:summonable_entities");
private String identifier;
private final String identifier;
SuggestionType(@NotNull String identifier) {
this.identifier = identifier;

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.minecraft.registry;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.registry.Registries;
import org.jetbrains.annotations.NotNull;
@ -14,4 +16,12 @@ public class ArgumentBlockState extends ArgumentRegistry<Block> {
public Block getRegistry(@NotNull String value) {
return Registries.getBlock(value);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:block_state";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.minecraft.registry;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.item.Enchantment;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.registry.Registries;
import org.jetbrains.annotations.NotNull;
@ -17,4 +19,12 @@ public class ArgumentEnchantment extends ArgumentRegistry<Enchantment> {
public Enchantment getRegistry(@NotNull String value) {
return Registries.getEnchantment(value);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:item_enchantment";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.minecraft.registry;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.entity.EntityType;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.registry.Registries;
import org.jetbrains.annotations.NotNull;
@ -17,4 +19,12 @@ public class ArgumentEntityType extends ArgumentRegistry<EntityType> {
public EntityType getRegistry(@NotNull String value) {
return Registries.getEntityType(value);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:entity_summon";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.command.builder.arguments.minecraft.registry;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.registry.Registries;
import org.jetbrains.annotations.NotNull;
@ -17,4 +19,12 @@ public class ArgumentParticle extends ArgumentRegistry<Particle> {
public Particle getRegistry(@NotNull String value) {
return Registries.getParticle(value);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:particle";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.command.builder.arguments.minecraft.registry;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.potion.PotionEffect;
import net.minestom.server.registry.Registries;
import org.jetbrains.annotations.NotNull;
@ -17,4 +19,12 @@ public class ArgumentPotionEffect extends ArgumentRegistry<PotionEffect> {
public PotionEffect getRegistry(@NotNull String value) {
return Registries.getPotionEffect(value);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:mob_effect";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.number;
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.jetbrains.annotations.NotNull;
public class ArgumentDouble extends ArgumentNumber<Double> {
@ -40,4 +42,20 @@ public class ArgumentDouble extends ArgumentNumber<Double> {
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:double";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeDouble(this.getMin());
if (this.hasMax())
packetWriter.writeDouble(this.getMax());
};
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.number;
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.jetbrains.annotations.NotNull;
public class ArgumentFloat extends ArgumentNumber<Float> {
@ -40,4 +42,20 @@ public class ArgumentFloat extends ArgumentNumber<Float> {
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:float";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeFloat(this.getMin());
if (this.hasMax())
packetWriter.writeFloat(this.getMax());
};
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.number;
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.jetbrains.annotations.NotNull;
public class ArgumentInteger extends ArgumentNumber<Integer> {
@ -31,4 +33,20 @@ public class ArgumentInteger extends ArgumentNumber<Integer> {
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:integer";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeInt(this.getMin());
if (this.hasMax())
packetWriter.writeInt(this.getMax());
};
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.number;
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.jetbrains.annotations.NotNull;
public class ArgumentLong extends ArgumentNumber<Long> {
@ -31,4 +33,22 @@ public class ArgumentLong extends ArgumentNumber<Long> {
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
// TODO maybe use ArgumentLiteral/ArgumentWord and impose long restriction server side?
argumentNode.parser = "brigadier:int";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeInt(this.getMin().intValue());
if (this.hasMax())
packetWriter.writeInt(this.getMax().intValue());
};
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -43,6 +43,20 @@ public abstract class ArgumentNumber<T extends Number> extends Argument<T> {
return this;
}
/**
* Creates the byteflag based on the number's min/max existance.
*
* @return A byteflag for argument specification.
*/
public byte getNumberProperties() {
byte result = 0;
if (this.hasMin())
result += 1;
if (this.hasMax())
result += 2;
return result;
}
/**
* Gets if the argument has a minimum.
*

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.relative;
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 net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.location.RelativeBlockPosition;
import org.apache.commons.lang3.StringUtils;
@ -78,4 +80,12 @@ public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBloc
return new RelativeBlockPosition(blockPosition, relativeX, relativeY, relativeZ);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:block_pos";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.relative;
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 net.minestom.server.utils.Vector;
import net.minestom.server.utils.location.RelativeVec;
import org.apache.commons.lang3.StringUtils;
@ -67,4 +69,12 @@ public class ArgumentRelativeVec2 extends ArgumentRelative<RelativeVec> {
return new RelativeVec(vector, relativeX, false, relativeZ);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:vec2";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.command.builder.arguments.relative;
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 net.minestom.server.utils.Vector;
import net.minestom.server.utils.location.RelativeVec;
import org.apache.commons.lang3.StringUtils;
@ -73,4 +75,12 @@ public class ArgumentRelativeVec3 extends ArgumentRelative<RelativeVec> {
return new RelativeVec(vector, relativeX, relativeY, relativeZ);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:vec3";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -0,0 +1,178 @@
package net.minestom.server.command.builder.parser;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import net.minestom.server.command.builder.Arguments;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CommandParser {
public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments, @NotNull String[] inputArguments,
@Nullable List<ValidSyntaxHolder> validSyntaxes,
@Nullable Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions) {
final Map<Argument<?>, Object> argsValues = new HashMap<>();
boolean syntaxCorrect = true;
// The current index in the raw command string arguments
int splitIndex = 0;
boolean useRemaining = false;
// Check the validity of the arguments...
for (int argCount = 0; argCount < commandArguments.length; argCount++) {
final boolean lastArgumentIteration = argCount + 1 == commandArguments.length;
final Argument<?> argument = commandArguments[argCount];
useRemaining = argument.useRemaining();
// the parsed argument value, null if incorrect
Object parsedValue;
// the argument exception, null if the input is correct
ArgumentSyntaxException argumentSyntaxException = null;
// true if the arg is valid, false otherwise
boolean correct = false;
// the raw string representing the correct argument syntax
StringBuilder argValue = new StringBuilder();
if (useRemaining) {
final boolean hasArgs = inputArguments.length > splitIndex;
// Verify if there is any string part available
if (hasArgs) {
// Argument is supposed to take the rest of the command input
for (int i = splitIndex; i < inputArguments.length; i++) {
final String arg = inputArguments[i];
if (argValue.length() > 0)
argValue.append(StringUtils.SPACE);
argValue.append(arg);
}
final String argValueString = argValue.toString();
try {
parsedValue = argument.parse(argValueString);
correct = true;
argsValues.put(argument, parsedValue);
} catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception;
}
}
} else {
// Argument is either single-word or can accept optional delimited space(s)
for (int i = splitIndex; i < inputArguments.length; i++) {
final String rawArg = inputArguments[i];
argValue.append(rawArg);
final String argValueString = argValue.toString();
try {
parsedValue = argument.parse(argValueString);
// Prevent quitting the parsing too soon if the argument
// does not allow space
if (lastArgumentIteration && i + 1 < inputArguments.length) {
if (!argument.allowSpace())
break;
argValue.append(StringUtils.SPACE);
continue;
}
correct = true;
argsValues.put(argument, parsedValue);
splitIndex = i + 1;
break;
} catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception;
if (!argument.allowSpace())
break;
argValue.append(StringUtils.SPACE);
}
}
}
if (!correct) {
// Argument is not correct, add it to the syntax suggestion with the number
// of correct argument(s) and do not check the next syntax argument
syntaxCorrect = false;
if (syntaxesSuggestions != null) {
CommandSuggestionHolder suggestionHolder = new CommandSuggestionHolder();
suggestionHolder.syntax = syntax;
suggestionHolder.argumentSyntaxException = argumentSyntaxException;
suggestionHolder.argIndex = argCount;
syntaxesSuggestions.put(argCount, suggestionHolder);
}
break;
}
}
// Add the syntax to the list of valid syntaxes if correct
if (syntaxCorrect) {
if (commandArguments.length == argsValues.size() || useRemaining) {
if (validSyntaxes != null) {
ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder();
validSyntaxHolder.syntax = syntax;
validSyntaxHolder.argumentsValue = argsValues;
validSyntaxes.add(validSyntaxHolder);
}
}
}
}
/**
* Retrieves from the valid syntax map the arguments condition result and get the one with the most
* valid arguments.
*
* @param validSyntaxes the list containing all the valid syntaxes
* @param executorArgs the recipient of the argument parsed values
* @return the command syntax with all of its arguments correct and with the most arguments count, null if not any
*/
@Nullable
public static ValidSyntaxHolder findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes,
@NotNull Arguments executorArgs) {
if (validSyntaxes.isEmpty()) {
return null;
}
ValidSyntaxHolder finalSyntax = null;
int maxArguments = 0;
Arguments finalArguments = null;
for (ValidSyntaxHolder validSyntaxHolder : validSyntaxes) {
final Map<Argument<?>, Object> argsValues = validSyntaxHolder.argumentsValue;
final int argsSize = argsValues.size();
// Check if the syntax has more valid arguments
if (argsSize > maxArguments) {
finalSyntax = validSyntaxHolder;
maxArguments = argsSize;
// Fill arguments map
Arguments syntaxValues = new Arguments();
for (Map.Entry<Argument<?>, Object> entry : argsValues.entrySet()) {
final Argument<?> argument = entry.getKey();
final Object argumentValue = entry.getValue();
syntaxValues.setArg(argument.getId(), argumentValue);
}
finalArguments = syntaxValues;
}
}
// Get the arguments values
if (finalSyntax != null) {
executorArgs.copy(finalArguments);
}
return finalSyntax;
}
}

View File

@ -0,0 +1,13 @@
package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
/**
* Holds the data of an invalidated syntax.
*/
public class CommandSuggestionHolder {
public CommandSyntax syntax;
public ArgumentSyntaxException argumentSyntaxException;
public int argIndex;
}

View File

@ -0,0 +1,14 @@
package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
import java.util.Map;
/**
* Holds the data of a validated syntax.
*/
public class ValidSyntaxHolder {
public CommandSyntax syntax;
public Map<Argument<?>, Object> argumentsValue;
}

View File

@ -74,4 +74,32 @@ public class DeclareCommandsPacket implements ServerPacket {
}
public static byte getFlag(@NotNull NodeType type, boolean executable, boolean redirect, boolean suggestionType) {
byte result = (byte) type.mask;
if (executable) {
result |= 0x04;
}
if (redirect) {
result |= 0x08;
}
if (suggestionType) {
result |= 0x10;
}
return result;
}
public enum NodeType {
ROOT(0), LITERAL(0b1), ARGUMENT(0b10), NONE(0x11);
private final int mask;
NodeType(int mask) {
this.mask = mask;
}
}
}

View File

@ -3,7 +3,6 @@ package demo.commands;
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.arguments.Argument;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.arguments.number.ArgumentNumber;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
@ -12,15 +11,15 @@ import net.minestom.server.entity.Player;
public class HealthCommand extends Command {
public HealthCommand() {
super("health", "h", "healthbar");
super("health");
setCondition(this::condition);
setDefaultExecutor(this::defaultExecutor);
Argument modeArg = ArgumentType.Word("mode").from("set", "add");
var modeArg = ArgumentType.Word("mode").from("set", "add");
Argument valueArg = ArgumentType.Integer("value").between(0, 100);
var valueArg = ArgumentType.Integer("value").between(0, 100);
setArgumentCallback(this::onModeError, modeArg);
setArgumentCallback(this::onValueError, valueArg);

View File

@ -1,15 +1,14 @@
package demo.commands;
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.entity.Player;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.batch.BlockBatch;
import net.minestom.server.command.builder.CommandResult;
import java.util.concurrent.ThreadLocalRandom;
import java.util.List;
import static net.minestom.server.command.builder.arguments.ArgumentType.Integer;
import static net.minestom.server.command.builder.arguments.ArgumentType.*;
public class TestCommand extends Command {
@ -17,26 +16,17 @@ public class TestCommand extends Command {
super("testcmd");
setDefaultExecutor(this::usage);
setDefaultExecutor((sender, args) -> {
if (!sender.isPlayer()) {
sender.sendMessage("This command may only be run by players.");
return;
}
Player player = sender.asPlayer();
addSyntax((sender, args) -> {
final CommandResult result = args.get("command");
System.out.println("test " + result.getType() + " " + result.getInput());
}, Literal("cmd"), Command("command"));
BlockBatch batch = new BlockBatch((InstanceContainer) player.getInstance());
int offset = 5;
for (int x = 0; x < 50; x += 1) {
for (int y = 0; y < 50; y += 1) {
for (int z = 0; z < 50; z += 1) {
batch.setBlockStateId(x + offset, y + offset+50, z + offset, (short) ThreadLocalRandom.current().nextInt(500));
}
}
}
batch.flush(() -> sender.sendMessage(ColoredText.of(ChatColor.BRIGHT_GREEN, "Created cube.")));
});
addSyntax((sender, args) -> {
List<Arguments> groups = args.get("groups");
System.out.println("size " + groups.size());
}, Literal("loop"), Loop("groups",
Group("group", Literal("name"), Word("word1")),
Group("group2", Literal("delay"), Integer("number2"))));
}
private void usage(CommandSender sender, Arguments arguments) {