--- a/net/minecraft/world/entity/decoration/BlockAttachedEntity.java
+++ b/net/minecraft/world/entity/decoration/BlockAttachedEntity.java
@@ -21,7 +_,7 @@
 
 public abstract class BlockAttachedEntity extends Entity {
     private static final Logger LOGGER = LogUtils.getLogger();
-    private int checkInterval;
+    private int checkInterval; { this.checkInterval = this.getId() % this.level().spigotConfig.hangingTickFrequency; } // Paper - Perf: offset item frame ticking
     protected BlockPos pos;
 
     protected BlockAttachedEntity(EntityType<? extends BlockAttachedEntity> entityType, Level level) {
@@ -39,10 +_,26 @@
     public void tick() {
         if (this.level() instanceof ServerLevel serverLevel) {
             this.checkBelowWorld();
-            if (this.checkInterval++ == 100) {
+            if (this.checkInterval++ == this.level().spigotConfig.hangingTickFrequency) { // Spigot
                 this.checkInterval = 0;
                 if (!this.isRemoved() && !this.survives()) {
-                    this.discard();
+                    // CraftBukkit start - fire break events
+                    final org.bukkit.event.hanging.HangingBreakEvent.RemoveCause cause;
+                    if (!this.level().getBlockState(this.blockPosition()).isAir()) {
+                        // TODO: This feels insufficient to catch 100% of suffocation cases
+                        cause = org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.OBSTRUCTION;
+                    } else {
+                        cause = org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.PHYSICS;
+                    }
+
+                    org.bukkit.event.hanging.HangingBreakEvent event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), cause);
+                    this.level().getCraftServer().getPluginManager().callEvent(event);
+
+                    if (this.isRemoved() || event.isCancelled()) {
+                        return;
+                    }
+                    // CraftBukkit end
+                    this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
                     this.dropItem(serverLevel, null);
                 }
             }
@@ -75,6 +_,21 @@
             return false;
         } else {
             if (!this.isRemoved()) {
+                // CraftBukkit start - fire break events
+                Entity damager = (!damageSource.isDirect() && damageSource.getEntity() != null) ? damageSource.getEntity() : damageSource.getDirectEntity(); // Paper - fix DamageSource API
+                org.bukkit.event.hanging.HangingBreakEvent event;
+                if (damager != null) {
+                    event = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), damageSource.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.ENTITY);
+                } else {
+                    event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damageSource.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.DEFAULT);
+                }
+
+                this.level().getCraftServer().getPluginManager().callEvent(event);
+
+                if (this.isRemoved() || event.isCancelled()) {
+                    return true;
+                }
+                // CraftBukkit end
                 this.kill(level);
                 this.markHurt();
                 this.dropItem(level, damageSource.getEntity());
@@ -93,18 +_,36 @@
     @Override
     public void move(MoverType type, Vec3 movement) {
         if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved() && movement.lengthSqr() > 0.0) {
-            this.kill(serverLevel);
-            this.dropItem(serverLevel, null);
-        }
-    }
-
-    @Override
-    public void push(double x, double y, double z) {
-        if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved() && x * x + y * y + z * z > 0.0) {
-            this.kill(serverLevel);
-            this.dropItem(serverLevel, null);
-        }
-    }
+            // CraftBukkit start - fire break events
+            // TODO - Does this need its own cause? Seems to only be triggered by pistons
+            org.bukkit.event.hanging.HangingBreakEvent event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.PHYSICS);
+            this.level().getCraftServer().getPluginManager().callEvent(event);
+
+            if (this.isRemoved() || event.isCancelled()) {
+                return;
+            }
+            // CraftBukkit end
+            this.kill(serverLevel);
+            this.dropItem(serverLevel, null);
+        }
+    }
+
+    @Override
+    public void push(double x, double y, double z, @Nullable Entity pushingEntity) { // Paper - override correct overload
+        if (false && this.level() instanceof ServerLevel serverLevel && !this.isRemoved() && x * x + y * y + z * z > 0.0) { // CraftBukkit - not needed
+            this.kill(serverLevel);
+            this.dropItem(serverLevel, null);
+        }
+    }
+
+    // CraftBukkit start - selectively save tile position
+    @Override
+    protected void addAdditionalSaveData(final net.minecraft.world.level.storage.ValueOutput output, final boolean includeAll) {
+        if (includeAll) {
+            this.addAdditionalSaveData(output);
+        }
+    }
+    // CraftBukkit end
 
     @Override
     protected void addAdditionalSaveData(ValueOutput output) {
