--- a/net/minecraft/world/entity/decoration/HangingEntity.java
+++ b/net/minecraft/world/entity/decoration/HangingEntity.java
@@ -3,12 +3,6 @@
 import com.mojang.logging.LogUtils;
 import java.util.function.Predicate;
 import javax.annotation.Nullable;
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.Direction;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.network.syncher.SynchedEntityData;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.util.Mth;
 import net.minecraft.world.damagesource.DamageSource;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.EntityType;
@@ -26,16 +20,33 @@
 import net.minecraft.world.phys.Vec3;
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.SynchedEntityData;
+import net.minecraft.server.level.ServerLevel;
+// CraftBukkit start
+import net.minecraft.tags.DamageTypeTags;
+import net.minecraft.util.Mth;
+import org.bukkit.entity.Hanging;
+import org.bukkit.event.entity.EntityRemoveEvent;
+import org.bukkit.event.hanging.HangingBreakByEntityEvent;
+import org.bukkit.event.hanging.HangingBreakEvent;
+// CraftBukkit end
 
 public abstract class HangingEntity extends Entity {
+
     private static final Logger LOGGER = LogUtils.getLogger();
-    protected static final Predicate<Entity> HANGING_ENTITY = entity -> entity instanceof HangingEntity;
-    private int checkInterval;
+    protected static final Predicate<Entity> HANGING_ENTITY = (entity) -> {
+        return entity instanceof HangingEntity;
+    };
+    private int checkInterval; { this.checkInterval = this.getId() % this.level().spigotConfig.hangingTickFrequency; } // Paper - Perf: offset item frame ticking
     public BlockPos pos;
-    protected Direction direction = Direction.SOUTH;
+    protected Direction direction;
 
     protected HangingEntity(EntityType<? extends HangingEntity> type, Level world) {
         super(type, world);
+        this.direction = Direction.SOUTH;
     }
 
     protected HangingEntity(EntityType<? extends HangingEntity> type, Level world, BlockPos pos) {
@@ -44,90 +55,125 @@
     }
 
     @Override
-    protected void defineSynchedData(SynchedEntityData.Builder builder) {
-    }
+    protected void defineSynchedData(SynchedEntityData.Builder builder) {}
 
     public void setDirection(Direction facing) {
         Validate.notNull(facing);
         Validate.isTrue(facing.getAxis().isHorizontal());
         this.direction = facing;
-        this.setYRot((float)(this.direction.get2DDataValue() * 90));
+        this.setYRot((float) (this.direction.get2DDataValue() * 90));
         this.yRotO = this.getYRot();
         this.recalculateBoundingBox();
     }
 
     protected void recalculateBoundingBox() {
         if (this.direction != null) {
-            double d = (double)this.pos.getX() + 0.5;
-            double e = (double)this.pos.getY() + 0.5;
-            double f = (double)this.pos.getZ() + 0.5;
-            double g = 0.46875;
-            double h = this.offs(this.getWidth());
-            double i = this.offs(this.getHeight());
-            d -= (double)this.direction.getStepX() * 0.46875;
-            f -= (double)this.direction.getStepZ() * 0.46875;
-            e += i;
-            Direction direction = this.direction.getCounterClockWise();
-            d += h * (double)direction.getStepX();
-            f += h * (double)direction.getStepZ();
-            this.setPosRaw(d, e, f);
-            double j = (double)this.getWidth();
-            double k = (double)this.getHeight();
-            double l = (double)this.getWidth();
-            if (this.direction.getAxis() == Direction.Axis.Z) {
-                l = 1.0;
+            // CraftBukkit start code moved in to calculateBoundingBox
+            this.setBoundingBox(HangingEntity.calculateBoundingBox(this, this.pos, this.direction, this.getWidth(), this.getHeight()));
+            // CraftBukkit end
+        }
+    }
+
+    // CraftBukkit start - break out BB calc into own method
+    public static AABB calculateBoundingBox(@Nullable Entity entity, BlockPos blockPosition, Direction direction, int width, int height) {
+        {
+            double d0 = (double) blockPosition.getX() + 0.5D;
+            double d1 = (double) blockPosition.getY() + 0.5D;
+            double d2 = (double) blockPosition.getZ() + 0.5D;
+            double d3 = 0.46875D;
+            double d4 = HangingEntity.offs(width);
+            double d5 = HangingEntity.offs(height);
+
+            d0 -= (double) direction.getStepX() * 0.46875D;
+            d2 -= (double) direction.getStepZ() * 0.46875D;
+            d1 += d5;
+            Direction enumdirection = direction.getCounterClockWise();
+
+            d0 += d4 * (double) enumdirection.getStepX();
+            d2 += d4 * (double) enumdirection.getStepZ();
+            if (entity != null) {
+                entity.setPosRaw(d0, d1, d2);
+            }
+            double d6 = (double) width;
+            double d7 = (double) height;
+            double d8 = (double) width;
+
+            if (direction.getAxis() == Direction.Axis.Z) {
+                d8 = 1.0D;
             } else {
-                j = 1.0;
+                d6 = 1.0D;
             }
 
-            j /= 32.0;
-            k /= 32.0;
-            l /= 32.0;
-            this.setBoundingBox(new AABB(d - j, e - k, f - l, d + j, e + k, f + l));
+            d6 /= 32.0D;
+            d7 /= 32.0D;
+            d8 /= 32.0D;
+            return new AABB(d0 - d6, d1 - d7, d2 - d8, d0 + d6, d1 + d7, d2 + d8);
         }
     }
+    // CraftBukkit end
 
-    private double offs(int i) {
-        return i % 32 == 0 ? 0.5 : 0.0;
+    private static double offs(int i) { // CraftBukkit - static
+        return i % 32 == 0 ? 0.5D : 0.0D;
     }
 
     @Override
     public void tick() {
         if (!this.level().isClientSide) {
             this.checkBelowWorld();
-            if (this.checkInterval++ == 100) {
+            if (this.checkInterval++ == this.level().spigotConfig.hangingTickFrequency) { // Spigot
                 this.checkInterval = 0;
                 if (!this.isRemoved() && !this.survives()) {
-                    this.discard();
-                    this.dropItem(null);
+                    // CraftBukkit start - fire break events
+                    BlockState material = this.level().getBlockState(this.blockPosition());
+                    HangingBreakEvent.RemoveCause cause;
+
+                    if (!material.isAir()) {
+                        // TODO: This feels insufficient to catch 100% of suffocation cases
+                        cause = HangingBreakEvent.RemoveCause.OBSTRUCTION;
+                    } else {
+                        cause = HangingBreakEvent.RemoveCause.PHYSICS;
+                    }
+
+                    HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), cause);
+                    this.level().getCraftServer().getPluginManager().callEvent(event);
+
+                    if (this.isRemoved() || event.isCancelled()) {
+                        return;
+                    }
+                    // CraftBukkit end
+                    this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+                    this.dropItem((Entity) null);
                 }
             }
         }
+
     }
 
     public boolean survives() {
-        if (!this.level().noCollision(this)) {
+        if (!this.level().noCollision((Entity) this)) {
             return false;
         } else {
             int i = Math.max(1, this.getWidth() / 16);
             int j = Math.max(1, this.getHeight() / 16);
-            BlockPos blockPos = this.pos.relative(this.direction.getOpposite());
-            Direction direction = this.direction.getCounterClockWise();
-            BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+            BlockPos blockposition = this.pos.relative(this.direction.getOpposite());
+            Direction enumdirection = this.direction.getCounterClockWise();
+            BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
 
-            for (int k = 0; k < i; k++) {
-                for (int l = 0; l < j; l++) {
-                    int m = (i - 1) / -2;
-                    int n = (j - 1) / -2;
-                    mutableBlockPos.set(blockPos).move(direction, k + m).move(Direction.UP, l + n);
-                    BlockState blockState = this.level().getBlockState(mutableBlockPos);
-                    if (!blockState.isSolid() && !DiodeBlock.isDiode(blockState)) {
+            for (int k = 0; k < i; ++k) {
+                for (int l = 0; l < j; ++l) {
+                    int i1 = (i - 1) / -2;
+                    int j1 = (j - 1) / -2;
+
+                    blockposition_mutableblockposition.set(blockposition).move(enumdirection, k + i1).move(Direction.UP, l + j1);
+                    BlockState iblockdata = this.level().getBlockState(blockposition_mutableblockposition);
+
+                    if (!iblockdata.isSolid() && !DiodeBlock.isDiode(iblockdata)) {
                         return false;
                     }
                 }
             }
 
-            return this.level().getEntities(this, this.getBoundingBox(), HANGING_ENTITY).isEmpty();
+            return this.level().getEntities((Entity) this, this.getBoundingBox(), HangingEntity.HANGING_ENTITY).isEmpty();
         }
     }
 
@@ -138,7 +184,11 @@
 
     @Override
     public boolean skipAttackInteraction(Entity attacker) {
-        return attacker instanceof Player player && (!this.level().mayInteract(player, this.pos) || this.hurt(this.damageSources().playerAttack(player), 0.0F));
+        if (attacker instanceof Player entityhuman) {
+            return !this.level().mayInteract(entityhuman, this.pos) ? true : this.hurt(this.damageSources().playerAttack(entityhuman), 0.0F);
+        } else {
+            return false;
+        }
     }
 
     @Override
@@ -152,6 +202,22 @@
             return false;
         } else {
             if (!this.isRemoved() && !this.level().isClientSide) {
+                // CraftBukkit start - fire break events
+                Entity damager = (source.isIndirect() && source.getEntity() != null) ? source.getEntity() : source.getDirectEntity(); // Paper - fix DamageSource API
+                HangingBreakEvent event;
+                if (damager != null) {
+                    event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY);
+                } else {
+                    event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.DEFAULT);
+                }
+
+                this.level().getCraftServer().getPluginManager().callEvent(event);
+
+                if (this.isRemoved() || event.isCancelled()) {
+                    return true;
+                }
+                // CraftBukkit end
+
                 this.kill();
                 this.markHurt();
                 this.dropItem(source.getEntity());
@@ -163,35 +229,60 @@
 
     @Override
     public void move(MoverType movementType, Vec3 movement) {
-        if (!this.level().isClientSide && !this.isRemoved() && movement.lengthSqr() > 0.0) {
+        if (!this.level().isClientSide && !this.isRemoved() && movement.lengthSqr() > 0.0D) {
+            if (this.isRemoved()) return; // CraftBukkit
+
+            // CraftBukkit start - fire break events
+            // TODO - Does this need its own cause? Seems to only be triggered by pistons
+            HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.PHYSICS);
+            this.level().getCraftServer().getPluginManager().callEvent(event);
+
+            if (this.isRemoved() || event.isCancelled()) {
+                return;
+            }
+            // CraftBukkit end
+
             this.kill();
-            this.dropItem(null);
+            this.dropItem((Entity) null);
         }
+
     }
 
     @Override
-    public void push(double deltaX, double deltaY, double deltaZ) {
-        if (!this.level().isClientSide && !this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0) {
+    public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { // Paper - add push source entity param
+        if (false && !this.level().isClientSide && !this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0D) { // CraftBukkit - not needed
             this.kill();
-            this.dropItem(null);
+            this.dropItem((Entity) null);
         }
+
     }
 
+    // CraftBukkit start - selectively save tile position
+    @Override
+    public void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
+        if (includeAll) {
+            this.addAdditionalSaveData(nbttagcompound);
+        }
+    }
+    // CraftBukkit end
+
     @Override
     public void addAdditionalSaveData(CompoundTag nbt) {
-        BlockPos blockPos = this.getPos();
-        nbt.putInt("TileX", blockPos.getX());
-        nbt.putInt("TileY", blockPos.getY());
-        nbt.putInt("TileZ", blockPos.getZ());
+        BlockPos blockposition = this.getPos();
+
+        nbt.putInt("TileX", blockposition.getX());
+        nbt.putInt("TileY", blockposition.getY());
+        nbt.putInt("TileZ", blockposition.getZ());
     }
 
     @Override
     public void readAdditionalSaveData(CompoundTag nbt) {
-        BlockPos blockPos = new BlockPos(nbt.getInt("TileX"), nbt.getInt("TileY"), nbt.getInt("TileZ"));
-        if (!blockPos.closerThan(this.blockPosition(), 16.0)) {
-            LOGGER.error("Hanging entity at invalid position: {}", blockPos);
+        BlockPos blockposition = new BlockPos(nbt.getInt("TileX"), nbt.getInt("TileY"), nbt.getInt("TileZ"));
+
+        if (!blockposition.closerThan(this.blockPosition(), 16.0D)) {
+            HangingEntity.LOGGER.error("Hanging entity at invalid position: {}", blockposition);
         } else {
-            this.pos = blockPos;
+            this.pos = blockposition;
         }
     }
 
@@ -205,16 +296,11 @@
 
     @Override
     public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) {
-        ItemEntity itemEntity = new ItemEntity(
-            this.level(),
-            this.getX() + (double)((float)this.direction.getStepX() * 0.15F),
-            this.getY() + (double)yOffset,
-            this.getZ() + (double)((float)this.direction.getStepZ() * 0.15F),
-            stack
-        );
-        itemEntity.setDefaultPickUpDelay();
-        this.level().addFreshEntity(itemEntity);
-        return itemEntity;
+        ItemEntity entityitem = new ItemEntity(this.level(), this.getX() + (double) ((float) this.direction.getStepX() * 0.15F), this.getY() + (double) yOffset, this.getZ() + (double) ((float) this.direction.getStepZ() * 0.15F), stack);
+
+        entityitem.setDefaultPickUpDelay();
+        this.level().addFreshEntity(entityitem);
+        return entityitem;
     }
 
     @Override
@@ -249,6 +335,7 @@
         }
 
         float f = Mth.wrapDegrees(this.getYRot());
+
         switch (rotation) {
             case CLOCKWISE_180:
                 return f + 180.0F;
@@ -267,10 +354,8 @@
     }
 
     @Override
-    public void thunderHit(ServerLevel world, LightningBolt lightning) {
-    }
+    public void thunderHit(ServerLevel world, LightningBolt lightning) {}
 
     @Override
-    public void refreshDimensions() {
-    }
+    public void refreshDimensions() {}
 }
