Added support for command syntaxes with optional arguments (default values)

This commit is contained in:
themode 2021-01-01 23:36:53 +01:00
parent 2ec727d5f8
commit 4bf120b714
5 changed files with 126 additions and 17 deletions

View File

@ -14,6 +14,7 @@ import net.minestom.server.utils.math.FloatRange;
import net.minestom.server.utils.math.IntRange;
import net.minestom.server.utils.time.UpdateOption;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
@ -164,4 +165,15 @@ public final class Arguments {
this.args.clear();
}
protected void retrieveDefaultValues(@Nullable Map<String, Object> defaultValuesMap) {
if (defaultValuesMap == null)
return;
for (Map.Entry<String, Object> entry : defaultValuesMap.entrySet()) {
final String key = entry.getKey();
if (!args.containsKey(key))
this.args.put(key, entry.getValue());
}
}
}

View File

@ -10,10 +10,10 @@ import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;
/**
* Represents a command which has suggestion/auto-completion.
@ -37,6 +37,8 @@ import java.util.List;
*/
public class Command {
public final static Logger LOGGER = LoggerFactory.getLogger(Command.class);
private final String name;
private final String[] aliases;
@ -114,27 +116,83 @@ public class Command {
* @param commandCondition the condition to use the syntax
* @param executor the executor to call when the syntax is successfully received
* @param args all the arguments of the syntax, the length needs to be higher than 0
* @return the created {@link CommandSyntax}
* @return the created {@link CommandSyntax syntaxes},
* there can be multiple of them when optional arguments are used
*/
public CommandSyntax addSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor executor,
@NotNull Argument<?>... args) {
@NotNull
public Collection<CommandSyntax> addSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor executor,
@NotNull Argument<?>... args) {
Check.argCondition(args.length == 0,
"The syntax argument cannot be empty, consider using Command#setDefaultExecutor");
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args);
this.syntaxes.add(syntax);
return syntax;
// Check optional argument(s)
boolean hasOptional = false;
{
for (Argument<?> argument : args) {
if (argument.isOptional()) {
hasOptional = true;
}
if (hasOptional && !argument.isOptional()) {
LOGGER.warn("Optional arguments are followed by a non-optional one, the default values will be ignored.");
hasOptional = false;
break;
}
}
}
if (!hasOptional) {
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args);
this.syntaxes.add(syntax);
return Collections.singleton(syntax);
} else {
List<CommandSyntax> optionalSyntaxes = new ArrayList<>();
// the 'args' array starts by all the required arguments, followed by the optional ones
List<Argument<?>> requiredArguments = new ArrayList<>();
Map<String, Object> defaultValuesMap = new HashMap<>();
boolean optionalBranch = false;
int i = 0;
for (Argument<?> argument : args) {
final boolean isLast = ++i == args.length;
if (argument.isOptional()) {
// Set default value
defaultValuesMap.put(argument.getId(), argument.getDefaultValue());
if (!optionalBranch && !requiredArguments.isEmpty()) {
// First optional argument, create a syntax with current cached arguments
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap,
requiredArguments.toArray(new Argument[0]));
optionalSyntaxes.add(syntax);
optionalBranch = true;
} else {
// New optional argument, save syntax with current cached arguments and save default value
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap,
requiredArguments.toArray(new Argument[0]));
optionalSyntaxes.add(syntax);
}
}
requiredArguments.add(argument);
if (isLast) {
// Create the last syntax
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap,
requiredArguments.toArray(new Argument[0]));
optionalSyntaxes.add(syntax);
}
}
this.syntaxes.addAll(optionalSyntaxes);
return optionalSyntaxes;
}
}
/**
* Adds a new syntax in the command without any condition.
* Adds a new syntax without condition.
*
* @param executor the executor to call when the syntax is successfully received
* @param args all the arguments of the syntax, the length needs to be higher than 0
* @return the created {@link CommandSyntax}
* @see #addSyntax(CommandCondition, CommandExecutor, Argument[])
*/
public CommandSyntax addSyntax(@NotNull CommandExecutor executor, @NotNull Argument<?>... args) {
@NotNull
public Collection<CommandSyntax> addSyntax(@NotNull CommandExecutor executor, @NotNull Argument<?>... args) {
return addSyntax(null, executor, args);
}

View File

@ -144,8 +144,8 @@ public class CommandDispatcher {
boolean useRemaining = false;
// Check the validity of the arguments...
for (int argCount = 0; argCount < syntax.getArguments().length; argCount++) {
final Argument<?> argument = syntax.getArguments()[argCount];
for (int argCount = 0; argCount < arguments.length; argCount++) {
final Argument<?> argument = arguments[argCount];
useRemaining = argument.useRemaining();
// the correction result of the argument
@ -399,6 +399,7 @@ public class CommandDispatcher {
// 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 {

View File

@ -6,6 +6,8 @@ import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
/**
* Represents a syntax in {@link Command}
* which is initialized with {@link Command#addSyntax(CommandExecutor, Argument[])}.
@ -14,16 +16,27 @@ public class CommandSyntax {
private CommandCondition commandCondition;
private CommandExecutor executor;
private final Map<String, Object> defaultValuesMap;
private final Argument<?>[] args;
protected CommandSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor commandExecutor,
@Nullable Map<String, Object> defaultValuesMap,
@NotNull Argument<?>... args) {
this.commandCondition = commandCondition;
this.executor = commandExecutor;
this.defaultValuesMap = defaultValuesMap;
this.args = args;
}
protected CommandSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor commandExecutor,
@NotNull Argument<?>... args) {
this(commandCondition, commandExecutor, null, args);
}
/**
* Gets the condition to use this syntax.
*
@ -66,6 +79,11 @@ public class CommandSyntax {
this.executor = executor;
}
@Nullable
protected Map<String, Object> getDefaultValuesMap() {
return defaultValuesMap;
}
/**
* Gets all the required {@link Argument} for this syntax.
*

View File

@ -3,6 +3,7 @@ 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.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -29,6 +30,8 @@ public abstract class Argument<T> {
private ArgumentCallback callback;
private T defaultValue;
/**
* Creates a new argument.
*
@ -146,6 +149,23 @@ public abstract class Argument<T> {
this.callback = callback;
}
public boolean isOptional() {
return defaultValue != null;
}
@Nullable
public T getDefaultValue() {
return defaultValue;
}
@NotNull
public Argument<T> setDefaultValue(@Nullable T defaultValue) {
Check.argCondition(defaultValue != null && getConditionResult(defaultValue) != SUCCESS,
"The default value needs to validate the argument condition!");
this.defaultValue = defaultValue;
return this;
}
/**
* Gets if the argument has any error callback.
*