--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
@@ -2,6 +2,7 @@
 
 import com.google.common.collect.Lists;
 import com.mojang.logging.LogUtils;
+import java.util.Iterator;
 import java.util.List;
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
@@ -37,19 +38,32 @@
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.level.GameRules;
 import net.minecraft.world.level.Level;
-import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.dimension.end.EndDragonFight;
 import net.minecraft.world.level.gameevent.GameEvent;
 import net.minecraft.world.level.levelgen.Heightmap;
 import net.minecraft.world.level.levelgen.feature.EndPodiumFeature;
 import net.minecraft.world.level.pathfinder.BinaryHeap;
 import net.minecraft.world.level.pathfinder.Node;
 import net.minecraft.world.level.pathfinder.Path;
+import net.minecraft.world.level.storage.loot.LootContext;
+import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.Vec3;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Explosion;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.dimension.end.EndDragonFight;
+import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.entity.EntityRegainHealthEvent;
+// CraftBukkit end
+
 public class EnderDragon extends Mob implements Enemy {
+
     private static final Logger LOGGER = LogUtils.getLogger();
     public static final EntityDataAccessor<Integer> DATA_PHASE = SynchedEntityData.defineId(EnderDragon.class, EntityDataSerializers.INT);
     private static final TargetingConditions CRYSTAL_DESTROY_TARGETING = TargetingConditions.forCombat().range(64.0D);
@@ -61,14 +75,14 @@
     public final double[][] positions = new double[64][3];
     public int posPointer = -1;
     public final EnderDragonPart[] subEntities;
-    public final EnderDragonPart head;
-    private final EnderDragonPart neck;
-    private final EnderDragonPart body;
-    private final EnderDragonPart tail1;
-    private final EnderDragonPart tail2;
-    private final EnderDragonPart tail3;
-    private final EnderDragonPart wing1;
-    private final EnderDragonPart wing2;
+    public final EnderDragonPart head = new EnderDragonPart(this, "head", 1.0F, 1.0F);
+    private final EnderDragonPart neck = new EnderDragonPart(this, "neck", 3.0F, 3.0F);
+    private final EnderDragonPart body = new EnderDragonPart(this, "body", 5.0F, 3.0F);
+    private final EnderDragonPart tail1 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
+    private final EnderDragonPart tail2 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
+    private final EnderDragonPart tail3 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
+    private final EnderDragonPart wing1 = new EnderDragonPart(this, "wing", 4.0F, 2.0F);
+    private final EnderDragonPart wing2 = new EnderDragonPart(this, "wing", 4.0F, 2.0F);
     public float oFlapTime;
     public float flapTime;
     public boolean inWall;
@@ -84,23 +98,20 @@
     private final Node[] nodes = new Node[24];
     private final int[] nodeAdjacency = new int[24];
     private final BinaryHeap openSet = new BinaryHeap();
+    private Explosion explosionSource = new Explosion(null, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit - reusable source for CraftTNTPrimed.getSource()
+    // Paper start - add var for save custom podium
+    @Nullable
+    private BlockPos podium;
+    // Paper end
 
-    public EnderDragon(EntityType<? extends EnderDragon> entityType, Level world) {
+    public EnderDragon(EntityType<? extends EnderDragon> entitytypes, Level world) {
         super(EntityType.ENDER_DRAGON, world);
-        this.head = new EnderDragonPart(this, "head", 1.0F, 1.0F);
-        this.neck = new EnderDragonPart(this, "neck", 3.0F, 3.0F);
-        this.body = new EnderDragonPart(this, "body", 5.0F, 3.0F);
-        this.tail1 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
-        this.tail2 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
-        this.tail3 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
-        this.wing1 = new EnderDragonPart(this, "wing", 4.0F, 2.0F);
-        this.wing2 = new EnderDragonPart(this, "wing", 4.0F, 2.0F);
         this.subEntities = new EnderDragonPart[]{this.head, this.neck, this.body, this.tail1, this.tail2, this.tail3, this.wing1, this.wing2};
         this.setHealth(this.getMaxHealth());
         this.noPhysics = true;
         this.noCulling = true;
         if (world instanceof ServerLevel) {
-            this.dragonFight = ((ServerLevel)world).dragonFight();
+            this.dragonFight = ((ServerLevel) world).dragonFight();
         } else {
             this.dragonFight = null;
         }
@@ -112,11 +123,25 @@
         return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D);
     }
 
+    // Paper start
+    public BlockPos getPodium() {
+        if (this.podium == null) {
+            return EndPodiumFeature.END_PODIUM_LOCATION;
+        }
+        return this.podium;
+    }
+
+    public void setPodium(@Nullable BlockPos blockPos) {
+        this.podium = blockPos;
+    }
+    // Paper end
+
     @Override
     public boolean isFlapping() {
-        float f = Mth.cos(this.flapTime * ((float)Math.PI * 2F));
-        float g = Mth.cos(this.oFlapTime * ((float)Math.PI * 2F));
-        return g <= -0.3F && f >= -0.3F;
+        float f = Mth.cos(this.flapTime * 6.2831855F);
+        float f1 = Mth.cos(this.oFlapTime * 6.2831855F);
+
+        return f1 <= -0.3F && f >= -0.3F;
     }
 
     @Override
@@ -130,7 +155,7 @@
     @Override
     protected void defineSynchedData() {
         super.defineSynchedData();
-        this.getEntityData().define(DATA_PHASE, EnderDragonPhase.HOVERING.getId());
+        this.getEntityData().define(EnderDragon.DATA_PHASE, EnderDragonPhase.HOVERING.getId());
     }
 
     public double[] getLatencyPos(int segmentNumber, float tickDelta) {
@@ -139,17 +164,18 @@
         }
 
         tickDelta = 1.0F - tickDelta;
-        int i = this.posPointer - segmentNumber & 63;
-        int j = this.posPointer - segmentNumber - 1 & 63;
-        double[] ds = new double[3];
-        double d = this.positions[i][0];
-        double e = Mth.wrapDegrees(this.positions[j][0] - d);
-        ds[0] = d + e * (double)tickDelta;
-        d = this.positions[i][1];
-        e = this.positions[j][1] - d;
-        ds[1] = d + e * (double)tickDelta;
-        ds[2] = Mth.lerp((double)tickDelta, this.positions[i][2], this.positions[j][2]);
-        return ds;
+        int j = this.posPointer - segmentNumber & 63;
+        int k = this.posPointer - segmentNumber - 1 & 63;
+        double[] adouble = new double[3];
+        double d0 = this.positions[j][0];
+        double d1 = Mth.wrapDegrees(this.positions[k][0] - d0);
+
+        adouble[0] = d0 + d1 * (double) tickDelta;
+        d0 = this.positions[j][1];
+        d1 = this.positions[k][1] - d0;
+        adouble[1] = d0 + d1 * (double) tickDelta;
+        adouble[2] = Mth.lerp((double) tickDelta, this.positions[j][2], this.positions[k][2]);
+        return adouble;
     }
 
     @Override
@@ -164,22 +190,27 @@
         }
 
         this.oFlapTime = this.flapTime;
