Skip to main content

Comparison Brigadier and Bukkit Commands

Registering commands

The old Bukkit way

In order to register Bukkit commands, you would define a class that extends BukkitCommand, and implements the execute(...) and tabComplete(...) methods. This might look like this:

BukkitPartyCommand.java
public class BukkitPartyCommand extends BukkitCommand {
public BukkitPartyCommand(@NotNull String name, @NotNull String description, @NotNull String usageMessage, @NotNull List<String> aliases) {
super(name, description, usageMessage, aliases);
}

@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
if (args.length == 0) {
sender.sendMessage("Please provide a player!");
return true;
}

final Player targetPlayer = Bukkit.getPlayer(args[0]);
if (targetPlayer == null) {
sender.sendMessage("Please provide a valid player!");
return true;
}

targetPlayer.sendMessage(sender.getName() + " started partying with you!");
sender.sendMessage("You are now partying with " + targetPlayer.getName() + "!");
return false;
}

@Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
if (args.length == 1) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}

return List.of();
}
}

After that, you can define your command like this:

PluginClass.java
this.getServer().getCommandMap().register(
this.getName().toLowerCase(),
new BukkitPartyCommand("bukkitparty", "Have a party", "/party <player>", List.of())
);

As you can see, you have to do a lot of manual checking in order to register a single, very simple command. But how does the Brigadier api do it?

The new Paper way

First, we need to retrieve a LiteralCommandNode<CommandSourceStack>. That's a special Brigadier class that holds some sort of command tree. In our case, it is the root of our command. We can do that by running Commands.literal(final String literal), which returns a LiteralArgumentBuilder<CommandSourceStack>, where we can define some arguments and executors. Once we are done, we can call LiteralArgumentBuilder#build() to retrieve our build LiteralCommandNode, which we can then register. That sounds complicated at first, but once you see it in action, it looks less terrifying:

PaperPartyCommand.java
public static LiteralCommandNode<CommandSourceStack> createCommand(final String commandName) {
return Commands.literal(commandName)
.then(Commands.argument("target", ArgumentTypes.player())
.executes(ctx -> {
final PlayerSelectorArgumentResolver playerSelector = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
final Player targetPlayer = playerSelector.resolve(ctx.getSource()).getFirst();
final CommandSender sender = ctx.getSource().getSender();

targetPlayer.sendMessage(sender.getName() + " started partying with you!");
sender.sendMessage("You are now partying with " + targetPlayer.getName() + "!");

return Command.SINGLE_SUCCESS;
}))
.build();
}

Each .then(...) defines a new branch in our tree, which can either be a literal (Commands.literal(String)) or an argument (Commands.argument(String, ArgumentType<T>)). Each branch may or may not define an .executes(Command) executor. This is where all the logic happens.

We will take a closer look at that in different pages, but for now, how do we register it? Paper uses a LifecycleEventManager system. In a nutshell, that is a way to register commands (or tags) that get loaded each time the server reloads its resources, like using /reload. Registering our command looks like this:

PluginClass.java
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
commands.registrar().register(PaperPartyCommand.createCommand("paperparty"), "Have a nice party");
});

And we are done! As you can see here, both commands do the same thing: