--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
+++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
@@ -10,6 +10,8 @@
 import com.mojang.serialization.Codec;
 import com.mojang.serialization.codecs.RecordCodecBuilder;
 import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -52,6 +54,7 @@
 import net.minecraft.world.level.chunk.LevelChunk;
 import net.minecraft.world.level.chunk.status.ChunkStatus;
 import net.minecraft.world.level.levelgen.Heightmap;
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
 import net.minecraft.world.level.levelgen.feature.EndPodiumFeature;
 import net.minecraft.world.level.levelgen.feature.SpikeFeature;
 import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
@@ -59,6 +62,7 @@
 import org.slf4j.Logger;
 
 public class EndDragonFight {
+
     private static final Logger LOGGER = LogUtils.getLogger();
     private static final int MAX_TICKS_BEFORE_DRAGON_RESPAWN = 1200;
     private static final int TIME_BETWEEN_CRYSTAL_SCANS = 100;
@@ -69,25 +73,22 @@
     private static final int GATEWAY_DISTANCE = 96;
     public static final int DRAGON_SPAWN_Y = 128;
     private final Predicate<Entity> validPlayer;
-    public final ServerBossEvent dragonEvent = (ServerBossEvent)new ServerBossEvent(
-            Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS
-        )
-        .setPlayBossMusic(true)
-        .setCreateWorldFog(true);
+    private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name
+    public final ServerBossEvent dragonEvent;
     public final ServerLevel level;
     private final BlockPos origin;
-    public final ObjectArrayList<Integer> gateways = new ObjectArrayList<>();
+    public final ObjectArrayList<Integer> gateways;
     private final BlockPattern exitPortalPattern;
     private int ticksSinceDragonSeen;
     private int crystalsAlive;
     private int ticksSinceCrystalsScanned;
-    private int ticksSinceLastPlayerScan = 21;
+    private int ticksSinceLastPlayerScan;
     private boolean dragonKilled;
     public boolean previouslyKilled;
-    private boolean skipArenaLoadedCheck = false;
+    private boolean skipArenaLoadedCheck;
     @Nullable
     public UUID dragonUUID;
-    private boolean needsStateScanning = true;
+    private boolean needsStateScanning;
     @Nullable
     public BlockPos portalLocation;
     @Nullable
@@ -101,34 +102,39 @@
     }
 
     public EndDragonFight(ServerLevel world, long gatewaysSeed, EndDragonFight.Data data, BlockPos origin) {
+        this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper - ensure reset EnderDragon boss event name
+        this.gateways = new ObjectArrayList();
+        this.ticksSinceLastPlayerScan = 21;
+        this.skipArenaLoadedCheck = false;
+        this.needsStateScanning = true;
         this.level = world;
         this.origin = origin;
-        this.validPlayer = EntitySelector.ENTITY_STILL_ALIVE
-            .and(EntitySelector.withinDistance((double)origin.getX(), (double)(128 + origin.getY()), (double)origin.getZ(), 192.0));
+        this.validPlayer = EntitySelector.ENTITY_STILL_ALIVE.and(EntitySelector.withinDistance((double) origin.getX(), (double) (128 + origin.getY()), (double) origin.getZ(), 192.0D));
         this.needsStateScanning = data.needsStateScanning;
-        this.dragonUUID = data.dragonUUID.orElse(null);
+        this.dragonUUID = (UUID) data.dragonUUID.orElse(null); // CraftBukkit - decompile error
         this.dragonKilled = data.dragonKilled;
         this.previouslyKilled = data.previouslyKilled;
         if (data.isRespawning) {
             this.respawnStage = DragonRespawnAnimation.START;
         }
+        // Paper start - Add config to disable ender dragon legacy check
+        if (data == EndDragonFight.Data.DEFAULT && !world.paperConfig().entities.spawning.scanForLegacyEnderDragon) {
+            this.needsStateScanning = false;
+            this.dragonKilled = true;
+        }
+        // Paper end - Add config to disable ender dragon legacy check
 
-        this.portalLocation = data.exitPortalLocation.orElse(null);
-        this.gateways.addAll(data.gateways.orElseGet(() -> {
-            ObjectArrayList<Integer> objectArrayList = new ObjectArrayList<>(ContiguousSet.create(Range.closedOpen(0, 20), DiscreteDomain.integers()));
-            Util.shuffle(objectArrayList, RandomSource.create(gatewaysSeed));
-            return objectArrayList;
+        this.portalLocation = (BlockPos) data.exitPortalLocation.orElse(null); // CraftBukkit - decompile error
+        this.gateways.addAll((Collection) data.gateways.orElseGet(() -> {
+            ObjectArrayList<Integer> objectarraylist = new ObjectArrayList(ContiguousSet.create(Range.closedOpen(0, 20), DiscreteDomain.integers()));
+
+            Util.shuffle(objectarraylist, RandomSource.create(gatewaysSeed));
+            return objectarraylist;
         }));
-        this.exitPortalPattern = BlockPatternBuilder.start()
-            .aisle("       ", "       ", "       ", "   #   ", "       ", "       ", "       ")
-            .aisle("       ", "       ", "       ", "   #   ", "       ", "       ", "       ")
-            .aisle("       ", "       ", "       ", "   #   ", "       ", "       ", "       ")
-            .aisle("  ###  ", " #   # ", "#     #", "#  #  #", "#     #", " #   # ", "  ###  ")
-            .aisle("       ", "  ###  ", " ##### ", " ##### ", " ##### ", "  ###  ", "       ")
-            .where('#', BlockInWorld.hasState(BlockPredicate.forBlock(Blocks.BEDROCK)))
-            .build();
+        this.exitPortalPattern = BlockPatternBuilder.start().aisle("       ", "       ", "       ", "   #   ", "       ", "       ", "       ").aisle("       ", "       ", "       ", "   #   ", "       ", "       ", "       ").aisle("       ", "       ", "       ", "   #   ", "       ", "       ", "       ").aisle("  ###  ", " #   # ", "#     #", "#  #  #", "#     #", " #   # ", "  ###  ").aisle("       ", "  ###  ", " ##### ", " ##### ", " ##### ", "  ###  ", "       ").where('#', BlockInWorld.hasState(BlockPredicate.forBlock(Blocks.BEDROCK))).build();
     }
 
+    /** @deprecated */
     @Deprecated
     @VisibleForTesting
     public void skipArenaLoadedCheck() {
@@ -136,15 +142,7 @@
     }
 
     public EndDragonFight.Data saveData() {
-        return new EndDragonFight.Data(
-            this.needsStateScanning,
-            this.dragonKilled,
-            this.previouslyKilled,
-            false,
-            Optional.ofNullable(this.dragonUUID),
-            Optional.ofNullable(this.portalLocation),
-            Optional.of(this.gateways)
-        );
+        return new EndDragonFight.Data(this.needsStateScanning, this.dragonKilled, this.previouslyKilled, false, Optional.ofNullable(this.dragonUUID), Optional.ofNullable(this.portalLocation), Optional.of(this.gateways));
     }
 
     public void tick() {
@@ -156,14 +154,15 @@
 
         if (!this.dragonEvent.getPlayers().isEmpty()) {
             this.level.getChunkSource().addRegionTicket(TicketType.DRAGON, new ChunkPos(0, 0), 9, Unit.INSTANCE);
-            boolean bl = this.isArenaLoaded();
-            if (this.needsStateScanning && bl) {
+            boolean flag = this.isArenaLoaded();
+
+            if (this.needsStateScanning && flag) {
                 this.scanState();
                 this.needsStateScanning = false;
             }
 
             if (this.respawnStage != null) {
-                if (this.respawnCrystals == null && bl) {
+                if (this.respawnCrystals == null && flag) {
                     this.respawnStage = null;
                     this.tryRespawn();
                 }
@@ -172,12 +171,12 @@
             }
 
             if (!this.dragonKilled) {
-                if ((this.dragonUUID == null || ++this.ticksSinceDragonSeen >= 1200) && bl) {
+                if ((this.dragonUUID == null || ++this.ticksSinceDragonSeen >= 1200) && flag) {
                     this.findOrCreateDragon();
                     this.ticksSinceDragonSeen = 0;
                 }
 
-                if (++this.ticksSinceCrystalsScanned >= 100 && bl) {
+                if (++this.ticksSinceCrystalsScanned >= 100 && flag) {
                     this.updateCrystalCount();
                     this.ticksSinceCrystalsScanned = 0;
                 }
@@ -185,16 +184,18 @@
         } else {
             this.level.getChunkSource().removeRegionTicket(TicketType.DRAGON, new ChunkPos(0, 0), 9, Unit.INSTANCE);
         }
+
     }
 
     private void scanState() {
-        LOGGER.info("Scanning for legacy world dragon fight...");
-        boolean bl = this.hasActiveExitPortal();
-        if (bl) {
-            LOGGER.info("Found that the dragon has been killed in this world already.");
+        EndDragonFight.LOGGER.info("Scanning for legacy world dragon fight...");
+        boolean flag = this.hasActiveExitPortal();
+
+        if (flag) {
+            EndDragonFight.LOGGER.info("Found that the dragon has been killed in this world already.");
             this.previouslyKilled = true;
         } else {
-            LOGGER.info("Found that the dragon has not yet been killed in this world.");
+            EndDragonFight.LOGGER.info("Found that the dragon has not yet been killed in this world.");
             this.previouslyKilled = false;
             if (this.findExitPortal() == null) {
                 this.spawnExitPortal(false);
@@ -202,16 +203,18 @@
         }
 
         List<? extends EnderDragon> list = this.level.getDragons();
+
         if (list.isEmpty()) {
             this.dragonKilled = true;
         } else {
-            EnderDragon enderDragon = list.get(0);
-            this.dragonUUID = enderDragon.getUUID();
-            LOGGER.info("Found that there's a dragon still alive ({})", enderDragon);
+            EnderDragon entityenderdragon = (EnderDragon) list.get(0);
+
+            this.dragonUUID = entityenderdragon.getUUID();
+            EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon);
             this.dragonKilled = false;
-            if (!bl) {
-                LOGGER.info("But we didn't have a portal, let's remove it.");
-                enderDragon.discard();
+            if (!flag && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon
+                EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it.");
+                entityenderdragon.discard(null); // CraftBukkit - add Bukkit remove cause
                 this.dragonUUID = null;
             }
         }
@@ -219,17 +222,20 @@
         if (!this.previouslyKilled && this.dragonKilled) {
             this.dragonKilled = false;
         }
+
     }
 
     private void findOrCreateDragon() {
         List<? extends EnderDragon> list = this.level.getDragons();
+
         if (list.isEmpty()) {
-            LOGGER.debug("Haven't seen the dragon, respawning it");
+            EndDragonFight.LOGGER.debug("Haven't seen the dragon, respawning it");
             this.createNewDragon();
         } else {
-            LOGGER.debug("Haven't seen our dragon, but found another one to use.");
-            this.dragonUUID = list.get(0).getUUID();
+            EndDragonFight.LOGGER.debug("Haven't seen our dragon, but found another one to use.");
+            this.dragonUUID = ((EnderDragon) list.get(0)).getUUID();
         }
+
     }
 
     public void setRespawnStage(DragonRespawnAnimation spawnState) {
@@ -240,28 +246,45 @@
             if (spawnState == DragonRespawnAnimation.END) {
                 this.respawnStage = null;
                 this.dragonKilled = false;
-                EnderDragon enderDragon = this.createNewDragon();
-                if (enderDragon != null) {
-                    for (ServerPlayer serverPlayer : this.dragonEvent.getPlayers()) {
-                        CriteriaTriggers.SUMMONED_ENTITY.trigger(serverPlayer, enderDragon);
+                EnderDragon entityenderdragon = this.createNewDragon();
+
+                if (entityenderdragon != null) {
+                    Iterator iterator = this.dragonEvent.getPlayers().iterator();
+
+                    while (iterator.hasNext()) {
+                        ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+
+                        CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayer, (Entity) entityenderdragon);
                     }
                 }
             } else {
                 this.respawnStage = spawnState;
             }
+
         }
     }
 
     private boolean hasActiveExitPortal() {
-        for (int i = -8; i <= 8; i++) {
-            for (int j = -8; j <= 8; j++) {
-                LevelChunk levelChunk = this.level.getChunk(i, j);
+        for (int i = -8; i <= 8; ++i) {
+            int j = -8;
 
-                for (BlockEntity blockEntity : levelChunk.getBlockEntities().values()) {
-                    if (blockEntity instanceof TheEndPortalBlockEntity) {
-                        return true;
+            label27:
+            while (j <= 8) {
+                LevelChunk chunk = this.level.getChunk(i, j);
+                Iterator iterator = chunk.getBlockEntities().values().iterator();
+
+                BlockEntity tileentity;
+
+                do {
+                    if (!iterator.hasNext()) {
+                        ++j;
+                        continue label27;
                     }
-                }
+
+                    tileentity = (BlockEntity) iterator.next();
+                } while (!(tileentity instanceof TheEndPortalBlockEntity));
+
+                return true;
             }
         }
 
@@ -270,39 +293,48 @@
 
     @Nullable
     public BlockPattern.BlockPatternMatch findExitPortal() {
-        ChunkPos chunkPos = new ChunkPos(this.origin);
+        ChunkPos chunkcoordintpair = new ChunkPos(this.origin);
 
-        for (int i = -8 + chunkPos.x; i <= 8 + chunkPos.x; i++) {
-            for (int j = -8 + chunkPos.z; j <= 8 + chunkPos.z; j++) {
-                LevelChunk levelChunk = this.level.getChunk(i, j);
+        int i;
+
+        for (int j = -8 + chunkcoordintpair.x; j <= 8 + chunkcoordintpair.x; ++j) {
+            for (i = -8 + chunkcoordintpair.z; i <= 8 + chunkcoordintpair.z; ++i) {
+                LevelChunk chunk = this.level.getChunk(j, i);
+                Iterator iterator = chunk.getBlockEntities().values().iterator();
+
+                while (iterator.hasNext()) {
+                    BlockEntity tileentity = (BlockEntity) iterator.next();
+
+                    if (tileentity instanceof TheEndPortalBlockEntity) {
+                        BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection = this.exitPortalPattern.find(this.level, tileentity.getBlockPos());
+
+                        if (shapedetector_shapedetectorcollection != null) {
+                            BlockPos blockposition = shapedetector_shapedetectorcollection.getBlock(3, 3, 3).getPos();
 
-                for (BlockEntity blockEntity : levelChunk.getBlockEntities().values()) {
-                    if (blockEntity instanceof TheEndPortalBlockEntity) {
-                        BlockPattern.BlockPatternMatch blockPatternMatch = this.exitPortalPattern.find(this.level, blockEntity.getBlockPos());
-                        if (blockPatternMatch != null) {
-                            BlockPos blockPos = blockPatternMatch.getBlock(3, 3, 3).getPos();
                             if (this.portalLocation == null) {
-                                this.portalLocation = blockPos;
+                                this.portalLocation = blockposition;
                             }
 
-                            return blockPatternMatch;
+                            return shapedetector_shapedetectorcollection;
                         }
                     }
                 }
             }
         }
 
-        BlockPos blockPos2 = EndPodiumFeature.getLocation(this.origin);
-        int k = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockPos2).getY();
+        BlockPos blockposition1 = EndPodiumFeature.getLocation(this.origin);
 
-        for (int l = k; l >= this.level.getMinBuildHeight(); l--) {
-            BlockPattern.BlockPatternMatch blockPatternMatch2 = this.exitPortalPattern.find(this.level, new BlockPos(blockPos2.getX(), l, blockPos2.getZ()));
-            if (blockPatternMatch2 != null) {
+        i = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockposition1).getY();
+
+        for (int k = i; k >= this.level.getMinBuildHeight(); --k) {
+            BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection1 = this.exitPortalPattern.find(this.level, new BlockPos(blockposition1.getX(), k, blockposition1.getZ()));
+
+            if (shapedetector_shapedetectorcollection1 != null) {
                 if (this.portalLocation == null) {
-                    this.portalLocation = blockPatternMatch2.getBlock(3, 3, 3).getPos();
+                    this.portalLocation = shapedetector_shapedetectorcollection1.getBlock(3, 3, 3).getPos();
                 }
 
-                return blockPatternMatch2;
+                return shapedetector_shapedetectorcollection1;
             }
         }
 
@@ -313,17 +345,19 @@
         if (this.skipArenaLoadedCheck) {
             return true;
         } else {
-            ChunkPos chunkPos = new ChunkPos(this.origin);
+            ChunkPos chunkcoordintpair = new ChunkPos(this.origin);
 
-            for (int i = -8 + chunkPos.x; i <= 8 + chunkPos.x; i++) {
-                for (int j = 8 + chunkPos.z; j <= 8 + chunkPos.z; j++) {
-                    ChunkAccess chunkAccess = this.level.getChunk(i, j, ChunkStatus.FULL, false);
-                    if (!(chunkAccess instanceof LevelChunk)) {
+            for (int i = -8 + chunkcoordintpair.x; i <= 8 + chunkcoordintpair.x; ++i) {
+                for (int j = 8 + chunkcoordintpair.z; j <= 8 + chunkcoordintpair.z; ++j) {
+                    ChunkAccess ichunkaccess = this.level.getChunk(i, j, ChunkStatus.FULL, false);
+
+                    if (!(ichunkaccess instanceof LevelChunk)) {
                         return false;
                     }
 
-                    FullChunkStatus fullChunkStatus = ((LevelChunk)chunkAccess).getFullStatus();
-                    if (!fullChunkStatus.isOrAfter(FullChunkStatus.BLOCK_TICKING)) {
+                    FullChunkStatus fullchunkstatus = ((LevelChunk) ichunkaccess).getFullStatus();
+
+                    if (!fullchunkstatus.isOrAfter(FullChunkStatus.BLOCK_TICKING)) {
                         return false;
                     }
                 }
@@ -335,29 +369,39 @@
 
     private void updatePlayers() {
         Set<ServerPlayer> set = Sets.newHashSet();
+        Iterator iterator = this.level.getPlayers(this.validPlayer).iterator();
 
-        for (ServerPlayer serverPlayer : this.level.getPlayers(this.validPlayer)) {
-            this.dragonEvent.addPlayer(serverPlayer);
-            set.add(serverPlayer);
+        while (iterator.hasNext()) {
+            ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+
+            this.dragonEvent.addPlayer(entityplayer);
+            set.add(entityplayer);
         }
 
-        Set<ServerPlayer> set2 = Sets.newHashSet(this.dragonEvent.getPlayers());
-        set2.removeAll(set);
+        Set<ServerPlayer> set1 = Sets.newHashSet(this.dragonEvent.getPlayers());
 
-        for (ServerPlayer serverPlayer2 : set2) {
-            this.dragonEvent.removePlayer(serverPlayer2);
+        set1.removeAll(set);
+        Iterator iterator1 = set1.iterator();
+
+        while (iterator1.hasNext()) {
+            ServerPlayer entityplayer1 = (ServerPlayer) iterator1.next();
+
+            this.dragonEvent.removePlayer(entityplayer1);
         }
+
     }
 
     private void updateCrystalCount() {
         this.ticksSinceCrystalsScanned = 0;
         this.crystalsAlive = 0;
 
-        for (SpikeFeature.EndSpike endSpike : SpikeFeature.getSpikesForLevel(this.level)) {
-            this.crystalsAlive = this.crystalsAlive + this.level.getEntitiesOfClass(EndCrystal.class, endSpike.getTopBoundingBox()).size();
+        SpikeFeature.EndSpike worldgenender_spike;
+
+        for (Iterator iterator = SpikeFeature.getSpikesForLevel(this.level).iterator(); iterator.hasNext(); this.crystalsAlive += this.level.getEntitiesOfClass(EndCrystal.class, worldgenender_spike.getTopBoundingBox()).size()) {
+            worldgenender_spike = (SpikeFeature.EndSpike) iterator.next();
         }
 
-        LOGGER.debug("Found {} end crystals still alive", this.crystalsAlive);
+        EndDragonFight.LOGGER.debug("Found {} end crystals still alive", this.crystalsAlive);
     }
 
     public void setDragonKilled(EnderDragon dragon) {
@@ -366,77 +410,112 @@
             this.dragonEvent.setVisible(false);
             this.spawnExitPortal(true);
             this.spawnNewGateway();
-            if (!this.previouslyKilled) {
-                this.level
-                    .setBlockAndUpdate(
-                        this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)),
-                        Blocks.DRAGON_EGG.defaultBlockState()
-                    );
+            // Paper start - Add DragonEggFormEvent
+            BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin));
+            org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition);
+            eggState.setData(Blocks.DRAGON_EGG.defaultBlockState());
+            io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState,
+                new org.bukkit.craftbukkit.boss.CraftDragonBattle(this));
+            // Paper end - Add DragonEggFormEvent
+            if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg
+                // Paper start - Add DragonEggFormEvent
+                // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState());
+            } else {
+                eggEvent.setCancelled(true);
+            }
+            if (eggEvent.callEvent()) {
+                eggEvent.getNewState().update(true);
+                // Paper end - Add DragonEggFormEvent
             }
 
             this.previouslyKilled = true;
             this.dragonKilled = true;
         }
+
     }
 
+    /** @deprecated */
     @Deprecated
     @VisibleForTesting
     public void removeAllGateways() {
         this.gateways.clear();
     }
 
+    // Paper start - More DragonBattle API
+    public boolean spawnNewGatewayIfPossible() {
+        if (!this.gateways.isEmpty()) {
+            this.spawnNewGateway();
+            return true;
+        }
+        return false;
+    }
+
+    public List<EndCrystal> getSpikeCrystals() {
+        final List<EndCrystal> endCrystals = new java.util.ArrayList<>();
+        for (final SpikeFeature.EndSpike spike : SpikeFeature.getSpikesForLevel(this.level)) {
+            endCrystals.addAll(this.level.getEntitiesOfClass(EndCrystal.class, spike.getTopBoundingBox()));
+        }
+        return endCrystals;
+    }
+    // Paper end - More DragonBattle API
+
     private void spawnNewGateway() {
         if (!this.gateways.isEmpty()) {
-            int i = this.gateways.remove(this.gateways.size() - 1);
-            int j = Mth.floor(96.0 * Math.cos(2.0 * (-Math.PI + (Math.PI / 20) * (double)i)));
-            int k = Mth.floor(96.0 * Math.sin(2.0 * (-Math.PI + (Math.PI / 20) * (double)i)));
+            int i = (Integer) this.gateways.remove(this.gateways.size() - 1);
+            int j = Mth.floor(96.0D * Math.cos(2.0D * (-3.141592653589793D + 0.15707963267948966D * (double) i)));
+            int k = Mth.floor(96.0D * Math.sin(2.0D * (-3.141592653589793D + 0.15707963267948966D * (double) i)));
+
             this.spawnNewGateway(new BlockPos(j, 75, k));
         }
     }
 
     public void spawnNewGateway(BlockPos pos) {
         this.level.levelEvent(3000, pos, 0);
-        this.level
-            .registryAccess()
-            .registry(Registries.CONFIGURED_FEATURE)
-            .flatMap(registry -> registry.getHolder(EndFeatures.END_GATEWAY_DELAYED))
-            .ifPresent(reference -> reference.value().place(this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), pos));
+        this.level.registryAccess().registry(Registries.CONFIGURED_FEATURE).flatMap((iregistry) -> {
+            return iregistry.getHolder(EndFeatures.END_GATEWAY_DELAYED);
+        }).ifPresent((holder_c) -> {
+            ((ConfiguredFeature) holder_c.value()).place(this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), pos);
+        });
     }
 
     public void spawnExitPortal(boolean previouslyKilled) {
-        EndPodiumFeature endPodiumFeature = new EndPodiumFeature(previouslyKilled);
-        if (this.portalLocation == null) {
-            this.portalLocation = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.origin)).below();
+        EndPodiumFeature worldgenendtrophy = new EndPodiumFeature(previouslyKilled);
 
-            while (this.level.getBlockState(this.portalLocation).is(Blocks.BEDROCK) && this.portalLocation.getY() > this.level.getSeaLevel()) {
-                this.portalLocation = this.portalLocation.below();
+        if (this.portalLocation == null) {
+            for (this.portalLocation = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.origin)).below(); this.level.getBlockState(this.portalLocation).is(Blocks.BEDROCK) && this.portalLocation.getY() > this.level.getSeaLevel(); this.portalLocation = this.portalLocation.below()) {
+                ;
             }
         }
 
-        if (endPodiumFeature.place(
-            FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation
-        )) {
+        // Paper start - Prevent "softlocked" exit portal generation
+        if (this.portalLocation.getY() <= this.level.getMinBuildHeight()) {
+            this.portalLocation = this.portalLocation.atY(this.level.getMinBuildHeight() + 1);
+        }
+        // Paper end - Prevent "softlocked" exit portal generation
+        if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) {
             int i = Mth.positiveCeilDiv(4, 16);
+
             this.level.getChunkSource().chunkMap.waitForLightBeforeSending(new ChunkPos(this.portalLocation), i);
         }
+
     }
 
     @Nullable
     private EnderDragon createNewDragon() {
         this.level.getChunkAt(new BlockPos(this.origin.getX(), 128 + this.origin.getY(), this.origin.getZ()));
-        EnderDragon enderDragon = EntityType.ENDER_DRAGON.create(this.level);
-        if (enderDragon != null) {
-            enderDragon.setDragonFight(this);
-            enderDragon.setFightOrigin(this.origin);
-            enderDragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN);
-            enderDragon.moveTo(
-                (double)this.origin.getX(), (double)(128 + this.origin.getY()), (double)this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F
-            );
-            this.level.addFreshEntity(enderDragon);
-            this.dragonUUID = enderDragon.getUUID();
+        EnderDragon entityenderdragon = (EnderDragon) EntityType.ENDER_DRAGON.create(this.level);
+
+        if (entityenderdragon != null) {
+            entityenderdragon.setDragonFight(this);
+            entityenderdragon.setFightOrigin(this.origin);
+            entityenderdragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN);
+            entityenderdragon.moveTo((double) this.origin.getX(), (double) (128 + this.origin.getY()), (double) this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F);
+            this.level.addFreshEntity(entityenderdragon);
+            this.dragonUUID = entityenderdragon.getUUID();
+            this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn
         }
 
-        return enderDragon;
+        return entityenderdragon;
     }
 
     public void updateDragon(EnderDragon dragon) {
@@ -445,8 +524,13 @@
             this.ticksSinceDragonSeen = 0;
             if (dragon.hasCustomName()) {
                 this.dragonEvent.setName(dragon.getDisplayName());
+                // Paper start - ensure reset EnderDragon boss event name
+            } else {
+                this.dragonEvent.setName(DEFAULT_BOSS_EVENT_NAME);
+                // Paper end - ensure reset EnderDragon boss event name
             }
         }
+
     }
 
     public int getCrystalsAlive() {
@@ -455,7 +539,7 @@
 
     public void onCrystalDestroyed(EndCrystal enderCrystal, DamageSource source) {
         if (this.respawnStage != null && this.respawnCrystals.contains(enderCrystal)) {
-            LOGGER.debug("Aborting respawn sequence");
+            EndDragonFight.LOGGER.debug("Aborting respawn sequence");
             this.respawnStage = null;
             this.respawnTime = 0;
             this.resetSpikeCrystals();
@@ -463,58 +547,89 @@
         } else {
             this.updateCrystalCount();
             Entity entity = this.level.getEntity(this.dragonUUID);
+
             if (entity instanceof EnderDragon) {
-                ((EnderDragon)entity).onCrystalDestroyed(enderCrystal, enderCrystal.blockPosition(), source);
+                ((EnderDragon) entity).onCrystalDestroyed(enderCrystal, enderCrystal.blockPosition(), source);
             }
         }
+
     }
 
     public boolean hasPreviouslyKilledDragon() {
         return this.previouslyKilled;
     }
 
-    public void tryRespawn() {
+    public boolean tryRespawn() { // CraftBukkit - return boolean
+        // Paper start - Perf: Do crystal-portal proximity check before entity lookup
+        return this.tryRespawn(null);
+    }
+
+    public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal
+        // Paper end - Perf: Do crystal-portal proximity check before entity lookup
         if (this.dragonKilled && this.respawnStage == null) {
-            BlockPos blockPos = this.portalLocation;
-            if (blockPos == null) {
-                LOGGER.debug("Tried to respawn, but need to find the portal first.");
-                BlockPattern.BlockPatternMatch blockPatternMatch = this.findExitPortal();
-                if (blockPatternMatch == null) {
-                    LOGGER.debug("Couldn't find a portal, so we made one.");
+            BlockPos blockposition = this.portalLocation;
+
+            if (blockposition == null) {
+                EndDragonFight.LOGGER.debug("Tried to respawn, but need to find the portal first.");
+                BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection = this.findExitPortal();
+
+                if (shapedetector_shapedetectorcollection == null) {
+                    EndDragonFight.LOGGER.debug("Couldn't find a portal, so we made one.");
                     this.spawnExitPortal(true);
                 } else {
-                    LOGGER.debug("Found the exit portal & saved its location for next time.");
+                    EndDragonFight.LOGGER.debug("Found the exit portal & saved its location for next time.");
                 }
 
-                blockPos = this.portalLocation;
+                blockposition = this.portalLocation;
             }
 
+            // Paper start - Perf: Do crystal-portal proximity check before entity lookup
+            if (placedEndCrystalPos != null) {
+                // The end crystal must be 0 or 1 higher than the portal origin
+                int dy = placedEndCrystalPos.getY() - blockposition.getY();
+                if (dy != 0 && dy != 1) {
+                    return false;
+                }
+                // The end crystal must be within a distance of 1 in one planar direction, and 3 in the other
+                int dx = placedEndCrystalPos.getX() - blockposition.getX();
+                int dz = placedEndCrystalPos.getZ() - blockposition.getZ();
+                if (!((dx >= -1 && dx <= 1 && dz >= -3 && dz <= 3) || (dx >= -3 && dx <= 3 && dz >= -1 && dz <= 1))) {
+                    return false;
+                }
+            }
+            // Paper end - Perf: Do crystal-portal proximity check before entity lookup
+
             List<EndCrystal> list = Lists.newArrayList();
-            BlockPos blockPos2 = blockPos.above(1);
+            BlockPos blockposition1 = blockposition.above(1);
+            Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
 
-            for (Direction direction : Direction.Plane.HORIZONTAL) {
-                List<EndCrystal> list2 = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockPos2.relative(direction, 2)));
-                if (list2.isEmpty()) {
-                    return;
+            while (iterator.hasNext()) {
+                Direction enumdirection = (Direction) iterator.next();
+                List<EndCrystal> list1 = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockposition1.relative(enumdirection, 2)));
+
+                if (list1.isEmpty()) {
+                    return false; // CraftBukkit - return value
                 }
 
-                list.addAll(list2);
+                list.addAll(list1);
             }
 
-            LOGGER.debug("Found all crystals, respawning dragon.");
-            this.respawnDragon(list);
+            EndDragonFight.LOGGER.debug("Found all crystals, respawning dragon.");
+            return this.respawnDragon(list); // CraftBukkit - return value
         }
+        return false; // CraftBukkit - return value
     }
 
-    public void respawnDragon(List<EndCrystal> crystals) {
+    public boolean respawnDragon(List<EndCrystal> list) { // CraftBukkit - return boolean
         if (this.dragonKilled && this.respawnStage == null) {
-            for (BlockPattern.BlockPatternMatch blockPatternMatch = this.findExitPortal(); blockPatternMatch != null; blockPatternMatch = this.findExitPortal()) {
-                for (int i = 0; i < this.exitPortalPattern.getWidth(); i++) {
-                    for (int j = 0; j < this.exitPortalPattern.getHeight(); j++) {
-                        for (int k = 0; k < this.exitPortalPattern.getDepth(); k++) {
-                            BlockInWorld blockInWorld = blockPatternMatch.getBlock(i, j, k);
-                            if (blockInWorld.getState().is(Blocks.BEDROCK) || blockInWorld.getState().is(Blocks.END_PORTAL)) {
-                                this.level.setBlockAndUpdate(blockInWorld.getPos(), Blocks.END_STONE.defaultBlockState());
+            for (BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection = this.findExitPortal(); shapedetector_shapedetectorcollection != null; shapedetector_shapedetectorcollection = this.findExitPortal()) {
+                for (int i = 0; i < this.exitPortalPattern.getWidth(); ++i) {
+                    for (int j = 0; j < this.exitPortalPattern.getHeight(); ++j) {
+                        for (int k = 0; k < this.exitPortalPattern.getDepth(); ++k) {
+                            BlockInWorld shapedetectorblock = shapedetector_shapedetectorcollection.getBlock(i, j, k);
+
+                            if (shapedetectorblock.getState().is(Blocks.BEDROCK) || shapedetectorblock.getState().is(Blocks.END_PORTAL)) {
+                                this.level.setBlockAndUpdate(shapedetectorblock.getPos(), Blocks.END_STONE.defaultBlockState());
                             }
                         }
                     }
@@ -524,17 +639,28 @@
             this.respawnStage = DragonRespawnAnimation.START;
             this.respawnTime = 0;
             this.spawnExitPortal(false);
-            this.respawnCrystals = crystals;
+            this.respawnCrystals = list;
+            return true; // CraftBukkit - return value
         }
+        return false; // CraftBukkit - return value
     }
 
     public void resetSpikeCrystals() {
-        for (SpikeFeature.EndSpike endSpike : SpikeFeature.getSpikesForLevel(this.level)) {
-            for (EndCrystal endCrystal : this.level.getEntitiesOfClass(EndCrystal.class, endSpike.getTopBoundingBox())) {
-                endCrystal.setInvulnerable(false);
-                endCrystal.setBeamTarget(null);
+        Iterator iterator = SpikeFeature.getSpikesForLevel(this.level).iterator();
+
+        while (iterator.hasNext()) {
+            SpikeFeature.EndSpike worldgenender_spike = (SpikeFeature.EndSpike) iterator.next();
+            List<EndCrystal> list = this.level.getEntitiesOfClass(EndCrystal.class, worldgenender_spike.getTopBoundingBox());
+            Iterator iterator1 = list.iterator();
+
+            while (iterator1.hasNext()) {
+                EndCrystal entityendercrystal = (EndCrystal) iterator1.next();
+
+                entityendercrystal.setInvulnerable(false);
+                entityendercrystal.setBeamTarget((BlockPos) null);
             }
         }
+
     }
 
     @Nullable
@@ -542,29 +668,11 @@
         return this.dragonUUID;
     }
 
-    public static record Data(
-        boolean needsStateScanning,
-        boolean dragonKilled,
-        boolean previouslyKilled,
-        boolean isRespawning,
-        Optional<UUID> dragonUUID,
-        Optional<BlockPos> exitPortalLocation,
-        Optional<List<Integer>> gateways
-    ) {
-        public static final Codec<EndDragonFight.Data> CODEC = RecordCodecBuilder.create(
-            instance -> instance.group(
-                        Codec.BOOL.fieldOf("NeedsStateScanning").orElse(true).forGetter(EndDragonFight.Data::needsStateScanning),
-                        Codec.BOOL.fieldOf("DragonKilled").orElse(false).forGetter(EndDragonFight.Data::dragonKilled),
-                        Codec.BOOL.fieldOf("PreviouslyKilled").orElse(false).forGetter(EndDragonFight.Data::previouslyKilled),
-                        Codec.BOOL.lenientOptionalFieldOf("IsRespawning", Boolean.valueOf(false)).forGetter(EndDragonFight.Data::isRespawning),
-                        UUIDUtil.CODEC.lenientOptionalFieldOf("Dragon").forGetter(EndDragonFight.Data::dragonUUID),
-                        BlockPos.CODEC.lenientOptionalFieldOf("ExitPortalLocation").forGetter(EndDragonFight.Data::exitPortalLocation),
-                        Codec.list(Codec.INT).lenientOptionalFieldOf("Gateways").forGetter(EndDragonFight.Data::gateways)
-                    )
-                    .apply(instance, EndDragonFight.Data::new)
-        );
-        public static final EndDragonFight.Data DEFAULT = new EndDragonFight.Data(
-            true, false, false, false, Optional.empty(), Optional.empty(), Optional.empty()
-        );
+    public static record Data(boolean needsStateScanning, boolean dragonKilled, boolean previouslyKilled, boolean isRespawning, Optional<UUID> dragonUUID, Optional<BlockPos> exitPortalLocation, Optional<List<Integer>> gateways) {
+
+        public static final Codec<EndDragonFight.Data> CODEC = RecordCodecBuilder.create((instance) -> {
+            return instance.group(Codec.BOOL.fieldOf("NeedsStateScanning").orElse(true).forGetter(EndDragonFight.Data::needsStateScanning), Codec.BOOL.fieldOf("DragonKilled").orElse(false).forGetter(EndDragonFight.Data::dragonKilled), Codec.BOOL.fieldOf("PreviouslyKilled").orElse(false).forGetter(EndDragonFight.Data::previouslyKilled), Codec.BOOL.lenientOptionalFieldOf("IsRespawning", false).forGetter(EndDragonFight.Data::isRespawning), UUIDUtil.CODEC.lenientOptionalFieldOf("Dragon").forGetter(EndDragonFight.Data::dragonUUID), BlockPos.CODEC.lenientOptionalFieldOf("ExitPortalLocation").forGetter(EndDragonFight.Data::exitPortalLocation), Codec.list(Codec.INT).lenientOptionalFieldOf("Gateways").forGetter(EndDragonFight.Data::gateways)).apply(instance, EndDragonFight.Data::new);
+        });
+        public static final EndDragonFight.Data DEFAULT = new EndDragonFight.Data(true, false, false, false, Optional.empty(), Optional.empty(), Optional.empty());
     }
 }
