--- 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;
@@ -38,22 +39,36 @@
 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 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 net.minecraft.world.level.storage.loot.LootParams;
+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;
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.entity.EntityRegainHealthEvent;
+import org.bukkit.event.entity.EntityRemoveEvent;
+// 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.0);
+    private static final TargetingConditions CRYSTAL_DESTROY_TARGETING = TargetingConditions.forCombat().range(64.0D);
     private static final int GROWL_INTERVAL_MIN = 200;
     private static final int GROWL_INTERVAL_MAX = 400;
     private static final float SITTING_ALLOWED_DAMAGE_PERCENTAGE = 0.25F;
@@ -79,16 +94,26 @@
     public EndCrystal nearestCrystal;
     @Nullable
     private EndDragonFight dragonFight;
-    private BlockPos fightOrigin = BlockPos.ZERO;
+    private BlockPos fightOrigin;
     private final EnderDragonPhaseManager phaseManager;
-    private int growlTime = 100;
+    private int growlTime;
     private float sittingDamageReceived;
-    private final Node[] nodes = new Node[24];
-    private final int[] nodeAdjacency = new int[24];
-    private final BinaryHeap openSet = new BinaryHeap();
+    private final Node[] nodes;
+    private final int[] nodeAdjacency;
+    private final BinaryHeap openSet;
+    private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource()
+    // Paper start - Allow changing the EnderDragon podium
+    @Nullable
+    private BlockPos podium;
+    // Paper end - Allow changing the EnderDragon podium
 
-    public EnderDragon(EntityType<? extends EnderDragon> entityType, Level world) {
+    public EnderDragon(EntityType<? extends EnderDragon> entitytypes, Level world) {
         super(EntityType.ENDER_DRAGON, world);
+        this.fightOrigin = BlockPos.ZERO;
+        this.growlTime = 100;
+        this.nodes = new Node[24];
+        this.nodeAdjacency = new int[24];
+        this.openSet = new BinaryHeap();
         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);
@@ -102,6 +127,7 @@
         this.noPhysics = true;
         this.noCulling = true;
         this.phaseManager = new EnderDragonPhaseManager(this);
+        this.explosionSource = new Explosion(world, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE); // CraftBukkit
     }
 
     public void setDragonFight(EndDragonFight fight) {
@@ -117,37 +143,42 @@
     }
 
     public static AttributeSupplier.Builder createAttributes() {
-        return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0);
+        return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D);
+    }
+
+    // Paper start - Allow changing the EnderDragon podium
+    public BlockPos getPodium() {
+        if (this.podium == null) {
+            return EndPodiumFeature.getLocation(this.getFightOrigin());
+        }
+        return this.podium;
     }
 
+    public void setPodium(@Nullable BlockPos blockPos) {
+        this.podium = blockPos;
+    }
+    // Paper end - Allow changing the EnderDragon podium
+
     @Override
     public boolean isFlapping() {
-        float f = Mth.cos(this.flapTime * (float) (Math.PI * 2));
-        float g = Mth.cos(this.oFlapTime * (float) (Math.PI * 2));
-        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
     public void onFlap() {
         if (this.level().isClientSide && !this.isSilent()) {
-            this.level()
-                .playLocalSound(
-                    this.getX(),
-                    this.getY(),
-                    this.getZ(),
-                    SoundEvents.ENDER_DRAGON_FLAP,
-                    this.getSoundSource(),
-                    5.0F,
-                    0.8F + this.random.nextFloat() * 0.3F,
-                    false
-                );
+            this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.ENDER_DRAGON_FLAP, this.getSoundSource(), 5.0F, 0.8F + this.random.nextFloat() * 0.3F, false);
         }
+
     }
 
     @Override
     protected void defineSynchedData(SynchedEntityData.Builder builder) {
         super.defineSynchedData(builder);
-        builder.define(DATA_PHASE, EnderDragonPhase.HOVERING.getId());
+        builder.define(EnderDragon.DATA_PHASE, EnderDragonPhase.HOVERING.getId());
     }
 
     public double[] getLatencyPos(int segmentNumber, float tickDelta) {
@@ -156,17 +187,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
@@ -175,45 +207,46 @@
         if (this.level().isClientSide) {
             this.setHealth(this.getHealth());
             if (!this.isSilent() && !this.phaseManager.getCurrentPhase().isSitting() && --this.growlTime < 0) {
-                this.level()
-                    .playLocalSound(
-                        this.getX(),
-                        this.getY(),
-                        this.getZ(),
-                        SoundEvents.ENDER_DRAGON_GROWL,
-                        this.getSoundSource(),
-                        2.5F,
-                        0.8F + this.random.nextFloat() * 0.3F,
-                        false
-                    );
+                this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.ENDER_DRAGON_GROWL, this.getSoundSource(), 2.5F, 0.8F + this.random.nextFloat() * 0.3F, false);
                 this.growlTime = 200 + this.random.nextInt(200);
             }
         }
 
