--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -1,6 +1,7 @@
 package net.minecraft.world.entity.item;
 
 import com.mojang.logging.LogUtils;
+import java.util.Iterator;
 import java.util.function.Predicate;
 import javax.annotation.Nullable;
 import net.minecraft.CrashReportCategory;
@@ -29,9 +30,11 @@
 import net.minecraft.world.entity.EntityType;
 import net.minecraft.world.entity.MoverType;
 import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.context.BlockPlaceContext;
 import net.minecraft.world.item.context.DirectionalPlaceContext;
 import net.minecraft.world.level.ClipContext;
 import net.minecraft.world.level.GameRules;
+import net.minecraft.world.level.ItemLike;
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.AnvilBlock;
 import net.minecraft.world.level.block.Block;
@@ -49,22 +52,32 @@
 import net.minecraft.world.phys.Vec3;
 import org.slf4j.Logger;
 
+// CraftBukkit start;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.event.entity.EntityRemoveEvent;
+// CraftBukkit end
+
 public class FallingBlockEntity extends Entity {
+
     private static final Logger LOGGER = LogUtils.getLogger();
-    public BlockState blockState = Blocks.SAND.defaultBlockState();
+    public BlockState blockState;
     public int time;
-    public boolean dropItem = true;
+    public boolean dropItem;
     public boolean cancelDrop;
     public boolean hurtEntities;
-    public int fallDamageMax = 40;
+    public int fallDamageMax;
     public float fallDamagePerDistance;
     @Nullable
     public CompoundTag blockData;
     public boolean forceTickAfterTeleportToDuplicate;
     protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
+    public boolean autoExpire = true; // Paper - Expand FallingBlock API
 
     public FallingBlockEntity(EntityType<? extends FallingBlockEntity> type, Level world) {
         super(type, world);
+        this.blockState = Blocks.SAND.defaultBlockState();
+        this.dropItem = true;
+        this.fallDamageMax = 40;
     }
 
     public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) {
@@ -80,16 +93,18 @@
     }
 
     public static FallingBlockEntity fall(Level world, BlockPos pos, BlockState state) {
-        FallingBlockEntity fallingBlockEntity = new FallingBlockEntity(
-            world,
-            (double)pos.getX() + 0.5,
-            (double)pos.getY(),
-            (double)pos.getZ() + 0.5,
-            state.hasProperty(BlockStateProperties.WATERLOGGED) ? state.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(false)) : state
-        );
-        world.setBlock(pos, state.getFluidState().createLegacyBlock(), 3);
-        world.addFreshEntity(fallingBlockEntity);
-        return fallingBlockEntity;
+        // CraftBukkit start
+        return FallingBlockEntity.fall(world, pos, state, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+    }
+
+    public static FallingBlockEntity fall(Level world, BlockPos blockposition, BlockState iblockdata, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
+        // CraftBukkit end
+        FallingBlockEntity entityfallingblock = new FallingBlockEntity(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, iblockdata.hasProperty(BlockStateProperties.WATERLOGGED) ? (BlockState) iblockdata.setValue(BlockStateProperties.WATERLOGGED, false) : iblockdata);
+        if (!CraftEventFactory.callEntityChangeBlockEvent(entityfallingblock, blockposition, iblockdata.getFluidState().createLegacyBlock())) return entityfallingblock; // CraftBukkit
+
+        world.setBlock(blockposition, iblockdata.getFluidState().createLegacyBlock(), 3);
+        world.addFreshEntity(entityfallingblock, spawnReason); // CraftBukkit
+        return entityfallingblock;
     }
 
     @Override
@@ -98,11 +113,11 @@
     }
 
     public void setStartPos(BlockPos pos) {
-        this.entityData.set(DATA_START_POS, pos);
+        this.entityData.set(FallingBlockEntity.DATA_START_POS, pos);
     }
 
     public BlockPos getStartPos() {
-        return this.entityData.get(DATA_START_POS);
+        return (BlockPos) this.entityData.get(FallingBlockEntity.DATA_START_POS);
     }
 
     @Override
@@ -112,7 +127,7 @@
 
     @Override
     protected void defineSynchedData(SynchedEntityData.Builder builder) {
-        builder.define(DATA_START_POS, BlockPos.ZERO);
+        builder.define(FallingBlockEntity.DATA_START_POS, BlockPos.ZERO);
     }
 
     @Override
