--- a/net/minecraft/world/entity/decoration/HangingEntity.java
+++ b/net/minecraft/world/entity/decoration/HangingEntity.java
@@ -9,6 +9,7 @@
 import net.minecraft.server.level.ServerLevel;
 import net.minecraft.util.Mth;
 import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.damagesource.IndirectEntityDamageSource;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.EntityType;
 import net.minecraft.world.entity.LightningBolt;
@@ -16,27 +17,34 @@
 import net.minecraft.world.entity.item.ItemEntity;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.ItemStack;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.DiodeBlock;
 import net.minecraft.world.level.block.Mirror;
 import net.minecraft.world.level.block.Rotation;
 import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.material.Material;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.Vec3;
-import org.apache.commons.lang3.Validate;
-import org.slf4j.Logger;
+import org.bukkit.entity.Hanging;
+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) -> {
         return entity instanceof HangingEntity;
     };
-    private int checkInterval;
+    private int checkInterval; { this.checkInterval = this.getId() % this.level.spigotConfig.hangingTickFrequency; } // Paper
     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) {
@@ -45,50 +53,64 @@
     }
 
     @Override
-    protected void defineSynchedData() {
-    }
+    protected void defineSynchedData() {}
 
     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.5D;
-            double e = (double)this.pos.getY() + 0.5D;
-            double f = (double)this.pos.getZ() + 0.5D;
-            double g = 0.46875D;
-            double h = this.offs(this.getWidth());
-            double i = this.offs(this.getHeight());
-            d -= (double)this.direction.getStepX() * 0.46875D;
-            f -= (double)this.direction.getStepZ() * 0.46875D;
-            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.0D;
+            // 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.0D;
+                d6 = 1.0D;
             }
 
-            j /= 32.0D;
-            k /= 32.0D;
-            l /= 32.0D;
-            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) {
+    private static double offs(int i) { // CraftBukkit - static
         return i % 32 == 0 ? 0.5D : 0.0D;
     }
 
@@ -96,11 +118,29 @@
     public void tick() {
         if (!this.level.isClientSide) {
             this.checkOutOfWorld();
-            if (this.checkInterval++ == 100) {
+            if (this.checkInterval++ == this.level.spigotConfig.hangingTickFrequency) { // Spigot
                 this.checkInterval = 0;
                 if (!this.isRemoved() && !this.survives()) {
+                    // CraftBukkit start - fire break events
+                    Material material = this.level.getBlockState(this.blockPosition()).getMaterial();
+                    HangingBreakEvent.RemoveCause cause;
+
+                    if (!material.equals(Material.AIR)) {
+                        // 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();
-                    this.dropItem((Entity)null);
+                    this.dropItem((Entity) null);
                 }
             }
         }
@@ -108,28 +148,30 @@
     }
 
     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.getMaterial().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.getMaterial().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();
         }
     }
 
@@ -140,8 +182,10 @@
 
     @Override
     public boolean skipAttackInteraction(Entity attacker) {
-        if (attacker instanceof Player player) {
-            return !this.level.mayInteract(player, this.pos) ? true : this.hurt(DamageSource.playerAttack(player), 0.0F);
+        if (attacker instanceof Player) {
+            Player entityhuman = (Player) attacker;
+
+            return !this.level.mayInteract(entityhuman, this.pos) ? true : this.hurt(DamageSource.playerAttack(entityhuman), 0.0F);
         } else {
             return false;
         }
@@ -158,6 +202,22 @@
             return false;
         } else {
             if (!this.isRemoved() && !this.level.isClientSide) {
+                // CraftBukkit start - fire break events
+                Entity damager = (source instanceof IndirectEntityDamageSource) ? ((IndirectEntityDamageSource) source).getProximateDamageSource() : source.getEntity();
+                HangingBreakEvent event;
+                if (damager != null) {
+                    event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.isExplosion() ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY);
+                } else {
+                    event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), source.isExplosion() ? 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());
@@ -170,36 +230,50 @@
     @Override
     public void move(MoverType movementType, Vec3 movement) {
         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((Entity)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.0D) {
+        if (false && !this.level.isClientSide && !this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0D) { // CraftBukkit - not needed
             this.kill();
-            this.dropItem((Entity)null);
+            this.dropItem((Entity) null);
         }
 
     }
 
     @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.0D)) {
-            LOGGER.error("Hanging entity at invalid position: {}", (Object)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;
         }
     }
 
@@ -213,10 +287,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
@@ -251,6 +326,7 @@
         }
 
         float f = Mth.wrapDegrees(this.getYRot());
+
         switch (rotation) {
             case CLOCKWISE_180:
                 return f + 180.0F;
@@ -269,10 +345,8 @@
     }
 
     @Override
-    public void thunderHit(ServerLevel world, LightningBolt lightning) {
-    }
+    public void thunderHit(ServerLevel world, LightningBolt lightning) {}
 
     @Override
-    public void refreshDimensions() {
-    }
+    public void refreshDimensions() {}
 }
