Compound Arguments

Compound Arguments

Compound arguments allow you to combine multiple input raw-arguments into a single argument, enabling more complex command structures and input parsing. This is particularly useful when you want to group related inputs together or when the input format is more complex than simple individual arguments.

Defining a compound argument involves defining a compound-based ArgumentType that can parse multiple raw-arguments and convert them into a single object that your command can use.

Example: Compound Argument for Coordinates

Suppose you want to create a command that accepts a pair of coordinates (x, y) as input. Instead of defining two separate arguments for x and y, you can use a compound argument to capture both values together.

Let's create our type Point3D for the coordinates:

public final class Point3D {
    public final double x; //ik that having public fields is a bad practice, it's just for space-saving purposes
    public final double y;
    public final double z;

    public Point2D(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Next, we can create a compound argument type that parses the input into a Point2D object. Here we have a complex scenario where we want to parse two separate inputs (x, y and z) into a single Point2D object, so we will implement the parsing logic in the parse(ExecutionContext, Cursor) method:

public class Point3DArgumentType extends ArgumentType<PLATFORMSOURCE, Point3D> {

    @Override
    public Point2D parse(
        CommandContext<PLATFORMSOURCE> context,
        Argument<PLATFORMSOURCE> argument,
        String correspondingInput
    ) {
        //example input: '/teleport 10 20'
        // the format for this arg is '<x> <y>'
        // ASSUME that the correspondingInput is the x coordinate and the next input in the cursor is the y coordinate, separated by a space.
        String[] coords = correspondingInput.split(" ");
        if (coords.length != 3) {
            throw new CommandException("Invalid coordinates, expected format: <x> <y> <z>");
        }
        try {
            double x = Double.parseDouble(coords[0]);
            double y = Double.parseDouble(coords[1]);
            double z = Double.parseDouble(coords[2]);
            return new Point3D(x, y, z);
        } catch (NumberFormatException e) {
            throw new CommandException("Invalid coordinates, expected numerical coordinates for <x> <y> <z>");
        }
    }

    @Override //CRITICAL: you must override this method to return the number of raw-arguments that your compound argument will consume, in this case, it will consume 3  raws for x and y
    public int getNumberOfParametersToConsume() {
        return 3; // The <x>, <y> and <z> inputs
    }
}

What defines a compound argument-type is the number of parameters/raw-argument it consumes, thus, its crucial to override ArgumentType#getNumberOfParametersToConsume to define the argument of this type as a compound argument.

Now you can use this Point3DArgumentType in your command to accept coordinates as a single compound argument:

@RootCommand("teleport")
public class TeleportCommand {

    @Execute
    public void teleport(Player source, Point3D coordinates) {
        // In a real implementation, you would teleport the player to the specified coordinates here.
        Location location = new Location(source.getWorld(), coordinates.x, coordinates.y, coordinates.z);
        source.teleport(location);
    }
    
}

In this example, the Point2DArgumentType is designed to parse two separate inputs (x and y) into a single Point2D object.

You must override the getNumberOfParametersToConsume method to return the number of raw-arguments that your compound argument will consume. This is crucial for the command parser to understand how many inputs to pass to your compound argument and to ensure that the command is parsed correctly.

Formatting Compound Arguments

When defining the input format for a compound argument, it's important to clearly specify how the inputs should be structured. Imperat internally automatically generates the format for the argument based on its name ONLY, meaning that if your argument is named coordinates, the expected format will be <coordinates>(or [coordinates] if its optional), this might not affect the parsing logic of your argument, but it can lead to confusion for users when they see the command usage message.

To provide a clear and accurate format for your compound argument, you can use the @Format annotation to specify the expected input format.

@Format

The @Format annotation allows you to define a custom format for your argument, which will be displayed in the command usage message. For example, you can specify the format for the coordinates argument as follows:

@RootCommand("teleport")
public class TeleportCommand {

    @Execute
    public void teleport(PLATFORMSOURCE source, @Format("<x> <y> <z>") Point3D coordinates) {
        // In a real implementation, you would teleport the player to the specified coordinates here.
        Location location = new Location(source.getWorld(), coordinates.x, coordinates.y, coordinates.z);
        source.teleport(location);
    }
    
}