Optional Arguments
Sometimes, you want to implement a command that has arguments that don’t need to be entered. Take a /sayhi
command for example. You may want to say "Hi" to yourself or to another player. For that, we want this command syntax:
/sayhi - Says "Hi!" to yourself
/sayhi <target> - Says "Hi!" to a target player
To implement these commands, the CommandAPI provides two methods to help you with that:
Argument withOptionalArguments(List<Argument<?>> args);
Argument withOptionalArguments(Argument<?>... args);
Example - `/sayhi` command with an optional argument
Example - /sayhi
command with an optional argument
For example, say we're registering a command /sayhi
:
/sayhi - Says "Hi!" to yourself
/sayhi <target> - Says "Hi!" to a target player
For that, we’re going to register a command /sayhi
. To add optional arguments, we’re going to use the withOptionalArguments(Argument... args)
method:
new CommandAPICommand("sayhi")
.withOptionalArguments(new PlayerArgument("target"))
.executesPlayer((player, args) -> {
Player target = (Player) args.get("target");
if (target != null) {
target.sendMessage("Hi!");
} else {
player.sendMessage("Hi!");
}
})
.register();
This allows us to run both /sayhi
and /sayhi <target>
with the same command name sayhi
, but have different results based on the arguments used.
You can notice two things:
- We use the
withOptionalArguments
method to add an optional argument to a command - We use
args.get("target")
to get our player out of the arguments
With optional arguments, there is a possibility of them being not present in the arguments of a command. The reason we use args.get("target")
is that this will just return null
and you can handle what should happen.
Setting existing arguments as optional arguments
To set arguments as optional, the CommandAPI has the method setOptional(boolean)
:
Argument setOptional(boolean optional);
When using the withOptionalArguments
method it might be interesting to know that it calls the setOptional()
method internally. This means that the following two examples are identical:
new CommandAPICommand("optional")
.withOptionalArguments(new PlayerArgument("target"))
new CommandAPICommand("optional")
.withArguments(new PlayerArgument("target").setOptional(true))
However, calling withOptionalArguments
is safer because it makes sure that the argument is optional because of that internal call.
Avoiding null values
Previously, we've looked at how to handle null values. To make all of this easier, the CommandAPI implements multiple additional methods for CommandArguments
:
Object getOrDefault(int index, Object defaultValue);
Object getOrDefault(int index, Supplier<?> defaultValue);
Object getOrDefault(String nodeName, Object defaultValue);
Object getOrDefault(String nodeName, Supplier<?> defaultValue);
Optional<Object> getOptional(int index)
Optional<Object> getOptional(String nodeName)
The examples will be using the getOptional
methods but there is no downside of using the getOrDefault
methods.
Example - `/sayhi` command while using the getOptional method
Example - /sayhi
command while using the getOptional method
Let's register the /sayhi
command from above a second time - this time using a getOptional
method. We are using the exact same command syntax:
/sayhi - Says "Hi!" to yourself
/sayhi <target> - Says "Hi!" to a target player
This is how the getOptional
method is being implemented:
new CommandAPICommand("sayhi")
.withOptionalArguments(new PlayerArgument("target"))
.executesPlayer((player, args) -> {
Player target = (Player) args.getOptional("target").orElse(player);
target.sendMessage("Hi!");
})
.register();
Implementing required arguments after optional arguments
We've now talked about how to implement optional arguments and how to avoid null values returned by optional arguments when they aren't provided when executing the command.
Now we also want to talk about how to implement required arguments after optional arguments. For this, the CommandAPI implements a combineWith
method for arguments:
AbstractArgument combineWith(Argument<?>... combinedArguments);
You will need to use this method if you want to have a required argument after an optional argument. In general, this is which pattern the CommandAPI follows while dealing with optional arguments:
- You have a
CommandAPICommand
and you add arguments to it. - After your required arguments, you can provide optional arguments.
At this point, your command is basically done. Any attempt to add a required argument will result in an OptionalArgumentException
. However, this is where the combineWith
method comes in. This method allows you to combine arguments. Let's say you have an optional StringArgument
(here simplified to A
) and you want a required PlayerArgument
(here simplified to B
). Argument B
should only be required if argument A
is given. To implement that logic, we’re going to use the combineWith
method so that we have this syntax:
A.combineWith(B)
This does two things:
- When checking optional argument constraints the argument
B
will be ignored so theOptionalArgumentException
will not be thrown - It allows you to define additional optional arguments afterwards which can only be entered if argument
B
has been entered
This is how you would add another optional PlayerArgument
(here simplified to C
):
new CommandAPICommand("mycommand")
.withOptionalArguments(A.combineWith(B))
.withOptionalArguments(C)
Let's say you declare your arguments like this:
new CommandAPICommand("mycommand")
.withOptionalArguments(A.combineWith(B))
.withArguments(C)
This would result in an OptionalArgumentException
because you’re declaring a required argument after an optional argument without creating that exception for argument C
like you do for argument B
.
Example - Required arguments after optional arguments
Example - Required arguments after optional arguments
We want to register a command /rate
with the following syntax:
/rate - Sends an information message
/rate <topic> <rating> - Rates a topic with a rating and sends a message to the command sender
/rate <topic> <rating> <target> - Rates a topic with a rating and sends a message to the target
To implement that structure, we make use of the combineWith
method to make the argument after the optional argument <topic> required:
new CommandAPICommand("rate")
.withOptionalArguments(new StringArgument("topic").combineWith(new IntegerArgument("rating", 0, 10)))
.withOptionalArguments(new PlayerArgument("target"))
.executes((sender, args) -> {
String topic = (String) args.get("topic");
if (topic == null) {
sender.sendMessage(
"Usage: /rate <topic> <rating> <player>(optional)",
"Select a topic to rate, then give a rating between 0 and 10",
"You can optionally add a player at the end to give the rating to"
);
return;
}
// We know this is not null because rating is required if topic is given
int rating = (int) args.get("rating");
// The target player is optional, so give it a default here
CommandSender target = (CommandSender) args.getOptional("target").orElse(sender);
target.sendMessage("Your " + topic + " was rated: " + rating + "/10");
})
.register();