-        if (this.dragonFight == null && this.level() instanceof ServerLevel serverLevel) {
-            EndDragonFight endDragonFight = serverLevel.getDragonFight();
-            if (endDragonFight != null && this.getUUID().equals(endDragonFight.getDragonUUID())) {
-                this.dragonFight = endDragonFight;
+        if (this.dragonFight == null) {
+            Level world = this.level();
+
+            if (world instanceof ServerLevel) {
+                ServerLevel worldserver = (ServerLevel) world;
+                EndDragonFight enderdragonbattle = worldserver.getDragonFight();
+
+                if (enderdragonbattle != null && this.getUUID().equals(enderdragonbattle.getDragonUUID())) {
+                    this.dragonFight = enderdragonbattle;
+                }
             }
         }
 
         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.0 + (double)g, this.getZ() + (double)h, 0.0, 0.0, 0.0);
+            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.0, 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()));
@@ -221,9 +254,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();
                     }
                 }
 
@@ -231,148 +264,147 @@
                     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();
+                float f3;
+                float f4;
+                float f5;
+
                 if (this.level().isClientSide) {
                     if (this.lerpSteps > 0) {
                         this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, this.lerpYRot, this.lerpXRot);
-                        this.lerpSteps--;
+                        --this.lerpSteps;
                     }
 
                     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 d = vec32.x - this.getX();
