Skip to content

Instantly share code, notes, and snippets.

@falkreon
Last active March 8, 2024 13:15
  • Star 30 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save falkreon/f58bb91e45ba558bc7fd827e81c6cb45 to your computer and use it in GitHub Desktop.
How to Brigadier Without Making a Mess

UPDATE: I made this markdown executable!

Please refer to https://github.com/falkreon/BrigadierAdvice for more tricks and tips!

How to Brigadier Without Making a Mess

So you may be familiar with some fluent command builder syntax for Brigadier, but let me tell you, it looks bad.

So Let's Talk About Nodes

The brigadier tree is a tree. Just to name a subset, let's look at /weather and /kill, and see what it would take to actually make a clean-looking command tree for them if they didn't exist.

     "/"
    /   \
 kill   weather
        /  |  \
   clear rain  thunder

(we'll talk about the optional arguments for kill later.)

Most commands start with the slash (not part of brigadier) followed by the command name. This is a LiteralCommandNode. In fact, any bare token you see is probably a literal. The clear, rain, and thunder are also literal nodes, for example. So at least for now, we've got a whole tree that just contains the root node and some literal nodes.

Some nodes on the tree have Command objects hung on them. You have a lot of flexibility on where to hang them. for kill, you'd definitely need to hang the KillCommand directly on the kill node, because it's the only one. But for weather, you could either hang one Command each onto clear, rain, and thunder, or you could hang a Command onto weather that could handle all three. If the "leaf" node doesn't contain an executes clause, Brigadier will walk one level back up the tree and check again, and keep doing so until it finds a command. If it walks all the way up to the root without finding one, it'll grab the "command usage" for that command, and print out an error with it.

It happens that vanilla puts the executes on clear, rain, and thunder for the weather command, so that's what I'll show here.

Building Them Cleanly

How do we put these nodes together?

CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
    //Make some new nodes
    LiteralCommandNode<ServerCommandSource> killNode = CommandManager
	.literal("kill")
        .executes(new KillCommand())
        .build();

    LiteralCommandNode<ServerCommandSource> weatherNode = CommandManager
	.literal("weather")
        .build();

    LiteralCommandNode<ServerCommandSource> clearNode = CommandManager
	.literal("clear")
	.executes(WeatherCommand::clear)
        .build();
    
    LiteralCommandNode<ServerCommandSource> rainNode = CommandManager
	.literal("rain")
	.executes(WeatherCommand::rain)
        .build();
    
    LiteralCommandNode<ServerCommandSource> thunderNode = CommandManager
	.literal("thunder")
	.executes(WeatherCommand::thunder)
        .build();
    
    //Now stitch them together
    
    //usage: /kill
    dispatcher.getRoot().addChild(killNode);
    
    //usage: /weather [clear|rain|thunder]
    dispatcher.getRoot().addChild(weatherNode);
    weatherNode.addChild(clearNode);
    weatherNode.addChild(rainNode);
    weatherNode.addChild(thunderNode);
});

It's often most readable to just build out the individual nodes, trying to follow branches down as coherently as possible, and then at the end use addChild to tell Brigadier - and other programmers - how they're connected.

Making Commands

Command is a functional interface, so you could use a lambda, but I often find I'd rather have a class for either one command or for a group of related commands:

public class KillCommand implements Command<ServerCommandSource> {
	@Override
	public int run(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
		context.getPlayer().kill(); //Good enough for now!
		return 1; //positive numbers are success! Negative numbers are failure.
	}
}
public class WeatherCommand {
	public static int clear(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
		ServerCommandSource source = context.getSource();
		source.getWorld().getLevelProperties().setClearWeatherTime(6000);
      		source.getWorld().getLevelProperties().setRainTime(0);
      		source.getWorld().getLevelProperties().setThunderTime(0);
      		source.getWorld().getLevelProperties().setRaining(false);
      		source.getWorld().getLevelProperties().setThundering(false);
      		source.sendFeedback(new TranslatableText("commands.weather.set.clear"), true);
		return 6000; //Often we return a positive number related to what we did,
		             //in this case the number of clear-weather ticks we set
	}

	public static int rain(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
		/* impl goes here */
		return 1;
	}
	
	public static int thunder(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
		/* impl goes here */
		return 1;
	}
}
@liach
Copy link

liach commented Sep 24, 2019

