--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
+++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
@@ -66,36 +_,37 @@
     public static final int TIME_BETWEEN_PLAYER_SCANS = 20;
     private static final int ARENA_SIZE_CHUNKS = 8;
     public static final int ARENA_TICKET_LEVEL = 9;
-    private static final int GATEWAY_COUNT = 20;
+    public static final int GATEWAY_COUNT = 20;
     private static final int GATEWAY_DISTANCE = 96;
     public static final int DRAGON_SPAWN_Y = 128;
     private final Predicate<Entity> validPlayer;
-    private final ServerBossEvent dragonEvent = (ServerBossEvent)new ServerBossEvent(
-            Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS
+    private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - reset EnderDragon boss event name
+    public final ServerBossEvent dragonEvent = (ServerBossEvent)new ServerBossEvent(
+            DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS // Paper - reset EnderDragon boss event name
         )
         .setPlayBossMusic(true)
         .setCreateWorldFog(true);
-    private final ServerLevel level;
+    public final ServerLevel level;
     private final BlockPos origin;
-    private final ObjectArrayList<Integer> gateways = new ObjectArrayList<>();
+    public final ObjectArrayList<Integer> gateways = new ObjectArrayList<>();
     private final BlockPattern exitPortalPattern;
     private int ticksSinceDragonSeen;
     private int crystalsAlive;
     private int ticksSinceCrystalsScanned;
     private int ticksSinceLastPlayerScan = 21;
     private boolean dragonKilled;
-    private boolean previouslyKilled;
+    public boolean previouslyKilled;
     private boolean skipArenaLoadedCheck = false;
     @Nullable
-    private UUID dragonUUID;
+    public UUID dragonUUID;
     private boolean needsStateScanning = true;
     @Nullable
-    private BlockPos portalLocation;
+    public BlockPos portalLocation;
     @Nullable
-    private DragonRespawnAnimation respawnStage;
+    public DragonRespawnAnimation respawnStage;
     private int respawnTime;
     @Nullable
-    private List<EndCrystal> respawnCrystals;
+    public List<EndCrystal> respawnCrystals;
 
     public EndDragonFight(ServerLevel level, long seed, EndDragonFight.Data data) {
         this(level, seed, data, BlockPos.ZERO);
@@ -112,7 +_,12 @@
         if (data.isRespawning) {
             this.respawnStage = DragonRespawnAnimation.START;
         }
-
+        // Paper start - Add config to disable ender dragon legacy check
+        if (data == EndDragonFight.Data.DEFAULT && !level.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> list = new ObjectArrayList<>(ContiguousSet.create(Range.closedOpen(0, 20), DiscreteDomain.integers()));
@@ -209,9 +_,9 @@
             this.dragonUUID = enderDragon.getUUID();
             LOGGER.info("Found that there's a dragon still alive ({})", enderDragon);
             this.dragonKilled = false;
-            if (!hasActiveExitPortal) {
+            if (!hasActiveExitPortal && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon
                 LOGGER.info("But we didn't have a portal, let's remove it.");
-                enderDragon.discard();
+                enderDragon.discard(null); // CraftBukkit - add Bukkit remove cause
                 this.dragonUUID = null;
             }
         }
@@ -232,7 +_,7 @@
         }
     }
 
-    protected void setRespawnStage(DragonRespawnAnimation state) {
+    public void setRespawnStage(DragonRespawnAnimation state) {
         if (this.respawnStage == null) {
             throw new IllegalStateException("Dragon respawn isn't in progress, can't skip ahead in the animation.");
         } else {
@@ -269,7 +_,7 @@
     }
 
     @Nullable
-    private BlockPattern.BlockPatternMatch findExitPortal() {
+    public BlockPattern.BlockPatternMatch findExitPortal() {
         ChunkPos chunkPos = new ChunkPos(this.origin);
 
         for (int i = -8 + chunkPos.x; i <= 8 + chunkPos.x; i++) {
@@ -366,12 +_,22 @@
             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;
@@ -385,6 +_,24 @@
         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);
@@ -394,7 +_,7 @@
         }
     }
 
-    private void spawnNewGateway(BlockPos pos) {
+    public void spawnNewGateway(BlockPos pos) {
         this.level.levelEvent(3000, pos, 0);
         this.level
             .registryAccess()
@@ -403,7 +_,7 @@
             .ifPresent(endGatewayFeature -> endGatewayFeature.value().place(this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), pos));
     }
 
-    private void spawnExitPortal(boolean active) {
+    public void spawnExitPortal(boolean active) {
         EndPodiumFeature endPodiumFeature = new EndPodiumFeature(active);
         if (this.portalLocation == null) {
             this.portalLocation = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.origin)).below();
@@ -413,6 +_,11 @@
             }
         }
 
+        // Paper start - Prevent "softlocked" exit portal generation
+        if (this.portalLocation.getY() <= this.level.getMinY()) {
+            this.portalLocation = this.portalLocation.atY(this.level.getMinY() + 1);
+        }
+        // Paper end - Prevent "softlocked" exit portal generation
         if (endPodiumFeature.place(
             FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation
         )) {
@@ -432,6 +_,7 @@
             enderDragon.moveTo(this.origin.getX(), 128 + this.origin.getY(), this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F);
             this.level.addFreshEntity(enderDragon);
             this.dragonUUID = enderDragon.getUUID();
+            this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn
         }
 
         return enderDragon;
@@ -443,6 +_,10 @@
             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
             }
         }
     }
@@ -470,7 +_,13 @@
         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) {
@@ -485,6 +_,22 @@
 
                 blockPos = 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() - blockPos.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() - blockPos.getX();
+                int dz = placedEndCrystalPos.getZ() - blockPos.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 blockPos1 = blockPos.above(1);
@@ -492,18 +_,19 @@
             for (Direction direction : Direction.Plane.HORIZONTAL) {
                 List<EndCrystal> entitiesOfClass = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockPos1.relative(direction, 2)));
                 if (entitiesOfClass.isEmpty()) {
-                    return;
+                    return false; // CraftBukkit - return value
                 }
 
                 list.addAll(entitiesOfClass);
             }
 
             LOGGER.debug("Found all crystals, respawning dragon.");
-            this.respawnDragon(list);
+            return this.respawnDragon(list); // CraftBukkit - return value
         }
+        return false; // CraftBukkit - return value
     }
 
-    private void respawnDragon(List<EndCrystal> crystals) {
+    public boolean respawnDragon(List<EndCrystal> crystals) { // 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++) {
@@ -522,7 +_,9 @@
             this.respawnTime = 0;
             this.spawnExitPortal(false);
             this.respawnCrystals = crystals;
+            return true; // CraftBukkit - return value
         }
+        return false; // CraftBukkit - return value
     }
 
     public void resetSpikeCrystals() {