-                        double e = vec32.y - this.getY();
-                        double k = vec32.z - this.getZ();
-                        double l = d * d + e * e + k * k;
-                        float m = dragonPhaseInstance.getFlySpeed();
-                        double n = Math.sqrt(d * d + k * k);
-                        if (n > 0.0) {
-                            e = Mth.clamp(e / n, (double)(-m), (double)m);
+                    Vec3 vec3d1 = idragoncontroller.getFlyTargetLocation();
+
+                    if (vec3d1 != null && idragoncontroller.getPhase() != EnderDragonPhase.HOVERING) { // CraftBukkit - Don't move when hovering
+                        double d0 = vec3d1.x - this.getX();
+                        double d1 = vec3d1.y - this.getY();
+                        double d2 = vec3d1.z - this.getZ();
+                        double d3 = d0 * d0 + d1 * d1 + d2 * d2;
+                        float f6 = idragoncontroller.getFlySpeed();
+                        double d4 = Math.sqrt(d0 * d0 + d2 * d2);
+
+                        if (d4 > 0.0D) {
+                            d1 = Mth.clamp(d1 / d4, (double) (-f6), (double) f6);
                         }
 
-                        this.setDeltaMovement(this.getDeltaMovement().add(0.0, e * 0.01, 0.0));
+                        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 / 180.0)),
-                                this.getDeltaMovement().y,
-                                (double)(-Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)))
-                            )
-                            .normalize();
-                        float o = Math.max(((float)vec34.dot(vec33) + 0.5F) / 1.5F, 0.0F);
-                        if (Math.abs(d) > 1.0E-5F || Math.abs(k) > 1.0E-5F) {
-                            float p = Mth.clamp(Mth.wrapDegrees(180.0F - (float)Mth.atan2(d, k) * (180.0F / (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 = this.yRotA + p * dragonPhaseInstance.getTurnSpeed();
+                            this.yRotA += f4 * idragoncontroller.getTurnSpeed();
                             this.setYRot(this.getYRot() + this.yRotA * 0.1F);
                         }
 
-                        float q = (float)(2.0 / (l + 1.0));
-                        float r = 0.06F;
-                        this.moveRelative(0.06F * (o * q + (1.0F - q)), new Vec3(0.0, 0.0, -1.0));
+                        f4 = (float) (2.0D / (d3 + 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(0.8F));
+                            this.move(MoverType.SELF, this.getDeltaMovement().scale(0.800000011920929D));
                         } else {
                             this.move(MoverType.SELF, this.getDeltaMovement());
                         }
 
-                        Vec3 vec35 = this.getDeltaMovement().normalize();
-                        double s = 0.8 + 0.15 * (vec35.dot(vec34) + 1.0) / 2.0;
-                        this.setDeltaMovement(this.getDeltaMovement().multiply(s, 0.91F, s));
+                        Vec3 vec3d4 = this.getDeltaMovement().normalize();
+                        double d5 = 0.8D + 0.15D * (vec3d4.dot(vec3d3) + 1.0D) / 2.0D;
+
+                        this.setDeltaMovement(this.getDeltaMovement().multiply(d5, 0.9100000262260437D, d5));
                     }
                 }
 
                 this.yBodyRot = this.getYRot();
-                Vec3[] vec3s = new Vec3[this.subEntities.length];
+                Vec3[] avec3d = new Vec3[this.subEntities.length];
 
-                for (int t = 0; t < this.subEntities.length; t++) {
-                    vec3s[t] = new Vec3(this.subEntities[t].getX(), this.subEntities[t].getY(), this.subEntities[t].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 u = (float)(this.getLatencyPos(5, 1.0F)[1] - this.getLatencyPos(10, 1.0F)[1]) * 10.0F * (float) (Math.PI / 180.0);
-                float v = Mth.cos(u);
-                float w = Mth.sin(u);
-                float x = this.getYRot() * (float) (Math.PI / 180.0);
-                float y = Mth.sin(x);
-                float z = Mth.cos(x);
-                this.tickPart(this.body, (double)(y * 0.5F), 0.0, (double)(-z * 0.5F));
-                this.tickPart(this.wing1, (double)(z * 4.5F), 2.0, (double)(y * 4.5F));
-                this.tickPart(this.wing2, (double)(z * -4.5F), 2.0, (double)(y * -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.0, 2.0, 4.0).move(0.0, -2.0, 0.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR)
-                    );
-                    this.knockBack(
-                        this.level()
-                            .getEntities(this, this.wing2.getBoundingBox().inflate(4.0, 2.0, 4.0).move(0.0, -2.0, 0.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR)
-                    );
-                    this.hurt(this.level().getEntities(this, this.head.getBoundingBox().inflate(1.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
-                    this.hurt(this.level().getEntities(this, this.neck.getBoundingBox().inflate(1.0), 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 aa = Mth.sin(this.getYRot() * (float) (Math.PI / 180.0) - this.yRotA * 0.01F);
-                float ab = Mth.cos(this.getYRot() * (float) (Math.PI / 180.0) - this.yRotA * 0.01F);
-                float ac = this.getHeadYOffset();
-                this.tickPart(this.head, (double)(aa * 6.5F * v), (double)(ac + w * 6.5F), (double)(-ab * 6.5F * v));
-                this.tickPart(this.neck, (double)(aa * 5.5F * v), (double)(ac + w * 5.5F), (double)(-ab * 5.5F * v));
-                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 ad = 0; ad < 3; ad++) {
-                    EnderDragonPart enderDragonPart = null;
-                    if (ad == 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 (ad == 1) {
-                        enderDragonPart = this.tail2;
+                    if (k == 1) {
+                        entitycomplexpart = this.tail2;
                     }
 
-                    if (ad == 2) {
-                        enderDragonPart = this.tail3;
+                    if (k == 2) {
+                        entitycomplexpart = this.tail3;
                     }
 
-                    double[] es = this.getLatencyPos(12 + ad * 2, 1.0F);
-                    float ae = this.getYRot() * (float) (Math.PI / 180.0) + this.rotWrap(es[0] - ds[0]) * (float) (Math.PI / 180.0);
-                    float af = Mth.sin(ae);
-                    float ag = Mth.cos(ae);
-                    float ah = 1.5F;
-                    float ai = (float)(ad + 1) * 2.0F;
-                    this.tickPart(
-                        enderDragonPart,
-                        (double)(-(y * 1.5F + af * ai) * v),
-                        es[1] - ds[1] - (double)((ai + 1.5F) * w) + 1.5,
-                        (double)((z * 1.5F + ag * ai) * v)
-                    );
+                    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) {
-                    this.inWall = this.checkWalls(this.head.getBoundingBox())
-                        | this.checkWalls(this.neck.getBoundingBox())
-                        | this.checkWalls(this.body.getBoundingBox());
+                    this.inWall = this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox());
                     if (this.dragonFight != null) {
                         this.dragonFight.updateDragon(this);
                     }
                 }
 
-                for (int aj = 0; aj < this.subEntities.length; aj++) {
-                    this.subEntities[aj].xo = vec3s[aj].x;
-                    this.subEntities[aj].yo = vec3s[aj].y;
-                    this.subEntities[aj].zo = vec3s[aj].z;
-                    this.subEntities[aj].xOld = vec3s[aj].x;
-                    this.subEntities[aj].yOld = vec3s[aj].y;
-                    this.subEntities[aj].zOld = vec3s[aj].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;
                 }
+
             }
         }
     }
@@ -385,9 +417,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]);
         }
     }
 
@@ -396,56 +429,77 @@
             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.0));
-            EndCrystal endCrystal = null;
-            double d = Double.MAX_VALUE;
+            List<EndCrystal> list = this.level().getEntitiesOfClass(EndCrystal.class, this.getBoundingBox().inflate(32.0D));
+            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.0;
-        double e = (this.body.getBoundingBox().minZ + this.body.getBoundingBox().maxZ) / 2.0;
+        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.1);
-                entity.push(f / h * 4.0, 0.2F, g / h * 4.0);
-                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, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+                if (!this.phaseManager.getCurrentPhase().isSitting() && ((LivingEntity) entity).getLastHurtByMobTimestamp() < entity.tickCount - 2) {
                     entity.hurt(this.damageSources().mobAttack(this), 5.0F);
                     this.doEnchantDamageEffects(this, entity);
                 }
             }
         }
