User Tools

Site Tools


Sidebar

Setup

Basics

Items and Item Groups

Blocks and Block Entities

Entities

World Generation

Miscellaneous

Advanced

Documentation

Examples


Fabric Сontributors

If you'd like to contribute to Fabric, you might be interested in these links:


Extremely Strange People


Wiki Meta

  • Wiki Meta - Starting point for contributing to the wiki
  • Wiki Agenda - See what is on the current agenda, and what other contributors are currently working on.
tutorial:commands

Creating Commands

Creating commands can allow a mod developer to add functionality that a player can use through a command. This tutorial will teach you how to register commands, and the command structure of Brigadier along with some more advanced commands structures.

Registering Commands

If you just want to see how to register commands you've come to the right place here.

Registering commands is done through CommandRegistry with the register method.

The register method specifies two arguments, the dedicated flag and a consumer representing the CommandDispatcher. These methods should be placed in your mod's initializer.

The dedicated flag if set to true will tell Fabric to only register the command on a DedicatedServer (if false than the command will register on both the InternalServer and DedicatedServer).

Below are a few examples of how the commands can be registered.

  1. CommandRegistry.INSTANCE.register(false, dispatcher -> TutorialCommands.register(dispatcher)); // All commands are registered in a single class that references every command.
  2.  
  3. CommandRegistry.INSTANCE.register(false, dispatcher -> { // You can also just reference every single class also. There is also the alternative of just using CommandRegistry
  4. TutorialCommand.register(dispatcher);
  5. TutorialHelpCommand.register(dispatcher);
  6. });
  7.  
  8. CommandRegistry.INSTANCE.register(true, dispatcher -> { // Or directly registering the command to the dispatcher.
  9. dispatcher.register(LiteralArgumentBuilder.literal("tutorial").executes(ctx -> execute(ctx)));
  10. });

A very basic command

Wait isn't this the exact same command from the Brigadier tutorial? Well yes it is but it is here to help explain the structure of a command.

  1. // The root of the command. This must be a literal argument.
  2. dispatcher.register(CommandManager.literal("foo")
  3. // Then add an argument named bar that is an integer
  4. .then(CommandManager.argument("bar", integer())
  5. // The command to be executed if the command "foo" is entered with the argument "bar"
  6. .executes(ctx -> {
  7. System.out.println("Bar is " + IntArgumentType.getInteger(ctx, "bar"));
  8. // Return a result. -1 is failure, 0 is a pass and 1 is success.
  9. return 1;
  10. }))
  11. // The command "foo" to execute if there are no arguments.
  12. .executes(ctx -> {
  13. System.out.println("Called foo with no arguments");
  14. return 1;
  15. })
  16. );

The main process registers the command “foo” (Root Node) with an optional argument of “bar” (Child node). Since the root node must be literal, The sender must enter the exact same sequence of letters to execute the command, so “Foo”, “fOo” or “fooo” will not execute the command.

Brigadier Explained

Brigadier starts with the CommandDispatcher which should be thought more as a tree rather than a list of methods. The trunk of the tree is the CommandDispatcher. The register(LiteralArgumentBuilder) methods specify the beginning of branches with the following then methods specifying the shape of length of the branches. The executes blocks can be seen at the leaves of the tree where it ends and also supplies the outcome of the system.

The execute blocks specify the command to be ran. As Brigadier's Command is a FunctionalInterface you can use lambdas to specify commands.

CommandContexts