the int return is actually the execute store result or the redstone output of the comparator on the command block

@i509VCB
Copy link

i509VCB commented Sep 24, 2019

You should be returning 1 almost all the time unless you have some special logic. Such as /kill returning how many entities it killed in its return result. Or how many entities an entity selector found.

@falkreon
Copy link
Author

falkreon commented Sep 24, 2019

I use 6000 in my example because it is the exact value returned by the actual /weather clear command

edit: I think this is because commands are ultimately expected to be composeable: you should somehow be able to use a command as a parameter for another command. I don't think this is currently possible in any way, but I highly recommend that design philosophy.

@SylvKT
Copy link

SylvKT commented Aug 2, 2020

CommandRegistry is deprecated now. I wouldn't suggest using it anymore.

@falkreon
Copy link
Author

falkreon commented Aug 9, 2020

I will update the gist soon when I have time, but almost all of this remains valid: You get your CommandDispatcher from net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback.register(CommandDispatcher<ServerCommandSource> dispatcher, boolean dedicated), and hang nodes on dispatcher.getRoot() as outlined here.

EDIT: Gist has been updated for fabric commands v1. Thanks for the heads-up!

Copy link

ghost commented Jan 11, 2021

Hello! Can we get some info on how to swap to this system? Previously I've been doing this:

public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
		dispatcher.register((LiteralArgumentBuilder<ServerCommandSource>)CommandManager.literal("greeting").then(CommandManager.argument("greeting message", StringArgumentType.greedyString()).executes((commandContext)->{
			String string = StringArgumentType.getString(commandContext, "greeting message");
			Entity entity = commandContext.getSource().getEntity();
			MinecraftServer minecraftServer = commandContext.getSource().getMinecraftServer();
			if(entity!= null) {
				if(entity instanceof ServerPlayerEntity) {
					TextStream textStream = ((ServerPlayerEntity)entity).getTextStream();
					if(textStream != null) {
						textStream.filterText(string).thenAcceptAsync((optional)->{
							optional.ifPresent((string1)->{
								minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());;
							});
						},minecraftServer);
						return 1;										
					}
				}
				minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());
			} else {
				minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.SYSTEM, Util.NIL_UUID);
			}
			return 1;
																
		})));
	}

This is a "/greeting" command based on minecraft's "/me" command.
I tried the easy way:

LiteralCommandNode<ServerCommandSource> greetingNode = CommandManager
					.literal("greeting")					
					.then(RequiredArgumentBuilder.argument("greeting message", StringArgumentType.greedyString()))
					.executes((commandContext)->{
						String string = StringArgumentType.getString(commandContext, "greeting message");
						Entity entity = commandContext.getSource().getEntity();
						MinecraftServer minecraftServer = commandContext.getSource().getMinecraftServer();
						if(entity!= null) {
							if(entity instanceof ServerPlayerEntity) {
								TextStream textStream = ((ServerPlayerEntity)entity).getTextStream();
								if(textStream != null) {
									textStream.filterText(string).thenAcceptAsync((optional)->{
										optional.ifPresent((string1)->{
											minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());;
										});
									},minecraftServer);
									return 1;										
								}
							}
							minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());
						} else {
							minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.SYSTEM, Util.NIL_UUID);
						}
						return 1;
									
					})
					.build();

but now I just get "Unknown or incomplete command, see below for error" when running it with a string argument and simply "An unexpected error occurred trying to execute that command" without an argument... I'm presuming it's something todo with failing to properly register, as we are no longer registering it via "dispatcher.register()".

I appreciate this post was probably aimed towards more experienced people, but it's being sent towards many people who have no experience with Brigadier as well :)

@i509VCB
Copy link

i509VCB commented Jan 11, 2021

Brigadier is very particular about braces linking between each argument and a literal. The first thing I would check is the usage text (you can get this from command dispatcher or /help in game)

I would also try getting the root node of the command dispatcher after you register and traverse the command node tree to see what nodes are children of other nodes. For an example of this look at fabric-command-api-v1's testmod source set.

@exLeute
Copy link

exLeute commented Nov 30, 2022

Hello! Can we get some info on how to swap to this system? Previously I've been doing this:

