diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityLandEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityLandEvent.java new file mode 100644 index 000000000000..be3a7514d823 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityLandEvent.java @@ -0,0 +1,84 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jspecify.annotations.NullMarked; + +/** + * Called when an {@link Entity} lands on the block after falling. + * This event is called before {@link org.bukkit.event.entity.EntityDamageEvent}, {@link org.bukkit.event.entity.EntityChangeBlockEvent}, {@link org.bukkit.event.player.PlayerInteractEvent} + * and {@link org.bukkit.event.entity.EntityInteractEvent}. + */ +@NullMarked +public class EntityLandEvent extends EntityEvent implements Cancellable { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final Block block; + private boolean cancelled; + private double fallDistance; + + public EntityLandEvent(final Entity entity, final Block block, double fallDistance) { + super(entity); + this.block = block; + this.fallDistance = fallDistance; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + + /** + * Returns the block that the entity landed on. + * + * @return the block that the entity landed on + */ + public Block getBlock() { + return this.block; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * Sets whether to cancel the landing, cancelling this event will prevent + * the entity from taking fall damage and interact with the block. (ex: break turtle egg) + * + * @param cancel true if the event should be cancelled, false otherwise + */ + @Override + public void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the fall distance of the entity before landing. + * + * @return the fall distance + */ + public double getFallDistance() { + return fallDistance; + } + + /** + * Sets the fall distance of the entity before landing. + * Fall distance in the event is used to calculate fall damage and + * the chance of transforming {@link org.bukkit.Material#FARMLAND} to {@link org.bukkit.Material#DIRT} and + * the type of falling sound when the entity lands on {@link org.bukkit.Material#POWDER_SNOW}. + * + * @param fallDistance the fall distance + */ + public void setFallDistance(final double fallDistance) { + this.fallDistance = fallDistance; + } +} diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index dd649faaddb7..8e756b39c041 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -29292,7 +29292,7 @@ index 09b6da207530987b483444cebeadc2f1c9a15247..3666c3efd188508153b6482db425648e + // Paper end - block counting } diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae7615f50524 100644 +index cc3c407b594b49cd6bf2eaccb2f63f453eb35f6e..4ac4e229a3dd1f924e1cff15b04006cffb3a8ad0 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -164,7 +164,7 @@ public abstract class Entity @@ -29643,7 +29643,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 } private static float[] collectCandidateStepUpHeights( -@@ -2832,17 +2980,106 @@ public abstract class Entity +@@ -2835,17 +2983,106 @@ public abstract class Entity return false; } @@ -29760,7 +29760,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 } public InteractionResult interact(final Player player, final InteractionHand hand, final Vec3 location) { -@@ -4449,15 +4686,17 @@ public abstract class Entity +@@ -4452,15 +4689,17 @@ public abstract class Entity } public Iterable getIndirectPassengers() { @@ -29786,7 +29786,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 } public int countPlayerPassengers() { -@@ -4768,6 +5007,15 @@ public abstract class Entity +@@ -4771,6 +5010,15 @@ public abstract class Entity } public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { @@ -29802,7 +29802,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 if (!checkPosition(this, x, y, z)) { return; } -@@ -4917,6 +5165,12 @@ public abstract class Entity +@@ -4920,6 +5168,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 @@ -29815,7 +29815,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { -@@ -4928,7 +5182,7 @@ public abstract class Entity +@@ -4931,7 +5185,7 @@ public abstract class Entity this.stopRiding(); } @@ -29824,7 +29824,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 this.levelCallback.onRemove(reason); this.onRemoval(reason); // Paper start - Folia schedulers -@@ -4966,7 +5220,7 @@ public abstract class Entity +@@ -4969,7 +5223,7 @@ public abstract class Entity public boolean shouldBeSaved() { return (this.removalReason == null || this.removalReason.shouldSave()) && !this.isPassenger() diff --git a/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch index c7b9ca610cfc..d2b182099960 100644 --- a/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch +++ b/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch @@ -66,10 +66,10 @@ index 6de2929fda582cc4ceecd7bfe0b440ac6608669d..2381f8571f6e614dba1cfb62a1d396be io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index f8f479bd0ce2498c0cf4beab07d961247735e23c..d958c4717739e588c5f8985dec5d215c745f49fc 100644 +index 57d0ca73435988079855d7f09af73af4fabf55ba..a9616f457a65015f27023a58a7eb30e6df70dcfd 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -5237,6 +5237,11 @@ public abstract class Entity +@@ -5240,6 +5240,11 @@ public abstract class Entity this.getBukkitEntity().taskScheduler.retire(); } // Paper end - Folia schedulers 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 ba31084206e7..a6bc5cc116e7 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 @@ -590,6 +590,34 @@ state.getFluidState().entityInside(this.level(), blockIntersection, this, effectCollector); } +@@ -1558,15 +_,18 @@ + + if (onGround) { + if (this.fallDistance > 0.0) { +- onState.getBlock().fallOn(this.level(), onState, pos, this, this.fallDistance); +- this.level() +- .gameEvent( +- GameEvent.HIT_GROUND, +- this.position, +- GameEvent.Context.of( +- this, this.mainSupportingBlockPos.map(blockPos -> this.level().getBlockState(blockPos)).orElse(onState) +- ) +- ); ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(this.level(), pos, this, this.fallDistance); // Paper start - Add EntityLandEvent ++ if(distance != -1){ ++ onState.getBlock().fallOn(this.level(), onState, pos, this, distance); ++ this.level() ++ .gameEvent( ++ GameEvent.HIT_GROUND, ++ this.position, ++ GameEvent.Context.of( ++ this, this.mainSupportingBlockPos.map(blockPos -> this.level().getBlockState(blockPos)).orElse(onState) ++ ) ++ ); ++ } // Paper end - Add EntityLandEvent + } + + this.resetFallDistance(); @@ -1778,6 +_,7 @@ this.setXRot(Mth.clamp(xRot, -90.0F, 90.0F) % 360.0F); this.yRotO = this.getYRot(); 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 a5ea91906abe..a59639ba752b 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.event.block.BlockLockCheckEvent; import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; import io.papermc.paper.event.entity.EntityIgniteEvent; +import io.papermc.paper.event.entity.EntityLandEvent; import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; import io.papermc.paper.event.player.PlayerBedFailEnterEvent; import io.papermc.paper.event.player.PlayerToggleEntityAgeLockEvent; @@ -69,6 +70,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.Blocks; 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; @@ -2425,4 +2427,13 @@ public static int callEntityIgniteEvent(Entity entity, int fuseTime) { } return event.getFuseTime(); } + + public static double callEntityLandEvent(Level level, BlockPos pos, Entity entity, double fallDistance) { + if (EntityLandEvent.getHandlerList().getRegisteredListeners().length == 0) { + return fallDistance; // No listeners, skip event creation + } + EntityLandEvent event = new EntityLandEvent(entity.getBukkitEntity(), CraftBlock.at(level, pos), fallDistance); + event.callEvent(); + return event.isCancelled() ? -1 : event.getFallDistance(); + } }