User Tools

Site Tools


tutorial:commands

This is an old revision of the document!


Licensing: The code in this article is licensed under the “Creative Commons Zero v1.0 Universal” license. The license grants you the rights to use the code examples shown in this article in your own mods.

Creating Commands

Creating commands can allow a mod developer to add functionality that can used through a command. This tutorial will teach you how to register commands, and the general command structure of Brigadier.

Note: All code written here was written for 1.19.2. For old versions, some versions and mappings may differ.

What is Brigadier?

Brigadier is a command parser & dispatcher written by Mojang for use in Minecraft. Brigadier is a tree based command library where you build a tree of arguments and commands.

The source code for brigadier can be found here: https://github.com/Mojang/brigadier

The ''Command'' interface

In Minecraft, Command (com.mojang.brigadier.Command) is an functional interface that run some specific things, and throw a CommandSyntaxException in some cases. It has a generic type S, which defines the type of the command source. The command source provides some context in which a command was ran. In Minecraft, the command source is typically a ServerCommandSource which can represent a server, a command block, rcon connection, a player or an entity. In some cases, it can also be a ClientCommandSource.

The single method in Command, run(CommandContext<S>) takes a CommandContext<S> as the sole parameter and returns an integer. The command context holds your command source of S and allows you to obtain arguments, look at the parsed command nodes and see the input used in this command.

Like other functional interfaces, it is usually used as a lambda or a method reference:

Command<ServerCommandSource> command = context -> {
    return 0;
};

In vanilla Minecraft, they are usually used as method references, such as static methods named register under classes named XXXCommand.

The integer can be considered the result of the command. In Minecraft, the result can correspond to the power of a redstone comparator feeding from a command block or the value that will be passed the chain command block the command block is facing. Typically negative values mean a command has failed and will do nothing. A result of 0 means the command has passed. Positive values mean the command was successful and did something.

What can the ServerCommandSource do?

A ServerCommandSource provides some additional implementation specific context when a command is run. This includes the ability to get the entity that executed the command, the world the command was ran in or the server the command was run on.

  1. // Get the source. This will always work.
  2. final ServerCommandSource source = ctx.getSource();
  3.  
  4. // Unchecked, may be null if the sender was the console.
  5. final Entity sender = source.getEntity();
  6.  
  7. // Will end the command if the source of the command was not an Entity.
  8. // The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity.
  9. // This method will require your methods to throw a CommandSyntaxException.
  10. // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player.
  11. final Entity sender2 = source.getEntityOrThrow();
  12.  
  13. // Will end the command if the source of the command was not explicitly a Player. Also will send feedback telling the sender of the command that they must be a player. This method will require your methods to throw a CommandSyntaxException
  14. final ServerPlayerEntity player = source.getPlayer();
  15.  
  16. // Get's the sender's position as a Vec3 when the command was sent. This could be the location of the entity/command block or in the case of the console, the world's spawn point.
  17. source.getPosition();
  18.  
  19. // Get's the world the sender is within. The console's world is the same as the default spawn world.
  20. source.getWorld();
  21.  
  22. // Get's the sender's rotation as a Vec2f.
  23. source.getRotation();
  24.  
  25. // Access to the instance of the MinecraftServer this command was ran on.
  26. source.getServer();
  27.  
  28. // The name of the command source. This could be the name of the entity, player, the name of a CommandBlock that has been renamed before being placed down or in the case of the Console, "Console"
  29. source.getName();
  30.  
  31. // Returns true if the source of the command has a certain permission level. This is based on the operator status of the sender. (On an integrated server, the player must have cheats enabled to execute these commands)
  32. source.hasPermissionLevel(int level);

Register a basic command

Commands are registered by registering in CommandRegistrationCallback in the Fabric API. For information on registering callbacks, please see the callbacks.

The event should be registered in your mod's initializer. The callback has three parameters. The CommmandDispatcher<S> is used to register, parse and execute commands. S is the type of command source the command dispatcher supports, which is usually ServerCommandSource. The second parameter provides an abstraction to registries which may be passed to certain command argument methods. The third parameter is a RegistrationEnvironment which identifies the type of server the commands are being registered on.

To simplify the code, it is highly recommended to static import the methods in CommandManager (see Static Imports):

import static net.minecraft.server.command.CommandManager.*;

