# GriefPrevention3D v18.2.9
**Wiki:** https://github.com/castledking/GriefPrevention3D/wiki
## Fixes
### `/abandonclaim` throwing a NullPointerException (unfixed since migration)
`abandonClaimHandler` adjusts a player's accrued claim blocks when
`AbandonReturnRatio` is configured below `1.0`, using:
```java
claim.ownerID.equals(playerData.playerID)
```
`claim.ownerID` is `null` for administrative claims (and any claim whose owner UUID
failed to parse on load), and this line dereferenced it with no null check. On any
server with `GriefPrevention.Claims.AbandonReturnRatio` set to something other than the
default `1.0` — a common setting used to penalize abandoning claims — this threw an
NPE for every `/abandonclaim` call:
```
org.bukkit.command.CommandException: Unhandled exception executing command 'abandonclaim'
Caused by: java.lang.NullPointerException
```
The claim was already deleted by this point (`deleteClaimWithResult` runs earlier in
the same method), so the abandon itself succeeded — only the claim-block refund step
crashed, leaving the player with no success message and a confusing failure.
**Fix:** added the missing `claim.ownerID != null` guard before the equality check.
### Claim merge could push an owner's claim blocks negative
`DataStore.mergeClaims` validates the block-limit before applying a merge by computing
the union polygon's area (`mergedPolygon.cellCount()`) and checking it against the
acting player's remaining claim blocks. Two issues let this validation be bypassed:
1. **Wrong player checked.** The check only ran `if
(player.getUniqueId().equals(firstClaim.ownerID))` — if the person performing the
merge had `Edit`/manage trust on the claim but wasn't the owner, the block-limit
check was skipped entirely, and the merge applied against the real owner's blocks
with no validation at all.
2. **Checked area could differ from the applied area.** The limit check validated the
real union polygon's area, but the shape is later applied via
`updateClaimPolygon()`, which calls `claim.setShapedCorners(...)`. If the merged
polygon's corners failed shape validation (e.g. a concave or otherwise irregular
union), `updateClaimPolygon` silently fell back to the full bounding-box rectangle
spanning the merged region — which can be larger than the polygon that was actually
checked. The merge would pass the block-limit check, then apply a bigger shape than
was validated, driving the owner's remaining claim blocks negative.
**Fix:**
- The block-limit check now validates against the claim **owner's** `PlayerData`
(falling back to the acting player's data only when they are the owner), so
trust-based merges by non-owners are checked correctly.
- Before any state is mutated, the merged polygon's corners are now validated the same
way `setShapedCorners` would validate them. If that validation would fail, the merge
is aborted up front with `"Failed to merge claims: invalid resulting shape."` instead
of silently substituting an oversized bounding-box rectangle after the block check has
already passed.
### `/acb` permanently hijacked by `/aclaim blocks` after a `/gpreload`
`/acb` (`adjustbonusclaimblocks`) is registered twice in `UnifiedAdminClaimCommand`:
once as a standalone name for the `/aclaim blocks` subcommand (`Alias.AClaimBlocks`,
driven by `alias.yml`), and once directly as the legacy 2-argument command
(`/acb <player> <amount>`). At clean boot both resolve to the same real `PluginCommand`
declared in `plugin.yml`, and since the legacy registration runs second, it wins.
`/gpreload` broke this permanently, via `UnifiedCommandHandler`:
1. `registerDynamicStandaloneCommand` has a fallback path for standalone names that
aren't found via `plugin.getCommand(name)`: it force-registers a raw
`DynamicStandaloneCommand` directly into Bukkit's `CommandMap`, overwriting whatever
was at that name.
2. `plugin.getCommand(name)` only ever returns objects that are `instanceof
PluginCommand`. Once `acb`'s slot in the command map holds a
`DynamicStandaloneCommand` instead, every future `plugin.getCommand("acb")` call
returns `null` — including the legacy registration's own lookup.
3. `registerLegacyStandaloneCommand` had no `else` branch for that case — if
`plugin.getCommand(name)` returned `null`, it silently registered nothing. No
warning, no log line, nothing in the console.
4. `unregisterAllDynamicCommands()` (run at the top of every `/gpreload`, before
commands are re-registered) only ever *deleted* the dynamic command's map entry —
it never restored whatever real command had been overwritten to make room for it.
Put together: the first time `acb`'s name ever got claimed by the dynamic path (e.g.
during a `/gpreload` after editing `alias.yml`), it was gone for good. Every reload
after that recreated `acb` as a `DynamicStandaloneCommand` bound to whatever
`/aclaim blocks` standalone config `alias.yml` specified, and the legacy 2-arg handler
could never reattach — with no error anywhere to explain why. This also explains the
"no permission" reports even for OP/wildcard players: the dynamic command's permission
falls back to a hardcoded `griefprevention.claims` when `alias.yml`'s `blocks`
subcommand entry doesn't specify one, which is a different node than the
`griefprevention.adjustclaimblocks` the legacy handler and most permission setups
expect. Console senders bypass permission checks entirely, which is why running it from
console "worked."
**Fix:**
- `registerDynamicStandaloneCommand` now snapshots any real command it's about to
overwrite before replacing it.
- `unregisterAllDynamicCommands()` now restores that original command on cleanup
instead of just deleting the map entry, so a legitimate `plugin.yml` command/alias
can no longer be permanently orphaned by a reload.
- `registerLegacyStandaloneCommand` now logs a warning instead of silently registering
nothing when it can't resolve the target command, so this failure mode is visible in
the console going forward.
If you have a custom `standalone:` entry for `acb` under `/aclaim blocks` in
`alias.yml`, remove it — it's redundant with the dedicated legacy `/acb` handler and is
what triggers the collision in the first place.
## Files Changed
```
M src/main/java/me/ryanhamshire/GriefPrevention/GriefPrevention.java
M src/main/java/me/ryanhamshire/GriefPrevention/DataStore.java
M src/main/java/com/griefprevention/commands/UnifiedCommandHandler.java
```
Optional dependencies
QuickShop-Hikari
Any compatible version
PlaceholderAPI
Any compatible version
VaultUnlocked
Any compatible version
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" }