Command trees
So far in this documentation, we've described many different ways to register commands. We've described writing commands by declaring a CommandAPICommand
object, using a list of arguments and providing an executor for the command. We've also described another way of registering commands with multiple "paths" using the withSubcommand
method to generate a tree-like structure. As of CommandAPI 7.0.0, another method for registering commands, command trees, has been introduced.
The executes()
and then()
methods
The Command Tree represents command structures in a tree-like fashion, in a very similar way that Brigadier's API lets you declare commands. Command tree commands effectively revolve around two methods:
public T executes(CommandExecutor executor);
public CommandTree then(ArgumentTree branch);
public ArgumentTree then(ArgumentTree branch);
The executes()
method is the same executes()
method that you have seen previously in this documentation for normal CommandAPI commands. This also includes all of the executes...()
methods described in Normal command executors, but for the sake of simplicity, we'll simply refer to all of these by executes()
.
The then()
method allows you to create new "branches" in your command "tree" data structure. If you are familiar with Brigadier's then()
method for argument nodes, then you should feel right at home. Otherwise, for all intents and purposes then()
lets you specify additional paths that a command can take when a user is typing their command.
Because the underlying type hierarchy of command trees is fairly complex (then()
having multiple return types and taking in ArgumentTree
objects), instead of trying to describe how all of that works, we'll instead describe how to make command trees by using the methods executes()
and then()
in practice.
Declaring a command tree
The basic syntax of a command tree is effectively identical to a normal CommandAPICommand
, but instead you use the CommandTree
object. For example, if we want to create a simple command which sends "Hi!" to a command sender, we declare the name of our command, make use of the executes()
method, and then we use the CommandTree
constructor instead of the CommandAPICommand
constructor:
/sayhi
new CommandAPICommand("sayhi")
.executes((sender, args) -> {
sender.sendMessage("Hi!");
})
.register();
$$\downarrow$$
new CommandTree("sayhi")
.executes((sender, args) -> {
sender.sendMessage("Hi!");
})
.register();
Adding arguments to a command tree
Unlike the CommandAPICommand
class, the CommandTree
class doesn't let you add arguments using the withArguments()
method. Instead, it makes use of the then()
method, which allows you to provide an argument to it. This is best described with an example.
Example - Declaring a command tree with a single argument
Say we want to take our /sayhi
command from above and also have an argument which lets you specify a target player. In this example, we'll have the following command syntax:
/sayhi - Says "Hi!" to the current sender
/sayhi <target> - Says "Hi!" to a target player
We can do this by adding a PlayerArgument
to our command. As described above, to add this argument, we must use the then()
method:
new CommandTree("sayhi")
.executes((sender, args) -> {
sender.sendMessage("Hi!");
})
.then(new PlayerArgument("target")
.executes((sender, args) -> {
Player target = (Player) args.get("target");
target.sendMessage("Hi");
}))
.register();
CommandTree("sayhi")
.executes(CommandExecutor { sender, _ ->
sender.sendMessage("Hi!")
})
.then(PlayerArgument("target")
.executes(CommandExecutor { _, args ->
val target = args["target"] as Player
target.sendMessage("Hi")
}))
.register()
In this example, we have our normal /sayhi
command using the executes()
method. We then add a new argument (a new "branch" in our "tree"), the PlayerArgument
, using the then()
method. We want to make this branch executable, so we also use the executes()
method on the argument itself. To register the full command tree (which includes both /sayhi
and /sayhi <target>
), we call register()
on the CommandTree
object.
That's effectively all of the basics of command trees! We start by writing a normal command, use executes()
to make it executable and use then()
to add additional paths to our command. Finally, we finish up with register()
to register our command. Below, I've included a few more examples showcasing how to design commands using command trees.
Command tree examples
Example - Sign editing plugin
Say we wanted to create a plugin to let a user edit signs. We have a single command tree /signedit
, with a number of branching paths set
, clear
, copy
and paste
which represent various operations that this command can be performed on a sign:
/signedit set <line_number> <text> - Sets the text for a line on a sign
/signedit clear <line_number> - Clears a sign's text on a specific line
/signedit copy <line_number> - Copies the current text from a line on a sign
/signedit paste <line_number> - Pastes the copied text onto a line on a sign
new CommandTree("signedit")
.then(new LiteralArgument("set")
.then(new IntegerArgument("line_number", 1, 4)
.then(new GreedyStringArgument("text")
.executesPlayer((player, args) -> {
// /signedit set <line_number> <text>
Sign sign = getTargetSign(player);
int lineNumber = (int) args.get("line_number");
String text = (String) args.get("text");
sign.setLine(lineNumber - 1, text);
sign.update(true);
}))))
.then(new LiteralArgument("clear")
.then(new IntegerArgument("line_number", 1, 4)
.executesPlayer((player, args) -> {
// /signedit clear <line_number>
Sign sign = getTargetSign(player);
int lineNumber = (int) args.get("line_number");
sign.setLine(lineNumber - 1, "");
sign.update(true);
})))
.then(new LiteralArgument("copy")
.then(new IntegerArgument("line_number", 1, 4)
.executesPlayer((player, args) -> {
// /signedit copy <line_number>
Sign sign = getTargetSign(player);
int lineNumber = (int) args.get("line_number");
player.setMetadata("copied_sign_text", new FixedMetadataValue(this, sign.getLine(lineNumber - 1)));
})))
.then(new LiteralArgument("paste")
.then(new IntegerArgument("line_number", 1, 4)
.executesPlayer((player, args) -> {
// /signedit copy <line_number>
Sign sign = getTargetSign(player);
int lineNumber = (int) args.get("line_number");
sign.setLine(lineNumber - 1, player.getMetadata("copied_sign_text").get(0).asString());
sign.update(true);
})))
.register();
CommandTree("signedit")
.then(LiteralArgument("set")
.then(IntegerArgument("line_number", 1, 4)
.then(GreedyStringArgument("text")
.executesPlayer(PlayerCommandExecutor { player, args ->
// /signedit set <line_number> <text>
val sign: Sign = getTargetSign(player)
val line_number = args["line_number"] as Int
val text = args["text"] as String
sign.setLine(line_number - 1, text)
sign.update(true)
}))))
.then(LiteralArgument("clear")
.then(IntegerArgument("line_number", 1, 4)
.executesPlayer(PlayerCommandExecutor { player, args ->
// /signedit clear <line_number>
val sign: Sign = getTargetSign(player)
val line_number = args["line_number"] as Int
sign.setLine(line_number - 1, "")
sign.update(true)
})))
.then(LiteralArgument("copy")
.then(IntegerArgument("line_number", 1, 4)
.executesPlayer(PlayerCommandExecutor { player, args ->
// /signedit copy <line_number>
val sign: Sign = getTargetSign(player)
val line_number = args["line_number"] as Int
player.setMetadata("copied_sign_text", FixedMetadataValue(this, sign.getLine(line_number - 1)))
})))
.then(LiteralArgument("paste")
.then(IntegerArgument("line_number", 1, 4)
.executesPlayer(PlayerCommandExecutor { player, args ->
// /signedit copy <line_number>
val sign: Sign = getTargetSign(player)
val line_number = args["line_number"] as Int
sign.setLine(line_number - 1, player.getMetadata("copied_sign_text")[0].asString())
sign.update(true)
})))
.register()