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..909b5488f9dd --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/BlockPlaceEntityEvent.java @@ -0,0 +1,50 @@ +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 {@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 + */ +@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 spawningItem, final Dispenser dispenser) { + super(entity, null, block, blockFace, spawningItem); + 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/PlaceEntityEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.java new file mode 100644 index 000000000000..a205e0dfeb0b --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/PlaceEntityEvent.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.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, spawn eggs 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 dispenser-only version with more context + */ +@NullMarked +public abstract class PlaceEntityEvent 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 spawningItem; + private boolean cancelled; + + protected PlaceEntityEvent(final Entity entity, final @Nullable Player player, final Block block, final BlockFace blockFace, final ItemStack spawningItem) { + super(entity); + this.player = player; + this.block = block; + this.blockFace = blockFace; + this.spawningItem = spawningItem; + } + + /** + * {@return the player placing the entity (if available)} + */ + public @Nullable Player getPlayer() { + return this.player; + } + + /** + * {@return the block that the entity was placed on} + */ + public Block getBlock() { + return this.block; + } + + /** + * {@return the face of the block that the entity was placed on} + */ + public BlockFace getBlockFace() { + return this.blockFace; + } + + /** + * Gets the item responsible for spawning the entity. Mutating + * this item has no effect. + *

+ * May return an empty item if not available. + * + * @return the spawning item + */ + public ItemStack getSpawningItem() { + return this.spawningItem.clone(); + } + + @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/org/bukkit/event/entity/EntityPlaceEvent.java b/paper-api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java index 236c1fa78c17..4e7bc4b84c4b 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, spawn eggs 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 { - 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 spawningItem + ) { + super(entity, player, block, blockFace, spawningItem); 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-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java b/paper-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java index b0e60d343eef..f337b37e96f0 100644 --- a/paper-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java +++ b/paper-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java @@ -63,7 +63,7 @@ protected TypeSpec getTypeSpec() { .returns(ParameterizedTypeName.get(ClassName.get(GoalKey.class), type)); List> classes; - try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) { + try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("net.minecraft").scan()) { classes = scanResult.getSubclasses(Goal.class.getName()).loadClasses(Goal.class); } diff --git a/paper-generator/src/test/java/io/papermc/generator/MobGoalConverterTest.java b/paper-generator/src/test/java/io/papermc/generator/MobGoalConverterTest.java index 0eed5bbf4ac0..f4de92a7cf62 100644 --- a/paper-generator/src/test/java/io/papermc/generator/MobGoalConverterTest.java +++ b/paper-generator/src/test/java/io/papermc/generator/MobGoalConverterTest.java @@ -17,7 +17,7 @@ public class MobGoalConverterTest { @Test public void testBukkitMap() { final List> classes; - try (ScanResult scanResult = new ClassGraph().enableClassInfo().whitelistPackages(Entity.class.getPackageName()).scan()) { + try (ScanResult scanResult = new ClassGraph().enableClassInfo().acceptPackages(Entity.class.getPackageName()).scan()) { classes = scanResult.getSubclasses(Mob.class.getName()).loadClasses(Mob.class); } diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 2f5fc143544b..daba11fb757d 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -28453,7 +28453,7 @@ index c974b6cafb1f6aa2a57cfdc8a39c887f02f42b1d..ec40f02032f965f548b0c0a29aa9d9bb // Paper start - PlayerChunkLoadEvent if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { diff --git a/net/minecraft/server/network/config/PrepareSpawnTask.java b/net/minecraft/server/network/config/PrepareSpawnTask.java -index 83af9ee3ba150da85b9b694cd76a5fabb5b2d8ef..1fb40837bd02672850ec9adc2797190df22b33fc 100644 +index b5d993095aa77bb132c513f73dbf6d2afb2e3943..8b318dd8aa918284c5fa89e990b13bec425102cb 100644 --- a/net/minecraft/server/network/config/PrepareSpawnTask.java +++ b/net/minecraft/server/network/config/PrepareSpawnTask.java @@ -171,7 +171,7 @@ public class PrepareSpawnTask implements ConfigurationTask { @@ -28885,7 +28885,7 @@ index 1ceb7e4a3abd4d9de5133d182d3267d2164918f6..eb2fa32cff6824c14f865c8731df7d08 + // Paper end - block counting } diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4e81a3d3a 100644 +index 9ab04735f1091ca73a2f6bd03368ad0f60ca6751..b52e3428e18d8580080e4185bd48901ee965aa01 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -167,7 +167,7 @@ public abstract class Entity @@ -29136,7 +29136,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4 public Entity(final EntityType type, final Level level) { this.type = type; -@@ -1421,34 +1527,76 @@ public abstract class Entity +@@ -1423,34 +1529,76 @@ public abstract class Entity } private Vec3 collide(final Vec3 movement) { @@ -29236,7 +29236,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4 } private static float[] collectCandidateStepUpHeights( -@@ -2738,21 +2886,110 @@ public abstract class Entity +@@ -2740,21 +2888,110 @@ public abstract class Entity } public boolean isInWall() { @@ -29358,7 +29358,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4 } public InteractionResult interact(final Player player, final InteractionHand hand, final Vec3 location) { -@@ -4363,15 +4600,17 @@ public abstract class Entity +@@ -4365,15 +4602,17 @@ public abstract class Entity } public Iterable getIndirectPassengers() { @@ -29384,7 +29384,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4 } public int countPlayerPassengers() { -@@ -4682,6 +4921,15 @@ public abstract class Entity +@@ -4684,6 +4923,15 @@ public abstract class Entity } public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { @@ -29400,7 +29400,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4 if (!checkPosition(this, x, y, z)) { return; } -@@ -4831,6 +5079,12 @@ public abstract class Entity +@@ -4833,6 +5081,12 @@ public abstract class Entity @Override public final void setRemoved(final Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.@Nullable Cause cause) { // CraftBukkit - add Bukkit remove cause @@ -29413,7 +29413,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4 org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { -@@ -4841,7 +5095,7 @@ public abstract class Entity +@@ -4843,7 +5097,7 @@ public abstract class Entity this.stopRiding(); } @@ -29422,7 +29422,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4 this.levelCallback.onRemove(reason); this.onRemoval(reason); // Paper start - Folia schedulers -@@ -4875,7 +5129,7 @@ public abstract class Entity +@@ -4877,7 +5131,7 @@ public abstract class Entity public boolean shouldBeSaved() { return (this.removalReason == null || this.removalReason.shouldSave()) && !this.isPassenger() @@ -31851,7 +31851,7 @@ index 9404f0b0b03dce54e2c9ad6ca2c36354888cc9f5..a0d7f6f8ecbc320267bdad27315fc2e2 public interface NoiseBiomeSource { diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java -index 7678701a578d2dc45b35da072a0bb2c027522043..41aab9a526be77b400bed7a74614eceec95345b5 100644 +index a3fd479b67a880a513281faf5cdf51d0d0c2d963..24bc5559f11997c674fe09dae0c7828455ba66ae 100644 --- a/net/minecraft/world/level/block/Block.java +++ b/net/minecraft/world/level/block/Block.java @@ -366,7 +366,7 @@ public class Block extends BlockBehaviour implements ItemLike { diff --git a/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch index 84c65b9c31ec..904954b60524 100644 --- a/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch +++ b/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch @@ -462,7 +462,7 @@ index 0df8332933203a904bd9ef9efb3c9bce21e65441..1a502cbd8acea9420fa6dd8d716018b5 public void tick() { super.tick(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index e087d5596979044fe7fbcf7f2cccdae4e81a3d3a..fea6c3b48c4eb162fbdc099fa775bc3161a4dd4e 100644 +index b52e3428e18d8580080e4185bd48901ee965aa01..1a43bbdfbd071b1ab533ef68152c988ac29d79a7 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -382,6 +382,15 @@ public abstract class Entity @@ -495,7 +495,7 @@ index e087d5596979044fe7fbcf7f2cccdae4e81a3d3a..fea6c3b48c4eb162fbdc099fa775bc31 SynchedEntityData.Builder entityDataBuilder = new SynchedEntityData.Builder(this); entityDataBuilder.define(DATA_SHARED_FLAGS_ID, (byte)0); entityDataBuilder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply()); -@@ -1135,6 +1151,10 @@ public abstract class Entity +@@ -1137,6 +1153,10 @@ public abstract class Entity } else { if (moverType == MoverType.PISTON) { delta = this.limitPistonMovement(delta); @@ -506,7 +506,7 @@ index e087d5596979044fe7fbcf7f2cccdae4e81a3d3a..fea6c3b48c4eb162fbdc099fa775bc31 if (delta.equals(Vec3.ZERO)) { return; } -@@ -1150,6 +1170,13 @@ public abstract class Entity +@@ -1152,6 +1172,13 @@ public abstract class Entity this.stuckSpeedMultiplier = Vec3.ZERO; this.setDeltaMovement(Vec3.ZERO); } @@ -521,7 +521,7 @@ index e087d5596979044fe7fbcf7f2cccdae4e81a3d3a..fea6c3b48c4eb162fbdc099fa775bc31 delta = this.maybeBackOffFromEdge(delta, moverType); Vec3 movement = this.collide(delta); diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index 8b0abe9c7a3907419f6e4d55ea22fb9bc749f28b..36c0081e3e8f8776293ae619156cd0828fe5b21d 100644 +index 3dab720ea9cbe227338118d78038879ec45d456e..1549732b53affebde6af5e145e9c099e27e246e3 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -3372,6 +3372,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin 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 8aad6f7a88e2..c38033dd87bb 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 0cfdbdee091f43ca011bc68f7479eb7b84801b09..8d9ce3d301d5f7e4106587ae00adb8dd scopedCollector.forChild(entity.problemPath()), entity.registryAccess() ); diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java -index 2cdbb5f9bace96a8c4ad7fc4de17678ec0db8a6c..6b19f8cd8655cc52a0d8deadb9ca9f24473637a4 100644 +index 21830548f9651e56c95e85d66f6612bbd5b755b4..56afd20372060ccce121e01e562045f32e776fa9 100644 --- a/net/minecraft/world/entity/EntityType.java +++ b/net/minecraft/world/entity/EntityType.java -@@ -1647,7 +1647,18 @@ public class EntityType implements EntityTypeTest, +@@ -1663,7 +1663,18 @@ public class EntityType implements EntityTypeTest, } public static Stream loadEntitiesRecursive(final ValueInput.ValueInputList entities, final Level level, final EntitySpawnReason reason) { diff --git a/paper-server/patches/features/0025-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0025-Optimise-EntityScheduler-ticking.patch index c929667c8ef8..58a3a3a8d527 100644 --- a/paper-server/patches/features/0025-Optimise-EntityScheduler-ticking.patch +++ b/paper-server/patches/features/0025-Optimise-EntityScheduler-ticking.patch @@ -20,7 +20,7 @@ index 2bc436cdf5180a7943c45fabb9fbbedae6f7db56..f312a7f5b1b2a777ab36b94ce7cbf387 @Override diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 8c4b40362f77cdea6e57315a6639006ac7ab27c7..a5217deb236ee922d020a7a6a03141a5f67acd77 100644 +index 72bc7f6efc8f23d6ff31d2dabb13490655c58074..9a438cbdb05c7a8c33804c3b66ff42f357a96bc3 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1794,32 +1794,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop armorStandx.setYRot(direction.toYRot()), serverLevel, dispensed, null + armorStandx -> armorStandx.setYRot(direction.toYRot()), serverLevel, newStack, null // Paper - track changed items in the dispense event ); ++ // Paper start - Call BlockPlaceEntityEvent ++ final java.util.concurrent.atomic.AtomicBoolean cancelled = new java.util.concurrent.atomic.AtomicBoolean(); ++ postSpawnConfig = postSpawnConfig.andThen(armorStand -> { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(source, armorStand, dispensed)) { ++ cancelled.set(true); ++ } ++ }); ++ // Paper end - Call BlockPlaceEntityEvent ArmorStand armorStand = EntityType.ARMOR_STAND.spawn(serverLevel, postSpawnConfig, pos, EntitySpawnReason.DISPENSER, false, false); ++ if (cancelled.get()) armorStand = null; // Paper - todo ideally should return null in the above method if (armorStand != null) { - dispensed.shrink(1); + if (shrink) dispensed.shrink(1); // Paper @@ -77,7 +86,7 @@ this.setSuccess(true); return dispensed; } -@@ -150,8 +_,45 @@ +@@ -150,8 +_,51 @@ DispensibleContainerItem bucket = (DispensibleContainerItem)dispensed.getItem(); BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING)); Level level = source.level(); @@ -120,7 +129,13 @@ + if (bucket.emptyContents(null, level, target, null)) { - bucket.checkExtraContent(null, level, dispensed, target); -+ bucket.checkExtraContent(null, level, dispensedItem, target); // Paper - track changed item from dispense event ++ // Paper start - Call BlockPlaceEntityEvent ++ final ItemStack finalDispensedItem = dispensedItem; ++ java.util.function.Function eventOp = mob -> { ++ return org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(source, mob, finalDispensedItem); ++ }; ++ bucket.checkExtraContent(null, level, dispensedItem, target, eventOp); // Paper - track changed item from dispense event ++ // Paper end - Call BlockPlaceEntityEvent return this.consumeWithRemainder(source, dispensed, new ItemStack(Items.BUCKET)); } else { return this.defaultDispenseItemBehavior.dispense(source, dispensed); @@ -220,7 +235,7 @@ return dispensed; } -@@ -242,11 +_,36 @@ +@@ -242,11 +_,41 @@ return dispensed; } else { BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING)); @@ -251,6 +266,11 @@ + + PrimedTnt tnt = new PrimedTnt(level, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), null); + // CraftBukkit end ++ // Paper start - Call BlockPlaceEntityEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(source, tnt, dispensed)) { ++ return dispensed; ++ } ++ // Paper end - Call BlockPlaceEntityEvent level.addFreshEntity(tnt); level.playSound(null, tnt.getX(), tnt.getY(), tnt.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F); - level.gameEvent(null, GameEvent.ENTITY_PLACE, target); diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch index f3a2e22b82ba..78c3f468e421 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java -@@ -58,12 +_,37 @@ +@@ -58,12 +_,42 @@ } Vec3 spawnPos = new Vec3(spawnX, spawnY + yOffset, spawnZ); @@ -36,6 +36,11 @@ if (minecart != null) { - level.addFreshEntity(minecart); - dispensed.shrink(1); ++ // Paper start - Call BlockPlaceEntityEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(source, minecart, dispensed)) { ++ return dispensed; ++ } ++ // Paper end - Call BlockPlaceEntityEvent + if (level.addFreshEntity(minecart) && shrink) dispensed.shrink(1); // Paper - if entity add was successful and supposed to shrink + // CraftBukkit end } diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/SpawnEggItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/SpawnEggItemBehavior.java.patch index 026114b948d0..10dafdb40119 100644 --- a/paper-server/patches/sources/net/minecraft/core/dispenser/SpawnEggItemBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/SpawnEggItemBehavior.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/core/dispenser/SpawnEggItemBehavior.java +++ b/net/minecraft/core/dispenser/SpawnEggItemBehavior.java -@@ -18,14 +_,37 @@ +@@ -18,14 +_,45 @@ if (type == null) { return dispensed; } else { @@ -29,7 +29,15 @@ + // Paper end - block dispense event try { - type.spawn(source.level(), dispensed, null, source.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false); -+ type.spawn(source.level(), singleDispensed, null, source.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false); // Paper - block dispense event - update used item stack ++ type.spawn(source.level(), singleDispensed, null, source.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false, // Paper - block dispense event - update used item stack ++ // Paper start - Call BlockPlaceEntityEvent ++ org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG, ++ entity -> { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEntityEvent(source, entity, dispensed)) { ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ }); ++ // Paper end - Call BlockPlaceEntityEvent } catch (Exception var6) { LOGGER.error("Error while dispensing spawn egg from dispenser at {}", source.pos(), var6); return ItemStack.EMPTY; diff --git a/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestHelper.java.patch b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestHelper.java.patch new file mode 100644 index 000000000000..e5a5e7e3f9d9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestHelper.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/gametest/framework/GameTestHelper.java ++++ b/net/minecraft/gametest/framework/GameTestHelper.java +@@ -213,7 +_,7 @@ + } + + public void discard(final Entity entity) { +- entity.discard(); ++ entity.discard(null); // Paper + } + + public E findOneEntity(final EntityType entityType) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch index cfc2d384c698..1c4748b8a8dd 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -203,7 +203,7 @@ } public boolean removeTag(final String tag) { -@@ -397,12 +_,18 @@ +@@ -397,12 +_,19 @@ } public void kill(final ServerLevel level) { @@ -212,6 +212,7 @@ this.gameEvent(GameEvent.ENTITY_DIE); } ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // CraftBukkit public final void discard() { - this.remove(Entity.RemovalReason.DISCARDED); + // CraftBukkit start - add Bukkit remove cause @@ -273,9 +274,11 @@ @Override public boolean equals(final Object obj) { return obj instanceof Entity && ((Entity)obj).id == this.id; -@@ -422,7 +_,13 @@ +@@ -421,8 +_,15 @@ + return this.id; } ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // CraftBukkit public void remove(final Entity.RemovalReason reason) { - this.setRemoved(reason); + // CraftBukkit start - add Bukkit remove cause @@ -1564,15 +1567,6 @@ if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) { for (Entity passenger : this.getPassengers()) { if (passenger instanceof ServerPlayer player && !player.seenCredits) { -@@ -3246,7 +_,7 @@ - } - } - -- public boolean teleportTo( -+ public final boolean teleportTo( // CraftBukkit - final - final ServerLevel level, - final double x, - final double y, @@ -3256,14 +_,30 @@ final float newXRot, final boolean resetCamera 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 849af41b4e84..f548f84e5baa 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(); -@@ -1288,6 +_,22 @@ +@@ -1288,14 +_,45 @@ final boolean tryMoveDown, final boolean movedUp ) { @@ -27,13 +27,28 @@ + final boolean movedUp, + final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason + ) { ++ return this.spawn(level, itemStack, user, spawnPos, spawnReason, tryMoveDown, movedUp, createSpawnReason, null); ++ } ++ ++ public @Nullable T spawn( ++ final ServerLevel level, ++ final @Nullable ItemStack itemStack, ++ final @Nullable LivingEntity user, ++ final BlockPos spawnPos, ++ final EntitySpawnReason spawnReason, ++ final boolean tryMoveDown, ++ final boolean movedUp, ++ final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason, ++ final @Nullable Consumer op ++ ) { + // CraftBukkit end Consumer postSpawnConfig; if (itemStack != null) { postSpawnConfig = createDefaultStackConfig(level, itemStack, user); -@@ -1295,7 +_,7 @@ + } else { postSpawnConfig = entity -> {}; } ++ if (op != null) postSpawnConfig = postSpawnConfig.andThen(op); // Paper - return this.spawn(level, postSpawnConfig, spawnPos, spawnReason, tryMoveDown, movedUp); + return this.spawn(level, postSpawnConfig, spawnPos, spawnReason, tryMoveDown, movedUp, createSpawnReason); // CraftBukkit @@ -69,11 +84,11 @@ + final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason + ) { + return this.spawn(level, null, spawnPos, spawnReason, false, false, creatureSpawnReason); -+ // CraftBukkit End ++ // CraftBukkit end } public @Nullable T spawn( -@@ -1331,9 +_,39 @@ +@@ -1331,9 +_,40 @@ final boolean tryMoveDown, final boolean movedUp ) { @@ -105,6 +120,7 @@ T entity = this.create(level, postSpawnConfig, spawnPos, spawnReason, tryMoveDown, movedUp); 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/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch index e6413834b5a7..9cb540a43aa5 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -127,13 +127,11 @@ output.putFloat("Health", this.getHealth()); output.putShort("HurtTime", (short)this.hurtTime); output.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp); -@@ -755,7 +_,12 @@ - } +@@ -756,6 +_,11 @@ } -- public @Nullable ItemEntity drop(final ItemStack itemStack, final boolean randomly, final boolean thrownFromHand) { -+ // Paper start - Extend dropItem API -+ public final @Nullable ItemEntity drop(final ItemStack itemStack, final boolean randomly, final boolean thrownFromHand) { + public @Nullable ItemEntity drop(final ItemStack itemStack, final boolean randomly, final boolean thrownFromHand) { ++ // Paper start - Extend dropItem API + return this.drop(itemStack, randomly, thrownFromHand, true, null); + } + public @Nullable ItemEntity drop(final ItemStack itemStack, final boolean randomly, final boolean thrownFromHand, final boolean callEvent, final java.util.function.@Nullable Consumer entityOperation) { @@ -1396,11 +1394,11 @@ } public void onAttack() { -+ // Paper start -+ onAttack(null); ++ // Paper start ++ this.onAttack(null); + } -+ public void onAttack(Entity entity) { -+ // Paper end ++ public void onAttack(@Nullable Entity entity) { ++ // Paper end } public void detectEquipmentUpdates() { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch index 7b511ad56f67..a4750519682b 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/entity/Shearable.java +++ b/net/minecraft/world/entity/Shearable.java -@@ -5,7 +_,16 @@ +@@ -5,7 +_,12 @@ import net.minecraft.world.item.ItemStack; public interface Shearable { @@ -11,9 +11,5 @@ + + net.minecraft.world.level.Level level(); // Shearable API - expose default level needed for shearing. + -+ // Paper start - custom shear drops; ensure all implementing entities override this -+ default java.util.List generateDefaultDrops(final ServerLevel level, final ItemStack tool) { -+ return java.util.Collections.emptyList(); -+ } -+ // Paper end - custom shear drops ++ java.util.List generateDefaultDrops(final ServerLevel level, final ItemStack tool); // Paper - custom shear drops; } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch index 291d4a74687c..4a69d54f6d24 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch @@ -152,7 +152,17 @@ if (destroyedBlock) { BlockPos randomPos = new BlockPos( x0 + this.random.nextInt(x1 - x0 + 1), y0 + this.random.nextInt(y1 - y0 + 1), z0 + this.random.nextInt(z1 - z0 + 1) -@@ -501,7 +_,15 @@ +@@ -493,15 +_,23 @@ + } + + @Override +- public void knockback(final double power, final double xd, final double zd) { ++ public void knockback(final double power, final double xd, final double zd, final @Nullable Entity attacker, final io.papermc.paper.event.entity.EntityKnockbackEvent.Cause eventCause) { // Paper - knockback events + if (!this.phaseManager.getCurrentPhase().isSitting()) { +- super.knockback(power, xd, zd); ++ super.knockback(power, xd, zd, attacker, eventCause); // Paper - knockback events + } + } @Override public void kill(final ServerLevel level) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch index 2d44e2ce4371..ecae6ff163eb 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch @@ -6,7 +6,7 @@ entity.snapTo(entity.getX(), entity.getY(), entity.getZ(), yRot, 0.0F); + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entity).isCancelled()) { -+ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.forceHeldSlot(context.getHand()); // Paper - Fix inventory desync + return InteractionResult.FAIL; + } + // CraftBukkit end 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 2c9c26a6af7c..ea01ae0a6c31 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, hitResult.getBlockPos(), player.getDirection(), player, boat, hand).isCancelled()) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, hitResult.getBlockPos(), player.getDirection(), player, boat, hand, itemStack).isCancelled()) { + 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 bcc7764e4ea5..b772c02e44ab 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,23 @@ if (!level.isClientSide()) { CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer)player, taken); } -@@ -74,7 +_,7 @@ +@@ -74,8 +_,13 @@ } else { BlockState clicked = level.getBlockState(pos); BlockPos placePos = clicked.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? pos : directionOffsetPos; - if (this.emptyContents(player, level, placePos, hitResult)) { +- this.checkExtraContent(player, level, itemStack, placePos); + if (this.emptyContents(player, level, placePos, hitResult, hitResult.getDirection(), pos, itemStack, hand)) { // CraftBukkit - this.checkExtraContent(player, level, itemStack, placePos); ++ // Paper start - Call EntityPlaceEvent ++ java.util.function.Function eventOp = mob -> { ++ return !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, placePos, hitResult.getDirection(), player, mob, hand, itemStack).isCancelled(); ++ }; ++ this.checkExtraContent(player, level, itemStack, placePos, eventOp); ++ // Paper end - Call EntityPlaceEvent if (player instanceof ServerPlayer) { CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, placePos, itemStack); -@@ -91,6 +_,13 @@ + } +@@ -91,15 +_,26 @@ } public static ItemStack getEmptySuccessItem(final ItemStack itemStack, final Player player) { @@ -55,7 +62,10 @@ return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : itemStack; } -@@ -100,6 +_,12 @@ +- @Override +- public void checkExtraContent(final @Nullable LivingEntity user, final Level level, final ItemStack itemStack, final BlockPos pos) { +- } ++ // Paper - delete checkExtraContents @Override public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @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..04d181e6211a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/DispensibleContainerItem.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/item/DispensibleContainerItem.java ++++ b/net/minecraft/world/item/DispensibleContainerItem.java +@@ -7,8 +_,15 @@ + import org.jspecify.annotations.Nullable; + + public interface DispensibleContainerItem { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper + default void checkExtraContent(final @Nullable LivingEntity user, final Level level, final ItemStack itemStack, final BlockPos pos) { +- } ++ // Paper start ++ this.checkExtraContent(user, level, itemStack, pos, null); ++ } ++ ++ default void checkExtraContent(final @Nullable LivingEntity user, final Level level, final ItemStack itemStack, final BlockPos pos, final java.util.function.@Nullable Function eventOp) { ++ } ++ // Paper end + + boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch index 3ec27164f56d..f3a4671b7895 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch @@ -6,7 +6,7 @@ crystal.setShowBottom(false); + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, crystal).isCancelled()) { -+ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.forceHeldSlot(context.getHand()); // Paper - Fix inventory desync + return InteractionResult.FAIL; + } + // CraftBukkit end 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..3dbdca270c8e 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,32 @@ --- a/net/minecraft/world/item/MobBucketItem.java +++ b/net/minecraft/world/item/MobBucketItem.java -@@ -49,7 +_,7 @@ +@@ -28,9 +_,9 @@ + } + + @Override +- public void checkExtraContent(final @Nullable LivingEntity user, final Level level, final ItemStack itemStack, final BlockPos pos) { ++ public void checkExtraContent(final @Nullable LivingEntity user, final Level level, final ItemStack itemStack, final BlockPos pos, final java.util.function.Function eventOp) { // Paper + if (level instanceof ServerLevel) { +- this.spawn((ServerLevel)level, itemStack, pos); ++ this.spawn((ServerLevel)level, itemStack, pos, eventOp); // Paper + level.gameEvent(user, GameEvent.ENTITY_PLACE, pos); + } + } +@@ -40,7 +_,7 @@ + level.playSound(user, pos, this.emptySound, SoundSource.NEUTRAL, 1.0F, 1.0F); + } + +- private void spawn(final ServerLevel level, final ItemStack itemStack, final BlockPos spawnPos) { ++ private void spawn(final ServerLevel level, final ItemStack itemStack, final BlockPos spawnPos, final java.util.function.@Nullable Function eventOp) { // Paper + Mob mob = this.type.create(level, EntityType.createDefaultStackConfig(level, itemStack, null), spawnPos, EntitySpawnReason.BUCKET, true, false); + if (mob instanceof Bucketable bucketable) { + CustomData entityData = itemStack.getOrDefault(DataComponents.BUCKET_ENTITY_DATA, CustomData.EMPTY); +@@ -49,7 +_,8 @@ } if (mob != null) { - level.addFreshEntityWithPassengers(mob); ++ if (eventOp != null && !eventOp.apply(mob)) return; // Paper - mob doesn't exist yet, no need to discard + 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 55e947834b83..0f57faf2e37c 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,28 @@ spawnerHolder.setEntityId(type, level.getRandom()); level.sendBlockUpdated(pos, blockState, blockState, Block.UPDATE_ALL); level.gameEvent(context.getPlayer(), GameEvent.BLOCK_CHANGE, pos); -@@ -91,7 +_,7 @@ +@@ -75,26 +_,35 @@ + spawnPos = pos.relative(clickedFace); + } + +- return spawnMob(context.getPlayer(), itemStack, level, spawnPos, true, !Objects.equals(pos, spawnPos) && clickedFace == Direction.UP); ++ return spawnMob(context.getPlayer(), itemStack, level, spawnPos, true, !Objects.equals(pos, spawnPos) && clickedFace == Direction.UP, clickedFace, context.getHand()); // Paper - pass clickedFace and hand + } + } + } + + private static InteractionResult spawnMob( +- final @Nullable LivingEntity user, ++ final @Nullable Player user, // Paper - LivingEntity -> Player + final ItemStack itemStack, + final Level level, + final BlockPos spawnPos, + final boolean tryMoveDown, +- final boolean movedUp ++ final boolean movedUp, ++ final Direction clickedFace, // Paper ++ final InteractionHand hand // Paper + ) { EntityType type = getType(itemStack); if (type == null) { return InteractionResult.FAIL; @@ -16,7 +37,27 @@ + } else if (!type.isAllowedInPeaceful(itemStack.get(DataComponents.ENTITY_DATA).getUnsafe()) && level.getDifficulty() == Difficulty.PEACEFUL) { // Paper - check peaceful override return InteractionResult.FAIL; } else { - if (type.spawn((ServerLevel)level, itemStack, user, spawnPos, EntitySpawnReason.SPAWN_ITEM_USE, tryMoveDown, movedUp) != null) { +- if (type.spawn((ServerLevel)level, itemStack, user, spawnPos, EntitySpawnReason.SPAWN_ITEM_USE, tryMoveDown, movedUp) != null) { ++ // Paper start - Call EntityPlaceEvent ++ java.util.function.Consumer op = e -> { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, spawnPos, clickedFace, user, e, hand, itemStack).isCancelled()) { ++ e.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ }; ++ if (type.spawn((ServerLevel)level, itemStack, user, spawnPos, EntitySpawnReason.SPAWN_ITEM_USE, tryMoveDown, movedUp, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG, op) != null) { ++ // Paper end - Call EntityPlaceEvent + itemStack.consume(1, user); + level.gameEvent(user, GameEvent.ENTITY_PLACE, spawnPos); + } +@@ -114,7 +_,7 @@ + if (!(level.getBlockState(pos).getBlock() instanceof LiquidBlock)) { + return InteractionResult.PASS; + } else if (level.mayInteract(player, pos) && player.mayUseItemAt(pos, hitResult.getDirection(), itemStack)) { +- InteractionResult result = spawnMob(player, itemStack, level, pos, false, false); ++ InteractionResult result = spawnMob(player, itemStack, level, pos, false, false, hitResult.getDirection(), hand); // Paper + if (result == InteractionResult.SUCCESS) { + player.awardStat(Stats.ITEM_USED.get(this)); + } @@ -163,7 +_,7 @@ } else { offspring.snapTo(pos.x(), pos.y(), pos.z(), 0.0F, 0.0F); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch index c2808c1ed09f..b9a02e13c52d 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch @@ -154,7 +154,7 @@ + // Paper start - fix drops not preventing stats/food exhaustion + @Deprecated @io.papermc.paper.annotation.DoNotUse + public void playerDestroy(final Level level, final Player player, final BlockPos pos, final BlockState state, final @Nullable BlockEntity blockEntity, final ItemStack destroyedWith) { -+ playerDestroy(level, player, pos, state, blockEntity, destroyedWith, true, true); ++ this.playerDestroy(level, player, pos, state, blockEntity, destroyedWith, true, true); + } + // Paper end - fix drops not preventing stats/food exhaustion + diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch index d662e184a0ca..e806940b1050 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch @@ -1,11 +1,12 @@ --- a/net/minecraft/world/level/entity/EntityAccess.java +++ b/net/minecraft/world/level/entity/EntityAccess.java -@@ -18,7 +_,13 @@ +@@ -18,7 +_,14 @@ Stream getPassengersAndSelf(); - void setRemoved(Entity.RemovalReason removalReason); + // CraftBukkit start - add Bukkit remove cause ++ @Deprecated @io.papermc.paper.annotation.DoNotUse + default void setRemoved(Entity.RemovalReason removalReason) { + this.setRemoved(removalReason, null); + } 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 7a42d1810f74..5f421c35c3a2 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 @@ -12,6 +12,7 @@ import io.papermc.paper.connection.PlayerConnection; import io.papermc.paper.event.block.BlockLockCheckEvent; import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; +import io.papermc.paper.event.entity.BlockPlaceEntityEvent; import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; import io.papermc.paper.event.player.PlayerBedFailEnterEvent; import io.papermc.paper.event.player.PlayerToggleEntityAgeLockEvent; @@ -26,6 +27,7 @@ import net.minecraft.commands.CommandSourceStack; 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; @@ -65,6 +67,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; @@ -553,20 +556,33 @@ 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 level, BlockPos clickedPos, Direction clickedFace, net.minecraft.world.entity.player.Player player, Entity entity, InteractionHand hand) { - Player cplayer = (player == null) ? null : (Player) player.getBukkitEntity(); + public static EntityPlaceEvent callEntityPlaceEvent(final Level level, final BlockPos clickedPos, final Direction clickedFace, final net.minecraft.world.entity.player.@Nullable Player player, final Entity entity, final InteractionHand hand, final ItemStack spawningItem) { + Player bukkitPlayer = player == null ? null : (Player) player.getBukkitEntity(); org.bukkit.block.Block clickedBlock = CraftBlock.at(level, 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)); - entity.level().getCraftServer().getPluginManager().callEvent(event); - + final EntityPlaceEvent event = new EntityPlaceEvent(entity.getBukkitEntity(), bukkitPlayer, clickedBlock, blockFace, CraftEquipmentSlot.getHand(hand), CraftItemStack.asBukkitCopy(spawningItem)); + event.callEvent(); return event; } + public static boolean callBlockPlaceEntityEvent(final BlockSource source, final Entity entity, final ItemStack spawningItem) { + final Direction fromFace = source.state().getValue(DispenserBlock.FACING); + return callBlockPlaceEntityEvent(source.level(), source.pos(), fromFace, entity, spawningItem); + } + + private static boolean callBlockPlaceEntityEvent(final Level level, final BlockPos sourcePos, final Direction fromFace, final Entity entity, final ItemStack spawningItem) { + final Block targetBlock = CraftBlock.at(level, sourcePos.relative(fromFace)); + final BlockFace blockFace = CraftBlock.notchToBlockFace(fromFace); + final org.bukkit.block.Dispenser dispenser = (org.bukkit.block.Dispenser) CraftBlockStates.getBlockState(CraftBlock.at(level, sourcePos)); + + final BlockPlaceEntityEvent event = new BlockPlaceEntityEvent(entity.getBukkitEntity(), targetBlock, blockFace, CraftItemStack.asBukkitCopy(spawningItem), dispenser); + return event.callEvent(); + } + public static PlayerBucketEmptyEvent callPlayerBucketEmptyEvent(Level level, net.minecraft.world.entity.player.Player player, BlockPos changed, BlockPos clicked, Direction clickedFace, ItemStack itemInHand, InteractionHand hand) { return (PlayerBucketEmptyEvent) CraftEventFactory.getPlayerBucketEvent(false, level, player, changed, clicked, clickedFace, itemInHand, Items.BUCKET, hand); } diff --git a/paper-server/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java b/paper-server/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java deleted file mode 100644 index 120263fbae53..000000000000 --- a/paper-server/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.papermc.paper.entity; - -import io.github.classgraph.ClassGraph; -import io.github.classgraph.ClassInfo; -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.entity.LivingEntity; -import org.bukkit.support.environment.Normal; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.junit.jupiter.api.Assertions.fail; - -@Normal -public class EntitySetItemSlotSilentOverrideTest { - - public static Stream parameters() { - final List classInfo = new ArrayList<>(); - try (ScanResult scanResult = new ClassGraph() - .enableClassInfo() - .enableMethodInfo() - .whitelistPackages("net.minecraft") - .scan() - ) { - for (final ClassInfo subclass : scanResult.getSubclasses(LivingEntity.class.getName())) { - final MethodInfoList setItemSlot = subclass.getDeclaredMethodInfo("setItemSlot"); - if (!setItemSlot.isEmpty()) { - classInfo.add(subclass); - } - } - } - return classInfo.stream(); - } - - @ParameterizedTest - @MethodSource("parameters") - public void checkSetItemSlotSilentOverrides(ClassInfo overridesSetItemSlot) { - final MethodInfoList setItemSlot = overridesSetItemSlot.getDeclaredMethodInfo("setItemSlot"); - for (final MethodInfo methodInfo : setItemSlot) { - for (final MethodParameterInfo methodParameterInfo : methodInfo.getParameterInfo()) { - if ("boolean".equals(methodParameterInfo.getTypeDescriptor().toStringWithSimpleNames())) { - return; - } - } - } - fail(overridesSetItemSlot.getName() + " needs to override setItemSlot with the boolean silent parameter as well"); - } -} diff --git a/paper-server/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java b/paper-server/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java deleted file mode 100644 index 5e6dfc93c86e..000000000000 --- a/paper-server/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.papermc.paper.entity; - -import io.github.classgraph.ClassGraph; -import io.github.classgraph.ClassInfo; -import io.github.classgraph.MethodInfoList; -import io.github.classgraph.ScanResult; -import java.util.ArrayList; -import net.minecraft.world.entity.Shearable; -import org.bukkit.support.environment.Normal; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@Normal -class ShearableDropsTest { - - static Iterable parameters() { - try (ScanResult scanResult = new ClassGraph() - .enableClassInfo() - .enableMethodInfo() - .whitelistPackages("net.minecraft") - .scan() - ) { - return new ArrayList<>(scanResult.getClassesImplementing(Shearable.class.getName())); - } - } - - @ParameterizedTest - @MethodSource("parameters") - void checkShearableDropOverrides(final ClassInfo classInfo) { - final MethodInfoList generateDefaultDrops = classInfo.getDeclaredMethodInfo("generateDefaultDrops"); - assertEquals(1, generateDefaultDrops.size(), classInfo.getName() + " doesn't implement Shearable#generateDefaultDrops"); - } -} diff --git a/paper-server/src/test/java/io/papermc/paper/entity/ShearableTest.java b/paper-server/src/test/java/io/papermc/paper/entity/ShearableTest.java index 709b90f0cd01..6c27433d3aec 100644 --- a/paper-server/src/test/java/io/papermc/paper/entity/ShearableTest.java +++ b/paper-server/src/test/java/io/papermc/paper/entity/ShearableTest.java @@ -15,7 +15,7 @@ class ShearableTest { static List> nmsShearables() { - try (final ScanResult result = new ClassGraph().enableClassInfo().whitelistPackages("net.minecraft.world.entity").scan()) { + try (final ScanResult result = new ClassGraph().enableClassInfo().acceptPackages("net.minecraft.world.entity").scan()) { return result.getClassesImplementing(Shearable.class.getName()).loadClasses(Shearable.class); } } diff --git a/paper-server/src/test/java/io/papermc/paper/util/SourceOverloadsTest.java b/paper-server/src/test/java/io/papermc/paper/util/SourceOverloadsTest.java new file mode 100644 index 000000000000..f060d5314a14 --- /dev/null +++ b/paper-server/src/test/java/io/papermc/paper/util/SourceOverloadsTest.java @@ -0,0 +1,222 @@ +package io.papermc.paper.util; + +import com.google.common.base.Preconditions; +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.ScanResult; +import io.papermc.paper.event.entity.EntityKnockbackEvent; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.item.DispensibleContainerItem; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.entity.EntityAccess; +import org.bukkit.event.entity.EntityRemoveEvent; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.support.environment.Normal; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.objectweb.asm.Type; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Normal +class SourceOverloadsTest { + + private static final String SUPPORTED_PACKAGE = "net.minecraft"; + + record MethodOverload(Class owner, String name, @Nullable MethodTypeDesc basedOn, int paramCount, ClassDesc... addedParams) { + MethodOverload { + Preconditions.checkState(owner.getPackageName().startsWith(SUPPORTED_PACKAGE)); + } + + // when there's no ambiguity and only one overload is expected you can use this + MethodOverload(Class owner, String name, int paramCount, ClassDesc... addedParams) { + this(owner, name, null, paramCount, addedParams); + } + } + + private static final MethodOverload[] OVERLOADS = new MethodOverload[] { + new MethodOverload( + EntityAccess.class, + "setRemoved", + 2, + EntityRemoveEvent.Cause.class.describeConstable().orElseThrow() + ), + new MethodOverload( + Entity.class, + "remove", + 2, + EntityRemoveEvent.Cause.class.describeConstable().orElseThrow() + ), + new MethodOverload( + Entity.class, + "discard", + 1, + EntityRemoveEvent.Cause.class.describeConstable().orElseThrow() + ), + new MethodOverload( + Entity.class, + "removeVehicle", + 1, + ConstantDescs.CD_boolean + ), + new MethodOverload( + Entity.class, + "removePassenger", + 2, + ConstantDescs.CD_boolean + ), + new MethodOverload( + Entity.class, + "stopRiding", + 1, + ConstantDescs.CD_boolean + ), + new MethodOverload( + Entity.class, + "teleportTo", + MethodTypeDesc.ofDescriptor("(Lnet/minecraft/server/level/ServerLevel;DDDLjava/util/Set;FFZ)Z"), + 9, + PlayerTeleportEvent.TeleportCause.class.describeConstable().orElseThrow() + ), + new MethodOverload( + Entity.class, + "push", + MethodTypeDesc.ofDescriptor("(DDD)V"), + 4, + Entity.class.describeConstable().orElseThrow(), + EntityKnockbackEvent.Cause.class.describeConstable().orElseThrow() + ), + new MethodOverload( + LivingEntity.class, + "onEquipItem", + 4, + ConstantDescs.CD_boolean + ), + new MethodOverload( + LivingEntity.class, + "setItemSlot", + 3, + ConstantDescs.CD_boolean + ), + new MethodOverload( + LivingEntity.class, + "drop", + MethodTypeDesc.ofDescriptor("(Lnet/minecraft/world/item/ItemStack;ZZ)Lnet/minecraft/world/entity/item/ItemEntity;"), + 5, + ConstantDescs.CD_boolean, + Consumer.class.describeConstable().orElseThrow() + ), + new MethodOverload( + LivingEntity.class, + "knockback", + 5, + EntityKnockbackEvent.Cause.class.describeConstable().orElseThrow() + ), + new MethodOverload( + LivingEntity.class, + "onAttack", + 1, + Entity.class.describeConstable().orElseThrow() + ), + new MethodOverload( + Mob.class, + "setTarget", + 2, + EntityTargetEvent.TargetReason.class.describeConstable().orElseThrow() + ), + new MethodOverload( + Block.class, + "playerDestroy", + 8, + ConstantDescs.CD_boolean, + ConstantDescs.CD_boolean + ), + new MethodOverload( + DispensibleContainerItem.class, + "checkExtraContent", + 5, + Function.class.describeConstable().orElseThrow() + ), + // TODO check Entity#isCollidable, LivingEntity#addEffect + }; + + public static Stream methods() { + final List args = new ArrayList<>(); + try (final ScanResult scanResult = new ClassGraph() + .enableClassInfo() + .enableMethodInfo() + .acceptPackages(SUPPORTED_PACKAGE) + .scan() + ) { + for (MethodOverload overload : OVERLOADS) { + final ClassInfoList classes; + if (overload.owner().isInterface()) { + classes = scanResult.getClassesImplementing(overload.owner().getName()); + } else { + classes = scanResult.getSubclasses(overload.owner().getName()); + } + for (final ClassInfo klass : classes) { + if (klass.hasDeclaredMethod(overload.name())) { + MethodInfoList methods = klass.getDeclaredMethodInfo(overload.name()); + if (overload.basedOn() != null) { + methods = matches(overload, methods); + if (methods.isEmpty()) { + continue; + } + } + + args.add(Arguments.of(klass, methods, overload)); + } + } + } + } + return args.stream(); + } + + private static MethodInfoList matches(final MethodOverload expected, final MethodInfoList methods) { + final MethodTypeDesc fromDesc = expected.basedOn(); + assert fromDesc != null; + final MethodTypeDesc toDesc = fromDesc.insertParameterTypes(expected.paramCount() - expected.addedParams().length, expected.addedParams()); + + final MethodInfoList result = new MethodInfoList(); + for (final MethodInfo method : methods) { + final String desc = method.getTypeDescriptorStr(); + if (desc.equals(fromDesc.descriptorString()) || desc.equals(toDesc.descriptorString())) { + result.add(method); + } + } + + return result; + } + + @ParameterizedTest + @MethodSource("methods") + public void checkOverloads(final ClassInfo owner, final MethodInfoList methodList, final MethodOverload expectedOverload) { + assertEquals(1, methodList.size(), owner.getName() + " has multiple " + expectedOverload.name() + " methods"); + final MethodInfo method = methodList.getFirst(); + + assertEquals(expectedOverload.paramCount(), method.getParameterInfo().length, owner.getName() + " doesn't have " + expectedOverload.paramCount() + " params for " + expectedOverload.name()); + + final Type[] currentParams = Type.getArgumentTypes(method.getTypeDescriptorStr()); + final ClassDesc[] addedParams = expectedOverload.addedParams(); + for (int i = 0, len = addedParams.length; i < len; i++) { + assertEquals(addedParams[i].descriptorString(), currentParams[expectedOverload.paramCount() - len + i].getDescriptor(), owner.getName() + " needs to change its override of " + expectedOverload.name()); + } + } +} diff --git a/paper-server/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java b/paper-server/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java deleted file mode 100644 index 750488cb2fa6..000000000000 --- a/paper-server/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.papermc.paper.world.block; - -import io.github.classgraph.ClassGraph; -import io.github.classgraph.ClassInfo; -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.level.block.Block; -import org.bukkit.support.environment.Normal; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@Normal -public class BlockPlayerDestroyOverrideTest { - - public static Stream parameters() { - final List classInfo = new ArrayList<>(); - try (ScanResult scanResult = new ClassGraph() - .enableClassInfo() - .enableMethodInfo() - .whitelistPackages("net.minecraft") - .scan() - ) { - for (final ClassInfo subclass : scanResult.getSubclasses(Block.class.getName())) { - final MethodInfoList playerDestroy = subclass.getDeclaredMethodInfo("playerDestroy"); - if (!playerDestroy.isEmpty()) { - classInfo.add(subclass); - } - } - } - return classInfo.stream(); - } - - @ParameterizedTest - @MethodSource("parameters") - public void checkPlayerDestroyOverrides(ClassInfo overridesPlayerDestroy) { - final MethodInfoList playerDestroy = overridesPlayerDestroy.getDeclaredMethodInfo("playerDestroy"); - assertEquals(1, playerDestroy.size(), overridesPlayerDestroy.getName() + " has multiple playerDestroy methods"); - final MethodInfo next = playerDestroy.iterator().next(); - final MethodParameterInfo[] parameterInfo = next.getParameterInfo(); - assertEquals("boolean", parameterInfo[parameterInfo.length - 1].getTypeDescriptor().toStringWithSimpleNames(), overridesPlayerDestroy.getName() + " needs to change its override of playerDestroy"); - } -} diff --git a/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java b/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java index dd282530b357..c744bc40ce9e 100644 --- a/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java +++ b/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java @@ -17,7 +17,7 @@ class MetaHandledTagsTest { @Test public void checkAllMetasHaveHandledTags() { try (final ScanResult result = new ClassGraph() - .whitelistPackages("org.bukkit.craftbukkit.inventory") + .acceptPackages("org.bukkit.craftbukkit.inventory") .enableClassInfo().scan()) { final ClassInfoList subclasses = result.getSubclasses(CraftMetaItem.class.getName()); assertFalse(subclasses.isEmpty(), "found 0 sub types"); diff --git a/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java b/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java deleted file mode 100644 index 53e7617591b3..000000000000 --- a/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.bukkit.event; - -import static org.junit.jupiter.api.Assertions.*; -import com.google.common.base.Joiner; -import java.util.ArrayList; -import java.util.List; -import net.minecraft.world.level.entity.EntityAccess; -import org.bukkit.support.environment.Normal; -import org.bukkit.support.test.ClassNodeTest; -import org.junit.jupiter.api.Disabled; -import org.objectweb.asm.Handle; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InvokeDynamicInsnNode; -import org.objectweb.asm.tree.LineNumberNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; - -@Normal -@Disabled // TODO Delete this test or re-enable it with changes -public class EntityRemoveEventTest { - - @ClassNodeTest(value = ClassNodeTest.ClassType.CRAFT_BUKKIT, - excludedClasses = EntityAccess.class, - excludedPackages = "net/minecraft/gametest/framework") - public void testForMissing(ClassNode classNode, String name) throws ClassNotFoundException { - List missingReason = new ArrayList<>(); - - boolean minecraftCause = false; - boolean bukkitCause = false; - - for (MethodNode methodNode : classNode.methods) { - if (methodNode.name.equals("remove") && methodNode.desc.contains("Lnet/minecraft/world/entity/Entity$RemovalReason;")) { - if (methodNode.desc.contains("Lorg/bukkit/event/entity/EntityRemoveEvent$Cause;")) { - bukkitCause = true; - } else { - minecraftCause = true; - } - } - - LineNumberNode lastLineNumber = null; - for (AbstractInsnNode instruction : methodNode.instructions) { - if (instruction instanceof LineNumberNode lineNumberNode) { - lastLineNumber = lineNumberNode; - continue; - } - - if (instruction instanceof MethodInsnNode methodInsnNode) { - // Check for discard and remove method call - if (this.check(methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc)) { - // Add to list - missingReason.add(String.format("Method name: %s, name: %s, line number: %s", methodNode.name, methodInsnNode.name, lastLineNumber.line)); - } - } else if (instruction instanceof InvokeDynamicInsnNode dynamicInsnNode) { - // Check for discard and remove method call - if (!dynamicInsnNode.bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory") - || !dynamicInsnNode.bsm.getName().equals("metafactory") || dynamicInsnNode.bsmArgs.length != 3) { - continue; - } - - Handle handle = (Handle) dynamicInsnNode.bsmArgs[1]; - - if (this.check(handle.getOwner(), handle.getName(), handle.getDesc())) { - // Add to list - missingReason.add(String.format("[D] Method name: %s, name: %s, line number: %s", methodNode.name, handle.getName(), lastLineNumber.line)); - } - } - } - } - - assertTrue(missingReason.isEmpty(), String.format(""" - The class %s has Entity#discard, Entity#remove and/or Entity#setRemoved method calls, which don't have a bukkit reason. - Please add a bukkit reason to them, if the event should not be called use null as reason. - - Following missing reasons where found: - %s""", classNode.name, Joiner.on('\n').join(missingReason))); - - if (minecraftCause == bukkitCause) { - return; - } - - if (minecraftCause) { - fail(String.format(""" - The class %s has the Entity#remove method override, but there is no bukkit override. - Please add a bukkit method override, which adds the bukkit cause. - """, classNode.name)); - return; // Will never reach ): - } - - fail(String.format(""" - The class %s has the Entity#remove method override, to add a bukkit cause, but there is no normal override. - Please remove the bukkit method override, since it is no longer needed. - """, classNode.name)); - } - - private boolean check(String owner, String name, String desc) throws ClassNotFoundException { - if (!name.equals("discard") && !name.equals("remove") && !name.equals("setRemoved")) { - if (!this.checkExtraMethod(owner, name, desc)) { - return false; - } - } - - if (desc.contains("Lorg/bukkit/event/entity/EntityRemoveEvent$Cause;")) { - return false; - } - - Class ownerClass = Class.forName(owner.replace('/', '.'), false, this.getClass().getClassLoader()); - if (ownerClass == EntityAccess.class) { - return false; - } - - // Found missing discard, remove or setRemoved method call - return EntityAccess.class.isAssignableFrom(ownerClass); - } - - private boolean checkExtraMethod(String owner, String name, String desc) { - if (owner.equals("net/minecraft/world/entity/projectile/EntityShulkerBullet")) { - return name.equals("destroy"); - } - - return false; - } -}