In the mod initializer, we just register the simplest command:

  1. public class ExampleMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(literal("foo")
  5. .executes(context -> {
  6. // For versions below 1.19, replace "Text.literal" with "new LiteralText".
  7. context.getSource().sendMessage(Text.literal("Called /foo with no arguments"));
  8.  
  9. return 1;
  10. })));
  11. }
  12. }

CommandManager.literal(“foo”) tells brigadier this command has one node, a literal called foo.

To execute this command, you must type /foo, with is case-sensitive. If /Foo, /FoO, /FOO, /fOO or /fooo is typed instead, the command will not run.

If desired, you can also make sure a command is only registered under some specific circumstances, for example, only in the dedicated environment:

  1. public class ExampleCommandMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
  5. if (environment.dedicated) {
  6. ...;
  7. }
  8. });
  9. }
  10. }

Static Imports

In the example above, the use of static imports is used for code simplifying. For a literal this would shorten the statement to literal(“foo”). This also works for getting the value of an argument. This shortens StringArgumentType.getString(ctx, “string”) to getString(ctx, “string”). This also works for Minecraft's own argument types.

Below is an example of some static imports:

  1. // getString(ctx, "string")
  2. import static com.mojang.brigadier.arguments.StringArgumentType.getString;
  3. // word()
  4. import static com.mojang.brigadier.arguments.StringArgumentType.word;
  5. // literal("foo")
  6. import static net.minecraft.server.command.CommandManager.literal;
  7. // argument("bar", word())
  8. import static net.minecraft.server.command.CommandManager.argument;
  9. // Import everything in the CommandManager
  10. import static net.minecraft.server.command.CommandManager.*;

Note: Please be sure you use the literal and argument from CommandManager instead of other classes, or you may have issues with generics when trying to compile.

Brigadier's default arguments are at com.mojang.brigadier.arguments

Minecraft's arguments are in net.minecraft.command.arguments. CommandManager is in the package net.minecraft.server.command.

Requirements

Let's say you have a command that you only want operators to be able to execute. This is where the requires method comes into play. The requires method has one argument of a Predicate<ServerCommandSource> which will supply a ServerCommandSource to test with and determine if the CommandSource can execute the command.

For example this may look like the following:

  1. dispatcher.register(literal("foo")
  2. .requires(source -> source.hasPermissionLevel(4))
  3. .executes(ctx -> {
  4. ctx.getSource().sendFeedback(Text.literal("You are an operator"), false);
  5. return 1;
  6. });

This command will only execute if the source of the command is a level 4 operator at minimum. Otherwise, the command is not registered. Also this has the side effect of not showing this command in tab completion to anyone who is not a level 4 operator. This is also why you cannot tab-complete most commands when you did not enable cheating.

Arguments

Arguments in Brigadier both parse and error check any inputted arguments. Minecraft creates some special argument types for it's own use such as the EntityArgumentType which represents the in-game entity selectors @a, @r, @p, @e[type=!player, limit=1, distance=..2], or an NbtTagArgumentType that parses stringified nbt (snbt) and verifies that the input is the correct syntax.

TODO: Go into more detail on how to use arguments

Redirects

Redirects are Brigadier's form of aliases and subcommands.

Aliases

Below is how Minecraft makes /msg have an alias of /tell and /w.

  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  2. LiteralCommandNode node = dispatcher
  3. .register(literal("msg")
  4. .then(argument("targets", EntityArgumentType.players())
  5. .then(argument("message", MessageArgumentType.message())
  6. .executes(context -> {... /* code ommited */ }))));
  7.  
  8. dispatcher.register(literal("tell")
  9. .redirect(node)); // Alias 1, redirect to main command
  10. dispatcher.register(literal("w")
  11. .redirect(node)); // Alias 2, redirect to main command
  12. }

The redirect tells brigadier to continue parsing the command at another command node.

Chainable Commands

Commands such as /execute as @e[type=player] in the_end run tp ~ ~ ~ are also possible because of redirects. Below is an example of a chainable command:

  1. LiteralCommandNode<ServerCommandSource> rootNode = dispatcher.register(literal("fabric_test"));
  2. LiteralCommandNode<ServerCommandSource> root1 = dispatcher.register(literal("fabric_test")
  3. // You can register under the same literal more than once, it will just register new parts of the branch as shown below if you register a duplicate branch an error will popup in console warning of conflicting commands but one will still work.
  4. .then(literal("extra")
  5. .then(literal("long")
  6. .redirect(rootNode, this::lengthen)) // Return to root for chaining
  7. .then(literal("short")
  8. .redirect(rootNode, this::shorten))) // Return to root for chaining
  9. .then(literal("command")
  10. .executes(ctx -> {
  11. ctx.getSource().sendFeedback(Text.literal("Chainable Command"), false);
  12. return Command.SINGLE_SUCCESS;
  13. })));

