--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -49,25 +_,31 @@
 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();
-    private BlockState blockState = Blocks.SAND.defaultBlockState();
+    public BlockState blockState = Blocks.SAND.defaultBlockState();
     public int time;
     public boolean dropItem = true;
-    private boolean cancelDrop;
-    private boolean hurtEntities;
-    private int fallDamageMax = 40;
-    private float fallDamagePerDistance;
+    public boolean cancelDrop;
+    public boolean hurtEntities;
+    public int fallDamageMax = 40;
+    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> entityType, Level level) {
         super(entityType, level);
     }
 
-    private FallingBlockEntity(Level level, double x, double y, double z, BlockState state) {
+    public FallingBlockEntity(Level level, double x, double y, double z, BlockState state) {
         this(EntityType.FALLING_BLOCK, level);
         this.blockState = state;
         this.blocksBuilding = true;
@@ -89,6 +_,7 @@
                 ? blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(false))
                 : blockState
         );
+        if (!CraftEventFactory.callEntityChangeBlockEvent(fallingBlockEntity, pos, blockState.getFluidState().createLegacyBlock())) return fallingBlockEntity; // CraftBukkit
         level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3);
         level.addFreshEntity(fallingBlockEntity);
         return fallingBlockEntity;
@@ -139,13 +_,22 @@
     @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.applyGravity();
             this.move(MoverType.SELF, this.getDeltaMovement());
             this.applyEffectsFromBlocks();
+            // Paper start - Configurable falling blocks height nerf
+            if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) {
+                if (this.dropItem && this.level() instanceof final ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+                    this.spawnAtLocation(serverLevel, block);
+                }
+                this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD);
+                return;
+            }
+            // Paper end - Configurable falling blocks height nerf
             this.handlePortal();
             if (this.level() instanceof ServerLevel serverLevel && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) {
                 BlockPos blockPos = this.blockPosition();
@@ -166,12 +_,12 @@
                 }
 
                 if (!this.onGround() && !flag1) {
-                    if (this.time > 100 && (blockPos.getY() <= this.level().getMinY() || blockPos.getY() > this.level().getMaxY()) || this.time > 600) {
+                    if ((this.time > 100 && autoExpire) && (blockPos.getY() <= this.level().getMinY() || blockPos.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API
                         if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
                             this.spawnAtLocation(serverLevel, block);
                         }
 
-                        this.discard();
+                        this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
                     }
                 } else {
                     BlockState blockState = this.level().getBlockState(blockPos);
@@ -189,12 +_,18 @@
                                     this.blockState = this.blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(true));
                                 }
 
+                                // CraftBukkit start
+                                if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockPos, this.blockState)) {
+                                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // SPIGOT-6586 called before the event in previous versions
+                                    return;
+                                }
+                                // CraftBukkit end
                                 if (this.level().setBlock(blockPos, this.blockState, 3)) {
                                     ((ServerLevel)this.level())
                                         .getChunkSource()
                                         .chunkMap
                                         .broadcast(this, new ClientboundBlockUpdatePacket(blockPos, this.level().getBlockState(blockPos)));
-                                    this.discard();
+                                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
                                     if (block instanceof Fallable) {
                                         ((Fallable)block).onLand(this.level(), blockPos, this.blockState, blockState, this);
                                     }
@@ -218,19 +_,19 @@
                                         }
                                     }
                                 } else if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-                                    this.discard();
+                                    this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
                                     this.callOnBrokenAfterFall(block, blockPos);
                                     this.spawnAtLocation(serverLevel, block);
                                 }
                             } else {
-                                this.discard();
+                                this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
                                 if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
                                     this.callOnBrokenAfterFall(block, blockPos);
                                     this.spawnAtLocation(serverLevel, block);
                                 }
                             }
                         } else {
-                            this.discard();
+                            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
                             this.callOnBrokenAfterFall(block, blockPos);
                         }
                     }
@@ -290,6 +_,7 @@
         }
 
         compound.putBoolean("CancelDrop", this.cancelDrop);
+        if (!this.autoExpire) compound.putBoolean("Paper.AutoExpire", false); // Paper - Expand FallingBlock API
     }
 
     @Override
@@ -308,7 +_,7 @@
             this.dropItem = compound.getBoolean("DropItem");
         }
 
-        if (compound.contains("TileEntityData", 10)) {
+        if (compound.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 = compound.getCompound("TileEntityData").copy();
         }
 
@@ -316,6 +_,12 @@
         if (this.blockState.isAir()) {
             this.blockState = Blocks.SAND.defaultBlockState();
         }
+
+        // Paper start - Expand FallingBlock API
+        if (compound.contains("Paper.AutoExpire")) {
+            this.autoExpire = compound.getBoolean("Paper.AutoExpire");
+        }
+        // Paper end - Expand FallingBlock API
     }
 
     public void setHurtsEntities(float fallDamagePerDistance, int fallDamageMax) {
@@ -372,7 +_,7 @@
         ResourceKey<Level> resourceKey1 = this.level().dimension();
         boolean flag = (resourceKey1 == Level.END || resourceKey == Level.END) && resourceKey1 != resourceKey;
         Entity entity = super.teleport(teleportTransition);
-        this.forceTickAfterTeleportToDuplicate = entity != null && flag;
+        this.forceTickAfterTeleportToDuplicate = entity != null && flag && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper
         return entity;
     }
 }
