Skip to content

Instantly share code, notes, and snippets.

@sciwhiz12
Last active April 8, 2022 00:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sciwhiz12/8b258d493c764d2cf009e121bdc654d3 to your computer and use it in GitHub Desktop.
Save sciwhiz12/8b258d493c764d2cf009e121bdc654d3 to your computer and use it in GitHub Desktop.
Explanation of Brigadier, in two parts; adapted from a private Discord convo when I was explaining this too

Brigadier's main object is CommandDispatcher<S>. This is where the command nodes are registered, and it does the parsing and execution of commands.

The generic type S can be any object; it is used as a context object that is stored in the CommandContext<S> that's passed around to commands/argument types. (MC uses CommandSource, while Janitor uses MessageReceivedEvent).


Brigadier's command model is of a tree. Each node can have children of other nodes, with one node having no parent but all the children (the root node).

There are three types of nodes:

At the root, there's the RootCommandNode<S>, of which only one can exist per tree and all other nodes are a child of. This is auto-created by CommandDispatcher<S> (you can also pass a previously created root node to the constructor to duplicate the tree).

Then there are LiteralCommandNode<S>s, which represent a literal "word" in the command. It has no other special meaning, other than to allow separating branches of the command tree (think of the branches as the commands). All children of RootCommandNode must be a LiteralCommandNode.

Then the last type of command node is the RequiredCommandNode<S>, for accepting some kind of argument in the command. This is used for player-controlled input, such as a number, string, or other object (corresponding to an ArgumentType<T>).


The mechanics of how the parsing of a command tree is done is a bit hard to explain using only words, which is why I usually visualize a tree for it.

An example of a command tree in action:

[root]
 +-> 'time' 
 |   +--> 'query' --> [executes]
 |   +--> 'add'   --> (number)   --> [executes]
 |   +--> 'set'   --> (number)   --> [executes]
 |   +--> 'fail'
 |
 +-> 'ban'
     +-> <requires OP lvl 4> 
         +-> (player username)
             +-> [executes]
             +--> (string)   --> [executes]

In this visualization, there's two commands: time and ban. (Brigadier has no concept of prefix, inputs should have the prefix removed already.)

The [root] represents the singular RootCommandNode. Each 'literal' is a LiteralCommandNode with the given literal word (this case, it's "literal"). Each (number) represents a RequiredCommandNode with the given argument needed.

At the end of a branch of the tree, an execution node (the executes() call) must be present to successfully parse

Each word in the command must correspond to a node in the command tree. If it reaches the end of the command text, and it stopped on a corresponding node, and there is an [executes] node (the executes(ctx -> {...})), then a command is successfully parsed and it can be executed. If one of the above conditions do not hold, then the command parsing fails.

(NOTE: there is no [executes] node in Brigader; execution information is stored in the same command node object. For the purposes of the visualization, I have it visualized.)

Parsing starts at the root command node, and travels down, searching for a compatible node for the command text at the position being parsed.


Here's a few examples of the former command tree in action:

= time set 10000 =

  • time corresponds to a node? -- YES, literal node time. Continue down that node
  • set corresponds to a node? -- YES, literal node set. Continue down that node
  • 10000 corresponds to a node? -- YES, required node (number), requiring a number. Continue down that node
  • Reached end of string. Any [executes] nodes? -- YES.
  • Parsing is successful. Command can be executed.

= time fail =

  • time corresponds to a node? -- YES, literal node time. Continue down that node
  • fail corresponds to a node? -- YES, literal node fail. Continue down that node
  • Reached end of string. Any [executes] nodes? -- NO.
  • Parsing failed; no [executes] node.

= ban SomePlayerFella =

  • ban corresponds to a node? -- YES, literal node time. Continue down that node
  • SomePlayerFella corresponds to a node? -- YES, required node (player username), requiring a player's username. Continue down that node
  • Reached end of string. Any [executes] nodes? -- YES.
  • *Parsing is successful. Command can be executed.

That's the basics of Brigadier's command tree system. (There's also redirects, and forking [not sure what this does yet], and suggestions, and others)


LiteralCommandNode and RequiredCommandNode are constructed using LiteralArgumentBuilder<S, T> and RequiredArgumentBuilder<S, T>, respectively. T is the same type as the object, this is used because they parent ArgumentBuilder<S, T> so the builder methods (then(...), executes(...)) return the same object/type and generics instead of a generic ArgumentBuilder.

Brigadier has builders for the two types of nodes: LiteralArgumentBuilder and RequiredArgumentBuilder (both of which extend ArgumentBuilder)

LiteralArgumentBuilder.literal(String) just returns an builder for a LiteralCommandNode you can add children to the builders using ArgumentBuilder#then(ArgumentBuilder), and the aforementioned [executes] node using ArgumentBuilder#executes(...)

RequiredArgumentBuilder.argument(String, ArgumentType) is more complicated, and requires a bit of touching on arguments:

Brigadier arguments are represented by an ArgumentType; there are default ones available such as StringArgumentType, IntegerArgumentType, BooleanArgumentType, etc. The default ones have static methods to get instances of these argument types, and also to get the argument value from a CommandContext; (for ex. StringArgument.word() accepts a word, StringArgument.greedyString() accepts a string that ranges from the current position all the way to the end of the command)

When building with RequiredArgumentBuilder,argument(String, ArgumentType), you pass in two things: the argument name (can be any string), and an instance of the ArgumentType you want to get.

To retrieve the argument from the CommandContext, you either do CommandContext#getArgument(String, Class) (where class is the expected argument type, for e.g. StringArgument then it would be String.class); or use the static helper method of the argument you are using (e.g. StringArgument.getString(CommandContext, String))

For example, to create the previously given command tree, here's the java code for it (literal(...) and argument(...) are the aforementioned two methods, but wrapped in a helper to hardcode the generics)

literal("time")
    .then(literal("query")
        .executes(ctx -> { /* ... */ return 1;})
    )
    .then(literal("add")
        .then(argument("number", IntegerArgumentType.integer())
            .executes(ctx -> { /* ... */ return 1;})
        )
    )
    .then(literal("set")
        .then(argument("number", IntegerArgumentType.integer())
            .executes(ctx -> { /* ... */ return 1;})
        )
    );
literal("ban")
    .then(argument("username", StringArgumentType.word())
        .executes(ctx -> { /* ... */ return 1;})
        .then(argument("reason", StringArgumentType.greedyString())
            .executes(ctx -> { /* ... */ return 1;})
        )
    );

(i usually format my command trees like seen here, to help in readability and to better understand what node follows from what)

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