The redirect can also modify the CommandSource by use of a “redirect modifier”, which is used as a lambda.

  1. .redirect(rootNode, context -> {
  2. // When redirecting, modify the looking direction of the command source.
  3. return ((ServerCommandSource) context.getSource()).withLookingAt(Vec3ArgumentType.getVec3(context, "pos"));
  4. })

A sub command

To add a sub command, you register the first literal node of the command normally.

  1. dispatcher.register(literal("foo"))

In order to have a sub command, one needs to append the next node to the existing node. This is done use the then(ArgumentBuilder) method which takes in an ArgumentBuilder.

This creates the command foo <bar> as shown below.

  1. dispatcher.register(literal("foo")
  2. .then(literal("bar"))
  3. );

It is advised to indent your code as you add nodes to the command. Usually the indentation corresponds to how many nodes deep one is on the command tree. The new line also makes it visible that another node is being added. There are alternative styles to formatting the tree command that are shown later on in this tutorial.

So let's try running the command

Most likely if you typed /foo bar in game, the command will fail to run. This is because there is no code for the game to execute when all the required arguments have been met. To fix this, you need to tell the game what to run when the command is being executed using the executes(Command) method. Below is how the command should look as an example.

  1. dispatcher.register(literal("foo")
  2. .then(literal("bar")
  3. .executes(context -> {
  4. // For versions below 1.19, use ''new LiteralText''.
  5. context.getSource().sendMessage(Text.literal("Called foo with bar"));
  6.  
  7. return 1;
  8. })
  9. )
  10. );

Custom Argument Types

Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type.

Warning: Custom arguments require client mod installation to be registered correctly! If you are making a server plugin, consider using existing argument type and a custom suggestions provider instead.

For this example we will create a UuidArgumentType.