When a command is ran, Brigadier provides a CommandContext to the command that is ran. The CommandContext contains all arguments and other objects such as the inputted String and the Command Source (ServerCommandSource in Minecraft's implementation).

Arguments

The arguments in Brigadier both parse and error check any inputted arguments. Minecraft creates some special arguments 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 NBT and verifies that the input is the correct syntax.

You could do the long method of typing CommandManager.literal(“foo”) and it would work, but you can statically import the arguments and shorten that to literal(“foo”). This also works for getting arguments, which shortens the already long StringArgumentType.getString(ctx, “string”) to getString(ctx, “string”). This also works for Minecraft's arguments.

And your imports would look something like this:

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

Brigadier's default arguments are located in com.mojang.brigadier.arguments

Minecraft's arguments hide in net.minecraft.command.arguments and the CommandManager is at net.minecraft.server.command

Suggestions

Suggestions can be provided to the client to recommend what to input into the command. This is used for Scoreboards and Loot Tables ingame. The game stores these in the SuggestionProviders. A few examples of Minecraft's built in suggestions providers are below

SUMMONABLE_ENTITIES
AVAILIBLE_SOUNDS
ALL_RECIPES
ASK_SERVER

Loot tables specify their own SuggestionProvider inside LootCommand for example.

The example below is a dynamically changing SuggestionProvider that lists several words for a StringArgumentType to demonstrate how it works:

  1. public static SuggestionProvider<ServerCommandSource> suggestedStrings() {
  2. return (ctx, builder) -> getSuggestionsBuilder(builder, /*Access to a list here*/);
  3. }
  4.  
  5. private static CompletableFuture<Suggestions> getSuggestionsBuilder(SuggestionsBuilder builder, List<String> list) {
  6. String remaining = builder.getRemaining().toLowerCase(Locale.ROOT);
  7.  
  8. if(list.isEmpty()) { // If the list is empty then return no suggestions
  9. return Suggestions.empty(); // No suggestions
  10. }
  11.  
  12. for (String str : list) { // Iterate through the supplied list
  13. if (str.toLowerCase(Locale.ROOT).startsWith(remaining)) {
  14. builder.suggest(str); // Add every single entry to suggestions list.
  15. }
  16. }
  17. return builder.buildFuture(); // Create the CompletableFuture containing all the suggestions
  18. }

The SuggestionProvider is a FunctionalInterface that returns a CompletableFuture containing a list of suggestions. These suggestions are given to client as a command is typed and can be changed while server is running. The SuggestionProvider provides a CommandContext and a SuggestionBuilder to determine all the suggestions. The CommandSource can also be taken into account during the suggestion creation process as it is available through the CommandContext.

Though remember these are suggestions. The inputted command may not contain an argument you suggested so you still have to parse check inside the command to see if the argument is what you want if it's a String for example and parsers may still throw exceptions if an invalid syntax is inputted.

To use the suggestion you would append it right after the argument you want to recommend arguments for. This can be any argument and the normal client side exception popups will still work. Note this cannot be applied to literals.

  1. argument(argumentName, word())
  2. .suggests(CompletionProviders.suggestedStrings())
  3. .then(/*Rest of the command*/));

Requires

Lets say you have a command 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(new LiteralText("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. If the predicate returns false, then the command will not execute. Also this has the side effect of not showing this command in tab completion to anyone who is not a level 4 operator.

Exceptions

Brigadier supports command exceptions which can be used to end a command such as if an argument didn't parse properly or the command failed to execute.

All the exceptions from Brigadier are based on the CommandSyntaxException. The two main types of exceptions Brigadier provides are Dynamic and Simple exception types, of which you must create() the exception to throw it. These exceptions also allow you to specify the context in which the exception was thrown using createWithContext(ImmutableStringReader). Though this can only be used with a custom parser. These can be defined and thrown under certain scenarios during the command. Below is a coin flip command to show an example of exceptions in use.

  1. dispatcher.register(CommandManager.literal("coinflip")
  2. .executes(ctx -> {
  3. Random random = new Random();
  4.  
  5. if(random.nextBoolean()) { // If heads succeed.
  6. ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads"))
  7. return Command.SINGLE_SUCCESS;
  8. }
  9. throw new SimpleCommandExceptionType(new TranslateableText("coin.flip.tails")).create(); // Oh no tails, you lose.
  10. }));

Though you are not just limited to a single type of exception as Brigadier also supplies Dynamic exceptions.

  1. DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> {
  2. return new LiteralText("The name: " + (String) name + " has been used");
  3. });

There are more Dynamic exception types which each take a different amount of arguments into account (Dynamic2CommandExceptionType, Dynamic3CommandExceptionType, Dynamic4CommandExceptionType, DynamicNCommandExceptionType). You should remember that the Dynamic exceptions takes an object as an argument so you may have to cast the argument for your use.

Redirects (Aliases)

Redirects are Brigadier's form of aliases. Below is how Minecraft handles /msg have an alias of /tell and /w.

  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  2. LiteralCommandNode node = registerMain(dispatcher); // Registers main command
  3. dispatcher.register(literal("tell")
  4. .redirect(node)); // Alias 1, redirect to main command
  5. dispatcher.register(literal("w")
  6. .redirect(node)); // Alias 2, redirect to main command
  7. }
  8.  
  9. public static LiteralCommandNode registerMain(CommandDispatcher<ServerCommandSource> dispatcher) {
  10. return dispatcher.register(literal("msg")
  11. .then(argument("targets", EntityArgumentType.players())
  12. .then(argument("message", MessageArgumentType.message())
  13. .executes(ctx -> {
  14. return execute(ctx.getSource(), getPlayers(ctx, "targets"), getMessage(ctx, "message"));
  15. }))));
  16. }

The redirect registers a branch into the command tree, where the dispatcher is told when executing a redirected command to instead look on a different branch for more arguments and executes blocks. The literal argument that the redirect is placed on will also rename first literal on the targeted branch in the new command.

