Commands
Package: dev.anchorlight.stonelib.command
A plugin with more than a couple of commands usually ends up with one root command and a set of sub-commands: /rally go, /rally optin, /rally reload. CommandRouter handles that dispatch, so each sub-command is one small class.
SubCommand
public interface SubCommand {
String getName(); // required
default String getPermission() { return null; }
default String getUsage() { return "/" + getName(); }
default String getDescription() { return ""; }
void execute(CommandSender sender, String[] args); // required
default List<String> tabComplete(CommandSender sender, String[] args) { return List.of(); }
}
| Method | Meaning |
|---|---|
getName() | The keyword players type. Matched case-insensitively. |
getPermission() | A node the sender must hold, or null for no check. Checked by the router before execute runs. |
getUsage(), getDescription() | Metadata for your own help output. The router does not display them. |
execute(sender, args) | Runs the sub-command. args does not include the sub-command name. |
tabComplete(sender, args) | Suggestions for the arguments after the name. args again excludes the name. |
CommandRouter
| Method | Meaning |
|---|---|
new CommandRouter(JavaPlugin plugin, String rootLabel) | rootLabel is used in the usage message, without a slash. |
register(SubCommand) | Adds a sub-command. Registering a second one with the same name replaces the first. |
getSubCommands() | An unmodifiable view, in registration order. Useful for building a help command. |
dispatch(CommandSender, String[] args) | Routes args[0]. Always returns true. |
tabComplete(CommandSender, String[] args) | Completes sub-command names for the first argument, then delegates to the sub-command. |
Example
public final class GoCommand implements SubCommand {
private final WaypointService waypoints;
public GoCommand(WaypointService waypoints) {
this.waypoints = waypoints;
}
@Override
public String getName() {
return "go";
}
@Override
public String getPermission() {
return "rally.host";
}
@Override
public void execute(CommandSender sender, String[] args) {
if (args.length == 0) {
sender.sendMessage("Usage: /rally go <waypoint>");
return;
}
waypoints.rallyTo(sender, args[0]);
}
@Override
public List<String> tabComplete(CommandSender sender, String[] args) {
return args.length == 1 ? waypoints.names(args[0]) : List.of();
}
}
Wire it in onEnable:
CommandRouter router = new CommandRouter(this, "rally");
router.register(new GoCommand(waypoints));
router.register(new OptInCommand(optIns));
PluginCommand command = getCommand("rally");
command.setExecutor((sender, cmd, label, args) -> router.dispatch(sender, args));
command.setTabCompleter((sender, cmd, alias, args) -> router.tabComplete(sender, args));
The root command still needs declaring in plugin.yml.
What dispatch does
| Input | Result |
|---|---|
| No arguments | Sends Usage: /<root> <sub1|sub2|...> in red, listing every registered name. |
| An unknown first argument | Sends Unknown sub-command: <arg> in red. |
| A sub-command whose permission the sender lacks | Sends You do not have permission to do that. in red. execute is not called. |
| Anything else | Calls execute with the remaining arguments. |
Tab completion
- With zero or one argument typed, suggests every sub-command name starting with what has been typed so far.
- Sub-command names are suggested whether or not the sender holds their permission. If you want to hide staff commands, filter in a wrapper around
tabComplete. - Past the first argument, it calls the matching sub-command's
tabComplete, or returns nothing for an unknown name.
Behaviour to know about
- The three router messages above are hard-coded English with legacy colour codes. They do not go through
MessageService, so they cannot be translated or restyled. If that matters, check the arguments yourself before callingdispatch. - Names are stored lower-case, so return a lower-case name from
getName()to keep usage output tidy. - The router never throws for bad input. An exception thrown from inside your
executepropagates as normal, and Paper reports it as an internal command error.