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/sources/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch index b3fcedc0af12..0c2b6bb5c64f 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 @@ -170,6 +170,27 @@ } public void setPlacedBy(final Level level, final BlockPos pos, final BlockState state, final @Nullable LivingEntity by, final ItemStack itemStack) { +@@ -488,8 +_,18 @@ + } + + public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { +- entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall()); +- } ++ fallOn(level, state, pos, entity, fallDistance, true); // Paper - Add EntityLandEvent ++ } ++ ++ // Paper start - Add EntityLandEvent ++ public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance, boolean callEvent) { ++ double distance = callEvent ? org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance) : fallDistance; ++ if(distance == -1){ ++ return; ++ } ++ entity.causeFallDamage(distance, 1.0F, entity.damageSources().fall()); ++ } ++ // Paper end - Add EntityLandEvent + + public float getBounceRestitution() { + return this.bounceRestitution; @@ -613,12 +_,20 @@ return this.builtInRegistryHolder; } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FarmlandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FarmlandBlock.java.patch index e9491079cab5..5f7b60cde3f8 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/FarmlandBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FarmlandBlock.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/level/block/FarmlandBlock.java +++ b/net/minecraft/world/level/block/FarmlandBlock.java -@@ -94,31 +_,59 @@ +@@ -94,31 +_,63 @@ @Override protected void randomTick(final BlockState state, final ServerLevel level, final BlockPos pos, final RandomSource random) { int moisture = state.getValue(MOISTURE); @@ -21,9 +21,14 @@ @Override public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { -+ super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance); // Paper start - Add EntityLandEvent ++ if(distance == -1){ ++ return; ++ } ++ super.fallOn(level, state, pos, entity, distance, false); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. if (level instanceof ServerLevel serverLevel - && level.getRandom().nextFloat() < fallDistance - 0.5 +- && level.getRandom().nextFloat() < fallDistance - 0.5 ++ && level.getRandom().nextFloat() < distance - 0.5 // Paper end - Add EntityLandEvent && entity instanceof LivingEntity && (entity instanceof Player || serverLevel.getGameRules().get(GameRules.MOB_GRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/HayBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HayBlock.java.patch new file mode 100644 index 000000000000..b0dc5ccb98b5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/HayBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/HayBlock.java ++++ b/net/minecraft/world/level/block/HayBlock.java +@@ -23,6 +_,10 @@ + + @Override + public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { +- entity.causeFallDamage(fallDistance, 0.2F, level.damageSources().fall()); ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance); // Paper start - Add EntityLandEvent ++ if(distance == -1){ ++ return; ++ } ++ entity.causeFallDamage(distance, 0.2F, level.damageSources().fall()); // Paper end - Add EntityLandEvent + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch index ca996ad35af6..46bfca68534e 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch @@ -1,5 +1,23 @@ --- a/net/minecraft/world/level/block/HoneyBlock.java +++ b/net/minecraft/world/level/block/HoneyBlock.java +@@ -51,12 +_,16 @@ + + @Override + public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance); // Paper start - Add EntityLandEvent ++ if(distance == -1){ ++ return; ++ } + entity.playSound(SoundEvents.HONEY_BLOCK_SLIDE, 1.0F, 1.0F); + if (!level.isClientSide()) { + level.broadcastEntityEvent(entity, EntityEvent.HONEY_JUMP); + } + +- if (entity.causeFallDamage(fallDistance, 0.2F, level.damageSources().fall())) { ++ if (entity.causeFallDamage(distance, 0.2F, level.damageSources().fall())) { // Paper end - Add EntityLandEvent + entity.playSound(this.soundType.getFallSound(), this.soundType.getVolume() * 0.5F, this.soundType.getPitch() * 0.75F); + } + } @@ -70,6 +_,7 @@ final InsideBlockEffectApplier effectApplier, final boolean isPrecise diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch index f06ade2e2409..c23e86daa88e 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch @@ -1,14 +1,22 @@ --- a/net/minecraft/world/level/block/PointedDripstoneBlock.java +++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -63,7 +_,7 @@ +@@ -62,10 +_,14 @@ + @Override public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance); // Paper start - Add EntityLandEvent ++ if(distance == -1){ ++ return; ++ } if (state.getValue(TIP_DIRECTION) == Direction.UP && state.getValue(THICKNESS) == SpeleothemThickness.TIP) { - entity.causeFallDamage(fallDistance + 2.5, 2.0F, level.damageSources().stalagmite()); -+ entity.causeFallDamage(fallDistance + 2.5, 2.0F, level.damageSources().stalagmite().eventBlockDamager(level, pos)); // CraftBukkit ++ entity.causeFallDamage(distance + 2.5, 2.0F, level.damageSources().stalagmite().eventBlockDamager(level, pos)); // CraftBukkit } else { - super.fallOn(level, state, pos, entity, fallDistance); +- super.fallOn(level, state, pos, entity, fallDistance); ++ super.fallOn(level, state, pos, entity, distance, false); // Paper end - Add EntityLandEvent } + } + @@ -110,10 +_,11 @@ if (stalactiteTipPos != null) { if (fluidInfo.get().sourceState.is(Blocks.MUD) && fluid == Fluids.WATER) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch index 95637c475007..3b029d9e0984 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch @@ -30,3 +30,19 @@ level.destroyBlock(position, false); } } +@@ -104,9 +_,13 @@ + + @Override + public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { +- if (!(fallDistance < 4.0) && entity instanceof LivingEntity livingEntity) { ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance); // Paper start - Add EntityLandEvent ++ if(distance == -1){ ++ return; ++ } ++ if (!(distance < 4.0) && entity instanceof LivingEntity livingEntity) { + LivingEntity.Fallsounds entityFallsounds = livingEntity.getFallSounds(); +- SoundEvent fallSound = fallDistance < 7.0 ? entityFallsounds.small() : entityFallsounds.big(); ++ SoundEvent fallSound = distance < 7.0 ? entityFallsounds.small() : entityFallsounds.big(); // Paper end - Add EntityLandEvent + entity.playSound(fallSound, 1.0F, 1.0F); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SlimeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SlimeBlock.java.patch new file mode 100644 index 000000000000..364641b5f49c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SlimeBlock.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/block/SlimeBlock.java ++++ b/net/minecraft/world/level/block/SlimeBlock.java +@@ -21,8 +_,12 @@ + + @Override + public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance); // Paper start - Add EntityLandEvent ++ if(distance == -1){ ++ return; ++ } + if (!entity.isSuppressingBounce()) { +- entity.causeFallDamage(fallDistance, 0.0F, level.damageSources().fall()); ++ entity.causeFallDamage(distance, 0.0F, level.damageSources().fall()); // Paper end - Add EntityLandEvent + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch index 49ba7862869d..94b1f7b182bb 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch @@ -1,5 +1,22 @@ --- a/net/minecraft/world/level/block/TurtleEggBlock.java +++ b/net/minecraft/world/level/block/TurtleEggBlock.java +@@ -63,11 +_,15 @@ + + @Override + public void fallOn(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final double fallDistance) { ++ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(level, pos, entity, fallDistance); // Paper start - Add EntityLandEvent ++ if(distance == -1){ ++ return; ++ } + if (!(entity instanceof Zombie)) { + this.destroyEgg(level, state, pos, entity, 3); + } + +- super.fallOn(level, state, pos, entity, fallDistance); ++ super.fallOn(level, state, pos, entity, distance, false); // Paper end - Add EntityLandEvent + } + + private void destroyEgg(final Level level, final BlockState state, final BlockPos pos, final Entity entity, final int randomness) { @@ -75,6 +_,19 @@ && level instanceof ServerLevel serverLevel && this.canDestroyEgg(serverLevel, entity) 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(); + } }