Redirects do not work in shortened aliases such as /mod thing <argument> having an alias of /thing <argument> as Brigadier does not allow forwarding nodes with children. Though you could use alternative methods to reduce the amount of duplicate code for this case.

Redirects (Chainable Commands)

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

  1. LiteralCommandNode<ServerCommandSource> root = 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(root)) // Return to root for chaining
  7. .then(literal("short")
  8. .redirect(root))) // Return to root for chaining
  9. .then(literal("command")
  10. .executes(ctx -> {
  11. ctx.getSource().sendFeedback(new LiteralText("Chainable Command"), false);
  12. return Command.SINGLE_SUCCESS;
  13. })));

The redirect can also modify the CommandSource.

  1. .redirect(rootNode, (commandContext_1x) -> {
  2. return ((ServerCommandSource) commandContext_1x.getSource()).withLookingAt(Vec3ArgumentType.getVec3(commandContext_1x, "pos"));
  3. })

ServerCommandSource

What if you wanted a command that the CommandSource must be an entity to execute? The ServerCommandSource provides this option with a couple of methods

  1. ServerCommandSource source = ctx.getSource();
  2. // Get the source. This will always work.
  3.  
  4. Entity sender = source.getEntity();
  5. // Unchecked, may be null if the sender was the console.
  6.  
  7. Entity sender2 = source.getEntityOrThrow();
  8. // Will end the command if the source of the command was not an Entity.
  9. // The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity.
  10. // This method will require your methods to throw a CommandSyntaxException.
  11. // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player.
  12.  
  13. ServerPlayerEntity player = source.getPlayer();
  14. // 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

The ServerCommandSource also provides other information about the sender of the command.

  1. source.getPosition();
  2. // 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.
  3.  
  4. source.getWorld();
  5. // Get's the world the sender is within. The console's world is the same as the default spawn world.
  6.  
  7. source.getRotation();
  8. // Get's the sender's rotation as a Vec2f.
  9.  
  10. source.getMinecraftServer();
  11. // Access to the instance of the MinecraftServer this command was ran on.
  12.  
  13. source.getName();
  14. // 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"
  15.  
  16. source.hasPermissionLevel(int level);
  17. // 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)

Some actual examples

Just a few to show:

Broadcast a message

  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher){
  2. dispatcher.register(literal("broadcast")
  3. .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 op's or any op that is permission level 1.
  4. .then(argument("color", ColorArgumentType.color())
  5. .then(argument("message", greedyString())
  6. .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.
  7. }
  8.  
  9. public static int broadcast(ServerCommandSource source, Formatting formatting, String message) {
  10. Text text = new LiteralText(message).formatting(formatting);
  11.  
  12. source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, false);
  13. return Command.SINGLE_SUCCESS; // Success
  14. }

/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. ServerCommandSource source = ctx.getSource();
  3.  
  4. 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(new TranslateableText("inventory.isfull")).create();
  3. }
  4. return 1;
  5. }

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.  
  3. if(blockPos==null) {
  4. blockPos = LocationUtil.calculateCursorOrThrow(source, source.getRotation()); // 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. This class is used as an example and actually doesn't exist yet.
  5. }
  6.  
  7. TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null);
  8.  
  9. source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe").formatting(Formatting.RED), false);
  10. source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("who being naughty in My sight, shall snuff it.").formatting(Formatting.RED), false);
  11. source.getWorld().spawnEntity(tnt);
  12. return 1;
  13. }

Finding Biomes via Command

