Skip to content

Creating your first command

At its heart, all commands are created by using the @Command annotation on a class. You then declare the command name as a parameter in the annotation:

@Command("firstcommand")
class MyFirstCommand {
}

And that is all you need for a simple command. This one obviously doesn’t do anything yet, but before we add some logic, let’s add some metadata.

Paper supports registering a command with aliases and description. StrokkCommands supports adding those with the additional, optional annotations @Aliases and @Description.

@Command("firstcommand")
@Aliases("fc")
@Description("My first StrokkCommands command!")
class MyFirstCommand {
}

Command logic is declared by using the @Executes annotation on a method. This method has to be at least package-private. You cannot mark the method as private. Adding a top-level executes path (with no arguments) looks like this:

@Command("firstcommand")
@Aliases("fc")
@Description("My first StrokkCommands-command!")
class MyFirstCommand {
@Executes
void onExecute(CommandSender sender) {
sender.sendRichMessage("<#f29def>Hey there! You just executed your first command ^-^");
}
}

Each method requires you to add a CommandSender parameter. If you don’t do that, you will get a compile-time error. This parameter describes the command sender of the command. In Brigadier terms, this is the object you get from calling ctx.getSource().getSender().

If your command requires a player to execute it, you have two ways to get it:

  1. instanceof-cast the sender:
@Executes
void onExecute(CommandSender sender) {
if (!(sender instanceof Player player)) {
sender.sendRichMessage("<red>This command requires a player!");
return;
}
player.sendRichMessage("<#f29def>Hey there! You just executed your first command ^-^");
}
  1. Add an Player parameter using the @Executor annotation:
@Executes
void onExecute(CommandSender sender, @Executor Player player) {
player.sendRichMessage("<#f29def>Hey there! You just executed your first command ^-^");
}

Whilst both ways are acceptable, the second approach is the more ‘correct’ way. The reason is that instead of casting the command sender, you directly get the Player entity with executed the command. This is a distinction from the /execute as command. Using the /executes command, you can change the player for which the command is executed. All Vanilla commands use the executor instead of the sender for this exact reason, so for parity reasons, you should do as well.

If the command was run by a Player, player == sender will evaluate to true, false, if not.

You can declare top-level literals (also referred to as subcommands) by adding a parameter to the @Executes annotation. If you want a path called /fc fling, you’d add this method:

@Command("firstcommand")
@Aliases("fc")
@Description("My first StrokkCommands-command!")
class MyFirstCommand {
@Executes
void onExecute(CommandSender sender) {
sender.sendRichMessage("<#f29def>Hey there! You just executed your first command ^-^");
}
@Executes("fling")
void onFling(CommandSender sender, @Executor Player player) {
player.setVelocity(player.getVelocity().add(new Vector(0, 10, 0)));
player.sendRichMessage("<b><#c4e6ff>WOOSH</b> <#c4fffd>You've been flung!");
}
}

As you can see, declaring the command is incredibly easy! But you (probably) don’t yet know how to register the command to test it out.

Command registration works the exact same way as with Paper: by using the Lifecycle API. The command registration page on the Paper docs can also provide some important insight, but for beginners, the page can be a bit complex.

To register your command with StrokkCommands, you have to do the following steps:

Wait, didn’t we skip a few steps? Nope! In order to be able to register the command, you have to first compile the project. The reason for this is so that the annotation processor can run and generate the required source files for your command.

The source file will always be generated in the same package as the command declaration. Its file name will always be <command_class_name>Brigadier.java. That means for your.package.MyCommand, the new class name will be your.package.MyCommandBrigadier.

The new source file will be generated in a separate sources root which is managed by your build system. For Gradle, the generated source files are located under /build/generated/sources/annotationProcessor.

Now that we have our Brigadier source file, we can register it.

Wherever you want to register your command, you can get the lifecycle event manager and register a new commands event handler. For the plugin’s main class, this would look like this:

YourPlugin.java
public void onLoad() {
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> {
// Register the command with the description and aliases declared as annotations:
MyFirstCommandBrigadier.register(event.registrar());
// Register the command with your own provided description and aliases:
event.registrar().register(MyFirstCommandBrigadier.create(), "description", List.of(/* aliases */));
}));
}

The generated Brigadier source file has two static methods for retrieving the build command tree and registering it. It is up to you which approach you prefer, either work just fine.

You should now be able to spin up a test server with your plugin and see your command in action!