--- a/net/minecraft/server/ChunkRegionLoader.java
+++ b/net/minecraft/server/ChunkRegionLoader.java
@@ -29,7 +29,8 @@
     private final File c;
     private final DataFixer d;
     private PersistentStructureLegacy e;
-    private boolean f;
+    // private boolean f; // CraftBukkit
+    public final LongSet blacklist = new LongOpenHashSet();
 
     public ChunkRegionLoader(File file, DataFixer datafixer) {
         this.c = file;
@@ -38,25 +39,69 @@
 
     @Nullable
     private NBTTagCompound a(GeneratorAccess generatoraccess, int i, int j) throws IOException {
-        return this.a(generatoraccess.o().getDimensionManager(), generatoraccess.h(), i, j);
+        return this.a(generatoraccess.o().getDimensionManager(), generatoraccess.h(), i, j, generatoraccess); // CraftBukkit
+    }
+
+    // CraftBukkit start
+    private boolean check(ChunkProviderServer cps, int x, int z) throws IOException {
+        if (cps != null) {
+            com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
+            if (cps.isLoaded(x, z)) {
+                return true;
+            }
+        }
+
+        if (this.chunkExists(x, z)) {
+            NBTTagCompound nbt = RegionFileCache.read(this.c, x, z);
+            if (nbt != null) {
+                NBTTagCompound level = nbt.getCompound("Level");
+                if (level.getBoolean("TerrainPopulated")) {
+                    return true;
+                }
+
+                ChunkStatus status = ChunkStatus.a(level.getString("Status"));
+                if (status != null && status.a(ChunkStatus.DECORATED)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public boolean chunkExists(int x, int z) {
+        return RegionFileCache.chunkExists(this.c, x, z);
     }
 
     @Nullable
-    private NBTTagCompound a(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection, int i, int j) throws IOException {
+    private NBTTagCompound a(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection, int i, int j, @Nullable GeneratorAccess generatoraccess) throws IOException {
+        // CraftBukkit start
+        if (blacklist.contains(ChunkCoordIntPair.a(i, j))) {
+            return null;
+        }
+        // CraftBukkit end
         NBTTagCompound nbttagcompound = (NBTTagCompound) this.b.get(new ChunkCoordIntPair(i, j));
 
         if (nbttagcompound != null) {
             return nbttagcompound;
         } else {
-            DataInputStream datainputstream = RegionFileCache.read(this.c, i, j);
+            NBTTagCompound nbttagcompound1 = RegionFileCache.read(this.c, i, j);
 
-            if (datainputstream == null) {
+            if (nbttagcompound1 == null) {
                 return null;
             } else {
-                NBTTagCompound nbttagcompound1 = NBTCompressedStreamTools.a(datainputstream);
-
-                datainputstream.close();
                 int k = nbttagcompound1.hasKeyOfType("DataVersion", 99) ? nbttagcompound1.getInt("DataVersion") : -1;
+                // CraftBukkit start
+                if (k < 1466) {
+                    NBTTagCompound level = nbttagcompound1.getCompound("Level");
+                    if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
+                        ChunkProviderServer cps = (generatoraccess == null) ? null : ((WorldServer) generatoraccess).getChunkProvider();
+                        if (check(cps, i - 1, j) && check(cps, i - 1, j - 1) && check(cps, i, j - 1)) {
+                            level.setBoolean("LightPopulated", true);
+                        }
+                    }
+                }
+                // CraftBukkit end
 
                 if (k < 1493) {
                     nbttagcompound1 = GameProfileSerializer.a(this.d, DataFixTypes.CHUNK, nbttagcompound1, k, 1493);
@@ -84,13 +129,29 @@
 
     }
 
+    // CraftBukkit start - Add async variant, provide compatibility
     @Nullable
     public Chunk a(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException {
+        Object[] data = loadChunk(generatoraccess, i, j, consumer);
+        if (data != null) {
+            Chunk chunk = (Chunk) data[0];
+            NBTTagCompound nbttagcompound = (NBTTagCompound) data[1];
+            consumer.accept(chunk);
+            this.loadEntities(nbttagcompound.getCompound("Level"), chunk);
+            return chunk;
+        }
+
+        return null;
+    }
+
+    public Object[] loadChunk(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException {
+        // CraftBukkit end
         NBTTagCompound nbttagcompound = this.a(generatoraccess, i, j);
 
         if (nbttagcompound == null) {
             return null;
         } else {
+            /*
             Chunk chunk = this.a(generatoraccess, i, j, nbttagcompound);
 
             if (chunk != null) {
@@ -99,6 +160,9 @@
             }
 
             return chunk;
+            */
+
+            return this.a(generatoraccess, i, j, nbttagcompound);
         }
     }
 
@@ -130,7 +194,7 @@
     }
 
     @Nullable
-    protected Chunk a(GeneratorAccess generatoraccess, int i, int j, NBTTagCompound nbttagcompound) {
+    protected Object[] a(GeneratorAccess generatoraccess, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
         if (nbttagcompound.hasKeyOfType("Level", 10) && nbttagcompound.getCompound("Level").hasKeyOfType("Status", 8)) {
             ChunkStatus.Type chunkstatus_type = this.a(nbttagcompound);
 
@@ -149,10 +213,28 @@
                         ChunkRegionLoader.a.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", i, j, i, j, chunk.locX, chunk.locZ);
                         nbttagcompound1.setInt("xPos", i);
                         nbttagcompound1.setInt("zPos", j);
+
+                        // CraftBukkit start - Have to move tile entities since we don't load them at this stage
+                        NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
+                        if (tileEntities != null) {
+                            for (int te = 0; te < tileEntities.size(); te++) {
+                                NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
+                                int x = tileEntity.getInt("x") - chunk.locX * 16;
+                                int z = tileEntity.getInt("z") - chunk.locZ * 16;
+                                tileEntity.setInt("x", i * 16 + x);
+                                tileEntity.setInt("z", j * 16 + z);
+                            }
+                        }
+                        // CraftBukkit end
                         chunk = this.a(generatoraccess, nbttagcompound1);
                     }
 
-                    return chunk;
+                    // CraftBukkit start
+                    Object[] data = new Object[2];
+                    data[0] = chunk;
+                    data[1] = nbttagcompound;
+                    return data;
+                    // CraftBukkit end
                 }
             }
         } else {
@@ -167,7 +249,7 @@
             ChunkStatus.Type chunkstatus_type = this.a(nbttagcompound);
 
             if (chunkstatus_type == ChunkStatus.Type.LEVELCHUNK) {
-                return new ProtoChunkExtension(this.a(generatoraccess, i, j, nbttagcompound));
+                return new ProtoChunkExtension((IChunkAccess) this.a(generatoraccess, i, j, nbttagcompound)[0]); // CraftBukkit - fix up access
             } else {
                 NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");
 
@@ -215,10 +297,15 @@
     }
 
     public boolean a() {
+        // CraftBukkit start
+        return this.processSaveQueueEntry(false);
+    }
+
+    private boolean processSaveQueueEntry(boolean logCompletion) {
         Iterator<Entry<ChunkCoordIntPair, NBTTagCompound>> iterator = this.b.entrySet().iterator();
 
         if (!iterator.hasNext()) {
-            if (this.f) {
+            if (logCompletion) { // CraftBukkit
                 ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.c.getName());
             }
 
@@ -234,10 +321,14 @@
                 return true;
             } else {
                 try {
-                    DataOutputStream dataoutputstream = RegionFileCache.write(this.c, chunkcoordintpair.x, chunkcoordintpair.z);
+                    // CraftBukkit start
+                    RegionFileCache.write(this.c, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound);
 
+                    /*
                     NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream);
                     dataoutputstream.close();
+                    */
+                    // CraftBukkit end
                     if (this.e != null) {
                         this.e.a(chunkcoordintpair.a());
                     }
@@ -264,15 +355,16 @@
 
     public void b() {
         try {
-            this.f = true;
+            // this.f = true; // CraftBukkit
 
             while (true) {
-                if (this.a()) {
+                if (this.processSaveQueueEntry(true)) { // CraftBukkit
                     continue;
                 }
+                break; // CraftBukkit - Fix infinite loop when saving chunks
             }
         } finally {
-            this.f = false;
+            // this.f = false; // CraftBukkit
         }
 
     }
@@ -301,7 +393,7 @@
 
         if (abiomebase != null) {
             for (int k = 0; k < abiomebase.length; ++k) {
-                aint[k] = IRegistry.BIOME.a((Object) abiomebase[k]);
+                aint[k] = IRegistry.BIOME.a(abiomebase[k]); // CraftBukkit - decompile error
             }
         }
 
@@ -383,7 +475,7 @@
         int[] aint = new int[abiomebase.length];
 
         for (int i = 0; i < abiomebase.length; ++i) {
-            aint[i] = IRegistry.BIOME.a((Object) abiomebase[i]);
+            aint[i] = IRegistry.BIOME.a(abiomebase[i]); // CraftBukkit - decompile error
         }
 
         nbttagcompound.setIntArray("Biomes", aint);
@@ -833,17 +925,29 @@
     }
 
     @Nullable
+    // CraftBukkit start
     public static Entity a(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag) {
+        return spawnEntity(nbttagcompound, world, d0, d1, d2, flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+    }
+
+    public static Entity spawnEntity(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
+        // CraftBukkit end
         return a(nbttagcompound, world, (entity) -> {
             entity.setPositionRotation(d0, d1, d2, entity.yaw, entity.pitch);
-            return flag && !world.addEntity(entity) ? null : entity;
+            return flag && !world.addEntity(entity, spawnReason) ? null : entity;
         });
     }
 
     @Nullable
+    // CraftBukkit start
     public static Entity a(NBTTagCompound nbttagcompound, World world, boolean flag) {
+        return spawnEntity(nbttagcompound, world, flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+    }
+
+    public static Entity spawnEntity(NBTTagCompound nbttagcompound, World world, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
+        // CraftBukkit end
         return a(nbttagcompound, world, (entity) -> {
-            return flag && !world.addEntity(entity) ? null : entity;
+            return flag && !world.addEntity(entity, spawnReason) ? null : entity; // CraftBukkit
         });
     }
 
@@ -857,8 +961,14 @@
         }
     }
 
+    // CraftBukkit start
     public static void a(Entity entity, GeneratorAccess generatoraccess) {
-        if (generatoraccess.addEntity(entity) && entity.isVehicle()) {
+        a(entity, generatoraccess, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+    }
+
+    public static void a(Entity entity, GeneratorAccess generatoraccess, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+        if (generatoraccess.addEntity(entity, reason) && entity.isVehicle()) {
+            // CraftBukkit end
             Iterator iterator = entity.bP().iterator();
 
             while (iterator.hasNext()) {
@@ -874,7 +984,7 @@
         boolean flag = false;
 
         try {
-            this.a(dimensionmanager, persistentcollection, chunkcoordintpair.x, chunkcoordintpair.z);
+            this.a(dimensionmanager, persistentcollection, chunkcoordintpair.x, chunkcoordintpair.z, null); // CraftBukkit
 
             while (this.a()) {
                 flag = true;