Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 18
Custom parameter types
This guide describes how you use custom parameter types when using the parametric builder.
For the code samples, the classes demonstrated are from Bukkit. CommandSender describes an actor that can execute commands (players, remote users, etc.). Player implements CommandSender and can be cast from CommandSender.
Let's say that you want users to be able to specify a player. For example, you might want to make an /ignite player command.
To achieve that, the command handler would need to take a Player argument:
publicclassIgniteCommand {
@Command(aliases = "ignite", desc = "Set a player on fire")
publicvoidignite(Playerplayer) {
player.setFireTicks(40);
player.sendMessage("You appear to be on fire.");
}
}However, the command framework doesn't know what Player is or how to instantiate one, so we have to create and register a Binding.
While you could write a dedicated class to implement the Binding interface, you can use BindingHelper instead. With that helper class, you can annotate methods with @BindingMatch.
publicclassPlayerBindingextendsBindingHelper {
@BindingMatch(type = Player.class,
behavior = BindingBehavior.CONSUMES,
consumedCount = 1)
publicPlayergetPlayerInput(ArgumentStackcontext) throwsParameterException {
Stringinput = context.next(); // Get the next string from the argument streamPlayerplayer = Bukkit.getServer().getPlayer(input); // Read the next argumentif (player == null) {
thrownewParameterException("Sorry, I couldn't find a player named '" + input + "'.");
} else {
returnplayer;
}
}
}The type= parameter tells the framework what kind of object to match, and the behavior= tells the command framework that you will be consuming a parameter.
The consumedCount= parameter indicates the most likely number of arguments that will be consumed. This is important to specify because it allows for optional parameters that are not at the end of a command. For example, //replace [<from>] <to> can be implemented in Intake. However, if you plan on using parameters that consume a variable number of parameters, such as a theoretical location parameter that accepts both <x> <y> <z> as well as <name>, such a location parameter should not appear optional in the middle of other required parameters.
In addition, consumedCount= is used to predict the number of arguments for those optional-arguments-in-the-middle cases, but the actual number of parameters that you consume is not checked.
Once you've created a binding, you have to register it:
ParametricBuilderbuilder = /* create a builder */;
builder.addBinding(newPlayerBinding());Once registered, the ignite command will work as intended.
You can also use a player as a flag argument too, such as in /ignite -b sk89q wizjany (-b is a flag, which takes a value). Your command handler would have to look like this:
publicclassIgniteCommand {
@Command(aliases = "ignite", desc = "Set a player on fire")
publicvoidignite(Playerplayer, @Switch("b") PlayerblameItOn) {
player.setFireTicks(40);
if (blameItOn != null) {
player.sendMessage("It looks like " + blameItOn + " set you on fire!");
} else {
player.sendMessage("You appear to be on fire.");
}
}
}Remember context.next() from the binding? Whether the argument was specified as a flag or not, you don't have to worry.
Previously demonstrated were arguments that users would specify, but there may be contextual arguments that you may want access to, such as the current CommandSender.
Currently you can create a CommandLocals object and store values on it by key, which is made available to registered bindings. An example of creating a context object is demonstrated below, which has to be done right before dispatching a command.
CommandLocalslocals = newCommandLocals();
locals.put(CommandSender.class, sender);Accessing the contextual value from a binding is easy:
publicclassMyBindingextendsBindingHelper {
@BindingMatch(type = CommandSender.class,
behavior = BindingBehavior.PROVIDES,
consumedCount = 0) // No arguments consumedpublicPlayergetCommandSender(ArgumentStackcontext) throwsParameterException {
CommandSendersender = context.getContext().getLocals().get(CommandSender.class);
if (sender == null) {
thrownewParameterException("Uh oh! The sender of the command is not known. This is a problem with the plugin.");
} else {
returnsender;
}
}
}The example takes the CommandSender from the context and checks to make sure that a sender is defined, just in case.
In the sample, behavior=PROVIDES is used to declare that the binding does not consume arguments.
After that binding is registered, it becomes possible to do the following:
publicclassIgniteCommand {
@Command(aliases = "ignite", desc = "Set a player on fire")
publicvoidignite(CommandSendersender, Playerplayer) {
player.setFireTicks(40);
player.sendMessage("You appear to be on fire.");
// Tell the command caller toosender.sendMessage(player.getName() + " was engulfed in flames.");
}
}What if a command can only be called by players and you want to cast CommandSender to a Player object automatically? Since you also want Player to be a possible input argument (i.e. /command player), you don't want to replace the binding that was previously made for Player.
One solution is to make a custom annotation such as @Sender. The particular design of the annotation is not notable except that it has to be available at runtime. In order to make the command framework properly use this annotation, you have to write a binding for it as well:
publicclassMyBindingextendsBindingHelper {
@BindingMatch(classifier = Sender.class, // The annotationtype = Player.class, // What is annotatedbehavior = BindingBehavior.PROVIDES,
consumedCount = 0)
publicPlayergetPlayerSender(ArgumentStackcontext, Sendersender) throwsParameterException {
CommandSendersender = context.getContext().getLocals().get(CommandSender.class);
if (sender == null) {
thrownewParameterException("Uh oh! The sender of the command is not known. This is a problem with the plugin.");
} elseif (senderinstanceofPlayer) {
return (Player) sender;
} else {
thrownewParameterException("Sorry, this command can only be used by players!");
}
}
}Once you have done that, it becomes possible to use @Sender to accept a Player sender:
publicclassIgniteSelfCommand {
@Command(aliases = "igniteme", desc = "Set yourself on fire")
publicvoidigniteMe(@SenderPlayerself) {
self.setFireTicks(40);
self.sendMessage("You might want to call the emergency services.");
}
}If a non-player attempts to call the command, then the error will be shown.