Register commands with Paper's Brigadier API (#3023 phases 1 and 2) - #3036
Merged
Conversation
Replaces the SimpleCommandMap reflection with Brigadier registration, and exposes the CompositeCommand subcommand tree to clients so completions appear while the player types rather than only after enter or TAB. Paper only allows Brigadier registration inside the COMMANDS lifecycle callback, which fires after onEnable returns but before the first server tick - and BentoBox enables its addons on that first tick, so game mode commands do not exist yet while the window is open. Using the registrar afterwards throws "No lifecycle owner context is set", and getDispatcher() on it throws "cannot access the dispatcher in this context". Moving the addon enable earlier is not an option; that was tried and reverted in #2214. The dispatcher instance itself, however, stays the live one, so the registrar captures it inside the window and registers nodes into it as addons come up. Those nodes survive syncCommands() and dispatch correctly for players and console. Brigadier has no API for removing a node, so nodes never capture a CompositeCommand; they resolve it by label on every use. A reload that swaps the command objects therefore leaves nothing stale behind, and a label that goes away is hidden because its requires predicate stops matching. Aliases and the plugin:label form redirect onto the main node instead of duplicating a tree that can run to dozens of nodes per game mode. The console keeps seeing player-only commands so it still gets "this command is only available in-game" rather than Brigadier's generic "unknown command". Guarded by general.brigadier-commands (default true); turning it off restores the command map path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATRqygFLZ5CA3zrWmutx7H
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #3023 (phases 1 and 2).
Replaces the
SimpleCommandMapreflection inCommandsManagerwith Brigadier registration, and exposes theCompositeCommandsubcommand tree to clients so completions appear while the player types rather than only after enter or TAB.Phases 1 and 2 are done together deliberately: the registration shim alone carries the entire risk of the change with none of the player-facing benefit.
The timing problem, and why the dispatcher is captured
Paper only permits Brigadier registration inside the
LifecycleEvents.COMMANDScallback. Probing the real ordering on a 26.2 server gives:completeSetup()is whereenableAddons()runs, so game mode commands such as/islandand/aciddo not exist yet while the lifecycle window is open. Using the registrar after the window closes throwsNo lifecycle owner context is set, and callinggetDispatcher()on it then throwscannot access the dispatcher in this context.Three escape routes were tested and rejected:
CraftServer#syncCommands()does not re-run the COMMANDS lifecycle. Verified directly.What does work: the
CommandDispatcherinstance is the live one the server dispatches against.BrigadierCommandRegistrarcaptures it inside the window and registers nodes into it afterwards as addons come up. Those nodes survivesyncCommands()and dispatch correctly for players and console.Design consequences
CompositeCommand— they resolve it by label on every use. A reload that swaps the command objects leaves nothing stale behind, and a label that goes away is hidden because itsrequirespredicate stops matching.plugin:labelare redirects, not duplicated trees. A game mode admin tree runs to dozens of nodes and every one of them is sent to every client./aifrom console still reports "This command is only available in-game" rather than Brigadier's generic "unknown command".Config
Guarded by
general.brigadier-commands(defaulttrue). Turning it off restores the legacy command map path, which is kept intact as the fallback and is also used automatically if the registrar cannot hook.Testing
Verified on a Paper 26.2 test server with AcidIsland, AOneBlock, CaveBlock, Biomes, Challenges and Level:
/bbox version)/acid range set)/acidisland:acid version)/bentobox reloadFull test suite passes. New unit tests cover the raw command line tokenizing and the suggestion offset arithmetic — the parts that have to agree exactly with what
CompositeCommandexpects.Left for phase 3
Deliberately not half-done here:
🤖 Generated with Claude Code
https://claude.ai/code/session_01ATRqygFLZ5CA3zrWmutx7H