From afa36a27df7d55d1a4e5c8a58f90b5f0b7e7622f Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Mon, 1 Jun 2026 18:46:47 +0200 Subject: [PATCH 1/4] Fix duplicate dispense events for some items Co-authored-by: Jake Potrebic --- .../DefaultDispenseItemBehavior.java.patch | 2 +- .../dispenser/DispenseItemBehavior.java.patch | 69 +++++++++++-------- ...intAndSteelDispenseItemBehavior.java.patch | 42 ++++++++--- 3 files changed, 72 insertions(+), 41 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch index 70a6553a3a17..8e56bc97a351 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch @@ -66,7 +66,7 @@ + itemEntity.setItem(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem())); + itemEntity.setDeltaMovement(org.bukkit.craftbukkit.util.CraftVector.toVec3(event.getVelocity())); + -+ if (source.state().is(net.minecraft.world.level.block.Blocks.DISPENSER) && !event.getItem().getType().equals(craftItem.getType())) { ++ if (source.state().is(net.minecraft.world.level.block.Blocks.DISPENSER) && event.getItem().getType() != craftItem.getType()) { + // Chain to handler for new item + ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); + DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(source, eventStack); diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch index 6f3c4e44b053..bccfb1480e22 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch @@ -134,14 +134,14 @@ return super.execute(source, dispensed); } - level.gameEvent(null, GameEvent.FLUID_PICKUP, target); - Item targetType = pickup.getItem(); + // Paper start - Call BlockDispenseEvent + ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); + if (result != null) { + return result; + } + // Paper end - Call BlockDispenseEvent + level.gameEvent(null, GameEvent.FLUID_PICKUP, target); + Item targetType = pickup.getItem(); + pickup = bucket.pickupBlock(null, level, target, blockState); // CraftBukkit - from above return this.consumeWithRemainder(source, dispensed, new ItemStack(targetType)); } else { @@ -250,20 +250,20 @@ this.setSuccess(true); return dispensed; } -@@ -229,6 +_,13 @@ - Level level = source.level(); +@@ -230,6 +_,13 @@ Direction direction = source.state().getValue(DispenserBlock.FACING); BlockPos target = source.pos().relative(direction); -+ // Paper start - Call BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call BlockDispenseEvent if (level.isEmptyBlock(target) && WitherSkullBlock.canSpawnMob(level, target, dispensed)) { ++ // Paper start - Call BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call BlockDispenseEvent level.setBlock( target, + Blocks.WITHER_SKELETON_SKULL.defaultBlockState().setValue(SkullBlock.ROTATION, RotationSegment.convertToSegment(direction)), @@ -243,7 +_,7 @@ dispensed.shrink(1); this.setSuccess(true); @@ -273,20 +273,20 @@ } return dispensed; -@@ -256,6 +_,13 @@ - Level level = source.level(); +@@ -257,6 +_,13 @@ BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING)); CarvedPumpkinBlock pumpkinBlock = (CarvedPumpkinBlock)Blocks.CARVED_PUMPKIN; -+ // Paper start - Call BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call BlockDispenseEvent if (level.isEmptyBlock(target) && pumpkinBlock.canSpawnGolem(level, target)) { ++ // Paper start - Call BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call BlockDispenseEvent if (!level.isClientSide()) { level.setBlock(target, pumpkinBlock.defaultBlockState(), Block.UPDATE_ALL); + level.gameEvent(null, GameEvent.BLOCK_PLACE, target); @@ -265,7 +_,7 @@ dispensed.shrink(1); this.setSuccess(true); @@ -296,19 +296,30 @@ } return dispensed; -@@ -288,6 +_,12 @@ - ServerLevel level = source.level(); - BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING)); +@@ -290,11 +_,23 @@ BlockState state = level.getBlockState(target); -+ // Paper start - Call BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); -+ if (result != null) { -+ return result; -+ } -+ // Paper end - Call BlockDispenseEvent if (state.is(BlockTags.BEEHIVES, s -> s.hasProperty(BeehiveBlock.HONEY_LEVEL) && s.getBlock() instanceof BeehiveBlock) && state.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) { ++ // Paper start - Call BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); ++ if (result != null) { ++ return result; ++ } ++ // Paper end - Call BlockDispenseEvent ((BeehiveBlock)state.getBlock()) + .releaseBeesAndResetHoneyLevel(level, state, target, null, BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED); + this.setSuccess(true); + return this.takeLiquid(source, dispensed, new ItemStack(Items.HONEY_BOTTLE)); + } else if (level.getFluidState(target).is(FluidTags.WATER)) { ++ // Paper start - Call BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this); ++ if (result != null) { ++ return result; ++ } ++ // Paper end - Call BlockDispenseEvent + this.setSuccess(true); + return this.takeLiquid(source, dispensed, PotionContents.createItemStack(Items.POTION, Potions.WATER)); + } else { @@ -313,6 +_,13 @@ this.setSuccess(true); if (blockState.is(Blocks.RESPAWN_ANCHOR)) { diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch index 868af11f1868..e7518adfc47d 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch @@ -1,19 +1,16 @@ --- a/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java -@@ -24,16 +_,27 @@ - this.setSuccess(true); - Direction facing = source.state().getValue(DispenserBlock.FACING); - BlockPos targetPos = source.pos().relative(facing); -+ // Paper start - Call BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call BlockDispenseEvent +@@ -27,13 +_,39 @@ BlockState target = level.getBlockState(targetPos); if (!tryIgniteExplosiveEntities(level, targetPos)) { if (BaseFireBlock.canBePlacedAt(level, targetPos, facing)) { ++ // Paper start - Call BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call BlockDispenseEvent + // CraftBukkit start - Ignition by dispensing flint and steel + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, targetPos, source.pos()).isCancelled()) { level.setBlockAndUpdate(targetPos, BaseFireBlock.getState(level, targetPos)); @@ -21,11 +18,34 @@ + } + // CraftBukkit end } else if (CampfireBlock.canLight(target) || CandleBlock.canLight(target) || CandleCakeBlock.canLight(target)) { ++ // Paper start - Call BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call BlockDispenseEvent level.setBlockAndUpdate(targetPos, target.setValue(BlockStateProperties.LIT, true)); level.gameEvent(null, GameEvent.BLOCK_CHANGE, targetPos); } else if (target.getBlock() instanceof TntBlock) { - if (TntBlock.prime(level, targetPos)) { ++ // Paper start - Call BlockDispenseEvent ++ // TODO ideally in the prime supplier otherwise it's a fail ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call BlockDispenseEvent + if (TntBlock.prime(level, targetPos, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, source.pos()))) { // CraftBukkit - TNTPrimeEvent level.removeBlock(targetPos, false); } else { this.setSuccess(false); +@@ -54,6 +_,7 @@ + return false; + } + ++ // TODO - call dispense event + entities.getFirst().primeTime(false); + return true; + } From 01316ff8ced70a43400135e8e5f8a14e165a2df9 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Thu, 22 Oct 2026 12:55:53 +0100 Subject: [PATCH 2/4] Implement dispense events for sulfur cube --- ...intAndSteelDispenseItemBehavior.java.patch | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch index d98f95c17c0d..d67945839685 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch @@ -1,8 +1,16 @@ --- a/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java -@@ -27,13 +_,39 @@ +@@ -25,21 +_,50 @@ + Direction facing = source.state().getValue(DispenserBlock.FACING); + BlockPos targetPos = source.pos().relative(facing); BlockState target = level.getBlockState(targetPos); - if (!tryIgniteExplosiveEntities(level, targetPos)) { +- if (!tryIgniteExplosiveEntities(level, targetPos)) { ++ // Paper start - Call BlockDispenseEvent ++ final @org.jspecify.annotations.Nullable ItemStack[] blockDispenseEventResult = new ItemStack[1]; ++ if (!tryIgniteExplosiveEntities(level, targetPos, () -> ++ (blockDispenseEventResult[0] = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this)) == null) ++ ) { ++ // Paper end - Call BlockDispenseEvent if (BaseFireBlock.canBePlacedAt(level, targetPos, facing)) { + // Paper start - Call BlockDispenseEvent + ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this); @@ -29,23 +37,39 @@ level.gameEvent(null, GameEvent.BLOCK_CHANGE, targetPos); } else if (target.getBlock() instanceof TntBlock) { - if (TntBlock.prime(level, targetPos, null, dispensed)) { -+ // Paper start - Call BlockDispenseEvent -+ // TODO ideally in the prime supplier otherwise it's a fail -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call BlockDispenseEvent -+ if (TntBlock.prime(level, targetPos, null, dispensed, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, source.pos()))) { // CraftBukkit - TNTPrimeEvent ++ // Paper start - Call BlockDispenseEvent / TNTPrimeEvent ++ final java.util.function.BooleanSupplier tntPrimedEvents = () -> ( ++ blockDispenseEventResult[0] = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this)) == null ++ && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, source.pos()); ++ if (TntBlock.prime(level, targetPos, null, dispensed, tntPrimedEvents)) { ++ // Paper end - Call BlockDispenseEvent / TNTPrimeEvent level.removeBlock(targetPos, false); } else { this.setSuccess(false); -@@ -54,6 +_,7 @@ + } + } + } ++ if (blockDispenseEventResult[0] != null) return blockDispenseEventResult[0]; // Paper - Call BlockDispenseEvent - yield back possibly changed result stack from sulfur or tnt dispesne events + + if (this.isSuccess()) { + dispensed.hurtAndBreak(1, level, null, item -> {}); +@@ -48,11 +_,18 @@ + return dispensed; + } + +- private static boolean tryIgniteExplosiveEntities(final ServerLevel level, final BlockPos pos) { ++ private static boolean tryIgniteExplosiveEntities(final ServerLevel level, final BlockPos pos, final java.util.function.BooleanSupplier eventSupplier) { // Paper - call BlockDispenseEvent + List entities = level.getEntitiesOfClass(SulfurCube.class, new AABB(pos), SulfurCube::canExplode); + if (entities.isEmpty()) { return false; } ++ ++ // Paper start - call BlockDispenseEvent ++ // If the event was canceled, we return true and stop further event calls. ++ // Vanilla would only consider other flint and steel dispense behaviors if no sulfur cube was in the AABB ++ // that could explode, which was not the case. ++ if (!eventSupplier.getAsBoolean()) return true; ++ // Paper end - call BlockDispenseEvent -+ // TODO - call dispense event entities.getFirst().primeTime(false); return true; - } From bb8ce5f954a7a3d82de94ada0f19f833acd94870 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Thu, 22 Oct 2026 17:22:50 +0100 Subject: [PATCH 3/4] Do not call dispense event twice for sulfur cube flow --- ...intAndSteelDispenseItemBehavior.java.patch | 9 ++- ...urCubeBlockDispenseItemBehavior.java.patch | 21 +++---- .../monster/cubemob/SulfurCube.java.patch | 31 ++++++++++ .../craftbukkit/event/CraftEventFactory.java | 60 +++++++++++++++---- 4 files changed, 99 insertions(+), 22 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch index d67945839685..dd0d87a7eb1f 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java -@@ -25,21 +_,50 @@ +@@ -25,21 +_,55 @@ Direction facing = source.state().getValue(DispenserBlock.FACING); BlockPos targetPos = source.pos().relative(facing); BlockState target = level.getBlockState(targetPos); @@ -49,7 +49,12 @@ } } } -+ if (blockDispenseEventResult[0] != null) return blockDispenseEventResult[0]; // Paper - Call BlockDispenseEvent - yield back possibly changed result stack from sulfur or tnt dispesne events ++ // Paper start - Call BlockDispenseEvent - yield back possibly changed result stack from sulfur or tnt dispesne events ++ if (blockDispenseEventResult[0] != null) { ++ this.setSuccess(false); ++ return blockDispenseEventResult[0]; ++ } ++ // Paper end - Call BlockDispenseEvent - yield back possibly changed result stack from sulfur or tnt dispesne events if (this.isSuccess()) { dispensed.hurtAndBreak(1, level, null, item -> {}); diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java.patch index 1d8957d7b7ce..9ac3d266827f 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java -@@ -12,12 +_,19 @@ +@@ -12,14 +_,19 @@ @Override protected ItemStack execute(final BlockSource source, final ItemStack dispensed) { @@ -11,14 +11,15 @@ } - public static boolean dispenseBlock(final ServerLevel level, final BlockPos pos, final ItemStack dispensed) { -+ // Paper start - Call BlockDispenseEvent -+ public static boolean dispenseBlock(final ServerLevel level, final BlockPos pos, final ItemStack dispensed, final BlockSource source, final @org.jspecify.annotations.Nullable DispenseItemBehavior currentBehavior) { -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, pos, dispensed, currentBehavior); -+ if (result != null) { // todo - snapshot - setItem should probably override the equipped item + possible event in the for loop -+ return false; -+ } -+ // Paper end - Call BlockDispenseEvent -+ ++ public static boolean dispenseBlock(final ServerLevel level, final BlockPos pos, final ItemStack dispensed, final BlockSource source, final @org.jspecify.annotations.Nullable DispenseItemBehavior currentBehavior) { // Paper - Call BlockDispenseEvent for (SulfurCube entity : level.getEntitiesOfClass(SulfurCube.class, new AABB(pos))) { - if (entity.equipItem(dispensed)) { +- if (entity.equipItem(dispensed)) { ++ // Paper start - Call BlockDispenseEvent ++ if (entity.canEquipItem(dispensed)) { ++ final org.bukkit.craftbukkit.event.CraftEventFactory.PaperBlockDispenseEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEventInspectable(source, pos, dispensed, currentBehavior); ++ if (event.result.preventBehaviour()) return true; // Pretend that we succeeded this so we do not call the normal flow. ++ entity.equipItem(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem())); ++ // Paper end - Call BlockDispenseEvent dispensed.shrink(1); + return true; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch index 948f86fc26d5..68dca292b79a 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch @@ -128,6 +128,37 @@ return itWorked ? InteractionResult.SUCCESS_SERVER : InteractionResult.PASS; } else { return Bucketable.bucketMobPickup(player, hand, this).orElse(super.mobInteract(player, hand)); +@@ -490,17 +_,22 @@ + } + } + ++ // Paper start - move out equip item checks ++ public boolean canEquipItem(final ItemStack heldItem) { ++ if (this.isBaby()) return false; ++ ++ final Item swallowedItem = this.getItemBySlot(EquipmentSlot.BODY).getItem(); ++ if (heldItem.is(swallowedItem)) return false; ++ ++ return true; ++ } ++ // Paper end - move out equip item checks ++ + public boolean equipItem(final ItemStack heldItem) { +- if (this.isBaby()) { +- return false; +- } ++ if (!canEquipItem(heldItem)) return false; // Paper - move out equip item checks + + if (this.hasBodyItem()) { +- Item swallowedItem = this.getItemBySlot(EquipmentSlot.BODY).getItem(); +- if (heldItem.is(swallowedItem)) { +- return false; +- } +- ++ // Paper - move out equip item checks + Vec3 equipmentSpawnOffset = this.getAttachments().getAverage(EntityAttachment.PASSENGER); + if (this.level() instanceof ServerLevel serverLevel) { + this.spawnAtLocation(serverLevel, this.getItemBySlot(EquipmentSlot.BODY), equipmentSpawnOffset); @@ -525,7 +_,7 @@ if (this.level() instanceof ServerLevel serverLevel) { for (SulfurCubeArchetype.ContactDamage damage : this.contactDamages) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index fc04dc08b263..2e5f43292f82 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -30,6 +30,8 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.dispenser.BlockSource; +import net.minecraft.core.dispenser.DispenseItemBehavior; import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; @@ -155,6 +157,7 @@ import org.bukkit.event.block.BellRingEvent; import org.bukkit.event.block.BlockDamageAbortEvent; import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.block.BlockDispenseLootEvent; import org.bukkit.event.block.BlockDropItemEvent; import org.bukkit.event.block.BlockExplodeEvent; @@ -2304,25 +2307,62 @@ public static boolean handleBlockPreDispenseEvent(ServerLevel level, BlockPos po return event.callEvent(); } - public static @Nullable ItemStack handleBlockDispenseEvent(net.minecraft.core.dispenser.BlockSource pointer, BlockPos to, ItemStack dispensed, net.minecraft.core.dispenser.DispenseItemBehavior instance) { - org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos()); - org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitMirror(dispensed.isDamageableItem() ? dispensed : dispensed.copyWithCount(1)); + public static class PaperBlockDispenseEvent extends BlockDispenseEvent { - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); + public enum Result { + CANCELLED, + REROUTED, + ITEM_CHANGE_NO_REROUTE, + NO_CHANGE, + ; + + public boolean preventBehaviour() { + return switch (this) { + case CANCELLED, REROUTED -> true; + case ITEM_CHANGE_NO_REROUTE, NO_CHANGE -> false; + }; + } + } + + public Result result = Result.NO_CHANGE; + + public PaperBlockDispenseEvent(final @NotNull Block block, final org.bukkit.inventory.@NotNull ItemStack item, final @NotNull Vector velocity) { + super(block, item, velocity); + } + + public PaperBlockDispenseEvent withResult(final Result result) { + this.result = result; + return this; + } + } + + public static @Nullable ItemStack handleBlockDispenseEvent(final BlockSource pointer, final BlockPos to, final ItemStack dispensed, final DispenseItemBehavior instance) { + return switch (handleBlockDispenseEventInspectable(pointer, to, dispensed, instance).result) { + case CANCELLED, REROUTED -> dispensed; + case NO_CHANGE, ITEM_CHANGE_NO_REROUTE -> null; + }; + } + + public static PaperBlockDispenseEvent handleBlockDispenseEventInspectable(final BlockSource pointer, final BlockPos to, final ItemStack dispensed, final DispenseItemBehavior instance) { + final org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos()); + final org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitMirror(dispensed.isDamageableItem() ? dispensed : dispensed.copyWithCount(1)); + + final PaperBlockDispenseEvent event = new PaperBlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); if (!event.callEvent()) { - return dispensed; + return event.withResult(PaperBlockDispenseEvent.Result.CANCELLED); } if (!event.getItem().equals(craftItem)) { // Chain to handler for new item - ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); - net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.getDispenseBehavior(pointer, eventStack); - if (itemBehavior != net.minecraft.core.dispenser.DispenseItemBehavior.NOOP && itemBehavior != instance) { + final ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); + final DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.getDispenseBehavior(pointer, eventStack); + if (itemBehavior != DispenseItemBehavior.NOOP && itemBehavior != instance) { itemBehavior.dispense(pointer, eventStack); - return dispensed; + return event.withResult(PaperBlockDispenseEvent.Result.REROUTED); } + return event.withResult(PaperBlockDispenseEvent.Result.ITEM_CHANGE_NO_REROUTE); } - return null; + return event.withResult(PaperBlockDispenseEvent.Result.NO_CHANGE); } /** From 3cee6b51838dc6646b82f43bb7e652f2b2ec6981 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Thu, 22 Oct 2026 17:27:22 +0100 Subject: [PATCH 4/4] Fix diff --- .../world/entity/monster/cubemob/SulfurCube.java.patch | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch index 68dca292b79a..05424e866896 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/cubemob/SulfurCube.java.patch @@ -128,7 +128,7 @@ return itWorked ? InteractionResult.SUCCESS_SERVER : InteractionResult.PASS; } else { return Bucketable.bucketMobPickup(player, hand, this).orElse(super.mobInteract(player, hand)); -@@ -490,17 +_,22 @@ +@@ -490,16 +_,22 @@ } } @@ -154,11 +154,10 @@ - if (heldItem.is(swallowedItem)) { - return false; - } -- + // Paper - move out equip item checks + Vec3 equipmentSpawnOffset = this.getAttachments().getAverage(EntityAttachment.PASSENGER); if (this.level() instanceof ServerLevel serverLevel) { - this.spawnAtLocation(serverLevel, this.getItemBySlot(EquipmentSlot.BODY), equipmentSpawnOffset); @@ -525,7 +_,7 @@ if (this.level() instanceof ServerLevel serverLevel) { for (SulfurCubeArchetype.ContactDamage damage : this.contactDamages) {