@@ -122,119 +137,132 @@
 
     @Override
     protected double getDefaultGravity() {
-        return 0.04;
+        return 0.04D;
     }
 
     @Override
     public void tick() {
         if (this.blockState.isAir()) {
-            this.discard();
+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
         } else {
             Block block = this.blockState.getBlock();
-            this.time++;
+
+            ++this.time;
             this.applyGravity();
             this.move(MoverType.SELF, this.getDeltaMovement());
+            // Paper start - Configurable falling blocks height nerf
+            if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) {
+                if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+                    this.spawnAtLocation(block);
+                }
+
+                this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD);
+                return;
+            }
+            // Paper end - Configurable falling blocks height nerf
             this.handlePortal();
             if (!this.level().isClientSide && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) {
-                BlockPos blockPos = this.blockPosition();
-                boolean bl = this.blockState.getBlock() instanceof ConcretePowderBlock;
-                boolean bl2 = bl && this.level().getFluidState(blockPos).is(FluidTags.WATER);
-                double d = this.getDeltaMovement().lengthSqr();
-                if (bl && d > 1.0) {
-                    BlockHitResult blockHitResult = this.level()
-                        .clip(
-                            new ClipContext(
-                                new Vec3(this.xo, this.yo, this.zo), this.position(), ClipContext.Block.COLLIDER, ClipContext.Fluid.SOURCE_ONLY, this
-                            )
-                        );
-                    if (blockHitResult.getType() != HitResult.Type.MISS && this.level().getFluidState(blockHitResult.getBlockPos()).is(FluidTags.WATER)) {
-                        blockPos = blockHitResult.getBlockPos();
-                        bl2 = true;
+                BlockPos blockposition = this.blockPosition();
+                boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock;
+                boolean flag1 = flag && this.level().getFluidState(blockposition).is(FluidTags.WATER);
+                double d0 = this.getDeltaMovement().lengthSqr();
+
+                if (flag && d0 > 1.0D) {
+                    BlockHitResult movingobjectpositionblock = this.level().clip(new ClipContext(new Vec3(this.xo, this.yo, this.zo), this.position(), ClipContext.Block.COLLIDER, ClipContext.Fluid.SOURCE_ONLY, this));
+
+                    if (movingobjectpositionblock.getType() != HitResult.Type.MISS && this.level().getFluidState(movingobjectpositionblock.getBlockPos()).is(FluidTags.WATER)) {
+                        blockposition = movingobjectpositionblock.getBlockPos();
+                        flag1 = true;
                     }
                 }
 
-                if (this.onGround() || bl2) {
-                    BlockState blockState = this.level().getBlockState(blockPos);
-                    this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7));
-                    if (!blockState.is(Blocks.MOVING_PISTON)) {
+                if (!this.onGround() && !flag1) {
+                    if (!this.level().isClientSide && ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinBuildHeight() || blockposition.getY() > this.level().getMaxBuildHeight()) || (this.time > 600 && autoExpire))) { // Paper - Expand FallingBlock API
+                        if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+                            this.spawnAtLocation((ItemLike) block);
+                        }
+
+                        this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+                    }
+                } else {
+                    BlockState iblockdata = this.level().getBlockState(blockposition);
+
+                    this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D));
+                    if (!iblockdata.is(Blocks.MOVING_PISTON)) {
                         if (!this.cancelDrop) {
-                            boolean bl3 = blockState.canBeReplaced(
-                                new DirectionalPlaceContext(this.level(), blockPos, Direction.DOWN, ItemStack.EMPTY, Direction.UP)
-                            );
-                            boolean bl4 = FallingBlock.isFree(this.level().getBlockState(blockPos.below())) && (!bl || !bl2);
-                            boolean bl5 = this.blockState.canSurvive(this.level(), blockPos) && !bl4;
-                            if (bl3 && bl5) {
-                                if (this.blockState.hasProperty(BlockStateProperties.WATERLOGGED)
-                                    && this.level().getFluidState(blockPos).getType() == Fluids.WATER) {
-                                    this.blockState = this.blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(true));
+                            boolean flag2 = iblockdata.canBeReplaced((BlockPlaceContext) (new DirectionalPlaceContext(this.level(), blockposition, Direction.DOWN, ItemStack.EMPTY, Direction.UP)));
+                            boolean flag3 = FallingBlock.isFree(this.level().getBlockState(blockposition.below())) && (!flag || !flag1);
+                            boolean flag4 = this.blockState.canSurvive(this.level(), blockposition) && !flag3;
+
+                            if (flag2 && flag4) {
+                                if (this.blockState.hasProperty(BlockStateProperties.WATERLOGGED) && this.level().getFluidState(blockposition).getType() == Fluids.WATER) {
+                                    this.blockState = (BlockState) this.blockState.setValue(BlockStateProperties.WATERLOGGED, true);
                                 }
 
-                                if (this.level().setBlock(blockPos, this.blockState, 3)) {
-                                    ((ServerLevel)this.level())
-                                        .getChunkSource()
-                                        .chunkMap
-                                        .broadcast(this, new ClientboundBlockUpdatePacket(blockPos, this.level().getBlockState(blockPos)));
-                                    this.discard();
+                                // CraftBukkit start
+                                if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, this.blockState)) {
+                                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // SPIGOT-6586 called before the event in previous versions
+                                    return;
+                                }
+                                // CraftBukkit end
+                                if (this.level().setBlock(blockposition, this.blockState, 3)) {
+                                    ((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockposition, this.level().getBlockState(blockposition)));
+                                    this.discard(EntityRemoveEvent.Cause.DESPAWN);
                                     if (block instanceof Fallable) {
-                                        ((Fallable)block).onLand(this.level(), blockPos, this.blockState, blockState, this);
+                                        ((Fallable) block).onLand(this.level(), blockposition, this.blockState, iblockdata, this);
                                     }
 
                                     if (this.blockData != null && this.blockState.hasBlockEntity()) {
-                                        BlockEntity blockEntity = this.level().getBlockEntity(blockPos);
-                                        if (blockEntity != null) {
-                                            CompoundTag compoundTag = blockEntity.saveWithoutMetadata(this.level().registryAccess());
+                                        BlockEntity tileentity = this.level().getBlockEntity(blockposition);
 
-                                            for (String string : this.blockData.getAllKeys()) {
-                                                compoundTag.put(string, this.blockData.get(string).copy());
+                                        if (tileentity != null) {
+                                            CompoundTag nbttagcompound = tileentity.saveWithoutMetadata(this.level().registryAccess());
+                                            Iterator iterator = this.blockData.getAllKeys().iterator();
+
+                                            while (iterator.hasNext()) {
+                                                String s = (String) iterator.next();
+
+                                                nbttagcompound.put(s, this.blockData.get(s).copy());
                                             }
 
                                             try {
-                                                blockEntity.loadWithComponents(compoundTag, this.level().registryAccess());
-                                            } catch (Exception var15) {
-                                                LOGGER.error("Failed to load block entity from falling block", (Throwable)var15);
+                                                tileentity.loadWithComponents(nbttagcompound, this.level().registryAccess());
+                                            } catch (Exception exception) {
+                                                FallingBlockEntity.LOGGER.error("Failed to load block entity from falling block", exception);
                                             }
 
-                                            blockEntity.setChanged();
+                                            tileentity.setChanged();
                                         }
                                     }
                                 } else if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-                                    this.discard();
-                                    this.callOnBrokenAfterFall(block, blockPos);
-                                    this.spawnAtLocation(block);
+                                    this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+                                    this.callOnBrokenAfterFall(block, blockposition);
+                                    this.spawnAtLocation((ItemLike) block);
                                 }
                             } else {
-                                this.discard();
+                                this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
                                 if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-                                    this.callOnBrokenAfterFall(block, blockPos);
-                                    this.spawnAtLocation(block);
+                                    this.callOnBrokenAfterFall(block, blockposition);
+                                    this.spawnAtLocation((ItemLike) block);
                                 }
                             }
                         } else {
-                            this.discard();
-                            this.callOnBrokenAfterFall(block, blockPos);
+                            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                            this.callOnBrokenAfterFall(block, blockposition);
                         }
                     }
