From 5ef0ea1ea74360a10b070cfabb562fdd1abc33c3 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Thu, 20 Jan 2022 10:55:19 -0800 Subject: [PATCH] Add ItemSpawnEntityEvent and expand EntityPlaceEvent --- ...ityEvent-and-expand-EntityPlaceEvent.patch | 304 ++++++++++++++ ...ityEvent-and-expand-EntityPlaceEvent.patch | 374 ++++++++++++++++++ 2 files changed, 678 insertions(+) create mode 100644 patches/api/0480-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch create mode 100644 patches/server/1049-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch diff --git a/patches/api/0480-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch b/patches/api/0480-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch new file mode 100644 index 0000000000..ebc8460c68 --- /dev/null +++ b/patches/api/0480-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch @@ -0,0 +1,304 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 Jan 2022 10:54:54 -0800 +Subject: [PATCH] Add ItemSpawnEntityEvent and expand EntityPlaceEvent + + +diff --git a/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java b/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7287ebcf3338ca30cc23efa22084cdaefbcad2da +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java +@@ -0,0 +1,48 @@ ++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.jetbrains.annotations.NotNull; ++import org.jetbrains.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 ++ */ ++public class BlockPlaceEntityEvent extends PlaceEntityEvent { ++ ++ private final Dispenser dispenser; ++ ++ @ApiStatus.Internal ++ public BlockPlaceEntityEvent(final @NotNull Entity entity, final @NotNull Block block, final @NotNull BlockFace blockFace, final @NotNull ItemStack spawningStack, final @NotNull 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 @NotNull 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/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java b/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6668171113257f5383904ab145da089600630544 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/entity/ItemSpawnEntityEvent.java +@@ -0,0 +1,96 @@ ++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.jetbrains.annotations.NotNull; ++import org.jetbrains.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. ++ */ ++public class ItemSpawnEntityEvent extends EntityEvent implements Cancellable { ++ ++ private static final HandlerList HANDLER_LIST = new HandlerList(); ++ ++ private final Player player; ++ private final Block block; ++ private final BlockFace blockFace; ++ private final ItemStack spawningStack; ++ private boolean cancelled; ++ ++ @ApiStatus.Internal ++ public ItemSpawnEntityEvent(final @NotNull Entity entity, final @Nullable Player player, final @NotNull Block block, final @NotNull BlockFace blockFace, final @NotNull 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 @NotNull 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 @NotNull 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 @NotNull ItemStack getSpawningStack() { ++ return this.spawningStack; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return this.cancelled; ++ } ++ ++ @Override ++ public void setCancelled(final boolean cancel) { ++ this.cancelled = cancel; ++ } ++ ++ @Override ++ public @NotNull HandlerList getHandlers() { ++ return HANDLER_LIST; ++ } ++ ++ public static @NotNull HandlerList getHandlerList() { ++ return HANDLER_LIST; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java b/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bfd7204c71a1ca46df3e5174aef3fe33e59b4ee6 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java +@@ -0,0 +1,28 @@ ++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.jetbrains.annotations.NotNull; ++import org.jetbrains.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 ++ */ ++public abstract class PlaceEntityEvent extends ItemSpawnEntityEvent { ++ ++ @ApiStatus.Internal ++ protected PlaceEntityEvent(final @NotNull Entity entity, final @Nullable Player player, final @NotNull Block block, final @NotNull BlockFace blockFace, final @NotNull ItemStack spawningStack) { ++ super(entity, player, block, blockFace, spawningStack); ++ } ++} +diff --git a/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java b/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java +index 71d664dd89995f088c47d17b38547d530319470c..645ef8dde4acce134531cb94344d9d84a8ffdc6a 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java +@@ -14,61 +14,34 @@ import org.jetbrains.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 { ++public class EntityPlaceEvent extends io.papermc.paper.event.entity.PlaceEntityEvent implements Cancellable { // Paper - move to superclass + +- private static final HandlerList handlers = new HandlerList(); +- private boolean cancelled; +- private final Player player; +- private final Block block; +- private final BlockFace blockFace; ++ // Paper - move to superclass + private final EquipmentSlot hand; + +- 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; +- this.hand = hand; +- } +- ++ // Paper start - move event to superclass + @Deprecated + 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 player; +- } +- +- /** +- * Returns the block that the entity was placed on +- * +- * @return the block that the entity was placed on +- */ +- @NotNull +- public Block getBlock() { +- return block; ++ @Deprecated ++ public EntityPlaceEvent(@NotNull final Entity entity, @Nullable final Player player, @NotNull final Block block, @NotNull final BlockFace blockFace, @NotNull final EquipmentSlot hand) { ++ this(entity, player, block, blockFace, hand, org.bukkit.inventory.ItemStack.empty()); + } + +- /** +- * 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 +- public BlockFace getBlockFace() { +- return blockFace; ++ @org.jetbrains.annotations.ApiStatus.Internal ++ public EntityPlaceEvent(final @NotNull Entity entity, final @Nullable Player player, final @NotNull Block block, final @NotNull BlockFace blockFace, final @NotNull EquipmentSlot hand, final @NotNull org.bukkit.inventory.ItemStack spawningStack) { ++ super(entity, player, block, blockFace, spawningStack); ++ this.hand = hand; + } +- ++ // Paper end + /** + * Get the hand used to place the entity. + * +@@ -79,24 +52,5 @@ public class EntityPlaceEvent extends EntityEvent implements Cancellable { + return hand; + } + +- @Override +- public boolean isCancelled() { +- return cancelled; +- } +- +- @Override +- public void setCancelled(boolean cancel) { +- this.cancelled = cancel; +- } +- +- @NotNull +- @Override +- public HandlerList getHandlers() { +- return handlers; +- } +- +- @NotNull +- public static HandlerList getHandlerList() { +- return handlers; +- } ++ // Paper - move to superclass + } diff --git a/patches/server/1049-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch b/patches/server/1049-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch new file mode 100644 index 0000000000..71b4bf4758 --- /dev/null +++ b/patches/server/1049-Add-ItemSpawnEntityEvent-and-expand-EntityPlaceEvent.patch @@ -0,0 +1,374 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 Jan 2022 10:54:45 -0800 +Subject: [PATCH] Add ItemSpawnEntityEvent and expand EntityPlaceEvent + + +diff --git a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +index 6df0db8b4cdab23494ea34236949ece4989110a3..9ebc4e88efcf1ab3130f150a84c3f2c72d18c92c 100644 +--- a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -90,6 +90,11 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { + EntityType.createDefaultStackConfig(worldserver, stack, (Player) null).accept(object); + ((Boat) object).setVariant(this.type); + ((Boat) object).setYRot(enumdirection.toYRot()); ++ // Paper start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(pointer, object, stack).isCancelled()) { ++ return stack; ++ } ++ // Paper end + if (worldserver.addFreshEntity((Entity) object) && shrink) stack.shrink(1); // Paper - if entity add was successful and supposed to shrink + return stack; + } +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 5dab1e10303177e5a4d97a91ee46ede66f30ae35..f1a1168d1cff8095eb8fdd6ac445ae8a26161692 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -140,7 +140,13 @@ public interface DispenseItemBehavior { + } + + try { +- entitytypes.spawn(pointer.level(), itemstack1, (Player) null, pointer.pos().relative(enumdirection), MobSpawnType.DISPENSER, enumdirection != Direction.UP, false); // Paper - track changed item in dispense event ++ // Paper start ++ entitytypes.spawn(pointer.level(), itemstack1, (Player) null, pointer.pos().relative(enumdirection), MobSpawnType.DISPENSER, enumdirection != Direction.UP, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG, entity -> { // Paper - track changed item in dispense event ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemSpawnEntityEvent(pointer.level(), pointer.pos().relative(enumdirection), enumdirection.getOpposite(), null, entity, stack).isCancelled()) { ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ }); ++ // Paper end + } catch (Exception exception) { + DispenseItemBehavior.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", pointer.pos(), exception); // CraftBukkit - decompile error + return ItemStack.EMPTY; +@@ -199,7 +205,16 @@ public interface DispenseItemBehavior { + Consumer consumer = EntityType.appendDefaultStackConfig((entityarmorstand) -> { + entityarmorstand.setYRot(enumdirection.toYRot()); + }, worldserver, newStack, (Player) null); // Paper - track changed items in the dispense event ++ // Paper start ++ 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(pointer, stand, stack).isCancelled()) { ++ cancelled.set(true); ++ } ++ }); ++ // Paper end + ArmorStand entityarmorstand = (ArmorStand) EntityType.ARMOR_STAND.spawn(worldserver, consumer, blockposition, MobSpawnType.DISPENSER, false, false); ++ if (cancelled.get()) shrink = false; // Paper + + if (entityarmorstand != null) { + if (shrink) stack.shrink(1); // Paper - actually handle here +@@ -456,7 +471,7 @@ public interface DispenseItemBehavior { + // CraftBukkit end + + if (dispensiblecontaineritem.emptyContents((Player) null, worldserver, blockposition, (BlockHitResult) null)) { +- dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, dispensedItem, blockposition); // Paper - track changed item from dispense event ++ dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, dispensedItem, blockposition, null, pointer.state().getValue(DispenserBlock.FACING).getOpposite()); // Paper - track changed item from dispense event + // CraftBukkit start - Handle stacked buckets + Item item = Items.BUCKET; + stack.shrink(1); +@@ -708,6 +723,11 @@ public interface DispenseItemBehavior { + + PrimedTnt entitytntprimed = new PrimedTnt(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), (LivingEntity) null); + // CraftBukkit end ++ // Paper start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(pointer, entitytntprimed, stack).isCancelled()) { ++ return stack; ++ } ++ // Paper end + + worldserver.addFreshEntity(entitytntprimed); + worldserver.playSound((Player) null, entitytntprimed.getX(), entitytntprimed.getY(), entitytntprimed.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F); +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index a46bf73c608641bf1f00fd55242de71a0f2ee06e..2589b77aee2ffcd2d743d28c3c222f2fcd5798c5 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -371,6 +371,13 @@ public class EntityType implements FeatureElement, EntityTypeT + + @Nullable + public T spawn(ServerLevel worldserver, @Nullable ItemStack itemstack, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start ++ return this.spawn(worldserver, itemstack, entityhuman, blockposition, enummobspawn, flag, flag1, spawnReason, null); ++ } ++ ++ @Nullable ++ public T spawn(ServerLevel worldserver, @Nullable ItemStack itemstack, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason, @Nullable final Consumer op) { ++ // Paper end + // CraftBukkit end + Consumer consumer; // CraftBukkit - decompile error + +@@ -380,6 +387,7 @@ public class EntityType implements FeatureElement, EntityTypeT + consumer = (entity) -> { + }; + } ++ if (op != null) consumer = consumer.andThen(op); // Paper + + return this.spawn(worldserver, consumer, blockposition, enummobspawn, flag, flag1, spawnReason); // CraftBukkit + } +@@ -443,6 +451,7 @@ public class EntityType implements FeatureElement, EntityTypeT + T t0 = this.create(worldserver, consumer, blockposition, enummobspawn, flag, flag1); + + if (t0 != null) { ++ if (t0.isRemoved()) return null; // Paper - if consumer removed entity, return null + worldserver.addFreshEntityWithPassengers(t0, spawnReason); + return !t0.isRemoved() ? t0 : null; // Don't return an entity when CreatureSpawnEvent is canceled + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/BoatItem.java b/src/main/java/net/minecraft/world/item/BoatItem.java +index eb74d45ad458b80cf8455297c3bc550186adaea3..2bc6df3255b34bfc6d3bb8c9bd9e2bd70af1b049 100644 +--- a/src/main/java/net/minecraft/world/item/BoatItem.java ++++ b/src/main/java/net/minecraft/world/item/BoatItem.java +@@ -77,7 +77,7 @@ public class BoatItem extends Item { + } else { + if (!world.isClientSide) { + // CraftBukkit start +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(world, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), user, entityboat, hand).isCancelled()) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(world, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), user, entityboat, hand, itemstack).isCancelled()) { // Paper + return InteractionResultHolder.fail(itemstack); + } + +diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java +index 49557d6f22c5725c663a231deab019d4f6fe95fa..d0d6a4525825d30fffce2efa86e77e8492109fea 100644 +--- a/src/main/java/net/minecraft/world/item/BucketItem.java ++++ b/src/main/java/net/minecraft/world/item/BucketItem.java +@@ -107,7 +107,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + BlockPos blockposition2 = iblockdata.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockposition : blockposition1; + + if (this.emptyContents(user, world, blockposition2, movingobjectpositionblock, movingobjectpositionblock.getDirection(), blockposition, itemstack, hand)) { // CraftBukkit +- this.checkExtraContent(user, world, itemstack, blockposition2); ++ this.checkExtraContent(user, world, itemstack, blockposition2, hand, movingobjectpositionblock.getDirection()); // Paper + if (user instanceof ServerPlayer) { + CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) user, blockposition2, itemstack); + } +@@ -135,8 +135,8 @@ public class BucketItem extends Item implements DispensibleContainerItem { + return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : stack; + } + +- @Override +- public void checkExtraContent(@Nullable Player player, Level world, ItemStack stack, BlockPos pos) {} ++ // @Override // Paper - comment out ++ // public void checkExtraContent(@Nullable Player player, Level world, ItemStack stack, BlockPos pos) {} + + @Override + public boolean emptyContents(@Nullable Player player, Level world, BlockPos pos, @Nullable BlockHitResult hitResult) { +diff --git a/src/main/java/net/minecraft/world/item/DispensibleContainerItem.java b/src/main/java/net/minecraft/world/item/DispensibleContainerItem.java +index 0ca0e2a0a5f63939bd30de22a55806152a5a7698..67f3bc5a88e23bb60e6b300cfa0a7c85fa3ba2d2 100644 +--- a/src/main/java/net/minecraft/world/item/DispensibleContainerItem.java ++++ b/src/main/java/net/minecraft/world/item/DispensibleContainerItem.java +@@ -7,8 +7,13 @@ import net.minecraft.world.level.Level; + import net.minecraft.world.phys.BlockHitResult; + + public interface DispensibleContainerItem { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper + default void checkExtraContent(@Nullable Player player, Level world, ItemStack stack, BlockPos pos) { + } ++ // Paper start ++ default void checkExtraContent(@Nullable Player player, Level world, ItemStack stack, BlockPos pos, @Nullable net.minecraft.world.InteractionHand hand, @Nullable net.minecraft.core.Direction direction) { ++ } ++ // Paper end + + boolean emptyContents(@Nullable Player player, Level world, BlockPos pos, @Nullable BlockHitResult hitResult); + } +diff --git a/src/main/java/net/minecraft/world/item/MobBucketItem.java b/src/main/java/net/minecraft/world/item/MobBucketItem.java +index dbbc7dd46484b3434ed3ef9bf5ef7ca7774f0d56..ad62f4fdf9aea0fed5c4953215f05fc048440bf8 100644 +--- a/src/main/java/net/minecraft/world/item/MobBucketItem.java ++++ b/src/main/java/net/minecraft/world/item/MobBucketItem.java +@@ -35,9 +35,9 @@ public class MobBucketItem extends BucketItem { + } + + @Override +- public void checkExtraContent(@Nullable Player player, Level world, ItemStack stack, BlockPos pos) { ++ public void checkExtraContent(@Nullable Player player, Level world, ItemStack stack, BlockPos pos, @Nullable net.minecraft.world.InteractionHand hand, @Nullable net.minecraft.core.Direction direction) { // Paper - add parameters + if (world instanceof ServerLevel) { +- this.spawn((ServerLevel)world, stack, pos); ++ this.spawn((ServerLevel)world, stack, pos, player, hand, direction); // Paper - add parameters + world.gameEvent(player, GameEvent.ENTITY_PLACE, pos); + } + } +@@ -47,8 +47,26 @@ public class MobBucketItem extends BucketItem { + world.playSound(player, pos, this.emptySound, SoundSource.NEUTRAL, 1.0F, 1.0F); + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper + private void spawn(ServerLevel world, ItemStack stack, BlockPos pos) { +- if (this.type.spawn(world, stack, null, pos, MobSpawnType.BUCKET, true, false) instanceof Bucketable bucketable) { ++ // Paper start ++ this.spawn(world, stack, pos, null, null, null); ++ } ++ private void spawn(ServerLevel world, ItemStack stack, BlockPos pos, @Nullable Player player, @Nullable net.minecraft.world.InteractionHand hand, @Nullable net.minecraft.core.Direction direction) { ++ if (this.type.spawn(world, stack, (Player)null, pos, MobSpawnType.BUCKET, true, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG, entity1 -> { ++ if (direction != null) { ++ if (hand == null) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(world, pos, direction, entity1, stack).isCancelled()) { ++ entity1.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ } else { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(world, pos, direction, player, entity1, hand, stack).isCancelled()) { ++ entity1.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ } ++ } ++ }) instanceof Bucketable bucketable) { ++ // Paper end + CustomData customData = stack.getOrDefault(DataComponents.BUCKET_ENTITY_DATA, CustomData.EMPTY); + bucketable.loadFromBucketTag(customData.copyTag()); + bucketable.setFromBucket(true); +diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +index 9cea8da84f39bb3f687139ef213ccea358724dee..a6704f39599164dd38c2a92999b7bb72b6e936b5 100644 +--- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java ++++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +@@ -66,7 +66,7 @@ public class SpawnEggItem extends Item { + Direction enumdirection = context.getClickedFace(); + BlockState iblockdata = world.getBlockState(blockposition); + BlockEntity tileentity = world.getBlockEntity(blockposition); +- EntityType entitytypes; ++ EntityType entitytypes; // Paper - fixme move to mc dev fixes + + if (tileentity instanceof Spawner) { + if (world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation +@@ -89,7 +89,13 @@ public class SpawnEggItem extends Item { + } + + entitytypes = this.getType(itemstack); +- if (entitytypes.spawn((ServerLevel) world, itemstack, context.getPlayer(), blockposition1, MobSpawnType.SPAWN_EGG, true, !Objects.equals(blockposition, blockposition1) && enumdirection == Direction.UP) != null) { ++ // Paper start ++ if (entitytypes.spawn((ServerLevel) world, itemstack, context.getPlayer(), blockposition1, MobSpawnType.SPAWN_EGG, true, !Objects.equals(blockposition, blockposition1) && enumdirection == Direction.UP, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG, entity -> { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemSpawnEntityEvent(context, entity).isCancelled()) { ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ }) != null) { ++ // Paper end + itemstack.shrink(1); + world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.ENTITY_PLACE, blockposition); + } +@@ -115,7 +121,13 @@ public class SpawnEggItem extends Item { + return InteractionResultHolder.pass(itemstack); + } else if (world.mayInteract(user, blockposition) && user.mayUseItemAt(blockposition, movingobjectpositionblock.getDirection(), itemstack)) { + EntityType entitytypes = this.getType(itemstack); +- Entity entity = entitytypes.spawn((ServerLevel) world, itemstack, user, blockposition, MobSpawnType.SPAWN_EGG, false, false); ++ // Paper start ++ Entity entity = entitytypes.spawn((ServerLevel) world, itemstack, user, blockposition, MobSpawnType.SPAWN_EGG, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG, e -> { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemSpawnEntityEvent(world, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), user, e, itemstack).isCancelled()) { ++ e.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ }); ++ // Paper end + + if (entity == null) { + return InteractionResultHolder.pass(itemstack); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 34b91eff3190848bae38b20e1d956ece497b1473..61c05182fa73f0b2f2c9d64d6fcc138929277309 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -479,19 +479,56 @@ public class CraftEventFactory { + } + + public static EntityPlaceEvent callEntityPlaceEvent(UseOnContext itemactioncontext, Entity entity) { +- return CraftEventFactory.callEntityPlaceEvent(itemactioncontext.getLevel(), itemactioncontext.getClickedPos(), itemactioncontext.getClickedFace(), itemactioncontext.getPlayer(), entity, itemactioncontext.getHand()); ++ return CraftEventFactory.callEntityPlaceEvent(itemactioncontext.getLevel(), itemactioncontext.getClickedPos(), itemactioncontext.getClickedFace(), itemactioncontext.getPlayer(), entity, itemactioncontext.getHand(), itemactioncontext.getItemInHand()); // Paper + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - use #callEntityPlaceEvent with spawningStack parameter + public static EntityPlaceEvent callEntityPlaceEvent(Level world, BlockPos clickPosition, Direction clickedFace, net.minecraft.world.entity.player.Player human, Entity entity, InteractionHand enumhand) { ++ // Paper start ++ return CraftEventFactory.callEntityPlaceEvent(world, clickPosition, clickedFace, human, entity, enumhand, ItemStack.EMPTY); ++ } ++ ++ public static EntityPlaceEvent callEntityPlaceEvent(final Level world, final BlockPos clickPosition, final Direction clickedFace, @Nullable final net.minecraft.world.entity.player.Player human, final Entity entity, final InteractionHand hand, final ItemStack spawningStack) { ++ // Paper end + Player who = (human == null) ? null : (Player) human.getBukkitEntity(); + org.bukkit.block.Block blockClicked = CraftBlock.at(world, clickPosition); + org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(clickedFace); + +- EntityPlaceEvent event = new EntityPlaceEvent(entity.getBukkitEntity(), who, blockClicked, blockFace, CraftEquipmentSlot.getHand(enumhand)); ++ final EntityPlaceEvent event = new EntityPlaceEvent(entity.getBukkitEntity(), who, blockClicked, blockFace, CraftEquipmentSlot.getHand(hand), spawningStack.asBukkitCopy()); // Paper + entity.level().getCraftServer().getPluginManager().callEvent(event); + + return event; + } ++ // Paper start - various spawn entity events ++ public static io.papermc.paper.event.entity.ItemSpawnEntityEvent callItemSpawnEntityEvent(final UseOnContext useOnContext, final Entity entity) { ++ return CraftEventFactory.callItemSpawnEntityEvent(useOnContext.getLevel(), useOnContext.getClickedPos(), useOnContext.getClickedFace(), useOnContext.getPlayer(), entity, useOnContext.getItemInHand()); ++ } ++ ++ public static io.papermc.paper.event.entity.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 io.papermc.paper.event.entity.ItemSpawnEntityEvent event = new io.papermc.paper.event.entity.ItemSpawnEntityEvent(entity.getBukkitEntity(), who, blockClicked, blockFace, CraftItemStack.asBukkitCopy(spawningStack)); ++ event.callEvent(); ++ return event; ++ } ++ ++ public static io.papermc.paper.event.entity.BlockPlaceEntityEvent callBlockPlaceEntityEvent(final net.minecraft.core.dispenser.BlockSource pointer, final Entity entity, final ItemStack spawningStack) { ++ final Direction direction = pointer.state().getValue(net.minecraft.world.level.block.DispenserBlock.FACING); ++ return callBlockPlaceEntityEvent(pointer.level(), pointer.pos().relative(direction), direction.getOpposite(), entity, spawningStack); ++ } ++ ++ public static io.papermc.paper.event.entity.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 io.papermc.paper.event.entity.BlockPlaceEntityEvent event = new io.papermc.paper.event.entity.BlockPlaceEntityEvent(entity.getBukkitEntity(), blockClicked, blockFace, CraftItemStack.asBukkitCopy(spawningStack), dispenser); ++ event.callEvent(); ++ return event; ++ } ++ // Paper end - various spawn entity events + + /** + * Bucket methods +diff --git a/src/test/java/io/papermc/paper/block/DispensibleContainerItemExtraContentsOverrideTest.java b/src/test/java/io/papermc/paper/block/DispensibleContainerItemExtraContentsOverrideTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b1e599a6802e823f94c7a8506a1cea2486acc54c +--- /dev/null ++++ b/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"); ++ } ++}