Arguments
Argumenttypes are datatypes that we can use instead of strings.
Paper's command system is still experimental and may change in the future.
Basic usage of arguments
You can add arguments to a command by doing the following:
public class YourPluginClass extends JavaPlugin {
@Override
public void onEnable() {
LifecycleEventManager<Plugin> manager = this.getLifecycleManager();
manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
final Commands commands = event.registrar();
commands.register(
Commands.literal("enchantmentargumentcommand")
.then(
Commands.argument("enchantmentargument", ArgumentTypes.resource(RegistryKey.ENCHANTMENT))
.executes(ctx -> {
ctx.getSource().getSender().sendPlainMessage(
ctx.getArgument("enchantmentargument", Enchantment.class).getKey().asString()
);
return Command.SINGLE_SUCCESS;
})
).build()
);
});
}
}
This command has one argument of the Enchantment
datatype. The advantage of using
Brigadier over the TabExecutor
interface is that you don't have to convert the
string that is passed to the CommandExecutor#onCommand()
function as an argument to
an enchantment in this case because ctx.getArgument("enchantmentargument", Enchantment.class)
returns a value of the Enchantment
datatype. Additionally, you don't have to write
error handling functionality yourself as the client checks whether the argument the
user wrote in the chat is one of the minecraft:enchantment
type and if it isn't,
the user will get an error. Another advantage ArgumentTypes
have over string-based argument systems like
Bukkit's TabExecutor
is that we can work with our own types and the completions list
is not always sorted alphanumerically. This may sound insignificant but when you want an input
of the type integer and suggest the numbers 0-10, for example, your list will look like this because
it needs to be converted to strings:0, 1, 10, 2, 3, ...
. Looks awful, right?
Enchantment types
By default, you can use registries to get simple argument types like blocks, items, potions and many more. In the example above, we used the Enchantment Argument type but there are many others:
Predefined types (Registry)
Registry key name | Return datatype class | Description |
---|---|---|
GAME_EVENT | GameEvent | Events in the game (eating, flying with an elytra etc.) |
STRUCTURE_TYPE | StructureType | Structures - Beware that, as of Paper 1.20.6-147, there is also a Registry named STRUCTURE that is not recognized by the client and makes it unable to connect. This has been reported to Paper developers and will be fixed in a future release. |
INSTRUMENT | CraftMusicInstrument | Note block instrument |
ENCHANTMENT | Enchantment | Enchantment type |
MOB_EFFECT | PotionEffectType | Potion effect |
BLOCK | BlockType | Block type - not modifiable |
ITEM | ItemType | Item type - not modifiable |
BIOME | Biome | Biome type |
TRIM_MATERIAL | TrimMaterial | Materials used to trim armor |
TRIM_PATTERN | TrimPattern | Trim patterns |
DAMAGE_TYPE | DamageType | All types of damage dealt to an entity |
WOLF_VARIANT | Wolf.Variant | Wolf variants since 1.20.5 |
PAINTING_VARIANT | Art | All paintings |
ATTRIBUTE | Attribute | Entity attribute |
BANNER_PATTERN | PatternType | Armor Pattern type |
CAT_VARIANT | Cat.Type | Cat variants |
ENTITY_TYPE | EntityType | Every entity type |
PARTICLE_TYPE | Particle | Every particle type |
POTION | PotionType | Every potion type |
SOUND_EVENT | Sound | Events that trigger sound effects |
VILLAGER_PROFESSION | Villager.Profession | Villager professions |
VILLAGER_TYPE | Villager.Type | Villager biome specific type |
MEMORY_MODULE_TYPE | MemoryKey | Keys for saving per-entity data |
FROG_VARIANT | Frog.Variant | Frog variants |
MAP_DECORATION_TYPE | MapCursor.Type | Types of sprites displayed on a map |
FLUID | Fluid | Fluid types |
Predefined types (in Brigadier itself)
Brigadier itself also specifies many argument types. For more information on them, see LifecycleEventManager
Custom types
Custom arguments can be created by implementing the CustomArgumentType interface.
Now, lets say that we want to implement a command which lets you order ice cream. For that, we add a
public enum IceCreamType {
VANILLA,
CHOCOLATE,
STRAWBERRY,
MINT,
COOKIES
}
Now, we have to define the argument itself. We do this by implementing the CustomArgumentType.Converted interface:
public class IceCreamTypeArgument implements CustomArgumentType.Converted<IceCreamType, String> {
@Override
public @NotNull IceCreamType convert(String nativeType) throws CommandSyntaxException {
try {
return IceCreamType.valueOf(nativeType.toUpperCase());
} catch (Exception e) {
Message message = MessageComponentSerializer.message().serialize(Component.text("Invalid flavor %s!".formatted(nativeType), NamedTextColor.RED));
throw new CommandSyntaxException(new SimpleCommandExceptionType(message), message);
}
}
@Override
public @NotNull ArgumentType<String> getNativeType() {
return StringArgumentType.word();
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
for (IceCreamType flavor : IceCreamType.values()) {
builder.suggest(flavor.name(), MessageComponentSerializer.message().serialize(Component.text("look at this cool green tooltip!", NamedTextColor.GREEN)));
}
return CompletableFuture.completedFuture(
builder.build()
);
}
}
That's a lot of code, so let's start from the top. We implemented the CustomArgumentType.Converted
interface. This interface takes two type arguments: our custom enum, T, and a type that is native to
Minecraft, such as String, Integer, etc., called N. The native type exists so that the client can use
the input data, as it doesn't know what our custom IceCreamType
is.
The first method called convert()
converts the native type (in this case String
)
into our own custom type called IceCreamType
, as the name suggests. This is so we
can keep using our custom type internally. If we wouldn't do that, there would be no point in using a
custom type. If the specified input isn't found in our enum of available flavors, we
output an error.
The second method called getNativeType()
returns the type of string that our command argument uses.
In our case, we're using a single word, so we return StringArgumentType.word()
.
In the last method, listSuggestions()
, we return CompletableFuture<Suggestions>
so that the client
can suggest all available options. We can even add tooltips to the suggestions to explain them in greater
detail.
As a last step, we need to register the Command:
public void onEnable() {
LifecycleEventManager manager = this.getLifecycleManager();
manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
final Commands commands = event.registrar();
commands.register(Commands.literal("ordericecream")
.then(
Commands.argument("flavor", new IceCreamTypeArgument()).executes((commandContext -> {
IceCreamType argumentResponse = commandContext.getArgument("flavor", IceCreamType.class); // Gets the raw argument
commandContext.getSource().getSender().sendMessage(Component.text("You ordered: " + argumentResponse));
return 1;
}))
).build()
);
});
}
Now that we have registered the command, we can execute it ingame:
Look, we can even see our tooltip and if we execute the command, we get the message we specified