# GriefPrevention3D v18.2.19
**Wiki:** https://github.com/castledking/GriefPrevention3D/wiki
## Fixes
### Sulfur cube buckets placed a mob inside anyone's claim
A player holding a `SULFUR_CUBE_BUCKET` could empty it anywhere, including inside a claim they
had no trust in. No message, no cancellation — the cube simply appeared.
The cause is a quirk of how Mojang built the item. Every other mob bucket carries water:
```java
// Items, Minecraft 26.2
public static final Item AXOLOTL_BUCKET = registerItem(..., p -> new MobBucketItem(EntityTypes.AXOLOTL, Fluids.WATER, SoundEvents.BUCKET_EMPTY_AXOLOTL, p), ...);
public static final Item SULFUR_CUBE_BUCKET = registerItem(..., p -> new MobBucketItem(EntityTypes.SULFUR_CUBE, Fluids.EMPTY, SoundEvents.BUCKET_EMPTY_SULFUR_CUBE, p), ...);
```
A sulfur cube is a land mob, so its bucket is built with `Fluids.EMPTY`. `MobBucketItem` special
cases exactly that:
```java
@Override
public boolean emptyContents(LivingEntity user, Level level, BlockPos pos, BlockHitResult hitResult,
Direction direction, BlockPos clicked, ItemStack itemStack, InteractionHand hand) {
if (this.content == Fluids.EMPTY) {
this.playEmptySound(user, level, pos);
return true; // never calls super
}
return super.emptyContents(user, level, pos, hitResult, direction, clicked, itemStack, hand);
}
```
`BucketItem.emptyContents` is the **only** place `CraftEventFactory.callPlayerBucketEmptyEvent`
is called. Returning early skips it, and `BucketItem.use` then proceeds to `checkExtraContent` →
`MobBucketItem.spawn` → `level.addFreshEntityWithPassengers(mob, SpawnReason.BUCKET)`.
So **no `PlayerBucketEmptyEvent` is fired at all** for sulfur cube buckets. GriefPrevention's
`onPlayerBucketEmpty` — which has correctly guarded water, lava and every fish bucket for years —
was never invoked. There was no event to cancel.
**Fix:** check `PlayerInteractEvent` instead, which every mob bucket passes through regardless of
what fluid it carries. `ServerPlayerGameMode.useItemOn` records the cancellation:
```java
this.interactResult = event.useItemInHand() == Event.Result.DENY;
```
and `ServerGamePacketListenerImpl.handleUseItem` reuses that result for the follow-up
`ServerboundUseItemPacket` when the position, hand and stack still match — returning before
`useItem`, so `BucketItem.use` never runs.
Two details of the implementation matter:
- **The check runs ahead of the block-type branches** in `onPlayerInteract`. Shift-right-clicking
sets `suppressUsingBlock` in `useItemOn`, which skips block use and empties the bucket instead.
A player with container trust but not build trust could otherwise have placed a cube by
shift-clicking a chest, because that click is claimed by the container branch first.
- **Permission is checked at the block adjacent to the clicked face**, not the clicked block.
For an empty-fluid bucket `placePos` is always `directionOffsetPos`, which is where the mob
actually spawns. This is the same relative-block rule `onPlayerBucketEmpty` already applies, and
it is what makes the check correct against 3D subdivisions rather than only horizontally.
The material set is derived by name — anything ending `_BUCKET`, minus the empty bucket, water,
lava, milk, powder snow and the `LEGACY_` aliases. On 26.2 that resolves to exactly the seven mob
buckets (cod, salmon, pufferfish, tropical fish, axolotl, tadpole, sulfur cube), it is empty on
older versions where those materials do not exist, and future mob buckets are picked up with no
code change. The water-carrying buckets now have two layers of protection; the fluid-less one has
its first.
Dispensers were already covered and are unchanged. `SULFUR_CUBE_BUCKET` is registered with the
standard `filledBucketBehavior`, and `BlockEventHandler.onDispense` already cancels any dispense
that crosses a claim boundary in either direction.
### The punch freeze threw on every swing under region threading
`SulfurCubeHandler` undoes an unauthorized push by teleporting the cube back to where it started
on the next tick. That teleport used `Entity#teleport`, which Folia and Canvas reject:
```
java.lang.UnsupportedOperationException: Must use teleportAsync while in region threading
at org.bukkit.craftbukkit.entity.CraftEntity.teleport0(CraftEntity.java:321)
at GriefPrevention3D.jar/me.ryanhamshire.GriefPrevention.SulfurCubeHandler.lambda$onPlayerSwing$0(SulfurCubeHandler.java:60)
```
The entity task was already being scheduled correctly through `SchedulerUtil.runLaterEntity`; the
teleport call inside it was the problem. Note where in the sequence this landed: the exception was
thrown *after* the permission check had already failed, so the cube was never moved back and
`setVelocity` never ran. On a region-threaded server the push protection did nothing at all except
fill the console — one stack trace per denied swing.
**Fix:** teleports go through a new `CompatUtil.teleportSafely(Entity, Location)`, which resolves
`teleportAsync` once via reflection and falls back to the blocking call where it does not exist
(Spigot, pre-1.19 Paper). `PlayerEventHandler.teleportFoliaSafe` — a near-identical private copy
that only handled `Player` — now delegates to it rather than a second copy being added.
## Notes
- No configuration changes, no data migration, no message changes.
- Denied bucket placements use the existing no-build message and the existing rate limiter, so a
player spamming right-click gets one message rather than a stream.
- This affects Minecraft 26.2 and newer, where the sulfur cube and its bucket exist. On older
versions the material set is empty and the interact check is a no-op.
- The bucket bypass has existed since sulfur cube protection was added in v18.1.4. That release
covered the punch and right-click paths but not placement, because the bucket was reasonably
assumed to behave like the axolotl bucket, which `onPlayerBucketEmpty` already handled.
- Upstream GriefPrevention is affected identically — it guards buckets solely through
`PlayerBucketEmptyEvent`, and the sulfur cube bucket never fires it.
## Tests
No new tests. The full suite passes — **226 tests, 0 failures, 0 errors**.
Neither fix has unit coverage. Both need a live `PlayerInteractEvent` against a real block, and
the teleport path additionally needs a region-threaded server to reproduce at all; the mock
harness can construct neither.
The item construction and event paths were verified by decompiling the shipped server jar rather
than inferred:
```
$ cfr canvas-26.2.jar net/minecraft/world/item/Items.class | grep SULFUR_CUBE_BUCKET
public static final Item SULFUR_CUBE_BUCKET = Items.registerItem(ItemIds.SULFUR_CUBE_BUCKET,
(Item.Properties p) -> new MobBucketItem(EntityTypes.SULFUR_CUBE, Fluids.EMPTY,
SoundEvents.BUCKET_EMPTY_SULFUR_CUBE, p), ...);
```
`MobBucketItem.emptyContents`, `BucketItem.emptyContents`, `ServerPlayerGameMode.useItemOn` and
`ServerGamePacketListenerImpl.handleUseItem` were each read from the same jar to confirm that no
bucket-empty event is fired and that cancelling the interact event does suppress the item use.
## Files Changed
```
M gradle.properties
M pom.xml
M src/main/java/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java
M src/main/java/me/ryanhamshire/GriefPrevention/SulfurCubeHandler.java
M src/main/java/me/ryanhamshire/GriefPrevention/compat/CompatUtil.java
```
Optional dependencies
VaultUnlocked
Any compatible version
QuickShop-Hikari
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" }