This example shows examples of redirects, exceptions, suggestions and a tiny bit of text. Note this command when used works but can take a bit of time to work similarly to /locate

  1. public class CommandLocateBiome {
  2. // First make method to register
  3. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  4. LiteralCommandNode<ServerCommandSource> basenode = dispatcher.register(literal("findBiome")
  5. .then(argument("biome_identifier", identifier()).suggests(BiomeCompletionProvider.BIOMES) // We use Biome suggestions for identifier argument
  6. .then(argument("distance", integer(0, 20000))
  7. .executes(ctx -> execute(ctx.getSource(), getIdentifier(ctx, "biome_identifier"), getInteger(ctx, "distance"))))
  8. .executes(ctx -> execute(ctx.getSource(), getIdentifier(ctx, "biome_identifier"), 1000))));
  9. // Register redirect
  10. dispatcher.register(literal("biome")
  11. .redirect(basenode));
  12. }
  13. // Beginning of the method
  14. private static int execute(ServerCommandSource source, Identifier biomeId, int range) throws CommandSyntaxException {
  15. Biome biome = Registry.BIOME.get(biomeId);
  16.  
  17. if(biome == null) { // Since the argument is an Identifier we need to check if the identifier actually exists in the registry
  18. throw new SimpleCommandExceptionType(new TranslatableText("biome.not.exist", biomeId)).create();
  19. }
  20.  
  21. List<Biome> bio = new ArrayList<Biome>();
  22. bio.add(biome);
  23.  
  24. ServerWorld world = source.getWorld();
  25.  
  26. BiomeSource bsource = world.getChunkManager().getChunkGenerator().getBiomeSource();
  27.  
  28. BlockPos loc = new BlockPos(source.getPosition());
  29. // Now here is the heaviest part of the method.
  30. BlockPos pos = bsource.locateBiome(loc.getX(), loc.getZ(), range, bio, new Random(world.getSeed()));
  31.  
  32. // Since this method can return null if it failed to find a biome
  33. if(pos == null) {
  34. throw new SimpleCommandExceptionType(new TranslatableText("biome.notfound", biome.getTranslationKey())).create();
  35. }
  36.  
  37. int distance = MathHelper.floor(getDistance(loc.getX(), loc.getZ(), pos.getX(), pos.getZ()));
  38. // Popup text that can suggest commands. This is the exact same system that /locate uses.
  39. Text teleportButtonPopup = Texts.bracketed(new TranslatableText("chat.coordinates", new Object[] { pos.getX(), "~", pos.getZ()})).styled((style_1x) -> {
  40. style_1x.setColor(Formatting.GREEN).setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/tp @s " + pos.getX() + " ~ " + pos.getZ())).setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableText("chat.coordinates.tooltip", new Object[0])));
  41. });
  42.  
  43. source.sendFeedback(new TranslatableText("commands.locate.success", new Object[] { new TranslatableText(Registry.BIOME.get(biomeId).getTranslationKey()), teleportButtonPopup, distance}), false);
  44.  
  45. return 1;
  46. }
  47. // Just a normal old 2d distance method.
  48. private static float getDistance(int int_1, int int_2, int int_3, int int_4) {
  49. int int_5 = int_3 - int_1;
  50. int int_6 = int_4 - int_2;
  51.  
  52. return MathHelper.sqrt((float) (int_5 * int_5 + int_6 * int_6));
  53. }
  54.  
  55.  
  56.  
  57. public static class BiomeCompletionProvider {
  58. // This provides suggestions of what biomes can be selected. Since this uses the registry, mods that add new biomes will work without modification.
  59. public static final SuggestionProvider<ServerCommandSource> BIOMES = SuggestionProviders.register(new Identifier("biomes"), (ctx, builder) -> {
  60. Registry.BIOME.getIds().stream().forEach(identifier -> builder.suggest(identifier.toString(), new TranslatableText(Registry.BIOME.get(identifier).getTranslationKey())));
  61. return builder.buildFuture();
  62. });
  63.  
  64. }

Custom Arguments (Coming Soon)

Coming Soon

FAQ

What else can I send feedback to the CommandSource?

You can choose between Brigadier's default LiteralMessage or use any one of Minecraft's Text classes (LiteralText, TranslatableText)

Why does my IDE complain saying that a method executed by my command needs to catch or throw a CommandSyntaxException

The solution to this is just to make the methods throw a CommandSyntaxException down the whole chain as the executes block handles the exceptions.

Can I register commands in run time?

You can do this but it is not reccomended. You would get the instance of the CommandManager and add anything you wish to the CommandDispatcher within it.

After that you will need to send the command tree to every player again using CommandManager.sendCommandTree(PlayerEntity)

Can I unregister commands in run time?

You can also do this but it is very unstable and could cause unwanted side effects. Lets just say it involves a bunch of Reflection.

Once again you will need to send the command tree to every player again using CommandManager.sendCommandTree(PlayerEntity) afterwards.

Can I register client side commands?

Well Fabric currently doesn't support this natively but there is a mod by the Cotton team that adds this functionality where the commands do not run on the server and only on the client: https://github.com/CottonMC/ClientCommands

If you only want the command to only be visible on the integrated server like /publish then you would modify your requires block:

dispatcher.register(literal("publish")
    // The permission level 4 on integrated server is the equivalent of having cheats enabled.
    .requires(source -> source.getMinecraftServer().isSinglePlayer() && source.hasPermissionLevel(4)));

I want to access X from my mod when a command is ran

This is going to require a way to statically access your mod with a getInstance call. Below is a very simple instance system you can place in your mod

private static Type instance;
 
static { // Static option on class initalize for seperate API class for example
   instance = new Type();
}
 
public void onInitalize() { // If within your mod initalizer
   instance = this;
}
 
public static Type getInstance() {
    return instance;
}
tutorial/commands.txt · Last modified: 2019/09/18 00:51 by i509vcb