First create a class which extends ArgumentType. Note that ArgumentType is a generic, so the generic will define what type the ArgumentType will return

  1. public class UuidArgumentType implements ArgumentType<UUID> {

ArgumentType requires you to implement the parse method, the type it returns will match with the Generic type.

@Override
public UUID parse(StringReader reader) throws CommandSyntaxException {

This method is where all of your parsing will occur. Either this method will return the object based on the arguments provided in the command line or throw a CommandSyntaxException and parsing will fail.

Next you will store the current position of the cursor, this is so you can substring out only the specific argument. This will always be at the beginning of where your argument appears on the command line.

  1. int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument.
  2. if (!reader.canRead()) {
  3. reader.skip();
  4. }

Now we grab the entire argument. Depending on your argument type, you may have a different criteria or be similar to some arguments where detecting a { on the command line will require it to be closed. For a UUID we will just figure out what cursor position the argument ends at.

  1. while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position.
  2. reader.skip(); // Tells the StringReader to move it's cursor to the next position.
  3. }

Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command line.

  1. String uuidString = reader.getString().substring(argBeginning, reader.getCursor());

Now finally we check if our argument is correct and parse the specific argument to our liking, and throwing an exception if the parsing fails.

  1. try {
  2. UUID uuid = UUID.fromString(uuidString); // Now our actual logic.
  3. return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on.
  4. } catch (Exception ex) {
  5. // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type.
  6. // Create with context tells Brigadier to supply some context to tell the user where the command failed at.
  7. // Though normal create method could be used.
  8. throw new SimpleCommandExceptionType(Text.literal(ex.getMessage())).createWithContext(reader);
  9. }

The ArgumentType is done, however your client will refuse the parse the argument and throw an error. This is because the server will tell the client what argument type the command node is. And the client will not parse any argument types it does not know how to parse. To fix this we need to register an ArgumentSerializer. Within your ModInitializer. For more complex argument types, you may need to create your own ArgumentSerializer.

  1. ArgumentTypeRegistry.registerArgumentType(new Identifier("tutorial", "uuid"), UuidArgumentType.class, ConstantArgumentSerializer.of(UuidArgumentType::uuid));
  2. // The argument should be what will create the ArgumentType.

And here is the whole ArgumentType:

UuidArgumentType.java
  1. import com.mojang.brigadier.StringReader;
  2. import com.mojang.brigadier.arguments.ArgumentType;
  3. import com.mojang.brigadier.context.CommandContext;
  4. import com.mojang.brigadier.exceptions.CommandSyntaxException;
  5. import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
  6. import net.minecraft.text.Text;
  7. import net.minecraft.util.SystemUtil;
  8.  
  9. import java.util.ArrayList;
  10. import java.util.Collection;
  11. import java.util.UUID;
  12.  
  13. /**
  14.  * Represents an ArgumentType that will return a UUID.
  15.  */
  16. public class UuidArgumentType implements ArgumentType<UUID> {
  17. public static UuidArgumentType uuid() {
  18. return new UuidArgumentType();
  19. }
  20.  
  21. public static <S> UUID getUuid(String name, CommandContext<S> context) {
  22. // Note that you should assume the CommandSource wrapped inside of the CommandContext will always be a generic type.
  23. // If you need to access the ServerCommandSource make sure you verify the source is a server command source before casting.
  24. return context.getArgument(name, UUID.class);
  25. }
  26.  
  27. private static final Collection<String> EXAMPLES = SystemUtil.consume(new ArrayList<>(), list -> {
  28. list.add("765e5d33-c991-454f-8775-b6a7a394c097"); // i509VCB: Username The_1_gamers
  29. list.add("069a79f4-44e9-4726-a5be-fca90e38aaf5"); // Notch
  30. list.add("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"); // Dinnerbone
  31. });
  32.  
  33. @Override
  34. public UUID parse(StringReader reader) throws CommandSyntaxException {
  35. int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument.
  36. if (!reader.canRead()) {
  37. reader.skip();
  38. }
  39.  
  40. // Now we check the contents of the argument till either we hit the end of the command line (When canRead becomes false)
  41. // Otherwise we go till reach reach a space, which signifies the next argument
  42. while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position.
  43. reader.skip(); // Tells the StringReader to move it's cursor to the next position.
  44. }
  45.  
  46. // Now we substring the specific part we want to see using the starting cursor position and the ends where the next argument starts.
  47. String uuidString = reader.getString().substring(argBeginning, reader.getCursor());
  48. try {
  49. UUID uuid = UUID.fromString(uuidString); // Now our actual logic.
  50. return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on.
  51. } catch (Exception ex) {
  52. // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type.
  53. // Create with context tells Brigadier to supply some context to tell the user where the command failed at.
  54. // Though normal create method could be used.
  55. throw new SimpleCommandExceptionType(Text.literal(ex.getMessage())).createWithContext(reader);
  56. }
  57. }
  58.  
  59. @Override
  60. public Collection<String> getExamples() { // Brigadier has support to show examples for what the argument should look like, this should contain a Collection of only the argument this type will return. This is mainly used to calculate ambiguous commands which share the exact same
  61. return EXAMPLES;
  62. }
  63. }

Advanced concepts

Below are links to the articles about more complex concepts used in brigadier.

Page Description
Exceptions Fail execution of a command with a descriptive message and in certain contexts.
Suggestions Suggesting command input for the client.
Redirects Allow use of aliases or repeating elements to execute commands.
Custom Argument Types Parse your own arguments into your own objects.

FAQ

Why does my command not compile

There are two immediate possibilities for why this could occur.

Catch or throw a CommandSyntaxException

The solution to this issue is to make the run or suggest methods throw a CommandSyntaxException. Brigadier will handle the checked exceptions and forward the proper error message in game for you.

Issues with generics

You may have an issue with generic types once in a while. Verify you are using CommandManager.literal(…) or CommandManager.argument(…) instead LiteralArgumentBuilder or RequiredArgumentBuilder in your static imports.

Can I register client side commands?

Fabric has a ClientCommandManager that can be used to register client side commands.

Dark Arts

A few things we don't recommend, but are possible.

Can I register commands in runtime?

You can do this but it is not recommended. You would get the CommandManager from the server and add anything commands you wish to it's CommandDispatcher.

After that you need to send the command tree to every player again using CommandManager.sendCommandTree(ServerPlayerEntity). This is required because the client locally caches the command tree it receives during login (or when operator packets are sent) for local completions rich error messages.

Can I unregister commands in runtime?

You can also do this, however it is much less stable than registering commands and could cause unwanted side effects. To keep things simple, you need to use reflection on brigadier and remove the nodes. After this, you need to send the command tree to every player again using sendCommandTree(ServerPlayerEntity). If you don't send the updated command tree, the client may think a command still exists, even though the server will fail execution.

Some example commands examples

These examples are available under the “CC Attribution-Noncommercial-Share Alike 4.0 International” license, which is the current license of the other wiki articles.

Broadcast a message

  1. public final class BroadCastCommand {
  2. public static void register(CommandDispatcher<ServerCommandSource> dispatcher){
  3. dispatcher.register(literal("broadcast")
  4. .requires(source -> source.hasPermissionLevel(2)) // Must be a game master to use the command. Command will not show up in tab completion or execute to non operators or any operator that is permission level 1.
  5. .then(argument("color", ColorArgumentType.color())
  6. .then(argument("message", greedyString())
  7. .executes(ctx -> broadcast(ctx.getSource(), getColor(ctx, "color"), getString(ctx, "message")))))); // You can deal with the arguments out here and pipe them into the command.
  8. }
  9.  
  10. public static int broadcast(ServerCommandSource source, Formatting formatting, String message) {
  11. final Text text = Text.literal(message).formatted(formatting);
  12.  
  13. source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, MessageType.CHAT, source.getPlayer().getUuid());
  14. return Command.SINGLE_SUCCESS; // Success
  15. }
  16. }