+        float f;
+
         if (this.isDeadOrDying()) {
-            float f = (this.random.nextFloat() - 0.5F) * 8.0F;
-            float g = (this.random.nextFloat() - 0.5F) * 4.0F;
-            float h = (this.random.nextFloat() - 0.5F) * 8.0F;
-            this.level.addParticle(ParticleTypes.EXPLOSION, this.getX() + (double)f, this.getY() + 2.0D + (double)g, this.getZ() + (double)h, 0.0D, 0.0D, 0.0D);
+            float f1 = (this.random.nextFloat() - 0.5F) * 8.0F;
+
+            f = (this.random.nextFloat() - 0.5F) * 4.0F;
+            float f2 = (this.random.nextFloat() - 0.5F) * 8.0F;
+
+            this.level.addParticle(ParticleTypes.EXPLOSION, this.getX() + (double) f1, this.getY() + 2.0D + (double) f, this.getZ() + (double) f2, 0.0D, 0.0D, 0.0D);
         } else {
             this.checkCrystals();
-            Vec3 vec3 = this.getDeltaMovement();
-            float i = 0.2F / ((float)vec3.horizontalDistance() * 10.0F + 1.0F);
-            i *= (float)Math.pow(2.0D, vec3.y);
+            Vec3 vec3d = this.getDeltaMovement();
+
+            f = 0.2F / ((float) vec3d.horizontalDistance() * 10.0F + 1.0F);
+            f *= (float) Math.pow(2.0D, vec3d.y);
             if (this.phaseManager.getCurrentPhase().isSitting()) {
                 this.flapTime += 0.1F;
             } else if (this.inWall) {
-                this.flapTime += i * 0.5F;
+                this.flapTime += f * 0.5F;
             } else {
-                this.flapTime += i;
+                this.flapTime += f;
             }
 
             this.setYRot(Mth.wrapDegrees(this.getYRot()));
@@ -187,9 +218,9 @@
                 this.flapTime = 0.5F;
             } else {
                 if (this.posPointer < 0) {
-                    for(int j = 0; j < this.positions.length; ++j) {
-                        this.positions[j][0] = (double)this.getYRot();
-                        this.positions[j][1] = this.getY();
+                    for (int i = 0; i < this.positions.length; ++i) {
+                        this.positions[i][0] = (double) this.getYRot();
+                        this.positions[i][1] = this.getY();
                     }
                 }
 
@@ -197,120 +228,140 @@
                     this.posPointer = 0;
                 }
 
-                this.positions[this.posPointer][0] = (double)this.getYRot();
+                this.positions[this.posPointer][0] = (double) this.getYRot();
                 this.positions[this.posPointer][1] = this.getY();
+                double d0;
+                double d1;
+                double d2;
+                float f3;
+                float f4;
+                float f5;
+
                 if (this.level.isClientSide) {
                     if (this.lerpSteps > 0) {
-                        double d = this.getX() + (this.lerpX - this.getX()) / (double)this.lerpSteps;
-                        double e = this.getY() + (this.lerpY - this.getY()) / (double)this.lerpSteps;
-                        double k = this.getZ() + (this.lerpZ - this.getZ()) / (double)this.lerpSteps;
-                        double l = Mth.wrapDegrees(this.lerpYRot - (double)this.getYRot());
-                        this.setYRot(this.getYRot() + (float)l / (float)this.lerpSteps);
-                        this.setXRot(this.getXRot() + (float)(this.lerpXRot - (double)this.getXRot()) / (float)this.lerpSteps);
+                        double d3 = this.getX() + (this.lerpX - this.getX()) / (double) this.lerpSteps;
+
+                        d0 = this.getY() + (this.lerpY - this.getY()) / (double) this.lerpSteps;
+                        d1 = this.getZ() + (this.lerpZ - this.getZ()) / (double) this.lerpSteps;
+                        d2 = Mth.wrapDegrees(this.lerpYRot - (double) this.getYRot());
+                        this.setYRot(this.getYRot() + (float) d2 / (float) this.lerpSteps);
+                        this.setXRot(this.getXRot() + (float) (this.lerpXRot - (double) this.getXRot()) / (float) this.lerpSteps);
                         --this.lerpSteps;
-                        this.setPos(d, e, k);
+                        this.setPos(d3, d0, d1);
                         this.setRot(this.getYRot(), this.getXRot());
                     }
 
                     this.phaseManager.getCurrentPhase().doClientTick();
                 } else {
-                    DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
-                    dragonPhaseInstance.doServerTick();
-                    if (this.phaseManager.getCurrentPhase() != dragonPhaseInstance) {
-                        dragonPhaseInstance = this.phaseManager.getCurrentPhase();
-                        dragonPhaseInstance.doServerTick();
+                    DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase();
+
+                    idragoncontroller.doServerTick();
+                    if (this.phaseManager.getCurrentPhase() != idragoncontroller) {
+                        idragoncontroller = this.phaseManager.getCurrentPhase();
+                        idragoncontroller.doServerTick();
                     }
 
-                    Vec3 vec32 = dragonPhaseInstance.getFlyTargetLocation();
-                    if (vec32 != null) {
-                        double m = vec32.x - this.getX();
-                        double n = vec32.y - this.getY();
-                        double o = vec32.z - this.getZ();
-                        double p = m * m + n * n + o * o;
-                        float q = dragonPhaseInstance.getFlySpeed();
-                        double r = Math.sqrt(m * m + o * o);
-                        if (r > 0.0D) {
-                            n = Mth.clamp(n / r, (double)(-q), (double)q);
+                    Vec3 vec3d1 = idragoncontroller.getFlyTargetLocation();
+
+                    if (vec3d1 != null && idragoncontroller.getPhase() != EnderDragonPhase.HOVERING) { // CraftBukkit - Don't move when hovering
+                        d0 = vec3d1.x - this.getX();
+                        d1 = vec3d1.y - this.getY();
+                        d2 = vec3d1.z - this.getZ();
+                        double d4 = d0 * d0 + d1 * d1 + d2 * d2;
+                        float f6 = idragoncontroller.getFlySpeed();
+                        double d5 = Math.sqrt(d0 * d0 + d2 * d2);
+
+                        if (d5 > 0.0D) {
+                            d1 = Mth.clamp(d1 / d5, (double) (-f6), (double) f6);
                         }
 
-                        this.setDeltaMovement(this.getDeltaMovement().add(0.0D, n * 0.01D, 0.0D));
+                        this.setDeltaMovement(this.getDeltaMovement().add(0.0D, d1 * 0.01D, 0.0D));
                         this.setYRot(Mth.wrapDegrees(this.getYRot()));
-                        Vec3 vec33 = vec32.subtract(this.getX(), this.getY(), this.getZ()).normalize();
-                        Vec3 vec34 = (new Vec3((double)Mth.sin(this.getYRot() * ((float)Math.PI / 180F)), this.getDeltaMovement().y, (double)(-Mth.cos(this.getYRot() * ((float)Math.PI / 180F))))).normalize();
-                        float s = Math.max(((float)vec34.dot(vec33) + 0.5F) / 1.5F, 0.0F);
-                        if (Math.abs(m) > (double)1.0E-5F || Math.abs(o) > (double)1.0E-5F) {
-                            float t = Mth.clamp(Mth.wrapDegrees(180.0F - (float)Mth.atan2(m, o) * (180F / (float)Math.PI) - this.getYRot()), -50.0F, 50.0F);
+                        Vec3 vec3d2 = vec3d1.subtract(this.getX(), this.getY(), this.getZ()).normalize();
+                        Vec3 vec3d3 = (new Vec3((double) Mth.sin(this.getYRot() * 0.017453292F), this.getDeltaMovement().y, (double) (-Mth.cos(this.getYRot() * 0.017453292F)))).normalize();
+
+                        f3 = Math.max(((float) vec3d3.dot(vec3d2) + 0.5F) / 1.5F, 0.0F);
+                        if (Math.abs(d0) > 9.999999747378752E-6D || Math.abs(d2) > 9.999999747378752E-6D) {
+                            f4 = Mth.clamp(Mth.wrapDegrees(180.0F - (float) Mth.atan2(d0, d2) * 57.295776F - this.getYRot()), -50.0F, 50.0F);
                             this.yRotA *= 0.8F;
-                            this.yRotA += t * dragonPhaseInstance.getTurnSpeed();
+                            this.yRotA += f4 * idragoncontroller.getTurnSpeed();
                             this.setYRot(this.getYRot() + this.yRotA * 0.1F);
                         }
 
-                        float u = (float)(2.0D / (p + 1.0D));
-                        float v = 0.06F;
-                        this.moveRelative(0.06F * (s * u + (1.0F - u)), new Vec3(0.0D, 0.0D, -1.0D));
+                        f4 = (float) (2.0D / (d4 + 1.0D));
+                        f5 = 0.06F;
+                        this.moveRelative(0.06F * (f3 * f4 + (1.0F - f4)), new Vec3(0.0D, 0.0D, -1.0D));
                         if (this.inWall) {
-                            this.move(MoverType.SELF, this.getDeltaMovement().scale((double)0.8F));
+                            this.move(MoverType.SELF, this.getDeltaMovement().scale(0.800000011920929D));
                         } else {
                             this.move(MoverType.SELF, this.getDeltaMovement());
                         }
 
-                        Vec3 vec35 = this.getDeltaMovement().normalize();
-                        double w = 0.8D + 0.15D * (vec35.dot(vec34) + 1.0D) / 2.0D;
-                        this.setDeltaMovement(this.getDeltaMovement().multiply(w, (double)0.91F, w));
+                        Vec3 vec3d4 = this.getDeltaMovement().normalize();
+                        double d6 = 0.8D + 0.15D * (vec3d4.dot(vec3d3) + 1.0D) / 2.0D;
+
+                        this.setDeltaMovement(this.getDeltaMovement().multiply(d6, 0.9100000262260437D, d6));
                     }
                 }
 
                 this.yBodyRot = this.getYRot();
-                Vec3[] vec3s = new Vec3[this.subEntities.length];
+                Vec3[] avec3d = new Vec3[this.subEntities.length];
 
-                for(int x = 0; x < this.subEntities.length; ++x) {
-                    vec3s[x] = new Vec3(this.subEntities[x].getX(), this.subEntities[x].getY(), this.subEntities[x].getZ());
+                for (int j = 0; j < this.subEntities.length; ++j) {
+                    avec3d[j] = new Vec3(this.subEntities[j].getX(), this.subEntities[j].getY(), this.subEntities[j].getZ());
                 }
 
-                float y = (float)(this.getLatencyPos(5, 1.0F)[1] - this.getLatencyPos(10, 1.0F)[1]) * 10.0F * ((float)Math.PI / 180F);
-                float z = Mth.cos(y);
-                float aa = Mth.sin(y);
-                float ab = this.getYRot() * ((float)Math.PI / 180F);
-                float ac = Mth.sin(ab);
-                float ad = Mth.cos(ab);
-                this.tickPart(this.body, (double)(ac * 0.5F), 0.0D, (double)(-ad * 0.5F));
-                this.tickPart(this.wing1, (double)(ad * 4.5F), 2.0D, (double)(ac * 4.5F));
-                this.tickPart(this.wing2, (double)(ad * -4.5F), 2.0D, (double)(ac * -4.5F));
+                float f7 = (float) (this.getLatencyPos(5, 1.0F)[1] - this.getLatencyPos(10, 1.0F)[1]) * 10.0F * 0.017453292F;
+                float f8 = Mth.cos(f7);
+                float f9 = Mth.sin(f7);
+                float f10 = this.getYRot() * 0.017453292F;
+                float f11 = Mth.sin(f10);
+                float f12 = Mth.cos(f10);
+
+                this.tickPart(this.body, (double) (f11 * 0.5F), 0.0D, (double) (-f12 * 0.5F));
+                this.tickPart(this.wing1, (double) (f12 * 4.5F), 2.0D, (double) (f11 * 4.5F));
+                this.tickPart(this.wing2, (double) (f12 * -4.5F), 2.0D, (double) (f11 * -4.5F));
                 if (!this.level.isClientSide && this.hurtTime == 0) {
-                    this.knockBack(this.level.getEntities(this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
-                    this.knockBack(this.level.getEntities(this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
-                    this.hurt(this.level.getEntities(this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
-                    this.hurt(this.level.getEntities(this, this.neck.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+                    this.knockBack(this.level.getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+                    this.knockBack(this.level.getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+                    this.hurt(this.level.getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+                    this.hurt(this.level.getEntities((Entity) this, this.neck.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
                 }
 
-                float ae = Mth.sin(this.getYRot() * ((float)Math.PI / 180F) - this.yRotA * 0.01F);
-                float af = Mth.cos(this.getYRot() * ((float)Math.PI / 180F) - this.yRotA * 0.01F);
-                float ag = this.getHeadYOffset();
-                this.tickPart(this.head, (double)(ae * 6.5F * z), (double)(ag + aa * 6.5F), (double)(-af * 6.5F * z));
-                this.tickPart(this.neck, (double)(ae * 5.5F * z), (double)(ag + aa * 5.5F), (double)(-af * 5.5F * z));
-                double[] ds = this.getLatencyPos(5, 1.0F);
+                float f13 = Mth.sin(this.getYRot() * 0.017453292F - this.yRotA * 0.01F);
+                float f14 = Mth.cos(this.getYRot() * 0.017453292F - this.yRotA * 0.01F);
+                float f15 = this.getHeadYOffset();
 
-                for(int ah = 0; ah < 3; ++ah) {
-                    EnderDragonPart enderDragonPart = null;
-                    if (ah == 0) {
-                        enderDragonPart = this.tail1;
+                this.tickPart(this.head, (double) (f13 * 6.5F * f8), (double) (f15 + f9 * 6.5F), (double) (-f14 * 6.5F * f8));
+                this.tickPart(this.neck, (double) (f13 * 5.5F * f8), (double) (f15 + f9 * 5.5F), (double) (-f14 * 5.5F * f8));
+                double[] adouble = this.getLatencyPos(5, 1.0F);
+
+                int k;
+
+                for (k = 0; k < 3; ++k) {
+                    EnderDragonPart entitycomplexpart = null;
+
+                    if (k == 0) {
+                        entitycomplexpart = this.tail1;
                     }
 
-                    if (ah == 1) {
-                        enderDragonPart = this.tail2;
+                    if (k == 1) {
+                        entitycomplexpart = this.tail2;
                     }
 
-                    if (ah == 2) {
-                        enderDragonPart = this.tail3;
+                    if (k == 2) {
+                        entitycomplexpart = this.tail3;
                     }
 
-                    double[] es = this.getLatencyPos(12 + ah * 2, 1.0F);
-                    float ai = this.getYRot() * ((float)Math.PI / 180F) + this.rotWrap(es[0] - ds[0]) * ((float)Math.PI / 180F);
-                    float aj = Mth.sin(ai);
-                    float ak = Mth.cos(ai);
-                    float al = 1.5F;
-                    float am = (float)(ah + 1) * 2.0F;
-                    this.tickPart(enderDragonPart, (double)(-(ac * 1.5F + aj * am) * z), es[1] - ds[1] - (double)((am + 1.5F) * aa) + 1.5D, (double)((ad * 1.5F + ak * am) * z));
+                    double[] adouble1 = this.getLatencyPos(12 + k * 2, 1.0F);
+                    float f16 = this.getYRot() * 0.017453292F + this.rotWrap(adouble1[0] - adouble[0]) * 0.017453292F;
+
+                    f3 = Mth.sin(f16);
+                    f4 = Mth.cos(f16);
+                    f5 = 1.5F;
+                    float f17 = (float) (k + 1) * 2.0F;
+
+                    this.tickPart(entitycomplexpart, (double) (-(f11 * 1.5F + f3 * f17) * f8), adouble1[1] - adouble[1] - (double) ((f17 + 1.5F) * f9) + 1.5D, (double) ((f12 * 1.5F + f4 * f17) * f8));
                 }
 
                 if (!this.level.isClientSide) {
@@ -320,13 +371,13 @@
                     }
                 }
 
-                for(int an = 0; an < this.subEntities.length; ++an) {
-                    this.subEntities[an].xo = vec3s[an].x;
-                    this.subEntities[an].yo = vec3s[an].y;
-                    this.subEntities[an].zo = vec3s[an].z;
-                    this.subEntities[an].xOld = vec3s[an].x;
-                    this.subEntities[an].yOld = vec3s[an].y;
-                    this.subEntities[an].zOld = vec3s[an].z;
+                for (k = 0; k < this.subEntities.length; ++k) {
+                    this.subEntities[k].xo = avec3d[k].x;
+                    this.subEntities[k].yo = avec3d[k].y;
+                    this.subEntities[k].zo = avec3d[k].z;
+                    this.subEntities[k].xOld = avec3d[k].x;
+                    this.subEntities[k].yOld = avec3d[k].y;
+                    this.subEntities[k].zOld = avec3d[k].z;
                 }
 
             }
@@ -341,9 +392,10 @@
         if (this.phaseManager.getCurrentPhase().isSitting()) {
             return -1.0F;
         } else {
-            double[] ds = this.getLatencyPos(5, 1.0F);
-            double[] es = this.getLatencyPos(0, 1.0F);
-            return (float)(ds[1] - es[1]);
+            double[] adouble = this.getLatencyPos(5, 1.0F);
+            double[] adouble1 = this.getLatencyPos(0, 1.0F);
+
+            return (float) (adouble[1] - adouble1[1]);
         }
     }
 
@@ -352,39 +404,53 @@
             if (this.nearestCrystal.isRemoved()) {
                 this.nearestCrystal = null;
             } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
-                this.setHealth(this.getHealth() + 1.0F);
+                // CraftBukkit start
+                EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL);
+                this.level.getCraftServer().getPluginManager().callEvent(event);
+
+                if (!event.isCancelled()) {
+                    this.setHealth((float) (this.getHealth() + event.getAmount()));
+                }
+                // CraftBukkit end
             }
         }
 
         if (this.random.nextInt(10) == 0) {
             List<EndCrystal> list = this.level.getEntitiesOfClass(EndCrystal.class, this.getBoundingBox().inflate(32.0D));
-            EndCrystal endCrystal = null;
-            double d = Double.MAX_VALUE;
+            EndCrystal entityendercrystal = null;
+            double d0 = Double.MAX_VALUE;
+            Iterator iterator = list.iterator();
 
-            for(EndCrystal endCrystal2 : list) {
-                double e = endCrystal2.distanceToSqr(this);
-                if (e < d) {
-                    d = e;
-                    endCrystal = endCrystal2;
+            while (iterator.hasNext()) {
+                EndCrystal entityendercrystal1 = (EndCrystal) iterator.next();
+                double d1 = entityendercrystal1.distanceToSqr((Entity) this);
+
+                if (d1 < d0) {
+                    d0 = d1;
+                    entityendercrystal = entityendercrystal1;
                 }
             }
 
-            this.nearestCrystal = endCrystal;
+            this.nearestCrystal = entityendercrystal;
         }
 
     }
 
     private void knockBack(List<Entity> entities) {
-        double d = (this.body.getBoundingBox().minX + this.body.getBoundingBox().maxX) / 2.0D;
-        double e = (this.body.getBoundingBox().minZ + this.body.getBoundingBox().maxZ) / 2.0D;
+        double d0 = (this.body.getBoundingBox().minX + this.body.getBoundingBox().maxX) / 2.0D;
+        double d1 = (this.body.getBoundingBox().minZ + this.body.getBoundingBox().maxZ) / 2.0D;
+        Iterator iterator = entities.iterator();
+
+        while (iterator.hasNext()) {
+            Entity entity = (Entity) iterator.next();
 
-        for(Entity entity : entities) {
             if (entity instanceof LivingEntity) {
-                double f = entity.getX() - d;
-                double g = entity.getZ() - e;
-                double h = Math.max(f * f + g * g, 0.1D);
-                entity.push(f / h * 4.0D, (double)0.2F, g / h * 4.0D);
-                if (!this.phaseManager.getCurrentPhase().isSitting() && ((LivingEntity)entity).getLastHurtByMobTimestamp() < entity.tickCount - 2) {
+                double d2 = entity.getX() - d0;
+                double d3 = entity.getZ() - d1;
+                double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D);
+
+                entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D);
+                if (!this.phaseManager.getCurrentPhase().isSitting() && ((LivingEntity) entity).getLastHurtByMobTimestamp() < entity.tickCount - 2) {
                     entity.hurt(DamageSource.mobAttack(this), 5.0F);
                     this.doEnchantDamageEffects(this, entity);
                 }
@@ -394,7 +460,11 @@
     }
 
     private void hurt(List<Entity> entities) {
-        for(Entity entity : entities) {
+        Iterator iterator = entities.iterator();
+
+        while (iterator.hasNext()) {
+            Entity entity = (Entity) iterator.next();
+
             if (entity instanceof LivingEntity) {
                 entity.hurt(DamageSource.mobAttack(this), 10.0F);
                 this.doEnchantDamageEffects(this, entity);
@@ -404,7 +474,7 @@
     }
 
     private float rotWrap(double yawDegrees) {
-        return (float)Mth.wrapDegrees(yawDegrees);
+        return (float) Mth.wrapDegrees(yawDegrees);
     }
 
     private boolean checkWalls(AABB box) {
@@ -412,33 +482,92 @@
         int j = Mth.floor(box.minY);
         int k = Mth.floor(box.minZ);
         int l = Mth.floor(box.maxX);
-        int m = Mth.floor(box.maxY);
-        int n = Mth.floor(box.maxZ);
-        boolean bl = false;
-        boolean bl2 = false;
+        int i1 = Mth.floor(box.maxY);
+        int j1 = Mth.floor(box.maxZ);
+        boolean flag = false;
+        boolean flag1 = false;
+        // CraftBukkit start - Create a list to hold all the destroyed blocks
+        List<org.bukkit.block.Block> destroyedBlocks = new java.util.ArrayList<org.bukkit.block.Block>();
+        // CraftBukkit end
 
-        for(int o = i; o <= l; ++o) {
-            for(int p = j; p <= m; ++p) {
-                for(int q = k; q <= n; ++q) {
-                    BlockPos blockPos = new BlockPos(o, p, q);
-                    BlockState blockState = this.level.getBlockState(blockPos);
-                    if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) {
-                        if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) {
-                            bl2 = this.level.removeBlock(blockPos, false) || bl2;
+        for (int k1 = i; k1 <= l; ++k1) {
+            for (int l1 = j; l1 <= i1; ++l1) {
+                for (int i2 = k; i2 <= j1; ++i2) {
+                    BlockPos blockposition = new BlockPos(k1, l1, i2);
+                    BlockState iblockdata = this.level.getBlockState(blockposition);
+
+                    if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) {
+                        if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) {
+                            // CraftBukkit start - Add blocks to list rather than destroying them
+                            // flag1 = this.level.removeBlock(blockposition, false) || flag1;
+                            flag1 = true;
+                            destroyedBlocks.add(CraftBlock.at(level, blockposition));
+                            // CraftBukkit end
                         } else {
-                            bl = true;
+                            flag = true;
                         }
                     }
                 }
             }
         }
 
-        if (bl2) {
-            BlockPos blockPos2 = new BlockPos(i + this.random.nextInt(l - i + 1), j + this.random.nextInt(m - j + 1), k + this.random.nextInt(n - k + 1));
-            this.level.levelEvent(2008, blockPos2, 0);
+        // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks
+        // SPIGOT-4882: don't fire event if nothing hit
+        if (!flag1) {
+            return flag;
         }
 
-        return bl;
+        org.bukkit.entity.Entity bukkitEntity = this.getBukkitEntity();
+        EntityExplodeEvent event = new EntityExplodeEvent(bukkitEntity, bukkitEntity.getLocation(), destroyedBlocks, 0F);
+        bukkitEntity.getServer().getPluginManager().callEvent(event);
+        if (event.isCancelled()) {
+            // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down.
+            // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled.
+            return flag;
+        } else if (event.getYield() == 0F) {
+            // Yield zero ==> no drops
+            for (org.bukkit.block.Block block : event.blockList()) {
+                this.level.removeBlock(new BlockPos(block.getX(), block.getY(), block.getZ()), false);
+            }
+        } else {
+            for (org.bukkit.block.Block block : event.blockList()) {
+                org.bukkit.Material blockId = block.getType();
+                if (blockId.isAir()) {
+                    continue;
+                }
+
+                CraftBlock craftBlock = ((CraftBlock) block);
+                BlockPos blockposition = craftBlock.getPosition();
+
+                Block nmsBlock = craftBlock.getNMS().getBlock();
+                if (nmsBlock.dropFromExplosion(explosionSource)) {
+                    BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level.getBlockEntity(blockposition) : null;
+                    LootContext.Builder loottableinfo_builder = (new LootContext.Builder((ServerLevel) this.level)).withRandom(this.level.random).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity);
+
+                    craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> {
+                        Block.popResource(level, blockposition, itemstack);
+                    });
+                    craftBlock.getNMS().spawnAfterBreak((ServerLevel) level, blockposition, ItemStack.EMPTY, false);
+                }
+                // Paper start - TNTPrimeEvent
+                org.bukkit.block.Block tntBlock = level.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
+                if(!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getSourceMob().getBukkitEntity()).callEvent())
+                    continue;
+                // Paper end
+                nmsBlock.wasExploded(level, blockposition, explosionSource);
+
+                this.level.removeBlock(blockposition, false);
+            }
+        }
+        // CraftBukkit end
+
+        if (flag1) {
+            BlockPos blockposition1 = new BlockPos(i + this.random.nextInt(l - i + 1), j + this.random.nextInt(i1 - j + 1), k + this.random.nextInt(j1 - k + 1));
+
+            this.level.levelEvent(2008, blockposition1, 0);
+        }
+
+        return flag;
     }
 
     public boolean hurt(EnderDragonPart part, DamageSource source, float amount) {
@@ -454,7 +583,8 @@
                 return false;
             } else {
                 if (source.getEntity() instanceof Player || source.isExplosion()) {
-                    float f = this.getHealth();
+                    float f1 = this.getHealth();
+
                     this.reallyHurt(source, amount);
                     if (this.isDeadOrDying() && !this.phaseManager.getCurrentPhase().isSitting()) {
                         this.setHealth(1.0F);
@@ -462,7 +592,7 @@
                     }
 
                     if (this.phaseManager.getCurrentPhase().isSitting()) {
-                        this.sittingDamageReceived = this.sittingDamageReceived + f - this.getHealth();
+                        this.sittingDamageReceived = this.sittingDamageReceived + f1 - this.getHealth();
                         if (this.sittingDamageReceived > 0.25F * this.getMaxHealth()) {
                             this.sittingDamageReceived = 0.0F;
                             this.phaseManager.setPhase(EnderDragonPhase.TAKEOFF);
@@ -495,6 +625,21 @@
 
     }
 
+    // CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time.
+    @Override
+    public int getExpReward() {
+        // CraftBukkit - Moved from #tickDeath method
+        boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
+        short short0 = 500;
+
+        if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
+            short0 = 12000;
+        }
+
+        return flag ? short0 : 0;
+    }
+    // CraftBukkit end
+
     @Override
     protected void tickDeath() {
         if (this.dragonFight != null) {
@@ -504,33 +649,58 @@
         ++this.dragonDeathTime;
         if (this.dragonDeathTime >= 180 && this.dragonDeathTime <= 200) {
             float f = (this.random.nextFloat() - 0.5F) * 8.0F;
-            float g = (this.random.nextFloat() - 0.5F) * 4.0F;
-            float h = (this.random.nextFloat() - 0.5F) * 8.0F;
-            this.level.addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + (double)f, this.getY() + 2.0D + (double)g, this.getZ() + (double)h, 0.0D, 0.0D, 0.0D);
+            float f1 = (this.random.nextFloat() - 0.5F) * 4.0F;
+            float f2 = (this.random.nextFloat() - 0.5F) * 8.0F;
+
+            this.level.addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + (double) f, this.getY() + 2.0D + (double) f1, this.getZ() + (double) f2, 0.0D, 0.0D, 0.0D);
         }
 
-        boolean bl = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
-        int i = 500;
+        // CraftBukkit start - SPIGOT-2420: Moved up to #getExpReward method
+        /*
+        boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
+        short short0 = 500;
+
         if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
-            i = 12000;
+            short0 = 12000;
         }
+        */
+        int short0 = expToDrop;
+        // CraftBukkit end
 
         if (this.level instanceof ServerLevel) {
-            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && bl) {
-                ExperienceOrb.award((ServerLevel)this.level, this.position(), Mth.floor((float)i * 0.08F));
+            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) {  // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
+                ExperienceOrb.award((ServerLevel) this.level, this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
             }
 
             if (this.dragonDeathTime == 1 && !this.isSilent()) {
-                this.level.globalLevelEvent(1028, this.blockPosition(), 0);
+                // CraftBukkit start - Use relative location for far away sounds
+                // this.world.b(1028, this.getChunkCoordinates(), 0);
+                //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
+                for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>) ((ServerLevel)level).players()) {
+                    final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader
+                    double deltaX = this.getX() - player.getX();
+                    double deltaZ = this.getZ() - player.getZ();
+                    double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
+                    if ( level.spigotConfig.dragonDeathSoundRadius > 0 && distanceSquared > level.spigotConfig.dragonDeathSoundRadius * level.spigotConfig.dragonDeathSoundRadius ) continue; // Spigot
+                    if (distanceSquared > viewDistance * viewDistance) {
+                        double deltaLength = Math.sqrt(distanceSquared);
+                        double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
+                        double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
+                    } else {
+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true));
+                    }
+                }
+                // CraftBukkit end
             }
         }
 
-        this.move(MoverType.SELF, new Vec3(0.0D, (double)0.1F, 0.0D));
+        this.move(MoverType.SELF, new Vec3(0.0D, 0.10000000149011612D, 0.0D));
         this.setYRot(this.getYRot() + 20.0F);
         this.yBodyRot = this.getYRot();
         if (this.dragonDeathTime == 200 && this.level instanceof ServerLevel) {
-            if (bl) {
-                ExperienceOrb.award((ServerLevel)this.level, this.position(), Mth.floor((float)i * 0.2F));
+            if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
+                ExperienceOrb.award((ServerLevel) this.level, this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
             }
 
             if (this.dragonFight != null) {
@@ -545,26 +715,32 @@
 
     public int findClosestNode() {
         if (this.nodes[0] == null) {
-            for(int i = 0; i < 24; ++i) {
+            for (int i = 0; i < 24; ++i) {
                 int j = 5;
+                int k;
                 int l;
-                int m;
+
                 if (i < 12) {
-                    l = Mth.floor(60.0F * Mth.cos(2.0F * (-(float)Math.PI + 0.2617994F * (float)i)));
-                    m = Mth.floor(60.0F * Mth.sin(2.0F * (-(float)Math.PI + 0.2617994F * (float)i)));
-                } else if (i < 20) {
-                    int k = i - 12;
-                    l = Mth.floor(40.0F * Mth.cos(2.0F * (-(float)Math.PI + ((float)Math.PI / 8F) * (float)k)));
-                    m = Mth.floor(40.0F * Mth.sin(2.0F * (-(float)Math.PI + ((float)Math.PI / 8F) * (float)k)));
-                    j += 10;
+                    k = Mth.floor(60.0F * Mth.cos(2.0F * (-3.1415927F + 0.2617994F * (float) i)));
+                    l = Mth.floor(60.0F * Mth.sin(2.0F * (-3.1415927F + 0.2617994F * (float) i)));
                 } else {
-                    int var7 = i - 20;
-                    l = Mth.floor(20.0F * Mth.cos(2.0F * (-(float)Math.PI + ((float)Math.PI / 4F) * (float)var7)));
-                    m = Mth.floor(20.0F * Mth.sin(2.0F * (-(float)Math.PI + ((float)Math.PI / 4F) * (float)var7)));
+                    int i1;
+
+                    if (i < 20) {
+                        i1 = i - 12;
+                        k = Mth.floor(40.0F * Mth.cos(2.0F * (-3.1415927F + 0.3926991F * (float) i1)));
+                        l = Mth.floor(40.0F * Mth.sin(2.0F * (-3.1415927F + 0.3926991F * (float) i1)));
+                        j += 10;
+                    } else {
+                        i1 = i - 20;
+                        k = Mth.floor(20.0F * Mth.cos(2.0F * (-3.1415927F + 0.7853982F * (float) i1)));
+                        l = Mth.floor(20.0F * Mth.sin(2.0F * (-3.1415927F + 0.7853982F * (float) i1)));
+                    }
                 }
 
-                int r = Math.max(this.level.getSeaLevel() + 10, this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(l, 0, m)).getY() + j);
-                this.nodes[i] = new Node(l, r, m);
+                int j1 = Math.max(this.level.getSeaLevel() + 10, this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(k, 0, l)).getY() + j);
+
+                this.nodes[i] = new Node(k, j1, l);
             }
 
             this.nodeAdjacency[0] = 6146;
@@ -599,18 +775,20 @@
     public int findClosestNode(double x, double y, double z) {
         float f = 10000.0F;
         int i = 0;
-        Node node = new Node(Mth.floor(x), Mth.floor(y), Mth.floor(z));
-        int j = 0;
+        Node pathpoint = new Node(Mth.floor(x), Mth.floor(y), Mth.floor(z));
+        byte b0 = 0;
+
         if (this.dragonFight == null || this.dragonFight.getCrystalsAlive() == 0) {
-            j = 12;
+            b0 = 12;
         }
 
-        for(int k = j; k < 24; ++k) {
-            if (this.nodes[k] != null) {
-                float g = this.nodes[k].distanceToSqr(node);
-                if (g < f) {
-                    f = g;
-                    i = k;
+        for (int j = b0; j < 24; ++j) {
+            if (this.nodes[j] != null) {
+                float f1 = this.nodes[j].distanceToSqr(pathpoint);
+
+                if (f1 < f) {
+                    f = f1;
+                    i = j;
                 }
             }
         }
@@ -620,96 +798,118 @@
 
     @Nullable
     public Path findPath(int from, int to, @Nullable Node pathNode) {
-        for(int i = 0; i < 24; ++i) {
-            Node node = this.nodes[i];
-            node.closed = false;
-            node.f = 0.0F;
-            node.g = 0.0F;
-            node.h = 0.0F;
-            node.cameFrom = null;
-            node.heapIdx = -1;
+        Node pathpoint1;
+
+        for (int k = 0; k < 24; ++k) {
+            pathpoint1 = this.nodes[k];
+            pathpoint1.closed = false;
+            pathpoint1.h = 0.0F;
+            pathpoint1.f = 0.0F;
+            pathpoint1.h = 0.0F;
+            pathpoint1.cameFrom = null;
+            pathpoint1.heapIdx = -1;
         }
 
-        Node node2 = this.nodes[from];
-        Node node3 = this.nodes[to];
-        node2.g = 0.0F;
-        node2.h = node2.distanceTo(node3);
-        node2.f = node2.h;
+        Node pathpoint2 = this.nodes[from];
+
+        pathpoint1 = this.nodes[to];
+        pathpoint2.f = 0.0F;
+        pathpoint2.h = pathpoint2.distanceTo(pathpoint1);
+        pathpoint2.h = pathpoint2.h;
         this.openSet.clear();
-        this.openSet.insert(node2);
-        Node node4 = node2;
-        int j = 0;
+        this.openSet.insert(pathpoint2);
+        Node pathpoint3 = pathpoint2;
+        byte b0 = 0;
+
         if (this.dragonFight == null || this.dragonFight.getCrystalsAlive() == 0) {
-            j = 12;
+            b0 = 12;
         }
 
-        while(!this.openSet.isEmpty()) {
-            Node node5 = this.openSet.pop();
-            if (node5.equals(node3)) {
+        label70:
+        while (!this.openSet.isEmpty()) {
+            Node pathpoint4 = this.openSet.pop();
+
+            if (pathpoint4.equals(pathpoint1)) {
                 if (pathNode != null) {
-                    pathNode.cameFrom = node3;
-                    node3 = pathNode;
+                    pathNode.cameFrom = pathpoint1;
+                    pathpoint1 = pathNode;
                 }
 
-                return this.reconstructPath(node2, node3);
+                return this.reconstructPath(pathpoint2, pathpoint1);
             }
 
-            if (node5.distanceTo(node3) < node4.distanceTo(node3)) {
-                node4 = node5;
+            if (pathpoint4.distanceTo(pathpoint1) < pathpoint3.distanceTo(pathpoint1)) {
+                pathpoint3 = pathpoint4;
             }
 
-            node5.closed = true;
-            int k = 0;
+            pathpoint4.closed = true;
+            int l = 0;
+            int i1 = 0;
 
-            for(int l = 0; l < 24; ++l) {
-                if (this.nodes[l] == node5) {
-                    k = l;
-                    break;
+            while (true) {
+                if (i1 < 24) {
+                    if (this.nodes[i1] != pathpoint4) {
+                        ++i1;
+                        continue;
+                    }
+
+                    l = i1;
                 }
-            }
 
-            for(int m = j; m < 24; ++m) {
-                if ((this.nodeAdjacency[k] & 1 << m) > 0) {
-                    Node node6 = this.nodes[m];
-                    if (!node6.closed) {
-                        float f = node5.g + node5.distanceTo(node6);
-                        if (!node6.inOpenSet() || f < node6.g) {
-                            node6.cameFrom = node5;
-                            node6.g = f;
-                            node6.h = node6.distanceTo(node3);
-                            if (node6.inOpenSet()) {
-                                this.openSet.changeCost(node6, node6.g + node6.h);
-                            } else {
-                                node6.f = node6.g + node6.h;
-                                this.openSet.insert(node6);
+                i1 = b0;
+
+                while (true) {
+                    if (i1 >= 24) {
+                        continue label70;
+                    }
+
+                    if ((this.nodeAdjacency[l] & 1 << i1) > 0) {
+                        Node pathpoint5 = this.nodes[i1];
+
+                        if (!pathpoint5.closed) {
+                            float f = pathpoint4.f + pathpoint4.distanceTo(pathpoint5);
+
+                            if (!pathpoint5.inOpenSet() || f < pathpoint5.f) {
+                                pathpoint5.cameFrom = pathpoint4;
+                                pathpoint5.f = f;
+                                pathpoint5.h = pathpoint5.distanceTo(pathpoint1);
+                                if (pathpoint5.inOpenSet()) {
+                                    this.openSet.changeCost(pathpoint5, pathpoint5.f + pathpoint5.h);
+                                } else {
+                                    pathpoint5.h = pathpoint5.f + pathpoint5.h;
+                                    this.openSet.insert(pathpoint5);
+                                }
                             }
                         }
                     }
+
+                    ++i1;
                 }
             }
         }
 
-        if (node4 == node2) {
+        if (pathpoint3 == pathpoint2) {
             return null;
         } else {
-            LOGGER.debug("Failed to find path from {} to {}", from, to);
+            EnderDragon.LOGGER.debug("Failed to find path from {} to {}", from, to);
             if (pathNode != null) {
-                pathNode.cameFrom = node4;
-                node4 = pathNode;
+                pathNode.cameFrom = pathpoint3;
+                pathpoint3 = pathNode;
             }
 
-            return this.reconstructPath(node2, node4);
+            return this.reconstructPath(pathpoint2, pathpoint3);
         }
     }
 
     private Path reconstructPath(Node unused, Node node) {
         List<Node> list = Lists.newArrayList();
-        Node node2 = node;
+        Node pathpoint2 = node;
+
         list.add(0, node);
 
-        while(node2.cameFrom != null) {
-            node2 = node2.cameFrom;
-            list.add(0, node2);
+        while (pathpoint2.cameFrom != null) {
+            pathpoint2 = pathpoint2.cameFrom;
+            list.add(0, pathpoint2);
         }
 
         return new Path(list, new BlockPos(node.x, node.y, node.z), true);
@@ -720,6 +920,7 @@
         super.addAdditionalSaveData(nbt);
         nbt.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
         nbt.putInt("DragonDeathTime", this.dragonDeathTime);
+        nbt.putInt("Bukkit.expToDrop", expToDrop); // CraftBukkit - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
     }
 
     @Override
@@ -733,11 +934,15 @@
             this.dragonDeathTime = nbt.getInt("DragonDeathTime");
         }
 
+        // CraftBukkit start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
+        if (nbt.contains("Bukkit.expToDrop")) {
+            this.expToDrop = nbt.getInt("Bukkit.expToDrop");
+        }
+        // CraftBukkit end
     }
 
     @Override
-    public void checkDespawn() {
-    }
+    public void checkDespawn() {}
 
     public EnderDragonPart[] getSubEntities() {
         return this.subEntities;
@@ -769,73 +974,81 @@
     }
 
     public float getHeadPartYOffset(int segmentOffset, double[] segment1, double[] segment2) {
-        DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
-        EnderDragonPhase<? extends DragonPhaseInstance> enderDragonPhase = dragonPhaseInstance.getPhase();
-        double f;
-        if (enderDragonPhase != EnderDragonPhase.LANDING && enderDragonPhase != EnderDragonPhase.TAKEOFF) {
-            if (dragonPhaseInstance.isSitting()) {
-                f = (double)segmentOffset;
+        DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase();
+        EnderDragonPhase<? extends DragonPhaseInstance> dragoncontrollerphase = idragoncontroller.getPhase();
+        double d0;
+
+        if (dragoncontrollerphase != EnderDragonPhase.LANDING && dragoncontrollerphase != EnderDragonPhase.TAKEOFF) {
+            if (idragoncontroller.isSitting()) {
+                d0 = (double) segmentOffset;
             } else if (segmentOffset == 6) {
-                f = 0.0D;
+                d0 = 0.0D;
             } else {
-                f = segment2[1] - segment1[1];
+                d0 = segment2[1] - segment1[1];
             }
         } else {
-            BlockPos blockPos = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION);
-            double d = Math.max(Math.sqrt(blockPos.distToCenterSqr(this.position())) / 4.0D, 1.0D);
-            f = (double)segmentOffset / d;
+            BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - use custom podium
+            double d1 = Math.max(Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0D, 1.0D);
+
+            d0 = (double) segmentOffset / d1;
         }
 
-        return (float)f;
+        return (float) d0;
     }
 
     public Vec3 getHeadLookVector(float tickDelta) {
-        DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
-        EnderDragonPhase<? extends DragonPhaseInstance> enderDragonPhase = dragonPhaseInstance.getPhase();
-        Vec3 vec32;
-        if (enderDragonPhase != EnderDragonPhase.LANDING && enderDragonPhase != EnderDragonPhase.TAKEOFF) {
-            if (dragonPhaseInstance.isSitting()) {
-                float j = this.getXRot();
-                float k = 1.5F;
+        DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase();
+        EnderDragonPhase<? extends DragonPhaseInstance> dragoncontrollerphase = idragoncontroller.getPhase();
+        float f1;
+        Vec3 vec3d;
+
+        if (dragoncontrollerphase != EnderDragonPhase.LANDING && dragoncontrollerphase != EnderDragonPhase.TAKEOFF) {
+            if (idragoncontroller.isSitting()) {
+                float f2 = this.getXRot();
+
+                f1 = 1.5F;
                 this.setXRot(-45.0F);
-                vec32 = this.getViewVector(tickDelta);
-                this.setXRot(j);
+                vec3d = this.getViewVector(tickDelta);
+                this.setXRot(f2);
             } else {
-                vec32 = this.getViewVector(tickDelta);
+                vec3d = this.getViewVector(tickDelta);
             }
         } else {
-            BlockPos blockPos = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION);
-            float f = Math.max((float)Math.sqrt(blockPos.distToCenterSqr(this.position())) / 4.0F, 1.0F);
-            float g = 6.0F / f;
-            float h = this.getXRot();
-            float i = 1.5F;
-            this.setXRot(-g * 1.5F * 5.0F);
-            vec32 = this.getViewVector(tickDelta);
-            this.setXRot(h);
+            BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - use custom podium
+
+            f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F);
+            float f3 = 6.0F / f1;
+            float f4 = this.getXRot();
+            float f5 = 1.5F;
+
+            this.setXRot(-f3 * 1.5F * 5.0F);
+            vec3d = this.getViewVector(tickDelta);
+            this.setXRot(f4);
         }
 
-        return vec32;
+        return vec3d;
     }
 
     public void onCrystalDestroyed(EndCrystal crystal, BlockPos pos, DamageSource source) {
-        Player player;
+        Player entityhuman;
+
         if (source.getEntity() instanceof Player) {
-            player = (Player)source.getEntity();
+            entityhuman = (Player) source.getEntity();
         } else {
-            player = this.level.getNearestPlayer(CRYSTAL_DESTROY_TARGETING, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ());
+            entityhuman = this.level.getNearestPlayer(EnderDragon.CRYSTAL_DESTROY_TARGETING, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ());
         }
 
         if (crystal == this.nearestCrystal) {
-            this.hurt(this.head, DamageSource.explosion(player), 10.0F);
+            this.hurt(this.head, DamageSource.explosion((LivingEntity) entityhuman), 10.0F);
         }
 
-        this.phaseManager.getCurrentPhase().onCrystalDestroyed(crystal, pos, source, player);
+        this.phaseManager.getCurrentPhase().onCrystalDestroyed(crystal, pos, source, entityhuman);
     }
 
     @Override
     public void onSyncedDataUpdated(EntityDataAccessor<?> data) {
-        if (DATA_PHASE.equals(data) && this.level.isClientSide) {
-            this.phaseManager.setPhase(EnderDragonPhase.getById(this.getEntityData().get(DATA_PHASE)));
+        if (EnderDragon.DATA_PHASE.equals(data) && this.level.isClientSide) {
+            this.phaseManager.setPhase(EnderDragonPhase.getById((Integer) this.getEntityData().get(EnderDragon.DATA_PHASE)));
         }
 
         super.onSyncedDataUpdated(data);
@@ -868,10 +1081,10 @@
     @Override
     public void recreateFromPacket(ClientboundAddEntityPacket packet) {
         super.recreateFromPacket(packet);
-        EnderDragonPart[] enderDragonParts = this.getSubEntities();
+        EnderDragonPart[] aentitycomplexpart = this.getSubEntities();
 
-        for(int i = 0; i < enderDragonParts.length; ++i) {
-            enderDragonParts[i].setId(i + packet.getId());
+        for (int i = 0; i < aentitycomplexpart.length; ++i) {
+            aentitycomplexpart[i].setId(i + packet.getId());
         }
 
     }
