Flags
A flag is a named modifier (--silent, -s), as opposed to a positional
argument. Flags can be given in any order among themselves,
but must come after every positional argument and sub-command.
Flags covers Brigadier primitives and Paper's argument types — intFlag,
doubleFlag, boolFlag, stringFlag, worldFlag, playerFlag,
itemStackFlag, componentFlag and more. Anything not covered has
Flags.argFlag(name, argumentType).
Declaring
this.addFlag(Flags.boolFlag("silent").alias("s"));
this.addFlag(Flags.intFlag("count", 1, 64).alias("c").defaultTo(1).suggests("1", "16", "64"));
Suggestions can carry hover tooltips, the way vanilla annotates registry keys:
Flags.stringFlag("mode").suggests(new LinkedHashMap<>() {{
put("fast", Component.text("Skips validation"));
put("safe", Component.text("Checks every entry"));
}});
Reading them back
dispatch.hasFlag("verbose"); // explicitly provided?
dispatch.getFlagValue("count", Integer.class); // value or declared default
dispatch.getFlagValue("count", Integer.class, 5); // value or this fallback
dispatch.getOptionalFlagValue("message", String.class); // Optional
getFlagValue is @Nullable — a flag may declare an explicit null default — so
prefer the three-argument overload when you are about to unbox a primitive.
Two limits worth knowing
Flags must come last
Flag nodes branch only into other flag nodes. So /cmd sub --verbose parses and
/cmd --verbose sub does not.
Keep the flag count low
Every ordered subset of the flag set becomes a real Brigadier node, so flags can be given in any order — and that tree is sent to every client. Measured, for a single command:
| Boolean flags | Nodes (no positional args) | Nodes (2 positional args) |
|---|---|---|
| 3 | 16 | 33 |
| 4 | 65 | 131 |
| 5 | 326 | 653 |
| 6 | 1957 | — |
Growth is faster than n!. Past four flags the library logs a warning at
startup.
Prefer a single argFlag carrying several options over many boolean flags:
// Instead of --fast --safe --verbose --dry-run
Flags.stringFlag("mode").suggests("fast", "safe", "verbose", "dry-run");
Brigadier redirects would make this O(n), but a redirect creates a child
CommandContext, so every argument lookup would have to walk getChild(). It
is a real change and deliberately deferred.
Ordering is load-bearing internally
Relevant if you are modifying the library itself: ArgumentBuilder#then(child)
calls child.build() immediately and stores an immutable node. Anything
added to a builder after it has been then()'d onto a parent is silently
discarded — no error, just missing nodes. Flag branches are therefore attached
to every argument-level builder before those builders are chained together.
This is the easiest thing in the library to break, and it fails silently.