public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
		dispatcher.register((LiteralArgumentBuilder<ServerCommandSource>)CommandManager.literal("greeting").then(CommandManager.argument("greeting message", StringArgumentType.greedyString()).executes((commandContext)->{
			String string = StringArgumentType.getString(commandContext, "greeting message");
			Entity entity = commandContext.getSource().getEntity();
			MinecraftServer minecraftServer = commandContext.getSource().getMinecraftServer();
			if(entity!= null) {
				if(entity instanceof ServerPlayerEntity) {
					TextStream textStream = ((ServerPlayerEntity)entity).getTextStream();
					if(textStream != null) {
						textStream.filterText(string).thenAcceptAsync((optional)->{
							optional.ifPresent((string1)->{
								minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());;
							});
						},minecraftServer);
						return 1;										
					}
				}
				minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());
			} else {
				minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.SYSTEM, Util.NIL_UUID);
			}
			return 1;
																
		})));
	}

This is a "/greeting" command based on minecraft's "/me" command. I tried the easy way:

LiteralCommandNode<ServerCommandSource> greetingNode = CommandManager
					.literal("greeting")					
					.then(RequiredArgumentBuilder.argument("greeting message", StringArgumentType.greedyString()))
					.executes((commandContext)->{
						String string = StringArgumentType.getString(commandContext, "greeting message");
						Entity entity = commandContext.getSource().getEntity();
						MinecraftServer minecraftServer = commandContext.getSource().getMinecraftServer();
						if(entity!= null) {
							if(entity instanceof ServerPlayerEntity) {
								TextStream textStream = ((ServerPlayerEntity)entity).getTextStream();
								if(textStream != null) {
									textStream.filterText(string).thenAcceptAsync((optional)->{
										optional.ifPresent((string1)->{
											minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());;
										});
									},minecraftServer);
									return 1;										
								}
							}
							minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.CHAT, entity.getUuid());
						} else {
							minecraftServer.getPlayerManager().broadcastChatMessage(sendMessage(commandContext, string), MessageType.SYSTEM, Util.NIL_UUID);
						}
						return 1;
									
					})
					.build();

but now I just get "Unknown or incomplete command, see below for error" when running it with a string argument and simply "An unexpected error occurred trying to execute that command" without an argument... I'm presuming it's something todo with failing to properly register, as we are no longer registering it via "dispatcher.register()".

This is late, but i wanna leave it for people who will face it again, this code should work with newest version on current moment.

    private static void registerCommands()
    {
        CommandRegistrationCallback.EVENT.register(YourClass::register);
    }

    public static void register(CommandDispatcher<ServerCommandSource> dispatcher,
                                CommandRegistryAccess ignoredCommandRegistryAccess,
                                CommandManager.RegistrationEnvironment ignoredRegistrationEnvironment)
    {
        dispatcher.register(LiteralArgumentBuilder
                .<ServerCommandSource>literal("mainCommand")
                .then(CommandManager.argument("anyArgumentName", IntegerArgumentType.integer(-90, 90))
                        .executes(YourClass::MethodToExecute)));
    }
    
        public static int MethodToExecute(CommandContext<ServerCommandSource> context) {
            Objects.requireNonNull(MinecraftClient.getInstance().player)
                    .setPitch(IntegerArgumentType.getInteger(context, "anyArgumentName"));

            return 1;
    }

@skycatminepokie
Copy link

If the "leaf" node doesn't contain an executes clause, Brigadier will walk one level back up the tree and check again, and keep doing so until it finds a command. If it walks all the way up to the root without finding one, it'll grab the "command usage" for that command, and print out an error with it.

As of 1.20.1, I can't get this to work.

var withdraw = literal("withdraw")
  .executes(this::withdraw)
  .build();
var withdrawAll = literal("all")
  .executes(this::withdrawAll)
  .build();
var points = argument("points", LongArgumentType.longArg(1))
  .build();

root.addChild(withdraw);
  withdraw.addChild(points);
  withdraw.addChild(withdrawAll);

/withdraw 1 yields:

Unknown or incomplete command, see below for error
withdraw 1<--[HERE]

Unsure if this is simply not a thing any more, or if it's my fault, but there it is.

@falkreon
Copy link
Author

falkreon commented Oct 6, 2023

As of 1.20.1, I can't get this to work.

You're right, I think that was a misunderstanding on my part in the first place. At any rate, this gist is outdated, please see https://github.com/falkreon/BrigadierAdvice for 1.19+ examples which are all double-checked for correctness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment