Skip to content

Custom parameter types

sk89q edited this page Aug 19, 2014 · 4 revisions

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.

Accepting a player argument

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:

public class IgniteCommand {

    @Command(aliases = "ignite", desc = "Set a player on fire")
    public void ignite(Player player) {
        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.

Creating 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.

public class PlayerBinding extends BindingHelper {

    @BindingMatch(type = Player.class,
                  behavior = BindingBehavior.CONSUMES,
                  consumedCount = 1)
    public Player getPlayerInput(ArgumentStack context) throws ParameterException {
        String input = context.next(); // Get the next string from the argument stream

        Player player = Bukkit.getServer().getPlayer(input); // Read the next argument
        if (player == null) {
            throw new ParameterException("Sorry, I couldn't find a player named '" + input + "'.");
        } else {
            return player;
        }
    }

}

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:

ParametricBuilder builder = /* create a builder */;
builder.addBinding(new PlayerBinding());

Once registered, the ignite command will work as intended.

Works as a flag

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:

public class IgniteCommand {

    @Command(aliases = "ignite", desc = "Set a player on fire")
    public void ignite(Player player, @Switch("b") Player blameItOn) {
        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.

Accepting a contextual value

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.

CommandLocals locals = new CommandLocals();
locals.put(CommandSender.class, sender);

Accessing the contextual value from a binding is easy:

public class MyBinding extends BindingHelper {

    @BindingMatch(type = CommandSender.class,
                  behavior = BindingBehavior.PROVIDES,
                  consumedCount = 0) // No arguments consumed
    public Player getCommandSender(ArgumentStack context) throws ParameterException {
        CommandSender sender = context.getContext().getLocals().get(CommandSender.class);
        if (sender == null) {
            throw new ParameterException("Uh oh! The sender of the command is not known. This is a problem with the plugin.");
        } else {
            return sender;
        }
    }

}

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:

public class IgniteCommand {

    @Command(aliases = "ignite", desc = "Set a player on fire")
    public void ignite(CommandSender sender, Player player) {
        player.setFireTicks(40);
        player.sendMessage("You appear to be on fire.");
        // Tell the command caller too
        sender.sendMessage(player.getName() + " was engulfed in flames.");
    }

}

Matching with annotations

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:

public class MyBinding extends BindingHelper {

    @BindingMatch(classifier = Sender.class, // The annotation
                  type = Player.class, // What is annotated
                  behavior = BindingBehavior.PROVIDES,
                  consumedCount = 0)
    public Player getPlayerSender(ArgumentStack context, Sender sender) throws ParameterException {
        CommandSender sender = context.getContext().getLocals().get(CommandSender.class);
        if (sender == null) {
            throw new ParameterException("Uh oh! The sender of the command is not known. This is a problem with the plugin.");
        } else if (sender instanceof Player) {
            return (Player) sender;
        } else {
            throw new ParameterException("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:

public class IgniteSelfCommand {
 
    @Command(aliases = "igniteme", desc = "Set yourself on fire")
    public void igniteMe(@Sender Player self) {
        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.

Clone this wiki locally