diff --git a/src/main/java/net/minestom/server/command/builder/Arguments.java b/src/main/java/net/minestom/server/command/builder/Arguments.java index 35fd337a8..1159755b7 100644 --- a/src/main/java/net/minestom/server/command/builder/Arguments.java +++ b/src/main/java/net/minestom/server/command/builder/Arguments.java @@ -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 defaultValuesMap) { + if (defaultValuesMap == null) + return; + + for (Map.Entry entry : defaultValuesMap.entrySet()) { + final String key = entry.getKey(); + if (!args.containsKey(key)) + this.args.put(key, entry.getValue()); + } + + } } diff --git a/src/main/java/net/minestom/server/command/builder/Command.java b/src/main/java/net/minestom/server/command/builder/Command.java index f2a631b08..dfd57f194 100644 --- a/src/main/java/net/minestom/server/command/builder/Command.java +++ b/src/main/java/net/minestom/server/command/builder/Command.java @@ -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 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 optionalSyntaxes = new ArrayList<>(); + + // the 'args' array starts by all the required arguments, followed by the optional ones + List> requiredArguments = new ArrayList<>(); + Map 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 addSyntax(@NotNull CommandExecutor executor, @NotNull Argument... args) { return addSyntax(null, executor, args); } diff --git a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java index e200e2f31..113a3cb92 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -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 { diff --git a/src/main/java/net/minestom/server/command/builder/CommandSyntax.java b/src/main/java/net/minestom/server/command/builder/CommandSyntax.java index 803fa9a78..c808a45eb 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandSyntax.java +++ b/src/main/java/net/minestom/server/command/builder/CommandSyntax.java @@ -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 defaultValuesMap; private final Argument[] args; protected CommandSyntax(@Nullable CommandCondition commandCondition, @NotNull CommandExecutor commandExecutor, + @Nullable Map 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 getDefaultValuesMap() { + return defaultValuesMap; + } + /** * Gets all the required {@link Argument} for this syntax. * diff --git a/src/main/java/net/minestom/server/command/builder/arguments/Argument.java b/src/main/java/net/minestom/server/command/builder/arguments/Argument.java index c8b0bfa92..4681848b9 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/Argument.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/Argument.java @@ -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 { private ArgumentCallback callback; + private T defaultValue; + /** * Creates a new argument. * @@ -146,6 +149,23 @@ public abstract class Argument { this.callback = callback; } + public boolean isOptional() { + return defaultValue != null; + } + + @Nullable + public T getDefaultValue() { + return defaultValue; + } + + @NotNull + public Argument 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. *