Release 3.22.0 - #3043
Open
tastybento wants to merge 18 commits into
Open
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDVXb9Lg33agNqUHrs5mzR
A cartographer villager levelling up in a void world triggers a synchronous findNearestMapStructure scan on the main thread. Because the world's custom generator never places structures, the search can never succeed and grinds through vanilla noise generation for every candidate position until the watchdog kills the server — on auto-restarting hosts this repeats as soon as the villager's chunk loads again, producing a reboot loop. StructureListener now asks the world's Bukkit ChunkGenerator whether it places structures at all (shouldGenerateStructures). If not, every StructuresLocateEvent in that world is cancelled (or narrowed) with no configuration needed — a search in a structureless world is always the pathological scan-to-the-cap case. A per-world getStructureSettings() entry of true still force-enables a structure, as an escape hatch for converted worlds containing pre-existing structures. Game modes that do generate structures (SkyGrid, Boxed with allow-structures, CaveBlock overworld) report true and are unaffected, so no addon changes are required. Also expands the world.disabled-structures config comments: how to list valid keys in-game (/locate structure tab-completion) and a concrete example disabling the structures that explorer/treasure maps search for (monument, mansion, trial_chambers, buried_treasure). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDVXb9Lg33agNqUHrs5mzR
…ld-locate-suppression Auto-suppress structure searches in structureless worlds
- Pin cloudnode-pro/modrinth-publish to commit SHA (security hotspot: unpinned third-party action) - Add null guards on paths Sonar traces as nullable (S2259): scan.world() in PurgeRegionsService, raw in User.computeLegacy, world in both Multiverse hooks, getEditedFlag() in PVPListener - Use possessive quantifiers in regexes flagged for super-linear backtracking (S8786) in PlaceholderGrouper and AddonClassLoader - Pass explicit ZoneId to LocalDate.now() (S8688) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDVXb9Lg33agNqUHrs5mzR
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
…-registration Register commands with Paper's Brigadier API (#3023 phases 1 and 2)
Seed worlds (`<world>/bentobox`) were dropped in 3.16.1 along with the chunk-regen deletion path, but IslandWorldManager still looked one up for every game mode world and registered it with the world management hooks. Nothing creates those worlds any more, so the lookup always returns null. Worth noting for #3037: this registration is why stale `acid/bentobox` entries linger in Multiverse's worlds.yml — BentoBox deleted the seed world folder on shutdown but never unregistered it, so MV autoload reports WORLD_FOLDER_INVALID. Removing the call stops any future version re-adding such an entry; existing ones still need `/mv remove`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATRqygFLZ5CA3zrWmutx7H
Six new bStats charts, plus a cleanup of three that were sending data into the void. Addon and game mode versions (drilldown pies) and addon API versions (advanced pie): we knew *that* a server ran Level, but not *which* Level, so there was no way to see how long the tail is after a release or judge when an API break is safe. Non-default settings (bar chart): only deviations from default are reported, so every bar is a signal. A default-off setting appears under its own name when enabled, a default-on setting as no-<name> when disabled. Comparing a bar to the server count gives the adoption or rejection rate of a feature. Command failures and top failing commands (#3022): CompositeCommand.call is the single funnel where every failure mode is distinguishable, so it now records the kind of failure and the command it happened on. Only the failure kind and a stable command key are sent - never what the player typed - because bStats data is public. Labels cannot be used as that key: the top level label comes from the game mode config and getLabel() is overwritten with whatever alias was typed. The permission is hard coded in setup() and carries the addon prefix, so bskyblock.island.team.invite means the same thing on every server. Commands with no permission fall back to their class name. Distinct keys are capped at 200 between submissions so an addon with generated commands cannot grow the map without limit. Removed the addons, gameModeAddons and hooks advanced pies. Those chart ids do not exist on bstats.org, so the data was discarded on arrival. The addonsBar, gameModeAddonsBar and hooksBar charts carry identical data and do have charts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATRqygFLZ5CA3zrWmutx7H
Fixes #3039 A top level command's literal children are baked in when its node is built, and that happens while Paper's COMMANDS lifecycle window is open - right after onEnable returns. Addons add their sub-commands to game mode commands from enableAddons(), which BentoBox defers to the first tick, so every addon sub-command missed the tree. They still ran, because the greedy argument catches anything the literals do not match and CompositeCommand.execute walks the live map, and they still showed in /<gamemode> help, which iterates that same live map. Only the client was never told they exist, so they were absent from tab completion and from the suggestions shown while typing. Brigadier merges same-named literals rather than replacing them (CommandNode#addChild copies the incoming command across and recursively merges its children), so re-registering a freshly built tree grafts the new children onto the live node. refreshTrees() does that for every registered top level command and is called at the end of enableAddons() and again after allLoaded(), which runs later still from the blueprint polling task. Aliases are not re-registered: they redirect to the original node, which is the one merged into. A rebuild that adds nothing does not push the tree to clients. literalSiblings() also had to change. It removed every live sub-command from the greedy argument's suggestions on the grounds that Brigadier already suggests them - true only for sub-commands that have a literal node, so a late sub-command was dropped from the dynamic fallback as well and had no route to the client at all. It now consults the dispatcher, so any future divergence between the tree and the live command map is covered by the fallback rather than silenced by it. Tested with AcidIsland plus an addon that adds a sub-command in onEnable. Adds BrigadierTreeRefreshTest, which runs a real dispatcher and pins both the broken state and the fix, plus two AddonsManagerTest cases for the wiring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011bRDVafWYB34EMKt21Hqxa
…ubcommands Rebuild the Brigadier tree once addons have enabled
Add addon version, feature adoption and command failure metrics
createMarkerSet() attached the new set to every map on the server via api.getMaps(), and populateAll() re-attached it to every map again after a BlueMap reload. An addon registering, say, a warps marker set for one game mode put that set on the sidebar of every other game mode's map, and of any non-BentoBox world's map too. removeMarkerSet() had the same fan-out. MapHook#createMarkerSet carries no world, so the set's worlds are now learned lazily from the markers themselves: addPointMarker/addAreaMarker/addPolygonMarker record the world they were given and attach the set to just that world's maps. populateAll() replays the recorded worlds after a BlueMap reload, and removeMarkerSet() detaches from only those worlds. A set with no markers yet is no longer attached anywhere, which is also the more useful behaviour - an empty set was previously listed on every map. Island marker sets are unaffected; registerGameMode() already scoped them to the game mode's own worlds. Also guards the api field before use. createMarkerSet() and removeMarkerSet() dereferenced it unconditionally, so an addon calling either before BlueMap had loaded, or while it was reloading, hit an NPE. getBlueMapAPI() is annotated @nullable for the same reason - it was marked @nonnull but returns null in both those windows, which suppressed the null checks callers needed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pHWC6XapJj5gRtsFr74Tf
…et-world-scope Scope addon-created BlueMap marker sets to their own worlds
registerWorld() set auto-load false inside a peek() on the importWorld() result, so it only ever ran on a first-time import. If Multiverse already had the world in worlds.yml the import fails with WORLD_EXIST_LOADED or WORLD_EXIST_UNLOADED, the peek is skipped, and the world keeps auto-load true - which is what #3037's worlds.yml shows for every BentoBox world. Look the world up after the import instead and clear the flag there, so existing entries get fixed too, saving worlds.yml when the flag changed. BentoBox creates and loads its own worlds in onEnable; Multiverse should not be autoloading them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kee1qur11mLWdE4ygvh4pu
Brigadier hides nodes the sender may not use, and hiding is not passive: CommandNode#getRelevantNodes returns only the literal that matches the typed token, so the catch-all argument beside it is never considered, and CommandDispatcher#parseNodes then skips that literal when canUse is false. A player without the permission for a sub-command therefore got Brigadier's "Incorrect argument for command" instead of the command's own no-permission message, and the command never ran. Paper reports any parse failure as an unknown command, so pick our own command lines up there and dispatch them through the command tree, which checks its own permissions and reports for itself. Nothing has run at that point - Paper fails in its parse step, before execution - so there is no double execution. Two knock-on faults from the same report: - the matcher re-attaches tokens it could not match verbatim, so it suggested the very line that had just failed. Drop matches identical to the input. - accepting a suggestion ran it through Player#performCommand in a scheduled task with nothing catching what Bukkit throws, which is how the report's unhandled CommandException got out. Log it instead. Fixes BentoBoxWorld/Border#179 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6rYr6Z66LQz9XTbnbUupy
…permission-parse Run our own commands that Brigadier will not parse
This release adds Brigadier command registration, expanded bStats telemetry and automatic structure-search suppression, so it takes a minor version rather than a patch. The new general.brigadier-commands config key already documented itself as "Added since 3.22.0"; the @SInCE tags on the new BStats and CompositeCommand members are corrected to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kee1qur11mLWdE4ygvh4pu
|
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.



Release 3.22.0 — 17 commits since 3.21.0.
A commands-and-visibility release: BentoBox registers its commands with Paper's Brigadier API so completions appear while the player types, six new bStats charts answer which addon versions are actually deployed and where commands fail, and structureless worlds now protect themselves from the
/locate-style searches that could freeze a server.New features
general.brigadier-commandsconfig key, default on, legacy command map kept as fallback.Bug fixes
apifield and correctgetBlueMapAPI()to@Nullable.auto-load: falseto worlds Multiverse already knows — relates to #3037.Version
buildVersionbumped 3.21.1 → 3.22.0 in this PR. Thegeneral.brigadier-commandskey on develop already documented itself as "Added since 3.22.0", and the release is feature-bearing rather than a patch.@sincetags on the newBStatsandCompositeCommandmembers were corrected to match.Admin-facing notes
general.brigadier-commands(defaulttrue) is written on first run. Setfalseto fall back to legacy registration if another plugin conflicts.<world>/bentoboxentries already in Multiverse'sworlds.ymlstill need a manual/mv remove; a89bf9d only stops new ones being created.Known limitation carried into this release
BrigadierCommandRegistrar#refreshTreescompareschildCount(label)before and after the rebuild, butdispatcher.getRoot().getChild(label)returns Paper's wrappedApiMirrorRootNodewhile the merge lands on the NMS copy — so the count never changes andrefreshClients()is never called. #3039's symptom is likely still present on a real server; the unit tests pass because they use a plainRootCommandNode. Subcommands themselves execute correctly either way. Flagged in #3042 for follow-up.Verification
./gradlew clean buildgreen on the release commit: 3391 tests, 0 failures, 0 errors, 0 skipped.Compatibility
✔️ Paper Minecraft 1.21.5 – 26.2
✔️ Java 25+
Full Changelog: 3.21.0...develop