In your initializer:

  1. public class ExampleMod implements ModInitializer{
  2. @Override
  3. public void onInitialize() {
  4. ...
  5.  
  6. CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> register(dispatcher));
  7. }
  8. }

In the examples below, registrations in the callbacks are ommited.

/giveMeDiamond

First the basic code where we register “giveMeDiamond” as a literal and then an executes block to tell the dispatcher which method to run.

  1. public static LiteralCommandNode register(CommandDispatcher<ServerCommandSource> dispatcher) { // You can also return a LiteralCommandNode for use with possible redirects
  2. return dispatcher.register(literal("giveMeDiamond")
  3. .executes(ctx -> giveDiamond(ctx)));
  4. }

Then since we only want to give to players, we check if the CommandSource is a player. But we can use getPlayer and do both at the same time and throw an error if the source is not a player.

  1. public static int giveDiamond(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
  2. final ServerCommandSource source = ctx.getSource();
  3.  
  4. final PlayerEntity self = source.getPlayer(); // If not a player than the command ends

Then we add to the player's inventory, with a check to see if the inventory is full:

  1. if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){
  2. throw new SimpleCommandExceptionType(Text.translatable("inventory.isfull")).create();
  3. }
  4.  
  5. return 1;
  6. }

Antioch

…lobbest thou thy Holy Hand Grenade of Antioch towards thy foe. who being naughty in My sight, shall snuff it.

Aside from the joke this command summons a primed TNT to a specified location or the location of the sender's cursor.

First create an entry into the CommandDispatcher that takes a literal of antioch with an optional argument of the location to summon the entity at.

  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  2. dispatcher.register(literal("antioch")
  3. .then(required("location", BlockPosArgumentType.blockPos()
  4. .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, "location")))))
  5. .executes(ctx -> antioch(ctx.getSource(), null)));
  6. }

Then the creation and messages behind the joke.

  1. public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException {
  2. if(blockPos == null) {
  3. // For the case of no inputted argument we calculate the cursor position of the player or throw an error if the nearest position is too far or is outside of the world.
  4. // This class is used as an example and actually doesn't exist yet.
  5. blockPos = LocationUtil.calculateCursorOrThrow(source, source.getRotation());
  6. }
  7.  
  8. final TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null);
  9. tnt.setFuse(3);
  10.  
  11. source.getServer().getPlayerManager().broadcastChatMessage(Text.literal("...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe", MessageType.CHAT, UUID.randomUUID()).formatted(Formatting.RED), false);
  12. source.getServer().getPlayerManager().broadcastChatMessage(Text.literal("who being naughty in My sight, shall snuff it.", MessageType.CHAT, UUID.randomUUID()).formatted(Formatting.RED), false);
  13. source.getWorld().spawnEntity(tnt);
  14. return 1;
  15. }
tutorial/commands.1659925054.txt.gz · Last modified: 2022/08/08 02:17 by solidblock