+
     }
 
     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(this.damageSources().mobAttack(this), 10.0F);
                 this.doEnchantDamageEffects(this, entity);
             }
         }
+
     }
 
     private float rotWrap(double yawDegrees) {
-        return (float)Mth.wrapDegrees(yawDegrees);
+        return (float) Mth.wrapDegrees(yawDegrees);
     }
 
     private boolean checkWalls(AABB box) {
@@ -453,33 +507,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(this.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(this.explosionSource)) {
+                    BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null;
+                    LootParams.Builder loottableinfo_builder = (new LootParams.Builder((ServerLevel) this.level())).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(this.level(), blockposition, itemstack);
+                    });
+                    craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, ItemStack.EMPTY, false);
+                }
+                // Paper start - TNTPrimeEvent
+                org.bukkit.block.Block tntBlock = CraftBlock.at(this.level(), blockposition);
+                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent())
+                    continue;
+                // Paper end - TNTPrimeEvent
+                nmsBlock.wasExploded(this.level(), blockposition, this.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) {
@@ -495,7 +608,8 @@
                 return false;
             } else {
                 if (source.getEntity() instanceof Player || source.is(DamageTypeTags.ALWAYS_HURTS_ENDER_DRAGONS)) {
-                    float f = this.getHealth();
+                    float f1 = this.getHealth();
+
                     this.reallyHurt(source, amount);
                     if (this.isDeadOrDying() && !this.phaseManager.getCurrentPhase().isSitting()) {
                         this.setHealth(1.0F);
@@ -503,7 +617,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);
@@ -518,7 +632,7 @@
 
     @Override
     public boolean hurt(DamageSource source, float amount) {
-        return !this.level().isClientSide && this.hurt(this.body, source, amount);
+        return !this.level().isClientSide ? this.hurt(this.body, source, amount) : false;
     }
 
     protected boolean reallyHurt(DamageSource source, float amount) {
@@ -527,13 +641,38 @@
 
     @Override
     public void kill() {
-        this.remove(Entity.RemovalReason.KILLED);
+        // Paper start - Fire entity death event
+        this.silentDeath = true;
+        org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill());
+        if (deathEvent.isCancelled()) {
+            this.silentDeath = false; // Reset to default if event was cancelled
+            return;
+        }
+        // Paper end - Fire entity death event
+
+        this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
         this.gameEvent(GameEvent.ENTITY_DIE);
         if (this.dragonFight != null) {
             this.dragonFight.updateDragon(this);
             this.dragonFight.setDragonKilled(this);
         }
+
+    }
+
+    // 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() {
@@ -541,70 +680,99 @@
             this.dragonFight.updateDragon(this);
         }
 
-        this.dragonDeathTime++;
+        ++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.0 + (double)g, this.getZ() + (double)h, 0.0, 0.0, 0.0);
+            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 = this.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.level().globalLevelEvent(1028, this.blockPosition(), 0);
+                int viewDistance = ((ServerLevel) this.level()).getCraftServer().getViewDistance() * 16;
+                for (net.minecraft.server.level.ServerPlayer player : this.level().getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
+                    double deltaX = this.getX() - player.getX();
+                    double deltaZ = this.getZ() - player.getZ();
+                    double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
+                    final double soundRadiusSquared = this.level().getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule
+                    if ( !this.level().getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule
+                    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.0, 0.1F, 0.0));
+        this.move(MoverType.SELF, new Vec3(0.0D, 0.10000000149011612D, 0.0D));
         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) {
                 this.dragonFight.setDragonKilled(this);
             }
 
