External subcommands
Having one file per command is nice - keeps everything organized… until it doesn’t. The reality of command design is that oftentimes, you end up with a lot of logic for individual root commands. The traditional way to deal with that issue is to put subcommands into their own files. Such an approach works wonderfully. And StrokkCommands supports it!
Prototype subcommand classes
Section titled “Prototype subcommand classes”You can declare external subcommands in an object-oriented manner simply by creating a class, just like you
are used to. This class can have a constructor, fields, and methods. But you can also declare
command handlers (with the @Executes annotation). Thorough this page, we will be building up a nice and
modular approach to creating a /gamemode <mode> [<target>] command, with /gm(s|c|a|sp) aliases using
this prototype system.
Creating the prototype class
Section titled “Creating the prototype class”For our example, we will use the following prototype class:
/** * A class, which can be used as a preset for commands, which set either the executing * player's or the optionally specified target's game mode. */@NullMarkedpublic class GameModePreset {
/* * The game mode of this prototype instance. */ private final GameMode mode;
/* * Prototypes support constructors with parameters. */ public GameModePreset(final GameMode mode) { this.mode = mode; }
/* * This is the default handler, which gets ran, if the command is * run by a player, which did not specify a target. */ @Executes public void executes(CommandSender sender, @Executor Player executor) { changeGameMode(sender, executor); }
/* * This handler gets run if a target was specified explicitly. */ @Executes public void executesTarget(CommandSender sender, Player target) { changeGameMode(sender, target); }
/* * A common method for handling both cases in order to reduce code repetition. */ private void changeGameMode(CommandSender sender, Player target) { final String targetName = sender == target ? "your" : target.getName() + "'s"; final String targetNameUpper = sender == target ? "Your" : targetName;
if (target.getGameMode() == mode) { sender.sendRichMessage("<red><target_name> game mode is already set to <mode>!", Placeholder.unparsed("target_name", targetNameUpper), Placeholder.component("mode", Component.translatable(mode)) ); return; }
target.setGameMode(mode); sender.sendRichMessage("<green>Successfully set <target_name> game mode to <mode>!", Placeholder.unparsed("target_name", targetName), Placeholder.component("mode", Component.translatable(mode)) ); }}There really isn’t much to be said about this prototype which would not be self-explanatory. The @Executes-annotated
methods work just the same way as normal commands do. The difference here is that you can reuse this prototype
in multiple commands.
Applying the prototype
Section titled “Applying the prototype”In order to use a prototype, you need to declare it as a field. This field should have its value initialized directly, if you wish for it to have a specific implementation, or the initialization may be omitted entirely to default to the non-args constructor. The general semantic looks like this:
@Command("mycommand")class MyCommandClass {
@Subcommand("some_sub") MySubcommand someSub; // Same as explicitly writing `= new MySubcommand()`}You can use the @Permission and @RequiresOP annotation on that field just like you would on a command handler
method or command class.
For the game mode example in specific, we’d write the following code:
@Command("gamemode")@Aliasa("gm")class GameModeCommand {
@Subcommand("creative") @Permission("myplugin.gamemode.creative") GameModePreset creative = new GameModePreset(GameMode.CREATIVE);
// Repeat for all other game modes}Once you have done that, all that is left is to compile the project once, register the generated GameModeCommandBrigadier
class, and test it out in-game!
Creating the /gm(c|s|a|sp) aliases
Section titled “Creating the /gm(c|s|a|sp) aliases”Due to each of these being their own command, you’d want to have each of them in their Java file.
You can then simply declare your prototype as a field, without providing a value to the @Subcommand annotation,
and it should work.
For example, the creative shortcut would look like this:
@Command("gmc")@Permission("testplugin.gamemode.creative")class GameModeCreativeCommand {
@Subcommand GameModePreset gameMode = new GameModePreset(GameMode.CREATIVE);}This is really simple and easy, isn’t it?
Some notes about prototype classes
Section titled “Some notes about prototype classes”Prototype classes support the full feature set normal commands support. This means you can even nest prototype classes into other prototype classes! You should just be careful to not accidentally create a cyclic dependency, where one prototype uses another prototype, which in turn uses the previous prototype.