# GriefPrevention3D v18.2.18
**Wiki:** https://github.com/castledking/GriefPrevention3D/wiki
## Fixes
### Block explosions were cancelled entirely inside claims, suppressing knockback and every other effect
`BlockExplodeEventHandler` cancelled the whole `BlockExplodeEvent` as soon as any affected
block was inside a claim:
```java
Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null);
if (claim != null)
{
explodeEvent.setCancelled(true);
return;
}
```
Cancelling that event does not merely spare the blocks — it suppresses the entire explosion,
including the knockback the server delivers to players. Every non-block effect of a block
explosion inside a claim has been silently dropped for as long as this class has existed.
The most visible symptom was the mace's **Wind Burst** enchantment: smashing a player inside a
claim produced particles, damage, and negated fall damage, but never launched the attacker.
The knockback event itself was completely healthy — it reached `MONITOR` priority uncancelled,
with a full `(0, 2.2, 0)` vector, and the velocity really was applied to the attacker
server-side. What never happened was the packet that carries that motion to the client, because
the explosion it belonged to had been cancelled. The server believed the player was flying
while their client kept falling.
Two settings that should have governed this were never consulted by the class at all, which is
why neither provided a workaround:
- `GriefPrevention.BlockLandClaimExplosions` — `config_blockClaimExplosions` was never read
- `/claimexplosions` — `claim.areExplosivesAllowed` was never read
`BlockSurfaceCreeperExplosions` and `BlockSurfaceOtherExplosions` were equally irrelevant here.
The only configuration that avoided the bug was disabling claims for the whole world.
**Fix:** both branches now strip protected blocks from the explosion instead of cancelling it,
matching what `EntityEventHandler` has always done for entity-caused explosions:
- `handleExplosion` filters into an allowed-blocks list and honours `config_blockClaimExplosions`
and `claim.areExplosivesAllowed`, so **`/claimexplosions` works on this path for the first time**
- `handleExplodeInteract` collects protected blocks and calls `blocks.removeAll(removed)`,
and now updates `playerData.lastClaim` like its entity-side counterpart
- Both use the cached-claim lookup instead of passing `null` on every iteration
Block protection is unchanged: claimed blocks still do not break. Everything else the explosion
does — knockback, damage, block triggering — now survives.
This class is GP3D-only. It was split out of `EntityEventHandler` for 1.8.8 compatibility and
the split copy was rewritten to cancel rather than filter. Upstream GriefPrevention routes
`BlockExplodeEvent` through the same shared `handleExplosion` / `handleExplodeInteract` methods
as entity explosions and is not affected.
### The investigation tool could not be placed on a shelf
[#10](https://github.com/castledking/GriefPrevention3D/issues/10) — right-clicking a shelf while
holding the investigation tool (a stick by default) ran claim investigation instead of storing
the item, so the tool could never be placed on a shelf. Item frames were unaffected because they
are entities and never reach this handler.
Shelves are `BlockInventoryHolder`s, so the interaction reached the container branch of
`PlayerEventHandler.onPlayerInteract`, which hands off to the claim tool dispatcher whenever the
player is holding the investigation or modification tool. The dispatcher consumed the click.
**Fix:** the claim tools no longer intercept right-clicks on shelf blocks, at both dispatcher
call sites. Shelves stay protected exactly as before — they are inventory holders, so the
container trust check still applies and untrusted players still cannot store or retrieve items
in a claim. This only decides whether the claim tools consume the interaction first.
Shelves are matched by name (`_SHELF` suffix, which excludes `BOOKSHELF` and
`CHISELED_BOOKSHELF`) so the plugin still compiles and runs on versions predating them, and so
future wood variants are covered automatically.
### Duplicate wind-charge knockback handlers were registered alongside the platform handlers
`PaperKnockbackHandler` and `SpigotKnockbackHandler` were GP3D-only classes added here on
2025-12-29 (`cab3d9cf`). Upstream solved the same problem independently on 2026-01-26 with
[GriefPrevention#2549](https://github.com/GriefPrevention/GriefPrevention/pull/2549), which
introduced `com.griefprevention.platform.knockback`. That package arrived via the upstream merge
in `b084c943`, but the older classes were never removed — so two independent listeners inspected
and could cancel the same knockback event.
The Paper one was the worse of the two. Its wind-charge test was:
```java
boolean isWindChargeKnockback = causeName.equals("EXPLOSION") && source instanceof Player;
```
which matches any player-sourced explosion knockback, including the mace smash AoE. Both classes
are removed; `KnockbackProtectionListener` and its Paper/Spigot handlers are now the only
knockback protection path.
### Paper's knockback handler processed the same event twice
On Paper, `com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent` extends
`io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent`, and only the base
`io.papermc.paper.event.entity.EntityKnockbackEvent` declares a `HandlerList` — so all three
event types share one handler list and both `@EventHandler` methods on
`PaperKnockbackProtectionHandler` ran for every `EntityKnockbackByEntityEvent`. That meant
duplicate claim lookups and permission checks on every melee hit, and a duplicate
`PreventPvPEvent` whenever the first pass did not cancel.
`onEntityPushedByEntityAttack` now skips events that are `EntityKnockbackByEntityEvent`.
This defect is inherited from upstream, not introduced here — our copy differs from #2549 only
in Java 8 desugaring. It has been submitted upstream separately.
## Features
### Knockback tracing under `/gpdebug`
Knockback protection now logs its decisions through the existing debug channel. Enable with
`/gpdebug on` (permission `griefprevention.debug`); entries are prefixed `[GP Debug] [knockback]`
and are suppressed entirely while debug logging is off, including the claim lookups they would
otherwise perform.
```
[GP Debug] [knockback] EntityKnockbackByEntityEvent cause=ENTITY_ATTACK entity=Player(bob) hitBy=Player(alice)
[GP Debug] [knockback] allow: attacker alice has Access trust in defender's claim 2
[GP Debug] [knockback] [in ] EntityKnockbackEvent cause=EXPLOSION entity=Player(alice) standingIn=claim 2 cancelled=false knockback=0.0,2.2,0.0
[GP Debug] [knockback] [out] EntityKnockbackEvent cause=EXPLOSION entity=Player(alice) standingIn=claim 2 cancelled=false knockback=0.0,2.2,0.0
```
Traced branches: self-knockback, `ignoreClaims`, Access trust in the defender's claim, GP PvP
rules disabled for the world, fresh-spawn immunity, defender in a PvP-safe claim, attacker in a
PvP-safe claim, another plugin cancelling `PreventPvPEvent`, and "no GP rule matched". Events GP
declines to act on are logged as `ignored:` with the reason.
On Paper, the `[in ]` / `[out]` pair additionally traces the base `EntityKnockbackEvent` — which
GP does not otherwise subscribe to, and which is how explosion knockback with no source entity
arrives — at `LOWEST` on arrival and again at `MONITOR` after every other plugin has run. A
knockback that is cancelled or zeroed in between is visible, along with the claim the affected
player is standing in. It never modifies the event. This is what isolated the explosion bug
above: it proved the knockback event was pristine, which ruled out the entire knockback path and
redirected the search to explosion handling.
### Platform listener registration is logged
`PlatformListener.register()` now records which handler it registered and on what:
```
[GP Debug] Registered PaperKnockbackProtectionHandler on Paper 26.2 (detected platform: PAPER).
```
Listeners that report themselves unsupported now say so instead of failing silently.
## Notes
- No configuration changes, no data migration, no message changes.
- `GriefPrevention.BlockLandClaimExplosions` and `/claimexplosions` now take effect for block
explosions. Servers that had set either expecting explosions to be permitted in claims will see
that setting honoured for the first time — the previous behaviour blocked such explosions
regardless.
- **Known fragility, not changed this release.** `KnockbackProtectionListener.isSupported()` gates
the whole Paper path on `com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent`
being present; if it ever goes away, `isSupported()` returns `false` and no knockback protection
registers on Paper at all, silently. In paper-api 26.2 that class is `@ApiStatus.Obsolete`, not
`@Deprecated(forRemoval = true)`, so removal is not currently scheduled. Upstream shares the
gate; GP3D additionally requires `io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent`.
- **Do not "modernize" onto the Bukkit knockback events on Paper.** In paper-api 26.2 both
`org.bukkit.event.entity.EntityKnockbackEvent` and `EntityKnockbackByEntityEvent` carry
class-level `@Deprecated(forRemoval = true)`, while Paper's
`io.papermc.paper.event.entity.EntityKnockbackEvent` carries no deprecation. In spigot-api 26.2
the same two Bukkit classes carry no class-level annotations and are the current API. The
platforms point in opposite directions, which is why the platform split from #2549 is the right
shape.
- [PaperMC/Paper#13079](https://github.com/PaperMC/Paper/issues/13079) — cancelling
`EntityPushedByEntityAttackEvent` does not stop Wind Burst knockback — remains open and limits
what GP can protect against. It is unrelated to the bug fixed above, and the two are easy to
conflate: #13079 is knockback happening when a plugin tries to block it, this release fixed
knockback *not* happening when nothing was trying to block it.
## Tests
No new tests. The full suite passes — **224 tests, 0 failures, 0 errors**.
The shelf fix has no unit coverage either: `onPlayerInteract` needs a live `PlayerInteractEvent`
against a real shelf `BlockState`, which the mock harness cannot construct. It was verified
in-game on Paper 26.2 — investigation tool onto a shelf inside a claim, with and without
container trust.
The explosion fix has no unit coverage because `BlockExplodeEventHandler` needs a live
`BlockExplodeEvent` with a populated block list and a `DataStore` with real claims; the current
mock harness has no fixture for either. It was verified in-game on Paper 26.2: mace smash with
Wind Burst inside a claim, before and after, with the claim abandoned and recreated at identical
coordinates as a control.
The Paper event hierarchy claim behind the double-dispatch fix was verified against the shipped
API jar:
```
$ javap -cp paper-api-26.2.jar com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent
public class com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent
extends io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent
```
## Files Changed
```
M gradle.properties
M pom.xml
M src/main/java/com/griefprevention/platform/PlatformListener.java
M src/main/java/com/griefprevention/platform/knockback/KnockbackProtectionHandler.java
M src/main/java/com/griefprevention/platform/knockback/PaperKnockbackProtectionHandler.java
M src/main/java/com/griefprevention/platform/knockback/SpigotKnockbackProtectionHandler.java
M src/main/java/me/ryanhamshire/GriefPrevention/BlockExplodeEventHandler.java
M src/main/java/me/ryanhamshire/GriefPrevention/EntityDamageHandler.java
M src/main/java/me/ryanhamshire/GriefPrevention/GriefPrevention.java
M src/main/java/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java
D src/main/java/me/ryanhamshire/GriefPrevention/PaperKnockbackHandler.java
D src/main/java/me/ryanhamshire/GriefPrevention/SpigotKnockbackHandler.java
```
Optional dependencies
QuickShop-Hikari
Any compatible version
VaultUnlocked
Any compatible version
PlaceholderAPI
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" }