-            this.remove(Entity.RemovalReason.KILLED);
+            this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
             this.gameEvent(GameEvent.ENTITY_DIE);
         }
+
     }
 
     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 + (float) (Math.PI / 12) * (float)i)));
-                    m = Mth.floor(60.0F * Mth.sin(2.0F * ((float) -Math.PI + (float) (Math.PI / 12) * (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 / 8) * (float)k)));
-                    m = Mth.floor(40.0F * Mth.sin(2.0F * ((float) -Math.PI + (float) (Math.PI / 8) * (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 / 4) * (float)var7)));
-                    m = Mth.floor(20.0F * Mth.sin(2.0F * ((float) -Math.PI + (float) (Math.PI / 4) * (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;
@@ -639,18 +807,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;
                 }
             }
         }
@@ -660,96 +830,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;
         }
 
+        label70:
         while (!this.openSet.isEmpty()) {
-            Node node5 = this.openSet.pop();
-            if (node5.equals(node3)) {
+            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);
@@ -760,6 +952,7 @@
         super.addAdditionalSaveData(nbt);
         nbt.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
         nbt.putInt("DragonDeathTime", this.dragonDeathTime);
+        nbt.putInt("Bukkit.expToDrop", this.expToDrop); // CraftBukkit - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
     }
 
     @Override
@@ -772,11 +965,16 @@
         if (nbt.contains("DragonDeathTime")) {
             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;
@@ -808,69 +1006,81 @@
     }
 
     public float getHeadPartYOffset(int segmentOffset, double[] segment1, double[] segment2) {
-        DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
-        EnderDragonPhase<? extends DragonPhaseInstance> enderDragonPhase = dragonPhaseInstance.getPhase();
-        double e;
-        if (enderDragonPhase == EnderDragonPhase.LANDING || enderDragonPhase == EnderDragonPhase.TAKEOFF) {
-            BlockPos blockPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin));
-            double d = Math.max(Math.sqrt(blockPos.distToCenterSqr(this.position())) / 4.0, 1.0);
-            e = (double)segmentOffset / d;
-        } else if (dragonPhaseInstance.isSitting()) {
-            e = (double)segmentOffset;
-        } else if (segmentOffset == 6) {
-            e = 0.0;
+        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) {
+                d0 = 0.0D;
+            } else {
+                d0 = segment2[1] - segment1[1];
+            }
         } else {
-            e = segment2[1] - segment1[1];
+            BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium
+            double d1 = Math.max(Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0D, 1.0D);
+
+            d0 = (double) segmentOffset / d1;
         }
 
-        return (float)e;
+        return (float) d0;
     }
 
     public Vec3 getHeadLookVector(float tickDelta) {
-        DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
-        EnderDragonPhase<? extends DragonPhaseInstance> enderDragonPhase = dragonPhaseInstance.getPhase();
-        Vec3 vec3;
-        if (enderDragonPhase == EnderDragonPhase.LANDING || enderDragonPhase == EnderDragonPhase.TAKEOFF) {
-            BlockPos blockPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin));
-            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);
-            vec3 = this.getViewVector(tickDelta);
-            this.setXRot(h);
-        } else if (dragonPhaseInstance.isSitting()) {
-            float j = this.getXRot();
-            float k = 1.5F;
-            this.setXRot(-45.0F);
-            vec3 = this.getViewVector(tickDelta);
-            this.setXRot(j);
+        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);
+                vec3d = this.getViewVector(tickDelta);
+                this.setXRot(f2);
+            } else {
+                vec3d = this.getViewVector(tickDelta);
+            }
         } else {
-            vec3 = this.getViewVector(tickDelta);
+            BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon 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 vec3;
+        return vec3d;
     }
 
     public void onCrystalDestroyed(EndCrystal endCrystal, 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 (endCrystal == this.nearestCrystal) {
-            this.hurt(this.head, this.damageSources().explosion(endCrystal, player), 10.0F);
+            this.hurt(this.head, this.damageSources().explosion(endCrystal, entityhuman), 10.0F);
         }
 
-        this.phaseManager.getCurrentPhase().onCrystalDestroyed(endCrystal, pos, source, player);
+        this.phaseManager.getCurrentPhase().onCrystalDestroyed(endCrystal, 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);
@@ -903,11 +1113,12 @@
     @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());
         }
+
     }
 
     @Override
