From 4767f64dc188742efb7f26724ce0d2be9095a797 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Fri, 27 Feb 2026 20:49:34 -0800 Subject: [PATCH 1/2] Add ItemSpawnEntityEvent and BlockPlaceEntityEvent and expand EntityPlaceEvent --- .../event/entity/BlockPlaceEntityEvent.java | 49 ++++++++++ .../event/entity/ItemSpawnEntityEvent.java | 97 +++++++++++++++++++ .../paper/event/entity/PlaceEntityEvent.java | 29 ++++++ .../bukkit/event/entity/EntityPlaceEvent.java | 91 ++++++----------- ...018-Entity-load-save-limit-per-chunk.patch | 4 +- .../BoatDispenseItemBehavior.java.patch | 7 +- .../dispenser/DispenseItemBehavior.java.patch | 36 ++++++- .../world/entity/EntityType.java.patch | 14 ++- .../minecraft/world/item/BoatItem.java.patch | 2 +- .../world/item/BucketItem.java.patch | 13 ++- .../item/DispensibleContainerItem.java.patch | 16 +++ .../world/item/MobBucketItem.java.patch | 36 ++++++- .../world/item/SpawnEggItem.java.patch | 38 +++++++- .../craftbukkit/event/CraftEventFactory.java | 35 ++++++- ...ontainerItemExtraContentsOverrideTest.java | 50 ++++++++++ 15 files changed, 434 insertions(+), 83 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java create mode 100644 paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java create mode 100644 paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java create mode 100644 paper-server/patches/sources/net/minecraft/world/item/DispensibleContainerItem.java.patch create mode 100644 paper-server/src/test/java/io/papermc/paper/block/DispensibleContainerItemExtraContentsOverrideTest.java diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java new file mode 100644 index 000000000000..385800be4067 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java @@ -0,0 +1,49 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Dispenser; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Called when a block, like a dispenser, places an + * entity. {@link #getPlayer()} will always be null. + * @see org.bukkit.event.hanging.HangingPlaceEvent for paintings, item frames, and leashes. + * @see org.bukkit.event.entity.EntityPlaceEvent for a player-only version with more context + * @see PlaceEntityEvent to listen to both blocks and players placing entities + */ +@NullMarked +public class BlockPlaceEntityEvent extends PlaceEntityEvent { + + private final Dispenser dispenser; + + @ApiStatus.Internal + public BlockPlaceEntityEvent(final Entity entity, final Block block, final BlockFace blockFace, final ItemStack spawningStack, final Dispenser dispenser) { + super(entity, null, block, blockFace, spawningStack); + this.dispenser = dispenser; + } + + /** + * Get the dispenser responsible for placing the entity. + * + * @return a non-snapshot Dispenser + */ + public Dispenser getDispenser() { + return this.dispenser; + } + + /** + * Player will always be null on this event. + */ + @Override + @Contract("-> null") + public @Nullable Player getPlayer() { + return null; + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java new file mode 100644 index 000000000000..c7af5b009531 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java @@ -0,0 +1,97 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * When an itemstack causes the spawning of an entity. Most event fires are going to + * be the through the sub-event {@link org.bukkit.event.entity.EntityPlaceEvent} but this + * event will also be fired for mob spawn eggs from players and dispensers. + */ +@NullMarked +public class ItemSpawnEntityEvent extends EntityEvent implements Cancellable { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final @Nullable Player player; + private final Block block; + private final BlockFace blockFace; + private final ItemStack spawningStack; + private boolean cancelled; + + @ApiStatus.Internal + public ItemSpawnEntityEvent(final Entity entity, final @Nullable Player player, final Block block, final BlockFace blockFace, final ItemStack spawningStack) { + super(entity); + this.player = player; + this.block = block; + this.blockFace = blockFace; + this.spawningStack = spawningStack; + } + + /** + * Returns the player placing the entity (if one is available). + * + * @return the player placing the entity + */ + public @Nullable Player getPlayer() { + return this.player; + } + + /** + * Returns the block that the entity was placed on + * + * @return the block that the entity was placed on + */ + public Block getBlock() { + return this.block; + } + + /** + * Returns the face of the block that the entity was placed on + * + * @return the face of the block that the entity was placed on + */ + public BlockFace getBlockFace() { + return this.blockFace; + } + + /** + * Gets the itemstack responsible for spawning the entity. Mutating + * this itemstack has no effect. + *

+ * May return an empty itemstack if the actual stack isn't available. + * + * @return the spawning itemstack + */ + public ItemStack getSpawningStack() { + return this.spawningStack; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java new file mode 100644 index 000000000000..4261d393cc3a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java @@ -0,0 +1,29 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Triggered when an entity is created in the world by "placing" an item + * on a block from a player or dispenser. + *
+ * Note that this event is currently only fired for these specific placements: + * armor stands, boats, minecarts, end crystals, mob buckets, and tnt (dispenser only). + * @see org.bukkit.event.hanging.HangingPlaceEvent for paintings, item frames, and leashes. + * @see org.bukkit.event.entity.EntityPlaceEvent for a player-only version with more context + * @see BlockPlaceEntityEvent for a dispener-only version with more context + */ +@NullMarked +public abstract class PlaceEntityEvent extends ItemSpawnEntityEvent { + + @ApiStatus.Internal + protected PlaceEntityEvent(final Entity entity, final @Nullable Player player, final Block block, final BlockFace blockFace, final ItemStack spawningStack) { + super(entity, player, block, blockFace, spawningStack); + } +} diff --git a/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java b/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java index 236c1fa78c17..c0f683f79639 100644 --- a/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java @@ -1,77 +1,58 @@ package org.bukkit.event.entity; +import io.papermc.paper.event.entity.PlaceEntityEvent; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; -import org.bukkit.event.HandlerList; import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Triggered when an entity is created in the world by a player "placing" an item * on a block. *
- * Note that this event is currently only fired for four specific placements: - * armor stands, boats, minecarts, and end crystals. + * Note that this event is currently only fired for these specific placements: + * armor stands, boats, minecarts, end crystals, and mob buckets. + * @see org.bukkit.event.hanging.HangingPlaceEvent for paintings, item frames, and leashes. + * @see io.papermc.paper.event.entity.BlockPlaceEntityEvent for a dispenser-only version + * @see io.papermc.paper.event.entity.PlaceEntityEvent to listen to both blocks and players placing entities */ -public class EntityPlaceEvent extends EntityEvent implements Cancellable { +@NullMarked +public class EntityPlaceEvent extends PlaceEntityEvent implements Cancellable { // Paper - Add superclass - private static final HandlerList HANDLER_LIST = new HandlerList(); - - private final Player player; - private final Block block; - private final BlockFace blockFace; private final EquipmentSlot hand; - private boolean cancelled; - @ApiStatus.Internal - public EntityPlaceEvent(@NotNull final Entity entity, @Nullable final Player player, @NotNull final Block block, @NotNull final BlockFace blockFace, @NotNull final EquipmentSlot hand) { - super(entity); - this.player = player; - this.block = block; - this.blockFace = blockFace; + public EntityPlaceEvent( + final Entity entity, + final @Nullable Player player, + final Block block, + final BlockFace blockFace, + final EquipmentSlot hand, + final ItemStack spawningStack + ) { + super(entity, player, block, blockFace, spawningStack); this.hand = hand; } - @ApiStatus.Internal - @Deprecated(since = "1.19.2", forRemoval = true) - public EntityPlaceEvent(@NotNull final Entity entity, @Nullable final Player player, @NotNull final Block block, @NotNull final BlockFace blockFace) { - this(entity, player, block, blockFace, EquipmentSlot.HAND); - } - - /** - * Returns the player placing the entity - * - * @return the player placing the entity - */ - @Nullable - public Player getPlayer() { - return this.player; + @Override + public @Nullable Player getPlayer() { + return super.getPlayer(); } - /** - * Returns the block that the entity was placed on - * - * @return the block that the entity was placed on - */ - @NotNull + @Override public Block getBlock() { - return this.block; + return super.getBlock(); } - /** - * Returns the face of the block that the entity was placed on - * - * @return the face of the block that the entity was placed on - */ - @NotNull + @Override public BlockFace getBlockFace() { - return this.blockFace; + return super.getBlockFace(); } /** @@ -79,29 +60,17 @@ public BlockFace getBlockFace() { * * @return the hand */ - @NotNull public EquipmentSlot getHand() { return this.hand; } @Override public boolean isCancelled() { - return this.cancelled; + return super.isCancelled(); } @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } - - @NotNull - @Override - public HandlerList getHandlers() { - return HANDLER_LIST; - } - - @NotNull - public static HandlerList getHandlerList() { - return HANDLER_LIST; + public void setCancelled(final boolean cancel) { + super.setCancelled(cancel); } } diff --git a/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch b/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch index 74c39921a062..3b3cfe70a83d 100644 --- a/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch +++ b/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch @@ -33,10 +33,10 @@ index c2363cfa5e93942fe837efd9f39478698f6d1a98..2dfd412344a0e57f25a08d9c65656a13 scopedCollector.forChild(entity.problemPath()), entity.registryAccess() ); diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java -index 3cf2378a2ccf117fab9fc6fc60fcb0ecdf638d45..abccad13c2bb3a33e98ad8eb6d7f08c0ef021811 100644 +index b2326a2785b649268d7521706b5d29426bfc181c..83c2347ce42b36f5c2272ac82f2d8276865c6a1c 100644 --- a/net/minecraft/world/entity/EntityType.java +++ b/net/minecraft/world/entity/EntityType.java -@@ -1615,7 +1615,18 @@ public class EntityType implements FeatureElement, EntityTypeT +@@ -1623,7 +1623,18 @@ public class EntityType implements FeatureElement, EntityTypeT } public static Stream loadEntitiesRecursive(ValueInput.ValueInputList input, Level level, EntitySpawnReason spawnReason) { diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch index cdf7f08ddbe2..619bc70b9122 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java -@@ -41,13 +_,36 @@ +@@ -41,13 +_,41 @@ d4 = 0.0; } @@ -36,6 +36,11 @@ abstractBoat.setYRot(direction.toYRot()); - serverLevel.addFreshEntity(abstractBoat); - item.shrink(1); ++ // Paper start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(blockSource, abstractBoat, item).isCancelled()) { ++ return item; ++ } ++ // Paper end + if (serverLevel.addFreshEntity(abstractBoat) && shrink) item.shrink(1); // Paper - if entity add was successful and supposed to shrink } 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 ba660f85657e..c7e43c40cb42 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 @@ -1,6 +1,6 @@ --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -88,10 +_,38 @@ +@@ -88,22 +_,59 @@ if (type == null) { return item; } else { @@ -40,7 +40,19 @@ null, blockSource.pos().relative(direction), EntitySpawnReason.DISPENSER, -@@ -103,7 +_,8 @@ + direction != Direction.UP, + false ++ // Paper start - ItemSpawnEntityEvent ++ , org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG, ++ entity -> { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemSpawnEntityEvent(blockSource.level(), blockSource.pos().relative(direction), direction.getOpposite(), null, entity, item).isCancelled()) { ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ } ++ // Paper end - ItemSpawnEntityEvent + ); + } catch (Exception var6) { + LOGGER.error("Error while dispensing spawn egg from dispenser at {}", blockSource.pos(), var6); return ItemStack.EMPTY; } @@ -50,7 +62,7 @@ blockSource.level().gameEvent(null, GameEvent.ENTITY_PLACE, blockSource.pos()); return item; } -@@ -122,12 +_,38 @@ +@@ -122,12 +_,47 @@ Direction direction = blockSource.state().getValue(DispenserBlock.FACING); BlockPos blockPos = blockSource.pos().relative(direction); ServerLevel serverLevel = blockSource.level(); @@ -84,7 +96,16 @@ - armorStand1 -> armorStand1.setYRot(direction.toYRot()), serverLevel, item, null + armorStand1 -> armorStand1.setYRot(direction.toYRot()), serverLevel, newStack, null // Paper - track changed items in the dispense event ); ++ // Paper start - BlockPlaceEntityEvent ++ final java.util.concurrent.atomic.AtomicBoolean cancelled = new java.util.concurrent.atomic.AtomicBoolean(false); ++ consumer = consumer.andThen(stand -> { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(blockSource, stand, item).isCancelled()) { ++ cancelled.set(true); ++ } ++ }); ++ // Paper end - BlockPlaceEntityEvent ArmorStand armorStand = EntityType.ARMOR_STAND.spawn(serverLevel, consumer, blockPos, EntitySpawnReason.DISPENSER, false, false); ++ if (cancelled.get()) shrink = false; // Paper if (armorStand != null) { - item.shrink(1); + if (shrink) item.shrink(1); // Paper @@ -172,7 +193,7 @@ + if (dispensibleContainerItem.emptyContents(null, level, blockPos, null)) { - dispensibleContainerItem.checkExtraContent(null, level, item, blockPos); -+ dispensibleContainerItem.checkExtraContent(null, level, dispensedItem, blockPos); // Paper - track changed item from dispense event ++ dispensibleContainerItem.checkExtraContent(null, level, dispensedItem, blockPos, null, blockSource.state().getValue(DispenserBlock.FACING).getOpposite()); // Paper - track changed item from dispense event return this.consumeWithRemainder(blockSource, item, new ItemStack(Items.BUCKET)); } else { return this.defaultDispenseItemBehavior.dispense(blockSource, item); @@ -274,7 +295,7 @@ return item; } -@@ -281,11 +_,36 @@ +@@ -281,11 +_,41 @@ return item; } else { BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); @@ -305,6 +326,11 @@ + + PrimedTnt primedTnt = new PrimedTnt(serverLevel, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), null); + // CraftBukkit end ++ // Paper start - BlockPlaceEntityEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(blockSource, primedTnt, item).isCancelled()) { ++ return item; ++ } ++ // Paper end - BlockPlaceEntityEvent serverLevel.addFreshEntity(primedTnt); serverLevel.playSound(null, primedTnt.getX(), primedTnt.getY(), primedTnt.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F); - serverLevel.gameEvent(null, GameEvent.ENTITY_PLACE, blockPos); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch index 54535b61d022..de3d4eedaa11 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch @@ -8,7 +8,7 @@ private static final Logger LOGGER = LogUtils.getLogger(); private final Holder.Reference> builtInRegistryHolder = BuiltInRegistries.ENTITY_TYPE.createIntrusiveHolder(this); public static final Codec> CODEC = BuiltInRegistries.ENTITY_TYPE.byNameCodec(); -@@ -1290,6 +_,22 @@ +@@ -1290,14 +_,37 @@ boolean shouldOffsetY, boolean shouldOffsetYMore ) { @@ -27,20 +27,27 @@ + boolean shouldOffsetYMore, + org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason + ) { ++ // Paper start ++ return this.spawn(level, spawnedFrom, owner, pos, spawnReason, shouldOffsetY, shouldOffsetYMore, createSpawnReason, null); ++ } ++ ++ public @Nullable T spawn(ServerLevel level, @Nullable ItemStack spawnedFrom, @Nullable LivingEntity owner, BlockPos pos, EntitySpawnReason spawnReason, boolean shouldOffsetY, boolean shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason, final @Nullable Consumer op) { ++ // Paper end + // CraftBukkit end Consumer consumer; if (spawnedFrom != null) { consumer = createDefaultStackConfig(level, spawnedFrom, owner); -@@ -1297,7 +_,7 @@ + } else { consumer = entity -> {}; } ++ if (op != null) consumer = consumer.andThen(op); // Paper - return this.spawn(level, consumer, pos, spawnReason, shouldOffsetY, shouldOffsetYMore); + return this.spawn(level, consumer, pos, spawnReason, shouldOffsetY, shouldOffsetYMore, createSpawnReason); // CraftBukkit } public static Consumer createDefaultStackConfig(Level level, ItemStack stack, @Nullable LivingEntity owner) { -@@ -1314,19 +_,54 @@ +@@ -1314,19 +_,55 @@ public static Consumer appendCustomEntityStackConfig(Consumer consumer, Level level, ItemStack stack, @Nullable LivingEntity owner) { TypedEntityData> typedEntityData = stack.get(DataComponents.ENTITY_DATA); @@ -89,6 +96,7 @@ T entity = this.create(level, consumer, pos, spawnReason, shouldOffsetY, shouldOffsetYMore); if (entity != null) { - level.addFreshEntityWithPassengers(entity); ++ if (entity.isRemoved()) return null; // Paper - if consumer removed entity, return null + // CraftBukkit start + level.addFreshEntityWithPassengers(entity, creatureSpawnReason); + if (entity.isRemoved()) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch index ebd369c1cf4c..dc7a25877ac1 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch @@ -29,7 +29,7 @@ if (!level.isClientSide()) { - level.addFreshEntity(boat); + // CraftBukkit start -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, playerPovHitResult.getBlockPos(), player.getDirection(), player, boat, hand).isCancelled()) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, playerPovHitResult.getBlockPos(), player.getDirection(), player, boat, hand, itemInHand).isCancelled()) { // Paper - pass hand ItemStack + return InteractionResult.FAIL; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch index 534428c9d894..93d64da07457 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch @@ -32,16 +32,18 @@ if (!level.isClientSide()) { CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer)player, itemStack); } -@@ -75,7 +_,7 @@ +@@ -75,8 +_,8 @@ } else { BlockState blockState = level.getBlockState(blockPos); BlockPos blockPos2 = blockState.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockPos : blockPos1; - if (this.emptyContents(player, level, blockPos2, playerPovHitResult)) { +- this.checkExtraContent(player, level, itemInHand, blockPos2); + if (this.emptyContents(player, level, blockPos2, playerPovHitResult, playerPovHitResult.getDirection(), blockPos, itemInHand, hand)) { // CraftBukkit - this.checkExtraContent(player, level, itemInHand, blockPos2); ++ this.checkExtraContent(player, level, itemInHand, blockPos2, hand, playerPovHitResult.getDirection()); // Paper - pass hand and clicked block face if (player instanceof ServerPlayer) { CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, blockPos2, itemInHand); -@@ -92,6 +_,13 @@ + } +@@ -92,15 +_,26 @@ } public static ItemStack getEmptySuccessItem(ItemStack bucketStack, Player player) { @@ -55,7 +57,10 @@ return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : bucketStack; } -@@ -101,6 +_,12 @@ +- @Override +- public void checkExtraContent(@Nullable LivingEntity entity, Level level, ItemStack stack, BlockPos pos) { +- } ++ // Paper - delete checkExtraContents @Override public boolean emptyContents(@Nullable LivingEntity entity, Level level, BlockPos pos, @Nullable BlockHitResult hitResult) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/DispensibleContainerItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/DispensibleContainerItem.java.patch new file mode 100644 index 000000000000..b58f4147fab4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/DispensibleContainerItem.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/item/DispensibleContainerItem.java ++++ b/net/minecraft/world/item/DispensibleContainerItem.java +@@ -7,8 +_,13 @@ + import org.jspecify.annotations.Nullable; + + public interface DispensibleContainerItem { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper + default void checkExtraContent(@Nullable LivingEntity entity, Level level, ItemStack stack, BlockPos pos) { + } ++ // Paper start ++ default void checkExtraContent(net.minecraft.world.entity.player.@Nullable Player player, Level level, ItemStack stack, BlockPos pos, net.minecraft.world.@Nullable InteractionHand hand, net.minecraft.core.@Nullable Direction direction) { ++ } ++ // Paper end + + boolean emptyContents(@Nullable LivingEntity entity, Level level, BlockPos pos, @Nullable BlockHitResult hitResult); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/MobBucketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/MobBucketItem.java.patch index 1159ff5092b7..eefa4d81c856 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/MobBucketItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/MobBucketItem.java.patch @@ -1,10 +1,44 @@ --- a/net/minecraft/world/item/MobBucketItem.java +++ b/net/minecraft/world/item/MobBucketItem.java -@@ -49,7 +_,7 @@ +@@ -28,9 +_,9 @@ + } + + @Override +- public void checkExtraContent(@Nullable LivingEntity entity, Level level, ItemStack stack, BlockPos pos) { ++ public void checkExtraContent(net.minecraft.world.entity.player.@Nullable Player entity, Level level, ItemStack stack, BlockPos pos, net.minecraft.world.@Nullable InteractionHand hand, net.minecraft.core.@Nullable Direction direction) { // Paper - add parameters + if (level instanceof ServerLevel) { +- this.spawn((ServerLevel)level, stack, pos); ++ this.spawn((ServerLevel)level, stack, pos, entity, hand, direction); // Paper - add parameters + level.gameEvent(entity, GameEvent.ENTITY_PLACE, pos); + } + } +@@ -40,7 +_,7 @@ + level.playSound(entity, pos, this.emptySound, SoundSource.NEUTRAL, 1.0F, 1.0F); + } + +- private void spawn(ServerLevel level, ItemStack bucketedMobStack, BlockPos pos) { ++ private void spawn(ServerLevel level, ItemStack bucketedMobStack, BlockPos pos, net.minecraft.world.entity.player.@Nullable Player player, net.minecraft.world.@Nullable InteractionHand hand, net.minecraft.core.@Nullable Direction direction) { // Paper - add more parameters for events + Mob mob = this.type.create(level, EntityType.createDefaultStackConfig(level, bucketedMobStack, null), pos, EntitySpawnReason.BUCKET, true, false); + if (mob instanceof Bucketable bucketable) { + CustomData customData = bucketedMobStack.getOrDefault(DataComponents.BUCKET_ENTITY_DATA, CustomData.EMPTY); +@@ -49,7 +_,20 @@ } if (mob != null) { - level.addFreshEntityWithPassengers(mob); ++ // Paper start - BlockPlaceEntityEvent & EntityPlaceEvent ++ if (direction != null) { ++ if (hand == null) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(level, pos, direction, mob, bucketedMobStack).isCancelled()) { ++ return; // mob doesn't exist yet, no need to discard ++ } ++ } else { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, pos, direction, player, mob, hand, bucketedMobStack).isCancelled()) { ++ return; // mob doesn't exist yet, no need to discard ++ } ++ } ++ } ++ // Paper end - BlockPlaceEntityEvent & EntityPlaceEvent + level.addFreshEntityWithPassengers(mob, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUCKET); // Paper - Add SpawnReason mob.playAmbientSound(); } diff --git a/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch index 9ce8994289c2..6e8180816e5b 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch @@ -8,7 +8,21 @@ spawner.setEntityId(type, level.getRandom()); level.sendBlockUpdated(clickedPos, blockState, blockState, Block.UPDATE_ALL); level.gameEvent(context.getPlayer(), GameEvent.BLOCK_CHANGE, clickedPos); -@@ -96,7 +_,7 @@ +@@ -84,22 +_,29 @@ + } + + return this.spawnMob( +- context.getPlayer(), itemInHand, level, blockPos, true, !Objects.equals(clickedPos, blockPos) && clickedFace == Direction.UP ++ context.getPlayer(), itemInHand, level, blockPos, true, !Objects.equals(clickedPos, blockPos) && clickedFace == Direction.UP, clickedFace // Paper - pass clickedFace + ); + } + } + } + + private InteractionResult spawnMob( +- @Nullable LivingEntity owner, ItemStack stack, Level level, BlockPos pos, boolean shouldOffsetY, boolean shouldOffsetYMore ++ @Nullable Player owner, ItemStack stack, Level level, BlockPos pos, boolean shouldOffsetY, boolean shouldOffsetYMore, Direction clickedFace + ) { EntityType type = this.getType(stack); if (type == null) { return InteractionResult.FAIL; @@ -16,7 +30,27 @@ + } else if (!type.isAllowedInPeaceful(stack.get(DataComponents.ENTITY_DATA).getUnsafe()) && level.getDifficulty() == Difficulty.PEACEFUL) { // Paper - check peaceful override return InteractionResult.FAIL; } else { - if (type.spawn((ServerLevel)level, stack, owner, pos, EntitySpawnReason.SPAWN_ITEM_USE, shouldOffsetY, shouldOffsetYMore) != null) { +- if (type.spawn((ServerLevel)level, stack, owner, pos, EntitySpawnReason.SPAWN_ITEM_USE, shouldOffsetY, shouldOffsetYMore) != null) { ++ // Paper start - call ItemSpawnEntityEvent ++ java.util.function.Consumer op = e -> { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemSpawnEntityEvent(level, pos, clickedFace, owner, e, stack).isCancelled()) { ++ e.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ }; ++ if (type.spawn((ServerLevel)level, stack, owner, pos, EntitySpawnReason.SPAWN_ITEM_USE, shouldOffsetY, shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG, op) != null) { ++ // Paper end - call ItemSpawnEntityEvent + stack.consume(1, owner); + level.gameEvent(owner, GameEvent.ENTITY_PLACE, pos); + } +@@ -119,7 +_,7 @@ + if (!(level.getBlockState(blockPos).getBlock() instanceof LiquidBlock)) { + return InteractionResult.PASS; + } else if (level.mayInteract(player, blockPos) && player.mayUseItemAt(blockPos, playerPovHitResult.getDirection(), itemInHand)) { +- InteractionResult interactionResult = this.spawnMob(player, itemInHand, level, blockPos, false, false); ++ InteractionResult interactionResult = this.spawnMob(player, itemInHand, level, blockPos, false, false, playerPovHitResult.getDirection()); // Paper - pass clicked block face + if (interactionResult == InteractionResult.SUCCESS) { + player.awardStat(Stats.ITEM_USED.get(this)); + } @@ -178,7 +_,7 @@ } else { breedOffspring.snapTo(pos.x(), pos.y(), pos.z(), 0.0F, 0.0F); 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 cd83ca2ace1d..52677c5613ed 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 @@ -6,6 +6,8 @@ import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Either; +import io.papermc.paper.event.entity.BlockPlaceEntityEvent; +import io.papermc.paper.event.entity.ItemSpawnEntityEvent; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; @@ -25,6 +27,7 @@ import io.papermc.paper.event.player.PlayerBedFailEnterEvent; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.dispenser.BlockSource; import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; @@ -68,6 +71,7 @@ import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.DispenserBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.SignBlockEntity; import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; @@ -554,20 +558,45 @@ public static void handleBlockDropItemEvent(Block block, BlockState state, Serve } public static EntityPlaceEvent callEntityPlaceEvent(UseOnContext context, Entity entity) { - return CraftEventFactory.callEntityPlaceEvent(context.getLevel(), context.getClickedPos(), context.getClickedFace(), context.getPlayer(), entity, context.getHand()); + return CraftEventFactory.callEntityPlaceEvent(context.getLevel(), context.getClickedPos(), context.getClickedFace(), context.getPlayer(), entity, context.getHand(), context.getItemInHand()); } - public static EntityPlaceEvent callEntityPlaceEvent(Level world, BlockPos clickedPos, Direction clickedFace, net.minecraft.world.entity.player.Player player, Entity entity, InteractionHand hand) { + public static EntityPlaceEvent callEntityPlaceEvent(final Level world, final BlockPos clickedPos, final Direction clickedFace, final net.minecraft.world.entity.player.@Nullable Player player, final Entity entity, final InteractionHand hand, final ItemStack spawningStack) { Player cplayer = (player == null) ? null : (Player) player.getBukkitEntity(); org.bukkit.block.Block clickedBlock = CraftBlock.at(world, clickedPos); org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(clickedFace); - EntityPlaceEvent event = new EntityPlaceEvent(entity.getBukkitEntity(), cplayer, clickedBlock, blockFace, CraftEquipmentSlot.getHand(hand)); + final EntityPlaceEvent event = new EntityPlaceEvent(entity.getBukkitEntity(), cplayer, clickedBlock, blockFace, CraftEquipmentSlot.getHand(hand), spawningStack.asBukkitCopy()); entity.level().getCraftServer().getPluginManager().callEvent(event); return event; } + public static ItemSpawnEntityEvent callItemSpawnEntityEvent(final Level world, final BlockPos clickPosition, final Direction clickedFace, @Nullable final net.minecraft.world.entity.player.Player human, final Entity entity, final ItemStack spawningStack) { + final Player who = (human == null) ? null : (Player) human.getBukkitEntity(); + final Block blockClicked = CraftBlock.at(world, clickPosition); + final BlockFace blockFace = CraftBlock.notchToBlockFace(clickedFace); + + final ItemSpawnEntityEvent event = new ItemSpawnEntityEvent(entity.getBukkitEntity(), who, blockClicked, blockFace, CraftItemStack.asBukkitCopy(spawningStack)); + event.callEvent(); + return event; + } + + public static BlockPlaceEntityEvent callBlockPlaceEntityEvent(final BlockSource pointer, final Entity entity, final ItemStack spawningStack) { + final Direction direction = pointer.state().getValue(DispenserBlock.FACING); + return callBlockPlaceEntityEvent(pointer.level(), pointer.pos().relative(direction), direction.getOpposite(), entity, spawningStack); + } + + public static BlockPlaceEntityEvent callBlockPlaceEntityEvent(final Level world, final BlockPos clickedPosition, final Direction clickedFace, final Entity entity, final ItemStack spawningStack) { + final Block blockClicked = CraftBlock.at(world, clickedPosition); + final BlockFace blockFace = CraftBlock.notchToBlockFace(clickedFace); + final org.bukkit.block.Dispenser dispenser = (org.bukkit.block.Dispenser) CraftBlockStates.getBlockState(CraftBlock.at(world, clickedPosition.relative(clickedFace))); + + final BlockPlaceEntityEvent event = new BlockPlaceEntityEvent(entity.getBukkitEntity(), blockClicked, blockFace, CraftItemStack.asBukkitCopy(spawningStack), dispenser); + event.callEvent(); + return event; + } + public static PlayerBucketEmptyEvent callPlayerBucketEmptyEvent(Level world, net.minecraft.world.entity.player.Player player, BlockPos changed, BlockPos clicked, Direction clickedFace, ItemStack itemInHand, InteractionHand hand) { return (PlayerBucketEmptyEvent) CraftEventFactory.getPlayerBucketEvent(false, world, player, changed, clicked, clickedFace, itemInHand, Items.BUCKET, hand); } diff --git a/paper-server/src/test/java/io/papermc/paper/block/DispensibleContainerItemExtraContentsOverrideTest.java b/paper-server/src/test/java/io/papermc/paper/block/DispensibleContainerItemExtraContentsOverrideTest.java new file mode 100644 index 000000000000..b1e599a6802e --- /dev/null +++ b/paper-server/src/test/java/io/papermc/paper/block/DispensibleContainerItemExtraContentsOverrideTest.java @@ -0,0 +1,50 @@ +package io.papermc.paper.block; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.MethodInfo; +import io.github.classgraph.MethodInfoList; +import io.github.classgraph.MethodParameterInfo; +import io.github.classgraph.ScanResult; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import net.minecraft.world.item.DispensibleContainerItem; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DispensibleContainerItemExtraContentsOverrideTest { + + public static Stream parameters() { + final List classInfo = new ArrayList<>(); + try (final ScanResult scanResult = new ClassGraph() + .enableClassInfo() + .enableMethodInfo() + .whitelistPackages("net.minecraft") + .scan() + ) { + final ClassInfoList classesImplementing = scanResult.getClassesImplementing(DispensibleContainerItem.class.getName()); + for (final ClassInfo info : classesImplementing) { + if (info.hasDeclaredMethod("checkExtraContent")) { + classInfo.add(info); + } + } + } + return classInfo.stream(); + } + + @ParameterizedTest + @MethodSource("parameters") + public void checkCheckExtraContentOverride(final ClassInfo implementsDispensibleContainerItem) { + final MethodInfoList checkExtraContent = implementsDispensibleContainerItem.getDeclaredMethodInfo("checkExtraContent"); + assertEquals(1, checkExtraContent.size(), implementsDispensibleContainerItem.getName() + " has multiple checkExtraContent methods"); + final MethodInfo next = checkExtraContent.iterator().next(); + final MethodParameterInfo[] parameterInfo = next.getParameterInfo(); + assertEquals(6, parameterInfo.length, implementsDispensibleContainerItem.getName() + " doesn't have 6 params for checkExtraContent"); + assertEquals("InteractionHand", parameterInfo[parameterInfo.length - 2].getTypeDescriptor().toStringWithSimpleNames(), implementsDispensibleContainerItem.getName() + " needs to change its override of checkExtraContent"); + assertEquals("Direction", parameterInfo[parameterInfo.length - 1].getTypeDescriptor().toStringWithSimpleNames(), implementsDispensibleContainerItem.getName() + " needs to change its override of checkExtraContent"); + } +} From c98589f21918e4aaf67c850de7adf62708c72481 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:00:53 +0100 Subject: [PATCH 2/2] format --- .../paper/event/entity/BlockPlaceEntityEvent.java | 3 ++- .../paper/event/entity/ItemSpawnEntityEvent.java | 10 +++++----- .../papermc/paper/event/entity/PlaceEntityEvent.java | 3 ++- .../java/org/bukkit/event/entity/EntityPlaceEvent.java | 3 ++- .../0018-Entity-load-save-limit-per-chunk.patch | 4 ++-- .../net/minecraft/world/entity/EntityType.java.patch | 4 +--- .../net/minecraft/world/item/BoatItem.java.patch | 2 +- .../bukkit/craftbukkit/event/CraftEventFactory.java | 4 ++-- 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java index 385800be4067..1a658afd2f12 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java @@ -13,7 +13,8 @@ /** * Called when a block, like a dispenser, places an - * entity. {@link #getPlayer()} will always be null. + * entity. {@link #getPlayer()} will always be {@code null}. + * * @see org.bukkit.event.hanging.HangingPlaceEvent for paintings, item frames, and leashes. * @see org.bukkit.event.entity.EntityPlaceEvent for a player-only version with more context * @see PlaceEntityEvent to listen to both blocks and players placing entities diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java index c7af5b009531..81b688bb6bac 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java @@ -13,7 +13,7 @@ import org.jspecify.annotations.Nullable; /** - * When an itemstack causes the spawning of an entity. Most event fires are going to + * When an item causes the spawning of an entity. Most event fires are going to * be the through the sub-event {@link org.bukkit.event.entity.EntityPlaceEvent} but this * event will also be fired for mob spawn eggs from players and dispensers. */ @@ -65,12 +65,12 @@ public BlockFace getBlockFace() { } /** - * Gets the itemstack responsible for spawning the entity. Mutating - * this itemstack has no effect. + * Gets the item responsible for spawning the entity. Mutating + * this item has no effect. *

- * May return an empty itemstack if the actual stack isn't available. + * May return an empty item if the actual stack isn't available. * - * @return the spawning itemstack + * @return the spawning item */ public ItemStack getSpawningStack() { return this.spawningStack; diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java index 4261d393cc3a..5bd4b85c8b57 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java @@ -15,9 +15,10 @@ *
* Note that this event is currently only fired for these specific placements: * armor stands, boats, minecarts, end crystals, mob buckets, and tnt (dispenser only). + * * @see org.bukkit.event.hanging.HangingPlaceEvent for paintings, item frames, and leashes. * @see org.bukkit.event.entity.EntityPlaceEvent for a player-only version with more context - * @see BlockPlaceEntityEvent for a dispener-only version with more context + * @see BlockPlaceEntityEvent for a dispenser-only version with more context */ @NullMarked public abstract class PlaceEntityEvent extends ItemSpawnEntityEvent { diff --git a/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java b/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java index c0f683f79639..9df6cff53909 100644 --- a/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java @@ -18,12 +18,13 @@ *
* Note that this event is currently only fired for these specific placements: * armor stands, boats, minecarts, end crystals, and mob buckets. + * * @see org.bukkit.event.hanging.HangingPlaceEvent for paintings, item frames, and leashes. * @see io.papermc.paper.event.entity.BlockPlaceEntityEvent for a dispenser-only version * @see io.papermc.paper.event.entity.PlaceEntityEvent to listen to both blocks and players placing entities */ @NullMarked -public class EntityPlaceEvent extends PlaceEntityEvent implements Cancellable { // Paper - Add superclass +public class EntityPlaceEvent extends PlaceEntityEvent { private final EquipmentSlot hand; diff --git a/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch b/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch index 3b3cfe70a83d..cba46e5fe6f7 100644 --- a/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch +++ b/paper-server/patches/features/0018-Entity-load-save-limit-per-chunk.patch @@ -33,10 +33,10 @@ index c2363cfa5e93942fe837efd9f39478698f6d1a98..2dfd412344a0e57f25a08d9c65656a13 scopedCollector.forChild(entity.problemPath()), entity.registryAccess() ); diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java -index b2326a2785b649268d7521706b5d29426bfc181c..83c2347ce42b36f5c2272ac82f2d8276865c6a1c 100644 +index da522a51978b3c0f67a533ab2085b3f499134dd1..7f7e2f856550737eb9dfbaf1cdb3ee2f655fc674 100644 --- a/net/minecraft/world/entity/EntityType.java +++ b/net/minecraft/world/entity/EntityType.java -@@ -1623,7 +1623,18 @@ public class EntityType implements FeatureElement, EntityTypeT +@@ -1621,7 +1621,18 @@ public class EntityType implements FeatureElement, EntityTypeT } public static Stream loadEntitiesRecursive(ValueInput.ValueInputList input, Level level, EntitySpawnReason spawnReason) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch index de3d4eedaa11..a7ea5cfb4a1b 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch @@ -8,7 +8,7 @@ private static final Logger LOGGER = LogUtils.getLogger(); private final Holder.Reference> builtInRegistryHolder = BuiltInRegistries.ENTITY_TYPE.createIntrusiveHolder(this); public static final Codec> CODEC = BuiltInRegistries.ENTITY_TYPE.byNameCodec(); -@@ -1290,14 +_,37 @@ +@@ -1290,14 +_,35 @@ boolean shouldOffsetY, boolean shouldOffsetYMore ) { @@ -27,12 +27,10 @@ + boolean shouldOffsetYMore, + org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason + ) { -+ // Paper start + return this.spawn(level, spawnedFrom, owner, pos, spawnReason, shouldOffsetY, shouldOffsetYMore, createSpawnReason, null); + } + + public @Nullable T spawn(ServerLevel level, @Nullable ItemStack spawnedFrom, @Nullable LivingEntity owner, BlockPos pos, EntitySpawnReason spawnReason, boolean shouldOffsetY, boolean shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason, final @Nullable Consumer op) { -+ // Paper end + // CraftBukkit end Consumer consumer; if (spawnedFrom != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch index dc7a25877ac1..3700fba9ebd1 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch @@ -29,7 +29,7 @@ if (!level.isClientSide()) { - level.addFreshEntity(boat); + // CraftBukkit start -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, playerPovHitResult.getBlockPos(), player.getDirection(), player, boat, hand, itemInHand).isCancelled()) { // Paper - pass hand ItemStack ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, playerPovHitResult.getBlockPos(), player.getDirection(), player, boat, hand, itemInHand).isCancelled()) { + return InteractionResult.FAIL; + } + 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 52677c5613ed..d24f7d1a76e4 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 @@ -562,11 +562,11 @@ public static EntityPlaceEvent callEntityPlaceEvent(UseOnContext context, Entity } public static EntityPlaceEvent callEntityPlaceEvent(final Level world, final BlockPos clickedPos, final Direction clickedFace, final net.minecraft.world.entity.player.@Nullable Player player, final Entity entity, final InteractionHand hand, final ItemStack spawningStack) { - Player cplayer = (player == null) ? null : (Player) player.getBukkitEntity(); + Player bukkitPlayer = (player == null) ? null : (Player) player.getBukkitEntity(); org.bukkit.block.Block clickedBlock = CraftBlock.at(world, clickedPos); org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(clickedFace); - final EntityPlaceEvent event = new EntityPlaceEvent(entity.getBukkitEntity(), cplayer, clickedBlock, blockFace, CraftEquipmentSlot.getHand(hand), spawningStack.asBukkitCopy()); + final EntityPlaceEvent event = new EntityPlaceEvent(entity.getBukkitEntity(), bukkitPlayer, clickedBlock, blockFace, CraftEquipmentSlot.getHand(hand), spawningStack.asBukkitCopy()); entity.level().getCraftServer().getPluginManager().callEvent(event); return event;