# BentoBox 3.22.2
A bug-fix patch on top of 3.22.0. Four fixes, all of them for things players hit directly: name lookups resolving to the wrong account, Bedrock players' panel clicks being swallowed, an addon whose dependency is missing leaving broken commands behind, and nether returns dropping players on somebody else's island.
No new features, no config keys, no locale changes. If you are on 3.22.0, this is a drop-in replacement.
> There is no 3.22.1 release. The version was incremented twice during development; 3.22.2 is the first build published from it.
### Compatibility
✔️ Paper Minecraft 1.21.5 – 26.2
✔️ Java 25+
### Upgrading
1. **As always, take backups just in case.** (Make a copy of everything!)
2. Stop the server.
3. Replace the BentoBox jar with this one.
4. Start the server.
5. 🔺 BentoBox now deletes stale `Names` records — old name→UUID entries that were dropped from memory but never removed from the database. Existing databases heal themselves as players log in; no manual migration is needed, but take the backup in step 1 before you let it run.
---
## Highlights
* 🔺 **Player name lookups no longer resolve to a stale account** — `/is team trust <name>` and `/is info <name>` could silently answer for a UUID that had not held that name in months.
* **Bedrock players can use panels again** — Geyser expands one tap into several Java click packets, and the click cooldown was eating the one that mattered.
* **A disabled addon no longer leaves live, broken commands behind** — an addon dropped for a missing dependency kept its commands registered, and every subcommand threw an NPE.
* **Returning from a standard nether puts you on your own island** — not on whichever island sits nearest 0,0.
---
## Bug Fixes
### 🔺 Player names resolve to the right account
[[PR #3057](https://github.com/BentoBoxWorld/BentoBox/pull/3057)] — Fixes [#3052](https://github.com/BentoBoxWorld/BentoBox/issues/3052)
`/ob team trust Oli713664` reported no such player, and `/ob info Oli713664` answered "that player does not have an island" for a player who has one.
Capitalization was the visible symptom but not the cause. `setPlayerName` removed the superseded `Names` record from the in-memory cache and **never deleted it from the database**, so every leftover was reloaded on restart, and `getUUID` took `findFirst()` over an unordered list — a name could resolve to a UUID that had not held it for months. On a case-sensitive file system a leftover `oli713664.json` and the current `Oli713664.json` both matched, and whichever the directory listing yielded first won. That also explains the misleading second symptom: the lookup returned a *stale* UUID rather than `null`.
Three changes:
- **`nameCache` is keyed on the lower-cased name.** Minecraft names are unique ignoring case, so there is exactly one entry per name and the match no longer depends on how it was spelled when stored. It also moves from an `ArrayList` — mutated by the join listener while commands read it — to a `ConcurrentHashMap`, turning a linear scan of every name ever seen into a single hash lookup.
- **Stale records are deleted.** `setPlayerName` removes any record still pointing at this player under another spelling, and `removePlayer` drops the name lookup too.
- **`getUUID` consults the online player first**, then the cache, then the server's own user cache for players BentoBox has never recorded.
Every step is in-memory, so resolving a name still never blocks the caller — the `getOfflinePlayerIfCached` variant is used deliberately, as it returns `null` on a miss instead of asking Mojang.
> 🔺 **Data note:** the stale-record purge runs on login and only when a player's stored name actually differs from their current one — once per rename, not once per join.
### Bedrock players' panel clicks are no longer eaten
[[PR #3058](https://github.com/BentoBoxWorld/BentoBox/pull/3058)] — Fixes [#3049](https://github.com/BentoBoxWorld/BentoBox/issues/3049)
A Bedrock player opened `/is settings`, tapped any button, and got `general.errors.slow-down` with nothing happening — on the *first* tap, even after waiting, with the default 250 ms `panel.click-cooldown-ms`.
Bedrock's inventory model sends a whole transaction per tap, and Geyser expands it into a sequence of Java click packets that arrive in the same tick. Packet 1 opened the cooldown window; packet 2 hit it. This became fatal in 3.22.0 when the check moved to `PanelListenerManager` as an early `return` ahead of the click handler, so whichever packet lost the race was discarded outright — and the packet that landed on the button was not necessarily the one that arrived first.
Clicks in the same tick as the window opened are now treated as one physical gesture: the first click that can actually do work is let through, the rest are dropped **silently**, and a no-op click on a filler icon or the player's own inventory no longer consumes the window the real click needs. Clicks in a later tick but still inside the cooldown are unchanged — rejected, with the notice sent at most once per window. Spam protection is intact at one action-bearing click per tick.
`onTimeout(User, Panel)` keeps its signature and behaviour; a three-argument overload carries the new flag, and `TabbedPanel.isActionableSlot(int)` is new. Both additive.
### A dropped addon no longer leaves broken commands registered
[[PR #3060](https://github.com/BentoBoxWorld/BentoBox/pull/3060)] — Fixes [#3059](https://github.com/BentoBoxWorld/BentoBox/issues/3059)
Reported on Discord: `/ch` on a server with ChunkBlock installed but not the Level addon it depends on answered "An unexpected error occurred while running the command" with an NPE, for every subcommand.
A game mode addon builds its commands in `onLoad()`, and `CompositeCommand`'s constructor registers them with the Bukkit command map immediately — but the world is only attached later, in `enableAddon`. `sortAddons()` spots the missing dependency after `onLoad()` has run: it unregistered the addon's flags and dropped it from the addon list, but **left the commands registered**. `/ch` stayed live with a null world, and `getWorldSettings(null)` threw on the first lookup. The same hole existed for an addon abandoned as incompatible, or one that threw during load or enable.
- **`AddonsManager#withdrawAddon`** now takes back listeners, flags *and* commands, and is called from all three abandonment paths. Previously only flags were unregistered, and only on the missing-dependency path.
- **`CommandsManager#unregisterCommands(Addon)`** is the new method backing it. Brigadier keeps its nodes — it has no removal API — but a node whose label no longer resolves is refused by its `requires` predicate, so it disappears from clients too.
- **`CompositeCommand#call`** refuses outright when a command belongs to a `GameModeAddon` and has no world, logging which addon is not enabled. BentoBox's own world-less commands such as `/bentobox` are unaffected.
- **`IslandWorldManager#getWorldSettings`** builds its "non-game world" message lazily; it used to evaluate `world.getName()` eagerly, so a null world threw inside the very string meant to explain the problem.
`CommandsManager#unregisterCommands(Addon)` is new and additive, so binary compatibility is unaffected.
### Returning from a standard nether lands you on your own island
[[PR #3061](https://github.com/BentoBoxWorld/BentoBox/pull/3061)] — Fixes [AOneBlock#549](https://github.com/BentoBoxWorld/AOneBlock/issues/549)
With a shared nether and `create-and-link-portals: false` — AOneBlock's default — a player who went through a nether portal and came back was always dumped on the island near 0,0, usually somebody else's, where they had no build rights. `/ob home` worked fine, so the island data was never the problem.
`handleFromStandardNetherOrEnd` resolved the destination from the island's spawn point only, and `Optional.map` collapses to empty when the mapper returns `null` — so an island with no spawn point was indistinguishable from having no island at all, and execution fell through to the world-spawn fallback. An island only gets a spawn point when its blueprint contains a `{spawn here}` sign; AOneBlock's default blueprint is a single bedrock block, so the fallback fired every single time. BSkyBlock was unaffected because it defaults to island nethers and never reaches this branch.
The destination now falls back to the island's home location, which is `@NonNull` and defaults to the protection centre. The end-exit-portal path already did the equivalent via `getSafeRespawnLocation`.
---
## Other Improvements
* **Local builds no longer stamp a doubled `-LOCAL` marker into `plugin.yml`** — `/bentobox version` on a local build reported `…-SNAPSHOT-LOCAL-LOCAL`. Development-only; CI and release builds were never affected ([`fa401ee`](https://github.com/BentoBoxWorld/BentoBox/commit/fa401ee8b)).
* **Expanded the Building from Source section of the README** ([`5124f00`](https://github.com/BentoBoxWorld/BentoBox/commit/5124f00c7)).
* **Corrected the version-numbering notes in `CLAUDE.md`** ([`dcfe89f`](https://github.com/BentoBoxWorld/BentoBox/commit/dcfe89f28)).
---
## Legend
| Marker | Meaning |
|---|---|
| 🔡 | Locale files may need regenerating or updating |
| ⚙️ | Config options added, renamed or removed |
| 🔺 | Special attention needed |
---
## What's Changed
* 🔺 Resolve player names without depending on stored capitalization by @tastybento in https://github.com/BentoBoxWorld/BentoBox/pull/3057
* Stop the panel click cooldown eating Bedrock players' clicks by @tastybento in https://github.com/BentoBoxWorld/BentoBox/pull/3058
* Withdraw an addon's commands when the addon never enables by @tastybento in https://github.com/BentoBoxWorld/BentoBox/pull/3060
* Send players returning from a standard nether to their own island by @tastybento in https://github.com/BentoBoxWorld/BentoBox/pull/3061
**Full Changelog**: https://github.com/BentoBoxWorld/BentoBox/compare/3.22.0...3.22.2
Developer information
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle.
maven { name = "Modrinth"; url = "https://api.modrinth.com/maven" }