Custom arguments

Custom arguments are an experimental feature which the CommandAPI offers, which allows you to represent any String, or Minecraft key (Something of the form String:String, such as minecraft:diamond) with a custom parser. They basically represent StringArgument with replaced suggestions and a built-in parser for any object of your choice. They are designed to be used for multiple commands - you can define the argument once and can use it wherever you want when declaring commands.


The CustomArgument<T> has four constructors, declared as follows:

public CustomArgument(String nodeName, CustomArgumentFunction<T> parser);
public CustomArgument(String nodeName, CustomArgumentFunction<T> parser, boolean keyed);

public CustomArgument(String nodeName, CustomArgumentFunction2<T> parser);
public CustomArgument(String nodeName, CustomArgumentFunction2<T> parser, boolean keyed);

There are effectively three forms that this can take:

A custom argument with a simple parser

The simplest form requires the node name as per any other argument, and a parser which takes in as input a string and returns a custom object of your choice. For example, if you wanted to create a custom argument that represents a World, you can use this to return a Bukkit World object.

new CustomArgument(nodeName, input -> { 
    // code here
    return T; 
});

A custom argument with a parser with the command sender

To fine-tune your parser, you can use the (really poorly named) CustomArgumentFunction2 parser type, which effectively means you can also access the command sender whilst parsing the input.

new CustomArgument(nodeName, (sender, input) -> { 
    // code here
    return T; 
});

A custom argument with a parser that takes in a Minecraft Key

In the two cases above, the input is a string, which can take in any string. If you provide true to the keyed field, the input can be of the form of a Minecraft key (so it can have : in the name).


The custom argument requires the type of the target object that the custom argument will return when parsing the arguments for a command. For instance, if you have a CustomArgument<Player>, then when parsing the arguments for the command, you would cast it to a Player object.


Example - World argument

Say we want to create an argument to represents the list of available worlds on the server. We basically want to have an argument which always returns a Bukkit World object as the result. Here, we create a method worldArgument() that returns our custom argument that returns a World. First, we retrieve our String[] of world names to be used for our suggestions. We then write our custom argument that creates a World object from the input (in this case, we simply convert the String to a World using Bukkit.getWorld(String)). We perform error handling before returning our result:

// Function that returns our custom argument
public Argument worldArgument(String nodeName) {
    
    // Construct our CustomArgument that takes in a String input and returns a World object
    return new CustomArgument<World>(nodeName, (input) -> {
        // Parse the world from our input
        World world = Bukkit.getWorld(input);
    
        if(world == null) {
            throw new CustomArgumentException(new MessageBuilder("Unknown world: ").appendArgInput());
        } else {
            return world;
        }
    }).replaceSuggestions(sender -> {
        // List of world names on the server
        return Bukkit.getWorlds().stream().map(World::getName).toArray(String[]::new);
    });
}

In our error handling step, we check if the world is equal to null (since the Bukkit.getWorld(String) is @Nullable). To handle this case, we throw a CustomArgumentException with an error from a MessageBuilder. The CustomArgumentException has two constructors, so a message builder isn't required each time:

new CustomArgumentException(String message);
new CustomArgumentException(MessageBuilder message);

We can use our custom argument like any other argument. Say we wanted to write a command to teleport to a specific world. We will create a command of the following syntax:

/tpworld <world>

Since we have defined the method worldArgument() which automatically generates our argument, we can use it as follows:

new CommandAPICommand("tpworld")
    .withArguments(worldArgument("world"))
    .executesPlayer((player, args) -> {
        player.teleport(((World) args[0]).getSpawnLocation());
    })
    .register();

By using a CustomArgument (as opposed to a simple StringArgument and replacing its suggestions), we are able to provide a much more powerful form of error handling (automatically handled inside the argument), and we can reuse this argument for other commands.


Message Builders

The MessageBuilder class is a class to easily create messages to describe errors when a sender sends a command which does not meet the expected syntax for an argument. It acts in a similar way to a StringBuilder, where you can append content to the end of a String.

The following methods are as follows:

MethodDescription
appendArgInput()Appends the argument that failed that the sender submitted to the end of the builder. E.g. /foo bar will append bar
appendFullInput()Appends the full command that a sender submitted to the end of the builder. E.g. /foo bar will append foo bar
appendHere()Appends the text <--[HERE] to the end of the builder
append(Object)Appends an object to the end of the builder

Example - Message builder for invalid objective argument

To create a MessageBuilder, simply call its constructor and use whatever methods as you see fit. Unlike a StringBuilder, you don't have to "build" it when you're done - the CommandAPI does that automatically:

new MessageBuilder("Unknown world: /").appendFullInput().appendHere();