-                } else if (!this.level().isClientSide
-                    && (
-                        this.time > 100 && (blockPos.getY() <= this.level().getMinBuildHeight() || blockPos.getY() > this.level().getMaxBuildHeight())
-                            || this.time > 600
-                    )) {
-                    if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-                        this.spawnAtLocation(block);
-                    }
-
-                    this.discard();
                 }
             }
 
-            this.setDeltaMovement(this.getDeltaMovement().scale(0.98));
+            this.setDeltaMovement(this.getDeltaMovement().scale(0.98D));
         }
     }
 
     public void callOnBrokenAfterFall(Block block, BlockPos pos) {
         if (block instanceof Fallable) {
-            ((Fallable)block).onBrokenAfterFall(this.level(), pos, this);
+            ((Fallable) block).onBrokenAfterFall(this.level(), pos, this);
         }
+
     }
 
     @Override
@@ -243,22 +271,37 @@
             return false;
         } else {
             int i = Mth.ceil(fallDistance - 1.0F);
+
             if (i < 0) {
                 return false;
             } else {
                 Predicate<Entity> predicate = EntitySelector.NO_CREATIVE_OR_SPECTATOR.and(EntitySelector.LIVING_ENTITY_STILL_ALIVE);
-                DamageSource damageSource2 = this.blockState.getBlock() instanceof Fallable fallable
-                    ? fallable.getFallDamageSource(this)
-                    : this.damageSources().fallingBlock(this);
-                float f = (float)Math.min(Mth.floor((float)i * this.fallDamagePerDistance), this.fallDamageMax);
-                this.level().getEntities(this, this.getBoundingBox(), predicate).forEach(entity -> entity.hurt(damageSource2, f));
-                boolean bl = this.blockState.is(BlockTags.ANVIL);
-                if (bl && f > 0.0F && this.random.nextFloat() < 0.05F + (float)i * 0.05F) {
-                    BlockState blockState = AnvilBlock.damage(this.blockState);
-                    if (blockState == null) {
+                Block block = this.blockState.getBlock();
+                DamageSource damagesource1;
+
+                if (block instanceof Fallable) {
+                    Fallable fallable = (Fallable) block;
+
+                    damagesource1 = fallable.getFallDamageSource(this);
+                } else {
+                    damagesource1 = this.damageSources().fallingBlock(this);
+                }
+
+                DamageSource damagesource2 = damagesource1;
+                float f2 = (float) Math.min(Mth.floor((float) i * this.fallDamagePerDistance), this.fallDamageMax);
+
+                this.level().getEntities((Entity) this, this.getBoundingBox(), predicate).forEach((entity) -> {
+                    entity.hurt(damagesource2, f2);
+                });
+                boolean flag = this.blockState.is(BlockTags.ANVIL);
+
+                if (flag && f2 > 0.0F && this.random.nextFloat() < 0.05F + (float) i * 0.05F) {
+                    BlockState iblockdata = AnvilBlock.damage(this.blockState);
+
+                    if (iblockdata == null) {
                         this.cancelDrop = true;
                     } else {
-                        this.blockState = blockState;
+                        this.blockState = iblockdata;
                     }
                 }
 
@@ -280,6 +323,7 @@
         }
 
         nbt.putBoolean("CancelDrop", this.cancelDrop);
+        if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API
     }
 
     @Override
@@ -298,7 +342,7 @@
             this.dropItem = nbt.getBoolean("DropItem");
         }
 
-        if (nbt.contains("TileEntityData", 10)) {
+        if (nbt.contains("TileEntityData", 10) && !(this.level().paperConfig().entities.spawning.filterBadTileEntityNbtFromFallingBlocks && this.blockState.getBlock() instanceof net.minecraft.world.level.block.GameMasterBlock)) { // Paper - Filter bad block entity nbt data from falling blocks
             this.blockData = nbt.getCompound("TileEntityData").copy();
         }
 
@@ -306,6 +350,12 @@
         if (this.blockState.isAir()) {
             this.blockState = Blocks.SAND.defaultBlockState();
         }
+
+        // Paper start - Expand FallingBlock API
+         if (nbt.contains("Paper.AutoExpire")) {
+            this.autoExpire = nbt.getBoolean("Paper.AutoExpire");
+         }
+        // Paper end - Expand FallingBlock API
     }
 
     public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) {
@@ -326,7 +376,7 @@
     @Override
     public void fillCrashReportCategory(CrashReportCategory section) {
         super.fillCrashReportCategory(section);
-        section.setDetail("Immitating BlockState", this.blockState.toString());
+        section.setDetail("Immitating BlockState", (Object) this.blockState.toString());
     }
 
     public BlockState getBlockState() {
@@ -353,21 +403,23 @@
         super.recreateFromPacket(packet);
         this.blockState = Block.stateById(packet.getData());
         this.blocksBuilding = true;
-        double d = packet.getX();
-        double e = packet.getY();
-        double f = packet.getZ();
-        this.setPos(d, e, f);
+        double d0 = packet.getX();
+        double d1 = packet.getY();
+        double d2 = packet.getZ();
+
+        this.setPos(d0, d1, d2);
         this.setStartPos(this.blockPosition());
     }
 
     @Nullable
     @Override
     public Entity changeDimension(DimensionTransition teleportTarget) {
-        ResourceKey<Level> resourceKey = teleportTarget.newLevel().dimension();
-        ResourceKey<Level> resourceKey2 = this.level().dimension();
-        boolean bl = (resourceKey2 == Level.END || resourceKey == Level.END) && resourceKey2 != resourceKey;
+        ResourceKey<Level> resourcekey = teleportTarget.newLevel().dimension();
+        ResourceKey<Level> resourcekey1 = this.level().dimension();
+        boolean flag = (resourcekey1 == Level.END || resourcekey == Level.END) && resourcekey1 != resourcekey;
         Entity entity = super.changeDimension(teleportTarget);
-        this.forceTickAfterTeleportToDuplicate = entity != null && bl;
+
+        this.forceTickAfterTeleportToDuplicate = entity != null && flag && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper
         return entity;
     }
 }
