Skip to content

Instantly share code, notes, and snippets.

@falkreon

falkreon/brigadier.md Secret

Last active Apr 25, 2021
Embed
What would you like to do?
How to Brigadier Without Making a Mess

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

This comment has been minimized.

Copy link

@liach 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

This comment has been minimized.

Copy link

@i509VCB 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

This comment has been minimized.

Copy link
Owner Author

@falkreon 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.

@TehcJS

This comment has been minimized.

Copy link

@TehcJS TehcJS commented Aug 2, 2020

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

@falkreon

This comment has been minimized.

Copy link
Owner Author

@falkreon 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!

@MatthewJDavison

This comment has been minimized.

Copy link

@MatthewJDavison MatthewJDavison 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

This comment has been minimized.

Copy link

@i509VCB 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.

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