-
Notifications
You must be signed in to change notification settings - Fork 0
Creating Commands
Commands are the main flesh of any command system, and General makes it relatively simple to make them. A builder for commands is provided in a static method of the Command
class. Commands can have various properties to them each of which can be set through the builder. A quick description of each is listed below. Bolded properties are required.
Property | Description |
---|---|
name | The name of the command. Extra names passed to this property are considered aliases, or alternate names that the command can be called with. |
syntax | The static syntax of the command. If this is set, the system will use it instead of syntax generated from the Arguments object. |
shortDescription | A short description of the command. Used by the help command when it needs to list all registered commands. If this isn't present, the help command will notify the user that it does not have short help text. |
description | A full description of the command. Used by the help command when it is asked about this specific command. If this isn't present, the help command will use shortDescription instead. |
executor | The executor holding the command's execution logic. |
Tip!
An alternate command builder can be received by instead calling the
parent()
method. The builder returned houses all the same properties with an additionaladdCommand
method. The resulting command holds all other commands provided to it (including even more parent commands) and comes with ahelp
subcommand.The parent command's executor property becomes the fallback executor called when no parameters are passed during the command's execution.
Command helloWorld = Command.builder()
.name("hello")
.shortDescription("Hello, world!")
.description("Responds with \"Hello, world!\"")
.executor((context, args, dry) -> {
if(dry) return;
context.sendMessage("Hello, world!");
})
.build();
See Custom Properties for information on assigning custom properties for Command
s.
CommandExecutor
objects hold the main execution logic for a command. FullCommandExecutor
should be implemented for executors intending to exist as their own class, though one can use CommandExecutor
for a quick lambda implementation.
The context of the command lets you know information about how the command was executed. This doesn't really contain too much; it's mostly meta information like what command was executed, the alias used to execute the command, etc. The context is also a command's main way to communicate with the person who sent the command, using its sendMessage
method.
@Override
public void execute(CommandContext ctx, Arguments args) {
ctx.sendMessage("Hello, user!");
}
Contexts may also have custom properties, retrievable from the <T>getCustom(String)
method. These custom properties may be set by a context factory in the call to the CommandManager
. These properties should allow you to introduce more meta information like a user object, a permissions value, etc.
The most special part of General is the Arguments object, as it is the core worker behind all of its special features -- namely, syntax generation and argument parsing.
You might wanna read about argument parsers here, first.
Arguments are parsed using the write
methods (alternatively the named
methods if you want to change the typename). The write methods dictate the syntax of your command using the parsers you pass to it, and processes user input into the tokens you can use. You can then retrieve the parsed parameters in the same order you declared them in using the next
method.
This process of declaring syntax is performed in the FullCommandExecutor
's initialize
method.
import static com.github.xemiru.general.ArgumentParsers.*;
// ...
@Override
public void initialize(CommandContext ctx, Arguments args) {
args.write(NUMBER)
.write(INTEGER)
.write(STRING);
}
@Override
public void execute(CommandContext ctx, Arguments args) {
double num = args.next();
int intt = args.next();
String str = args.next();
// do stuff with these
}
Tip!
The
write
/named
methods have useful overloads for verifying that a value is actually usable! You can pass a Predicate to these methods along with an error message should you need some extra checks on a parameter.args.named("amount", NUMBER, it -> it > 0, "amount must be at least one")
The next
method is what causes any user errors to come to light, though calling it does not prevent one from adding more parameters. Should the user provide bad input, the write
methods will hold the error until the next
method is called. A SyntaxException
will then be thrown -- though in most cases, you won't need to care about it as the command manager who ran the command will catch it and notify the user of their error.
Because user error isn't shown until the first call to next()
, you can perform state checks prior to calling it for the first time to ensure the command runs in its preferred environment. It is recommended to do so to make sure the user finds out that the command can't be ran first before being notified of their errors in input.
If you've chosen to implement a CommandExecutor
instead of a FullCommandExecutor
, the dry
parameter determines whether or not we're calling only initialize
, or both initialize
and execute
. The FullCommandExecutor
follows this rule when it implements CommandExecutor
's execute
itself:
@Override
default void execute(CommandContext context, Arguments args, boolean dry) {
this.initialize(context, args);
if(!dry) this.execute(context, args);
}
Should you feel something bad may happen during your command, it is up to you to throw a CommandException
. The command system will recognize this as a foreseen failure, and pass the message within the exception onto the user.
@Override
public void initialize(CommandContext ctx, Arguments args) {
args.write(STRING);
}
@Override
public void execute(CommandContext ctx, Arguments args) {
int i;
try {
i = Integer.parseInt(args.next());
} catch(NumberFormatException e) {
throw new CommandException("Not a number!");
}
// side note: the INTEGER parser exists and you could do this with args.write(INTEGER) instead
}
Any other exceptions that occur during the execution runtime of a command is considered a crash; the system will report it to the user accordingly.