diff --git a/README.md b/README.md
index ae6f79e90..09b28f24b 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ FAWE is a fork of WorldEdit that has huge speed and memory improvements and cons
 ## Downloads
 ### 1.13+
 * [Download](https://intellectualsites.github.io/download/fawe.html)
-* [Jenkins](https://ci.athion.net/job/FastAsyncWorldEdit-1.15/)
+* [Jenkins](https://ci.athion.net/job/FastAsyncWorldEdit-1.16/)
 
 ### < 1.12.2
 * [Download](https://intellectualsites.github.io/download/fawe.html)
@@ -35,7 +35,7 @@ $ gradlew setupDecompWorkspace
 $ gradlew build
 ```
 
-The jar is located in `worldedit-bukkit/build/libs/FastAsyncWorldEdit-1.15-###.jar`
+The jar is located in `worldedit-bukkit/build/libs/FastAsyncWorldEdit-1.16-###.jar`
 
 ## Contributing
 Have an idea for an optimization, or a cool feature?
diff --git a/build.gradle.kts b/build.gradle.kts
index b6e47f76c..84e6e066f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -15,7 +15,7 @@ logger.lifecycle("""
 *******************************************
 """)
 //TODO FIX THIS WHEN I FEEL LIKE IT
-var rootVersion = "1.15"
+var rootVersion = "1.16"
 var revision: String = ""
 var buildNumber = ""
 var date: String = ""
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java
index 823904be1..6a5327618 100644
--- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java
@@ -58,6 +58,7 @@ public class FaweBukkit implements IFawe, Listener {
     private boolean listeningImages;
     private BukkitImageListener imageListener;
     private CFIPacketListener packetListener;
+    private final boolean chunksStretched;
 
     public VaultUtil getVault() {
         return this.vault;
@@ -81,6 +82,8 @@ public class FaweBukkit implements IFawe, Listener {
             e.printStackTrace();
             Bukkit.getServer().shutdown();
         }
+
+        chunksStretched = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]) >= 16;
         
         //Vault is Spigot/Paper only so this needs to be done in the Bukkit module
         setupVault();
@@ -302,6 +305,11 @@ public class FaweBukkit implements IFawe, Listener {
         return null;
     }
 
+    @Override
+    public boolean isChunksStretched() {
+        return chunksStretched;
+    }
+
     private void setupPlotSquared() {
         Plugin plotSquared = this.plugin.getServer().getPluginManager().getPlugin("PlotSquared");
         if (plotSquared == null)
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java
index eace17737..ae871b419 100644
--- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java
@@ -56,7 +56,7 @@ import net.minecraft.server.v1_14_R1.Entity;
 import net.minecraft.server.v1_14_R1.EntityTypes;
 import net.minecraft.server.v1_14_R1.EnumSkyBlock;
 import net.minecraft.server.v1_14_R1.IBlockData;
-import net.minecraft.server.v1_14_R1.LightEngineThreaded;
+import net.minecraft.server.v1_14_R1.LightEngine;
 import net.minecraft.server.v1_14_R1.NBTTagCompound;
 import net.minecraft.server.v1_14_R1.NBTTagInt;
 import net.minecraft.server.v1_14_R1.NibbleArray;
@@ -69,8 +69,13 @@ import org.bukkit.craftbukkit.v1_14_R1.CraftWorld;
 import org.bukkit.craftbukkit.v1_14_R1.block.CraftBlock;
 import org.bukkit.event.entity.CreatureSpawnEvent;
 import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class BukkitGetBlocks_1_14 extends CharGetBlocks {
+
+    private static final Logger log = LoggerFactory.getLogger(BukkitGetBlocks_1_14.class);
+
     public ChunkSection[] sections;
     public Chunk nmsChunk;
     public WorldServer world;
@@ -129,7 +134,17 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks {
         int layer = y >> 4;
         if (skyLight[layer] == null) {
             //getDataLayerData
-            skyLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(SectionPosition.a(nmsChunk.getPos(), layer));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer);
+            NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(sectionPosition);
+            // If the server hasn't generated the section's NibbleArray yet, it will be null
+            if (nibbleArray == null) {
+               byte[] a = new byte[2048];
+               // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway.
+               Arrays.fill(a, (byte) 15);
+               nibbleArray = new NibbleArray(a);
+               ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.SKY, sectionPosition, nibbleArray);
+            }
+            skyLight[layer] = nibbleArray;
         }
         long l = BlockPosition.a(x, y, z);
         return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l)));
@@ -140,7 +155,17 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks {
         int layer = y >> 4;
         if (blockLight[layer] == null) {
             //getDataLayerData
-            blockLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(SectionPosition.a(nmsChunk.getPos(), layer));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer);
+            NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(sectionPosition);
+            // If the server hasn't generated the section's NibbleArray yet, it will be null
+            if (nibbleArray == null) {
+                byte[] a = new byte[2048];
+                // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway.
+                Arrays.fill(a, (byte) 15);
+                nibbleArray = new NibbleArray(a);
+                ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.BLOCK, sectionPosition, nibbleArray);
+            }
+            blockLight[layer] = nibbleArray;
         }
         long l = BlockPosition.a(x, y, z);
         return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l)));
@@ -299,7 +324,7 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks {
                         } else {
                             existingSection = sections[layer];
                             if (existingSection == null) {
-                                System.out.println("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer);
+                                log.error("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer);
                                 continue;
                             }
                         }
@@ -326,7 +351,7 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks {
                             }
                             newSection = BukkitAdapter_1_14.newChunkSection(layer, this::load, setArr, fastmode);
                             if (!BukkitAdapter_1_14.setSectionAtomic(sections, existingSection, newSection, layer)) {
-                                System.out.println("Failed to set chunk section:" + X + "," + Z + " layer: " + layer);
+                                log.error("Failed to set chunk section:" + X + "," + Z + " layer: " + layer);
                                 continue;
                             } else {
                                 updateGet(this, nmsChunk, sections, newSection, setArr, layer);
@@ -667,9 +692,13 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks {
             if (light[Y] == null) {
                 continue;
             }
-            NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(SectionPosition.a(nmsChunk.getPos(), Y));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), Y);
+            NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(sectionPosition);
             if (nibble == null) {
-                continue;
+                byte[] a = new byte[2048];
+                Arrays.fill(a, skyBlock == EnumSkyBlock.SKY ? (byte) 15 : (byte) 0);
+                nibble = new NibbleArray(a);
+                ((LightEngine) world.getChunkProvider().getLightEngine()).a(skyBlock, sectionPosition, nibble);
             }
             synchronized (nibble) {
                 for (int i = 0; i < 4096; i++) {
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java
index 3292cc556..e0cbf7266 100644
--- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java
@@ -40,6 +40,7 @@ import net.minecraft.server.v1_15_R1.Entity;
 import net.minecraft.server.v1_15_R1.EntityTypes;
 import net.minecraft.server.v1_15_R1.EnumSkyBlock;
 import net.minecraft.server.v1_15_R1.IBlockData;
+import net.minecraft.server.v1_15_R1.LightEngine;
 import net.minecraft.server.v1_15_R1.NBTTagCompound;
 import net.minecraft.server.v1_15_R1.NBTTagInt;
 import net.minecraft.server.v1_15_R1.NibbleArray;
@@ -140,7 +141,17 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks {
     public int getSkyLight(int x, int y, int z) {
         int layer = y >> 4;
         if (skyLight[layer] == null) {
-            skyLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(SectionPosition.a(nmsChunk.getPos(), layer));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer);
+            NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(sectionPosition);
+            // If the server hasn't generated the section's NibbleArray yet, it will be null
+            if (nibbleArray == null) {
+               byte[] a = new byte[2048];
+               // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway.
+               Arrays.fill(a, (byte) 15);
+               nibbleArray = new NibbleArray(a);
+               ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.SKY, sectionPosition, nibbleArray);
+            }
+            skyLight[layer] = nibbleArray;
         }
         long l = BlockPosition.a(x, y, z);
         return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l)));
@@ -150,7 +161,17 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks {
     public int getEmmittedLight(int x, int y, int z) {
         int layer = y >> 4;
         if (blockLight[layer] == null) {
-            blockLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(SectionPosition.a(nmsChunk.getPos(), layer));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer);
+            NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(sectionPosition);
+            // If the server hasn't generated the section's NibbleArray yet, it will be null
+            if (nibbleArray == null) {
+                byte[] a = new byte[2048];
+                // Safe enough to assume if it's not created, it's not got any emitted light. Unlikely to be created before lighting is fixed anyway.
+                Arrays.fill(a, (byte) 0);
+                nibbleArray = new NibbleArray(a);
+                ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.BLOCK, sectionPosition, nibbleArray);
+            }
+            blockLight[layer] = nibbleArray;
         }
         long l = BlockPosition.a(x, y, z);
         return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l)));
@@ -316,7 +337,7 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks {
                         } else {
                             existingSection = sections[layer];
                             if (existingSection == null) {
-                                System.out.println("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer);
+                                log.error("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer);
                                 continue;
                             }
                         }
@@ -343,7 +364,7 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks {
                             }
                             newSection = BukkitAdapter_1_15_2.newChunkSection(layer, this::load, setArr, fastmode);
                             if (!BukkitAdapter_1_15_2.setSectionAtomic(sections, existingSection, newSection, layer)) {
-                                System.out.println("Failed to set chunk section:" + X + "," + Z + " layer: " + layer);
+                                log.error("Failed to set chunk section:" + X + "," + Z + " layer: " + layer);
                                 continue;
                             } else {
                                 updateGet(this, nmsChunk, sections, newSection, setArr, layer);
@@ -689,9 +710,13 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks {
             if (light[Y] == null) {
                 continue;
             }
-            NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(SectionPosition.a(nmsChunk.getPos(), Y));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), Y);
+            NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(sectionPosition);
             if (nibble == null) {
-                continue;
+                byte[] a = new byte[2048];
+                Arrays.fill(a, skyBlock == EnumSkyBlock.SKY ? (byte) 15 : (byte) 0);
+                nibble = new NibbleArray(a);
+                ((LightEngine) world.getChunkProvider().getLightEngine()).a(skyBlock, sectionPosition, nibble);
             }
             synchronized (nibble) {
                 for (int i = 0; i < 4096; i++) {
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java
index 7b1549d65..a8e096e0c 100644
--- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java
@@ -5,7 +5,7 @@ import com.boydti.fawe.FaweCache;
 import com.boydti.fawe.bukkit.adapter.DelegateLock;
 import com.boydti.fawe.bukkit.adapter.NMSAdapter;
 import com.boydti.fawe.config.Settings;
-import com.boydti.fawe.object.collection.BitArray;
+import com.boydti.fawe.object.collection.BitArrayUnstretched;
 import com.boydti.fawe.util.MathMan;
 import com.boydti.fawe.util.ReflectionUtils;
 import com.boydti.fawe.util.TaskManager;
@@ -232,11 +232,13 @@ public final class BukkitAdapter_1_16_1 extends NMSAdapter {
                 bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries
             }
 
-            final int blockBitArrayEnd = (bitsPerEntry * 4096) >> 6;
+            final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry);
+            final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong);
+
             if (num_palette == 1) {
                 for (int i = 0; i < blockBitArrayEnd; i++) blockStates[i] = 0;
             } else {
-                final BitArray bitArray = new BitArray(bitsPerEntry, 4096, blockStates);
+                final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, blockStates);
                 bitArray.fromRaw(blocksCopy);
             }
 
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java
index b07589339..d6dada109 100644
--- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java
@@ -10,7 +10,7 @@ import com.boydti.fawe.bukkit.adapter.DelegateLock;
 import com.boydti.fawe.bukkit.adapter.mc1_16_1.nbt.LazyCompoundTag_1_16_1;
 import com.boydti.fawe.config.Settings;
 import com.boydti.fawe.object.collection.AdaptedMap;
-import com.boydti.fawe.object.collection.BitArray;
+import com.boydti.fawe.object.collection.BitArrayUnstretched;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.Iterables;
 import com.sk89q.jnbt.Tag;
@@ -43,8 +43,7 @@ import static org.slf4j.LoggerFactory.getLogger;
 
 public class BukkitGetBlocks_1_16_1 extends CharGetBlocks {
 
-    private static final Logger log = LoggerFactory.getLogger(
-        BukkitGetBlocks_1_16_1.class);
+    private static final Logger log = LoggerFactory.getLogger(BukkitGetBlocks_1_16_1.class);
 
     private static final Function<BlockPosition, BlockVector3> posNms2We = v -> BlockVector3.at(v.getX(), v.getY(), v.getZ());
     private final static Function<TileEntity, CompoundTag> nmsTile2We = tileEntity -> new LazyCompoundTag_1_16_1(Suppliers.memoize(() -> tileEntity.save(new NBTTagCompound())));
@@ -110,7 +109,17 @@ public class BukkitGetBlocks_1_16_1 extends CharGetBlocks {
     public int getSkyLight(int x, int y, int z) {
         int layer = y >> 4;
         if (skyLight[layer] == null) {
-            skyLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(SectionPosition.a(nmsChunk.getPos(), layer));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer);
+            NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(sectionPosition);
+            // If the server hasn't generated the section's NibbleArray yet, it will be null
+            if (nibbleArray == null) {
+               byte[] a = new byte[2048];
+               // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway.
+               Arrays.fill(a, (byte) 15);
+               nibbleArray = new NibbleArray(a);
+               ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.SKY, sectionPosition, nibbleArray, true);
+            }
+            skyLight[layer] = nibbleArray;
         }
         long l = BlockPosition.a(x, y, z);
         return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l)));
@@ -119,8 +128,18 @@ public class BukkitGetBlocks_1_16_1 extends CharGetBlocks {
     @Override
     public int getEmmittedLight(int x, int y, int z) {
         int layer = y >> 4;
-        if (blockLight[layer] == null) {
-            blockLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(SectionPosition.a(nmsChunk.getPos(), layer));
+        if (skyLight[layer] == null) {
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer);
+            NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(sectionPosition);
+            // If the server hasn't generated the section's NibbleArray yet, it will be null
+            if (nibbleArray == null) {
+               byte[] a = new byte[2048];
+               // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway.
+               Arrays.fill(a, (byte) 15);
+               nibbleArray = new NibbleArray(a);
+               ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.BLOCK, sectionPosition, nibbleArray, true);
+            }
+            skyLight[layer] = nibbleArray;
         }
         long l = BlockPosition.a(x, y, z);
         return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l)));
@@ -286,7 +305,7 @@ public class BukkitGetBlocks_1_16_1 extends CharGetBlocks {
                         } else {
                             existingSection = sections[layer];
                             if (existingSection == null) {
-                                System.out.println("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer);
+                                log.error("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer);
                                 continue;
                             }
                         }
@@ -315,7 +334,7 @@ public class BukkitGetBlocks_1_16_1 extends CharGetBlocks {
                                 .newChunkSection(layer, this::load, setArr, fastmode);
                             if (!BukkitAdapter_1_16_1
                                 .setSectionAtomic(sections, existingSection, newSection, layer)) {
-                                System.out.println("Failed to set chunk section:" + X + "," + Z + " layer: " + layer);
+                                log.error("Failed to set chunk section:" + X + "," + Z + " layer: " + layer);
                                 continue;
                             } else {
                                 updateGet(this, nmsChunk, sections, newSection, setArr, layer);
@@ -553,7 +572,7 @@ public class BukkitGetBlocks_1_16_1 extends CharGetBlocks {
                 final int bitsPerEntry = (int) BukkitAdapter_1_16_1.fieldBitsPerEntry.get(bits);
                 final long[] blockStates = bits.a();
 
-                new BitArray(bitsPerEntry, 4096, blockStates).toRaw(data);
+                new BitArrayUnstretched(bitsPerEntry, blockStates).toRaw(data);
 
                 int num_palette;
                 if (palette instanceof DataPaletteLinear) {
@@ -662,9 +681,13 @@ public class BukkitGetBlocks_1_16_1 extends CharGetBlocks {
             if (light[Y] == null) {
                 continue;
             }
-            NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(SectionPosition.a(nmsChunk.getPos(), Y));
+            SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), Y);
+            NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(sectionPosition);
             if (nibble == null) {
-                continue;
+                byte[] a = new byte[2048];
+                Arrays.fill(a, skyBlock == EnumSkyBlock.SKY ? (byte) 15 : (byte) 0);
+                nibble = new NibbleArray(a);
+                ((LightEngine) world.getChunkProvider().getLightEngine()).a(skyBlock, sectionPosition, nibble, true);
             }
             synchronized (nibble) {
                 for (int i = 0; i < 4096; i++) {
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java
index 60e0f8037..847b17a62 100644
--- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java
@@ -84,7 +84,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     private BukkitImplAdapter adapter;
 
     @Override
-    public <T> void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6, T t) {
+    public <T> void spawnParticle(@NotNull Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6, T t) {
         parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, v6, t);
     }
 
@@ -158,77 +158,77 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public WorldBorder getWorldBorder() {
+    public @NotNull WorldBorder getWorldBorder() {
         return TaskManager.IMP.sync(() -> parent.getWorldBorder());
     }
 
     @Override
-    public void spawnParticle(Particle particle, Location location, int i) {
+    public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int i) {
         parent.spawnParticle(particle, location, i);
     }
 
     @Override
-    public void spawnParticle(Particle particle, double v, double v1, double v2, int i) {
+    public void spawnParticle(@NotNull Particle particle, double v, double v1, double v2, int i) {
         parent.spawnParticle(particle, v, v1, v2, i);
     }
 
     @Override
-    public <T> void spawnParticle(Particle particle, Location location, int i, T t) {
+    public <T> void spawnParticle(@NotNull Particle particle, @NotNull Location location, int i, T t) {
         parent.spawnParticle(particle, location, i, t);
     }
 
     @Override
-    public <T> void spawnParticle(Particle particle, double x, double y, double z, int count, T data) {
+    public <T> void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, T data) {
         parent.spawnParticle(particle, x, y, z, count, data);
     }
 
     @Override
-    public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ) {
+    public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ) {
         parent.spawnParticle(particle, location, count, offsetX, offsetY, offsetZ);
     }
 
     @Override
-    public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5) {
+    public void spawnParticle(@NotNull Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5) {
         parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5);
     }
 
     @Override
-    public <T> void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, T t) {
+    public <T> void spawnParticle(@NotNull Particle particle, @NotNull Location location, int i, double v, double v1, double v2, T t) {
         parent.spawnParticle(particle, location, i, v, v1, v2, t);
     }
 
     @Override
-    public <T> void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, T t) {
+    public <T> void spawnParticle(@NotNull Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, T t) {
         parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, t);
     }
 
     @Override
-    public void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, double v3) {
+    public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int i, double v, double v1, double v2, double v3) {
         parent.spawnParticle(particle, location, i, v, v1, v2, v3);
     }
 
     @Override
-    public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6) {
+    public void spawnParticle(@NotNull Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6) {
         parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, v6);
     }
 
     @Override
-    public <T> void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, double v3, T t) {
+    public <T> void spawnParticle(@NotNull Particle particle, @NotNull Location location, int i, double v, double v1, double v2, double v3, T t) {
         parent.spawnParticle(particle, location, i, v, v1, v2, v3, t);
     }
 
     @Override
-    public boolean setSpawnLocation(Location location) {
+    public boolean setSpawnLocation(@NotNull Location location) {
         return parent.setSpawnLocation(location);
     }
 
     @Override
-    public AsyncBlock getBlockAt(final int x, final int y, final int z) {
+    public @NotNull AsyncBlock getBlockAt(final int x, final int y, final int z) {
         return new AsyncBlock(this, x, y, z);
     }
 
     @Override
-    public AsyncBlock getBlockAt(Location loc) {
+    public @NotNull AsyncBlock getBlockAt(Location loc) {
         return getBlockAt(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
     }
 
@@ -247,13 +247,13 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public AsyncBlock getHighestBlockAt(int x, int z) {
+    public @NotNull AsyncBlock getHighestBlockAt(int x, int z) {
         int y = getHighestBlockYAt(x, z);
         return getBlockAt(x, y, z);
     }
 
     @Override
-    public AsyncBlock getHighestBlockAt(Location loc) {
+    public @NotNull AsyncBlock getHighestBlockAt(Location loc) {
         return getHighestBlockAt(loc.getBlockX(), loc.getBlockZ());
     }
 
@@ -279,17 +279,17 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public AsyncChunk getChunkAt(int x, int z) {
+    public @NotNull AsyncChunk getChunkAt(int x, int z) {
         return new AsyncChunk(this, x, z);
     }
 
     @Override
-    public AsyncChunk getChunkAt(Location location) {
+    public @NotNull AsyncChunk getChunkAt(Location location) {
         return getChunkAt(location.getBlockX(), location.getBlockZ());
     }
 
     @Override
-    public AsyncChunk getChunkAt(Block block) {
+    public @NotNull AsyncChunk getChunkAt(Block block) {
         return getChunkAt(block.getX(), block.getZ());
     }
 
@@ -405,17 +405,17 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public Item dropItem(final Location location, final ItemStack item) {
+    public @NotNull Item dropItem(final @NotNull Location location, final @NotNull ItemStack item) {
         return TaskManager.IMP.sync(() -> parent.dropItem(location, item));
     }
 
     @Override
-    public Item dropItemNaturally(final Location location, final ItemStack item) {
+    public @NotNull Item dropItemNaturally(final @NotNull Location location, final @NotNull ItemStack item) {
         return TaskManager.IMP.sync(() -> parent.dropItemNaturally(location, item));
     }
 
     @Override
-    public Arrow spawnArrow(final Location location, final Vector direction, final float speed, final float spread) {
+    public @NotNull Arrow spawnArrow(final @NotNull Location location, final @NotNull Vector direction, final float speed, final float spread) {
         return TaskManager.IMP.sync(() -> parent.spawnArrow(location, direction, speed, spread));
     }
 
@@ -425,78 +425,78 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public boolean generateTree(final Location location, final TreeType type) {
+    public boolean generateTree(final @NotNull Location location, final @NotNull TreeType type) {
         return TaskManager.IMP.sync(() -> parent.generateTree(location, type));
     }
 
     @Override
-    public boolean generateTree(final Location loc, final TreeType type, final BlockChangeDelegate delegate) {
+    public boolean generateTree(final @NotNull Location loc, final @NotNull TreeType type, final @NotNull BlockChangeDelegate delegate) {
         return TaskManager.IMP.sync(() -> parent.generateTree(loc, type, delegate));
     }
 
     @Override
-    public Entity spawnEntity(Location loc, EntityType type) {
+    public @NotNull Entity spawnEntity(@NotNull Location loc, EntityType type) {
         return spawn(loc, type.getEntityClass());
     }
 
     @Override
-    public LightningStrike strikeLightning(final Location loc) {
+    public @NotNull LightningStrike strikeLightning(final @NotNull Location loc) {
         return TaskManager.IMP.sync(() -> parent.strikeLightning(loc));
     }
 
     @Override
-    public LightningStrike strikeLightningEffect(final Location loc) {
+    public @NotNull LightningStrike strikeLightningEffect(final @NotNull Location loc) {
         return TaskManager.IMP.sync(() -> parent.strikeLightningEffect(loc));
     }
 
     @Override
-    public List getEntities() {
+    public @NotNull List getEntities() {
         return TaskManager.IMP.sync(() -> parent.getEntities());
     }
 
     @Override
-    public List<LivingEntity> getLivingEntities() {
+    public @NotNull List<LivingEntity> getLivingEntities() {
         return TaskManager.IMP.sync(() -> parent.getLivingEntities());
     }
 
     @Override
     @Deprecated
-    public <T extends Entity> Collection<T> getEntitiesByClass(final Class<T>... classes) {
+    public <T extends Entity> @NotNull Collection<T> getEntitiesByClass(final Class<T>... classes) {
         return TaskManager.IMP.sync(() -> parent.getEntitiesByClass(classes));
     }
 
     @Override
-    public <T extends Entity> Collection<T> getEntitiesByClass(final Class<T> cls) {
+    public <T extends Entity> @NotNull Collection<T> getEntitiesByClass(final @NotNull Class<T> cls) {
         return TaskManager.IMP.sync(() -> parent.getEntitiesByClass(cls));
     }
 
     @Override
-    public Collection<Entity> getEntitiesByClasses(final Class<?>... classes) {
+    public @NotNull Collection<Entity> getEntitiesByClasses(final Class<?>... classes) {
         return TaskManager.IMP.sync(() -> parent.getEntitiesByClasses(classes));
     }
 
     @Override
-    public List<Player> getPlayers() {
+    public @NotNull List<Player> getPlayers() {
         return TaskManager.IMP.sync(() -> parent.getPlayers());
     }
 
     @Override
-    public Collection<Entity> getNearbyEntities(final Location location, final double x, final double y, final double z) {
+    public @NotNull Collection<Entity> getNearbyEntities(final @NotNull Location location, final double x, final double y, final double z) {
         return TaskManager.IMP.sync(() -> parent.getNearbyEntities(location, x, y, z));
     }
 
     @Override
-    public String getName() {
+    public @NotNull String getName() {
         return parent.getName();
     }
 
     @Override
-    public UUID getUID() {
+    public @NotNull UUID getUID() {
         return parent.getUID();
     }
 
     @Override
-    public Location getSpawnLocation() {
+    public @NotNull Location getSpawnLocation() {
         return parent.getSpawnLocation();
     }
 
@@ -590,7 +590,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public boolean createExplosion(Location loc, float power) {
+    public boolean createExplosion(@NotNull Location loc, float power) {
         return this.createExplosion(loc, power, false);
     }
 
@@ -636,17 +636,17 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public List<BlockPopulator> getPopulators() {
+    public @NotNull List<BlockPopulator> getPopulators() {
         return parent.getPopulators();
     }
 
     @Override
-    public <T extends Entity> T spawn(final Location location, final Class<T> clazz) throws IllegalArgumentException {
+    public <T extends Entity> @NotNull T spawn(final @NotNull Location location, final @NotNull Class<T> clazz) throws IllegalArgumentException {
         return TaskManager.IMP.sync(() -> parent.spawn(location, clazz));
     }
 
     @Override
-    public <T extends Entity> T spawn(Location location, Class<T> clazz, Consumer<T> function) throws IllegalArgumentException {
+    public <T extends Entity> @NotNull T spawn(@NotNull Location location, @NotNull Class<T> clazz, Consumer<T> function) throws IllegalArgumentException {
         return TaskManager.IMP.sync(() -> parent.spawn(location, clazz, function));
     }
 
@@ -656,28 +656,28 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public FallingBlock spawnFallingBlock(Location location, MaterialData data) throws IllegalArgumentException {
+    public @NotNull FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull MaterialData data) throws IllegalArgumentException {
         return TaskManager.IMP.sync(() -> parent.spawnFallingBlock(location, data));
     }
 
     @Override
     @Deprecated
-    public FallingBlock spawnFallingBlock(Location location, Material material, byte data) throws IllegalArgumentException {
+    public @NotNull FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull Material material, byte data) throws IllegalArgumentException {
         return TaskManager.IMP.sync(() -> parent.spawnFallingBlock(location, material, data));
     }
 
     @Override
-    public FallingBlock spawnFallingBlock(Location location, BlockData blockData) throws IllegalArgumentException {
+    public @NotNull FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull BlockData blockData) throws IllegalArgumentException {
         return TaskManager.IMP.sync(() -> parent.spawnFallingBlock(location, blockData));
     }
 
     @Override
-    public void playEffect(Location location, Effect effect, int data) {
+    public void playEffect(@NotNull Location location, @NotNull Effect effect, int data) {
         this.playEffect(location, effect, data, 64);
     }
 
     @Override
-    public void playEffect(final Location location, final Effect effect, final int data, final int radius) {
+    public void playEffect(final @NotNull Location location, final @NotNull Effect effect, final int data, final int radius) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -687,12 +687,12 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public <T> void playEffect(Location loc, Effect effect, T data) {
+    public <T> void playEffect(@NotNull Location loc, @NotNull Effect effect, T data) {
         this.playEffect(loc, effect, data, 64);
     }
 
     @Override
-    public <T> void playEffect(final Location location, final Effect effect, final T data, final int radius) {
+    public <T> void playEffect(final @NotNull Location location, final @NotNull Effect effect, final T data, final int radius) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -702,7 +702,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public ChunkSnapshot getEmptyChunkSnapshot(final int x, final int z, final boolean includeBiome, final boolean includeBiomeTempRain) {
+    public @NotNull ChunkSnapshot getEmptyChunkSnapshot(final int x, final int z, final boolean includeBiome, final boolean includeBiomeTempRain) {
         return TaskManager.IMP.sync(
             () -> parent.getEmptyChunkSnapshot(x, z, includeBiome, includeBiomeTempRain));
     }
@@ -723,7 +723,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public Biome getBiome(int x, int z) {
+    public @NotNull Biome getBiome(int x, int z) {
         return adapter.adapt(getExtent().getBiomeType(x, 0, z));
     }
 
@@ -733,7 +733,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void setBiome(int x, int z, Biome bio) {
+    public void setBiome(int x, int z, @NotNull Biome bio) {
         BiomeType biome = adapter.adapt(bio);
         getExtent().setBiome(x, 0, z, biome);
     }
@@ -800,17 +800,17 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void setDifficulty(Difficulty difficulty) {
+    public void setDifficulty(@NotNull Difficulty difficulty) {
         parent.setDifficulty(difficulty);
     }
 
     @Override
-    public Difficulty getDifficulty() {
+    public @NotNull Difficulty getDifficulty() {
         return parent.getDifficulty();
     }
 
     @Override
-    public File getWorldFolder() {
+    public @NotNull File getWorldFolder() {
         return parent.getWorldFolder();
     }
 
@@ -884,14 +884,12 @@ public class AsyncWorld extends PassthroughExtent implements World {
         parent.setWaterAnimalSpawnLimit(limit);
     }
 
-    @Override
-    public int getWaterAmbientSpawnLimit() {
-        return 0;
+    @Override public int getWaterAmbientSpawnLimit() {
+        return parent.getWaterAmbientSpawnLimit();
     }
 
-    @Override
-    public void setWaterAmbientSpawnLimit(int limit) {
-
+    @Override public void setWaterAmbientSpawnLimit(int limit) {
+        parent.setWaterAmbientSpawnLimit(limit);
     }
 
     @Override
@@ -905,7 +903,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void playSound(final Location location, final Sound sound, final float volume, final float pitch) {
+    public void playSound(final @NotNull Location location, final @NotNull Sound sound, final float volume, final float pitch) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -915,7 +913,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void playSound(final Location location, final String sound, final float volume, final float pitch) {
+    public void playSound(final @NotNull Location location, final @NotNull String sound, final float volume, final float pitch) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -925,7 +923,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) {
+    public void playSound(@NotNull Location location, @NotNull Sound sound, @NotNull SoundCategory category, float volume, float pitch) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -935,7 +933,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) {
+    public void playSound(@NotNull Location location, @NotNull String sound, @NotNull SoundCategory category, float volume, float pitch) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -955,32 +953,32 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public boolean setGameRuleValue(String rule, String value) {
+    public boolean setGameRuleValue(@NotNull String rule, @NotNull String value) {
         return parent.setGameRuleValue(rule, value);
     }
 
     @Override
-    public boolean isGameRule(String rule) {
+    public boolean isGameRule(@NotNull String rule) {
         return parent.isGameRule(rule);
     }
 
     @Override
-    public <T> T getGameRuleValue(GameRule<T> gameRule) {
+    public <T> T getGameRuleValue(@NotNull GameRule<T> gameRule) {
         return parent.getGameRuleValue(gameRule);
     }
 
     @Override
-    public <T> T getGameRuleDefault(GameRule<T> gameRule) {
+    public <T> T getGameRuleDefault(@NotNull GameRule<T> gameRule) {
         return parent.getGameRuleDefault(gameRule);
     }
 
     @Override
-    public <T> boolean setGameRule(GameRule<T> gameRule, T t) {
+    public <T> boolean setGameRule(@NotNull GameRule<T> gameRule, @NotNull T t) {
         return parent.setGameRule(gameRule, t);
     }
 
     @Override
-    public Spigot spigot() {
+    public @NotNull Spigot spigot() {
         return parent.spigot();
     }
 
@@ -995,7 +993,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void setMetadata(final String key, final MetadataValue meta) {
+    public void setMetadata(final @NotNull String key, final @NotNull MetadataValue meta) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -1005,17 +1003,17 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public List<MetadataValue> getMetadata(String key) {
+    public @NotNull List<MetadataValue> getMetadata(@NotNull String key) {
         return parent.getMetadata(key);
     }
 
     @Override
-    public boolean hasMetadata(String key) {
+    public boolean hasMetadata(@NotNull String key) {
         return parent.hasMetadata(key);
     }
 
     @Override
-    public void removeMetadata(final String key, final Plugin plugin) {
+    public void removeMetadata(final @NotNull String key, final @NotNull Plugin plugin) {
         TaskManager.IMP.sync(new RunnableVal<Object>() {
             @Override
             public void run(Object value) {
@@ -1025,12 +1023,12 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void sendPluginMessage(Plugin source, String channel, byte[] message) {
+    public void sendPluginMessage(@NotNull Plugin source, @NotNull String channel, byte[] message) {
         parent.sendPluginMessage(source, channel, message);
     }
 
     @Override
-    public Set<String> getListeningPluginChannels() {
+    public @NotNull Set<String> getListeningPluginChannels() {
         return parent.getListeningPluginChannels();
     }
 
@@ -1039,17 +1037,17 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public Collection<Entity> getNearbyEntities(BoundingBox arg0) {
+    public @NotNull Collection<Entity> getNearbyEntities(@NotNull BoundingBox arg0) {
         return parent.getNearbyEntities(arg0);
     }
 
     @Override
-    public Collection<Entity> getNearbyEntities(BoundingBox arg0, Predicate<Entity> arg1) {
+    public @NotNull Collection<Entity> getNearbyEntities(@NotNull BoundingBox arg0, Predicate<Entity> arg1) {
         return parent.getNearbyEntities(arg0, arg1);
     }
 
     @Override
-    public Collection<Entity> getNearbyEntities(Location arg0, double arg1, double arg2, double arg3,
+    public @NotNull Collection<Entity> getNearbyEntities(@NotNull Location arg0, double arg1, double arg2, double arg3,
             Predicate<Entity> arg4) {
         return parent.getNearbyEntities(arg0, arg1, arg2, arg3, arg4);
     }
@@ -1060,7 +1058,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public Location locateNearestStructure(Location arg0, StructureType arg1, int arg2, boolean arg3) {
+    public Location locateNearestStructure(@NotNull Location arg0, @NotNull StructureType arg1, int arg2, boolean arg3) {
         return parent.locateNearestStructure(arg0, arg1, arg2, arg3);
     }
 
@@ -1085,44 +1083,45 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public RayTraceResult rayTrace(Location arg0, Vector arg1, double arg2, FluidCollisionMode arg3, boolean arg4,
+    public RayTraceResult rayTrace(
+        @NotNull Location arg0, @NotNull Vector arg1, double arg2, @NotNull FluidCollisionMode arg3, boolean arg4,
             double arg5, Predicate<Entity> arg6) {
         return parent.rayTrace(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
     }
 
     @Override
-    public RayTraceResult rayTraceBlocks(Location arg0, Vector arg1, double arg2) {
+    public RayTraceResult rayTraceBlocks(@NotNull Location arg0, @NotNull Vector arg1, double arg2) {
         return parent.rayTraceBlocks(arg0, arg1, arg2);
     }
 
     @Override
-    public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) {
+    public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode) {
         return parent.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode);
     }
 
     @Override
-    public RayTraceResult rayTraceBlocks(Location start, Vector direction, double arg2, FluidCollisionMode fluidCollisionMode,
+    public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double arg2, @NotNull FluidCollisionMode fluidCollisionMode,
             boolean ignorePassableBlocks) {
         return parent.rayTraceBlocks(start, direction, arg2, fluidCollisionMode, ignorePassableBlocks);
     }
 
     @Override
-    public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance) {
+    public RayTraceResult rayTraceEntities(@NotNull Location start, @NotNull Vector direction, double maxDistance) {
         return parent.rayTraceEntities(start, direction, maxDistance);
     }
 
     @Override
-    public RayTraceResult rayTraceEntities(Location arg0, Vector arg1, double arg2, double arg3) {
+    public RayTraceResult rayTraceEntities(@NotNull Location arg0, @NotNull Vector arg1, double arg2, double arg3) {
         return parent.rayTraceEntities(arg0, arg1, arg2, arg3);
     }
 
     @Override
-    public RayTraceResult rayTraceEntities(Location arg0, Vector arg1, double arg2, Predicate<Entity> arg3) {
+    public RayTraceResult rayTraceEntities(@NotNull Location arg0, @NotNull Vector arg1, double arg2, Predicate<Entity> arg3) {
         return parent.rayTraceEntities(arg0, arg1, arg2, arg3);
     }
 
     @Override
-    public RayTraceResult rayTraceEntities(Location arg0, Vector arg1, double arg2, double arg3,
+    public RayTraceResult rayTraceEntities(@NotNull Location arg0, @NotNull Vector arg1, double arg2, double arg3,
             Predicate<Entity> arg4) {
         return parent.rayTraceEntities(arg0, arg1, arg2, arg3, arg4);
     }
@@ -1141,7 +1140,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public Collection<Chunk> getForceLoadedChunks() {
+    public @NotNull Collection<Chunk> getForceLoadedChunks() {
         return parent.getForceLoadedChunks();
     }
 
@@ -1171,7 +1170,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public int getHighestBlockYAt(int x, int z, com.destroystokyo.paper.HeightmapType heightmap) throws UnsupportedOperationException {
+    public int getHighestBlockYAt(int x, int z, com.destroystokyo.paper.@NotNull HeightmapType heightmap) throws UnsupportedOperationException {
         return TaskManager.IMP.sync(() -> parent.getHighestBlockYAt(x, z, heightmap));
     }
 
@@ -1201,7 +1200,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public CompletableFuture<Chunk> getChunkAtAsync(int arg0, int arg1, boolean arg2) {
+    public @NotNull CompletableFuture<Chunk> getChunkAtAsync(int arg0, int arg1, boolean arg2) {
         return parent.getChunkAtAsync(arg0, arg1, arg2);
     }
 
@@ -1216,22 +1215,22 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public void getChunkAtAsync(int x, int z, ChunkLoadCallback cb) {
+    public void getChunkAtAsync(int x, int z, @NotNull ChunkLoadCallback cb) {
         parent.getChunkAtAsync(x, z, cb);
     }
 
     @Override
-    public void getChunkAtAsync(Location location, ChunkLoadCallback cb) {
+    public void getChunkAtAsync(@NotNull Location location, @NotNull ChunkLoadCallback cb) {
         parent.getChunkAtAsync(location, cb);
     }
 
     @Override
-    public void getChunkAtAsync(Block block, ChunkLoadCallback cb) {
+    public void getChunkAtAsync(@NotNull Block block, @NotNull ChunkLoadCallback cb) {
         parent.getChunkAtAsync(block, cb);
     }
 
     @Override
-    public Entity getEntity(UUID uuid) {
+    public Entity getEntity(@NotNull UUID uuid) {
         return TaskManager.IMP.sync(() -> parent.getEntity(uuid));
     }
 
@@ -1242,7 +1241,7 @@ public class AsyncWorld extends PassthroughExtent implements World {
     }
 
     @Override
-    public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) {
+    public boolean createExplosion(Entity source, @NotNull Location loc, float power, boolean setFire, boolean breakBlocks) {
         return TaskManager.IMP.sync(() -> parent.createExplosion(source, loc, power, setFire, breakBlocks));
     }
 
@@ -1260,12 +1259,13 @@ public class AsyncWorld extends PassthroughExtent implements World {
 
 
     @Override
-    public <T> void spawnParticle(Particle particle, List<Player> receivers, Player source, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) {
+    public <T> void spawnParticle(
+        @NotNull Particle particle, List<Player> receivers, @NotNull Player source, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) {
         parent.spawnParticle(particle, receivers, source, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
     }
 
     @Override
-    public <T> void spawnParticle(Particle particle, List<Player> list, Player player, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6, T t, boolean b) {
+    public <T> void spawnParticle(@NotNull Particle particle, List<Player> list, Player player, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6, T t, boolean b) {
         parent.spawnParticle(particle, list, player, v, v1, v2, i, v3, v4, v5, v6, t, b);
     }
 
@@ -1294,10 +1294,12 @@ public class AsyncWorld extends PassthroughExtent implements World {
         return parent.getHighestBlockAt(location, heightmap);
     }
 
+    @Override
     public long getTicksPerWaterSpawns() {
         return parent.getTicksPerWaterSpawns();
     }
 
+    @Override
     public void setTicksPerWaterSpawns(int ticksPerWaterSpawns) {
         parent.setTicksPerWaterSpawns(ticksPerWaterSpawns);
     }
@@ -1312,11 +1314,14 @@ public class AsyncWorld extends PassthroughExtent implements World {
         parent.setTicksPerWaterAmbientSpawns(ticksPerAmbientSpawns);
     }
 
+    @Override
     public long getTicksPerAmbientSpawns() {
         return parent.getTicksPerAmbientSpawns();
     }
 
+    @Override
     public void setTicksPerAmbientSpawns(int ticksPerAmbientSpawns) {
         parent.setTicksPerAmbientSpawns(ticksPerAmbientSpawns);
     }
+
 }
diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/state/AsyncDataContainer.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/state/AsyncDataContainer.java
index e64db1124..f504932db 100644
--- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/state/AsyncDataContainer.java
+++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/state/AsyncDataContainer.java
@@ -4,7 +4,12 @@ import com.boydti.fawe.FaweCache;
 import com.sk89q.jnbt.CompoundTag;
 import com.sk89q.jnbt.Tag;
 
-import java.util.*;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 
 import org.apache.commons.lang.Validate;
 import org.bukkit.NamespacedKey;
diff --git a/worldedit-bukkit/src/main/resources/DummyFawe.src b/worldedit-bukkit/src/main/resources/DummyFawe.src
index c0bd44906..53af3b007 100644
Binary files a/worldedit-bukkit/src/main/resources/DummyFawe.src and b/worldedit-bukkit/src/main/resources/DummyFawe.src differ
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java b/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
index 6f61bdda8..6675390eb 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
@@ -305,7 +305,7 @@ public class Fawe {
             br.close();
             this.version = FaweVersion.tryParse(versionString, commitString, dateString);
             Settings.IMP.DATE = new Date(100 + version.year, version.month, version.day).toGMTString();
-            Settings.IMP.BUILD = "https://ci.athion.net/job/FastAsyncWorldEdit-1.15/" + version.build;
+            Settings.IMP.BUILD = "https://ci.athion.net/job/FastAsyncWorldEdit-1.16/" + version.build;
             Settings.IMP.COMMIT = "https://github.com/IntellectualSites/FastAsyncWorldEdit/commit/" + Integer.toHexString(version.hash);
         } catch (Throwable ignore) {}
         try {
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java
index ee43fdc36..859503291 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java
@@ -1,7 +1,6 @@
 package com.boydti.fawe;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static org.slf4j.LoggerFactory.getLogger;
 
 import com.boydti.fawe.beta.IChunkSet;
 import com.boydti.fawe.beta.Trimable;
@@ -9,6 +8,7 @@ import com.boydti.fawe.beta.implementation.queue.Pool;
 import com.boydti.fawe.beta.implementation.queue.QueuePool;
 import com.boydti.fawe.config.Settings;
 import com.boydti.fawe.object.collection.BitArray;
+import com.boydti.fawe.object.collection.BitArrayUnstretched;
 import com.boydti.fawe.object.collection.CleanableThreadLocal;
 import com.boydti.fawe.object.collection.VariableThreadLocal;
 import com.boydti.fawe.object.exception.FaweBlockBagException;
@@ -299,6 +299,102 @@ public enum FaweCache implements Trimable {
         }
     }
 
+    /**
+     * Convert raw int array to unstretched palette (1.16)
+     * @param layerOffset
+     * @param blocks
+     * @return palette
+     */
+    public Palette toPaletteUnstretched(int layerOffset, char[] blocks) {
+        return toPaletteUnstretched(layerOffset, null, blocks);
+    }
+
+    /**
+     * Convert raw int array to unstretched palette (1.16)
+     * @param layerOffset
+     * @param blocks
+     * @return palette
+     */
+    public Palette toPaletteUnstretched(int layerOffset, int[] blocks) {
+        return toPaletteUnstretched(layerOffset, blocks, null);
+    }
+
+    private Palette toPaletteUnstretched(int layerOffset, int[] blocksInts, char[] blocksChars) {
+        int[] blockToPalette = BLOCK_TO_PALETTE.get();
+        int[] paletteToBlock = PALETTE_TO_BLOCK.get();
+        long[] blockStates = BLOCK_STATES.get();
+        int[] blocksCopy = SECTION_BLOCKS.get();
+
+        try {
+            int num_palette = 0;
+            int blockIndexStart = layerOffset << 12;
+            int blockIndexEnd = blockIndexStart + 4096;
+            if (blocksChars != null) {
+                for (int i = blockIndexStart, j = 0; i < blockIndexEnd; i++, j++) {
+                    int ordinal = blocksChars[i];
+                    int palette = blockToPalette[ordinal];
+                    if (palette == Integer.MAX_VALUE) {
+                        blockToPalette[ordinal] = palette = num_palette;
+                        paletteToBlock[num_palette] = ordinal;
+                        num_palette++;
+                    }
+                    blocksCopy[j] = palette;
+                }
+            } else if (blocksInts != null) {
+                for (int i = blockIndexStart, j = 0; i < blockIndexEnd; i++, j++) {
+                    int ordinal = blocksInts[i];
+                    int palette = blockToPalette[ordinal];
+                    if (palette == Integer.MAX_VALUE) {
+                        //                        BlockState state = BlockTypesCache.states[ordinal];
+                        blockToPalette[ordinal] = palette = num_palette;
+                        paletteToBlock[num_palette] = ordinal;
+                        num_palette++;
+                    }
+                    blocksCopy[j] = palette;
+                }
+            } else {
+                throw new IllegalArgumentException();
+            }
+
+            for (int i = 0; i < num_palette; i++) {
+                blockToPalette[paletteToBlock[i]] = Integer.MAX_VALUE;
+            }
+
+            // BlockStates
+            int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
+            if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) {
+                bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry
+            } else {
+                bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries
+            }
+            int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry);
+            int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong);
+            if (num_palette == 1) {
+                // Set a value, because minecraft needs it for some  reason
+                blockStates[0] = 0;
+                blockBitArrayEnd = 1;
+            } else {
+                BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, blockStates);
+                bitArray.fromRaw(blocksCopy);
+            }
+
+            // Construct palette
+            Palette palette = PALETTE_CACHE.get();
+            palette.bitsPerEntry = bitsPerEntry;
+            palette.paletteToBlockLength = num_palette;
+            palette.paletteToBlock = paletteToBlock;
+
+            palette.blockStatesLength = blockBitArrayEnd;
+            palette.blockStates = blockStates;
+
+            return palette;
+        } catch (Throwable e) {
+            e.printStackTrace();
+            Arrays.fill(blockToPalette, Integer.MAX_VALUE);
+            throw e;
+        }
+    }
+
     /*
      * Vector cache
      */
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java
index 7fd480dfb..3c4f08062 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java
@@ -32,9 +32,9 @@ public class FaweVersion {
 
     @Override public String toString() {
         if (hash == 0 && build == 0) {
-            return "FastAsyncWorldEdit-1.15-NoVer-SNAPSHOT";
+            return "FastAsyncWorldEdit-1.16-NoVer-SNAPSHOT";
         } else {
-            return "FastAsyncWorldEdit-1.15" + build;
+            return "FastAsyncWorldEdit-1.16" + build;
         }
     }
 
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java b/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java
index 20a15faab..6941fb728 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java
@@ -41,4 +41,8 @@ public interface IFawe {
 
     Preloader getPreloader();
 
+    default boolean isChunksStretched() {
+        return true;
+    }
+
 }
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java
index c042e7a23..42fbac580 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java
@@ -45,11 +45,11 @@ public interface IBlocks extends Trimable {
 
     IBlocks reset();
 
-    default byte[] toByteArray(boolean full) {
-        return toByteArray(null, getBitMask(), full);
+    default byte[] toByteArray(boolean full, boolean stretched) {
+        return toByteArray(null, getBitMask(), full, stretched);
     }
 
-    default byte[] toByteArray(byte[] buffer, int bitMask, boolean full) {
+    default byte[] toByteArray(byte[] buffer, int bitMask, boolean full, boolean stretched) {
         if (buffer == null) {
             buffer = new byte[1024];
         }
@@ -81,7 +81,12 @@ public interface IBlocks extends Trimable {
                 }
 
                 sectionWriter.writeShort(nonEmpty); // non empty
-                FaweCache.Palette palette = FaweCache.IMP.toPalette(0, ids);
+                FaweCache.Palette palette;
+                if (stretched) {
+                     palette = FaweCache.IMP.toPalette(0, ids);
+                } else {
+                    palette = FaweCache.IMP.toPaletteUnstretched(0, ids);
+                }
 
                 sectionWriter.writeByte(palette.bitsPerEntry); // bits per block
                 sectionWriter.writeVarInt(palette.paletteToBlockLength);
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java
index 1dda73c3a..744e92f06 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java
@@ -181,6 +181,18 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
     }
 
     @Override public void setFullBright(int layer) {
+        if (light == null) {
+            light = new char[16][];
+        }
+        if (light[layer] == null) {
+            light[layer] = new char[4096];
+        }
+        if (skyLight == null) {
+            skyLight = new char[16][];
+        }
+        if (skyLight[layer] == null) {
+            skyLight[layer] = new char[4096];
+        }
         Arrays.fill(light[layer], (char) 15);
         Arrays.fill(skyLight[layer], (char) 15);
     }
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java
index b904c5cd6..5638c962e 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java
@@ -1,10 +1,12 @@
 package com.boydti.fawe.beta.implementation.packet;
 
+import com.boydti.fawe.Fawe;
 import com.boydti.fawe.FaweCache;
 import com.boydti.fawe.beta.IBlocks;
 import com.boydti.fawe.object.FaweOutputStream;
 import com.boydti.fawe.object.io.FastByteArrayOutputStream;
 import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.worldedit.WorldEdit;
 
 import java.util.HashMap;
 import java.util.function.Function;
@@ -64,7 +66,7 @@ public class ChunkPacket implements Function<byte[], byte[]>, Supplier<byte[]> {
                 if (sectionBytes == null) {
                     IBlocks tmpChunk = getChunk();
                     byte[] buf = FaweCache.IMP.BYTE_BUFFER_8192.get();
-                    sectionBytes = tmpChunk.toByteArray(buf, tmpChunk.getBitMask(), this.full);
+                    sectionBytes = tmpChunk.toByteArray(buf, tmpChunk.getBitMask(), this.full, Fawe.imp().isChunksStretched());
                 }
                 tmp = sectionBytes;
             }
@@ -72,6 +74,7 @@ public class ChunkPacket implements Function<byte[], byte[]>, Supplier<byte[]> {
         return tmp;
     }
 
+
     public Object getNativePacket() {
         return nativePacket;
     }
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/BitArrayUnstretched.java b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/BitArrayUnstretched.java
new file mode 100644
index 000000000..9572eecc9
--- /dev/null
+++ b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/BitArrayUnstretched.java
@@ -0,0 +1,115 @@
+package com.boydti.fawe.object.collection;
+
+import com.boydti.fawe.util.MathMan;
+
+public final class BitArrayUnstretched {
+
+    private final long[] data;
+    private final int bitsPerEntry;
+    private final int maxSeqLocIndex;
+    private final int emptyBitCount;
+    private final long mask;
+    private final int longLen;
+
+    public BitArrayUnstretched(int bitsPerEntry, long[] buffer) {
+        this.bitsPerEntry = bitsPerEntry;
+        this.mask = (1L << bitsPerEntry) - 1L;
+        this.emptyBitCount = 64 % bitsPerEntry;
+        this.maxSeqLocIndex = 64 - (bitsPerEntry + emptyBitCount);
+        final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry);
+        this.longLen = MathMan.ceilZero((float) 4096 / blocksPerLong);
+        if (buffer.length < longLen) {
+            this.data = new long[longLen];
+        } else {
+            this.data = buffer;
+        }
+    }
+
+    public long[] getData() {
+        return data;
+    }
+
+    public final void set(int index, int value) {
+        if (longLen == 0) return;
+        int bitIndexStart = index * bitsPerEntry + MathMan.floorZero((double) index / longLen) * emptyBitCount;
+        int longIndexStart = bitIndexStart >> 6;
+        int localBitIndexStart = bitIndexStart & 63;
+        this.data[longIndexStart] = this.data[longIndexStart] & ~(mask << localBitIndexStart) | (long) value << localBitIndexStart;
+    }
+
+    public final int get(int index) {
+        if (longLen == 0) return 0;
+        int bitIndexStart = index * bitsPerEntry + MathMan.floorZero((double) index / longLen) * emptyBitCount;
+
+        int longIndexStart = bitIndexStart >> 6;
+
+        int localBitIndexStart = bitIndexStart & 63;
+        return (int)(this.data[longIndexStart] >>> localBitIndexStart & mask);
+    }
+
+    public int getLength() {
+        return longLen;
+    }
+    
+    public final void fromRaw(int[] arr) {
+        final long[] data = this.data;
+        final int bitsPerEntry = this.bitsPerEntry;
+        final int maxSeqLocIndex = this.maxSeqLocIndex;
+
+        int localStart = 0;
+        int arrI = 0;
+        long l = 0;
+        for (int i = 0; i < longLen; i++) {
+            int lastVal;
+            for (; localStart <= maxSeqLocIndex && arrI < 4096; localStart += bitsPerEntry) {
+                lastVal = arr[arrI++];
+                l |= ((long) lastVal << localStart);
+            }
+            localStart = 0;
+            data[i] = l;
+            l = 0;
+        }
+    }
+
+    public final int[] toRaw() {
+        return toRaw(new int[4096]);
+    }
+
+    public final int[] toRaw(int[] buffer) {
+        final long[] data = this.data;
+        final int bitsPerEntry = this.bitsPerEntry;
+        final int maxSeqLocIndex = this.maxSeqLocIndex;
+
+        int localStart = 0;
+        int arrI = 0;
+        for (int i = 0; i < longLen; i++) {
+            long l = data[i];
+            char lastVal;
+            for (; localStart <= maxSeqLocIndex && arrI < 4096; localStart += bitsPerEntry) {
+                lastVal = (char) (l >>> localStart & this.mask);
+                buffer[arrI++] = lastVal;
+            }
+            localStart = 0;
+        }
+        return buffer;
+    }
+
+    public final char[] toRaw(char[] buffer) {
+        final long[] data = this.data;
+        final int bitsPerEntry = this.bitsPerEntry;
+        final int maxSeqLocIndex = this.maxSeqLocIndex;
+
+        int localStart = 0;
+        int arrI = 0;
+        for (int i = 0; i < longLen; i++) {
+            long l = data[i];
+            char lastVal;
+            for (; localStart <= maxSeqLocIndex && arrI < 4096; localStart += bitsPerEntry) {
+                lastVal = (char) (l >>> localStart & this.mask);
+                buffer[arrI++] = lastVal;
+            }
+            localStart = 0;
+        }
+        return buffer;
+    }
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java b/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java
index 1aceda930..8231d3aa8 100644
--- a/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java
+++ b/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java
@@ -20,6 +20,7 @@
 package com.sk89q.util;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
@@ -331,4 +332,37 @@ public final class StringUtil {
 
         return parsableBlocks;
     }
+
+    /**
+     * Splits a string respecting enclosing quotes.
+     *
+     * @param input the input to split.
+     * @param delimiter the delimiter to split on.
+     * @param open the opening quote character.
+     * @param close the closing quote character.
+     * @return a list of split strings.
+     */
+    public static List<String> split(String input, char delimiter, char open, char close) {
+        if (input.indexOf(open) == -1 && input.indexOf(close) == -1) {
+            return Arrays.asList(input.split(String.valueOf(delimiter)));
+        }
+        int level = 0;
+        int begin = 0;
+        List<String> split = new ArrayList<>();
+        for (int i = 0; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (c == delimiter && level == 0) {
+                split.add(input.substring(begin, i));
+                begin = i + 1;
+            } else if (c == open) {
+                level++;
+            } else if (c == close) {
+                level--;
+            }
+        }
+        if (begin < input.length()) {
+            split.add(input.substring(begin));
+        }
+        return split;
+    }
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java
index 4f5a4fdbc..693cd9b8e 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java
@@ -58,8 +58,8 @@ public class BlockFactory extends AbstractFactory<BaseBlock> {
      */
     public Set<BaseBlock> parseFromListInput(String input, ParserContext context) throws InputParseException {
         Set<BaseBlock> blocks = new HashSet<>();
-        String[] splits = input.split(",");
-        for (String token : StringUtil.parseListInQuotes(splits, ',', '[', ']', true)) {
+        // String[] splits = input.split(",");
+        for (String token : StringUtil.split(input, ',', '[', ']')) {
             blocks.add(parseFromInput(token, context));
         }
         return blocks;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java
index 014229a3e..41bece423 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java
@@ -24,6 +24,7 @@ import com.sk89q.worldedit.extension.factory.parser.pattern.BlockCategoryPattern
 import com.sk89q.worldedit.extension.factory.parser.pattern.ClipboardPatternParser;
 import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
 import com.sk89q.worldedit.extension.factory.parser.pattern.RandomStatePatternParser;
+import com.sk89q.worldedit.extension.factory.parser.pattern.SimplexPatternParser;
 import com.sk89q.worldedit.extension.factory.parser.pattern.SingleBlockPatternParser;
 import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser;
 import com.sk89q.worldedit.function.pattern.Pattern;
@@ -54,6 +55,9 @@ public final class PatternFactory extends AbstractFactory<Pattern> {
         register(new TypeOrStateApplyingPatternParser(worldEdit));
         register(new RandomStatePatternParser(worldEdit));
         register(new BlockCategoryPatternParser(worldEdit));
+
+        // FAWE
+        register(new SimplexPatternParser(worldEdit));
     }
 
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/RichParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/RichParser.java
new file mode 100644
index 000000000..6bd9261fa
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/RichParser.java
@@ -0,0 +1,118 @@
+package com.sk89q.worldedit.extension.factory.parser;
+
+import com.sk89q.util.StringUtil;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.extension.input.InputParseException;
+import com.sk89q.worldedit.extension.input.ParserContext;
+import com.sk89q.worldedit.internal.registry.InputParser;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+import java.util.stream.Stream;
+
+/**
+ * A rich parser allows parsing of patterns and masks with extra arguments,
+ * e.g. #simplex[scale][pattern].
+ *
+ * @param <E> the parse result.
+ */
+public abstract class RichParser<E> extends InputParser<E> {
+    private final String prefix;
+    private final String required;
+
+    /**
+     * Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}.
+     *
+     * @param worldEdit the worldedit instance.
+     * @param prefix    the prefix of this parser result.
+     */
+    protected RichParser(WorldEdit worldEdit, String prefix) {
+        super(worldEdit);
+        this.prefix = prefix;
+        this.required = prefix + "[";
+    }
+
+    @Override
+    public Stream<String> getSuggestions(String input) {
+        // we don't even want to start suggesting if it's not meant to be this parser result
+        if (input.length() > this.required.length() && !input.startsWith(this.required)) {
+            return Stream.empty();
+        }
+        // suggest until the first [ as long as it isn't fully typed
+        if (input.length() < this.required.length()) {
+            return Stream.of(this.required).filter(s -> s.startsWith(input));
+        }
+        // we know that it is at least "<required>"
+        String[] strings = extractArguments(input.substring(this.prefix.length()), false);
+        StringJoiner joiner = new StringJoiner(",");
+        for (int i = 0; i < strings.length - 1; i++) {
+            joiner.add("[" + strings[i] + "]");
+        }
+        String previous = this.prefix + joiner;
+        return getSuggestions(strings[strings.length - 1], strings.length - 1).map(s -> previous + "[" + s + "]");
+    }
+
+    @Override
+    public E parseFromInput(String input, ParserContext context) throws InputParseException {
+        if (!input.startsWith(this.prefix)) return null;
+        if (input.length() < this.prefix.length()) return null;
+        String[] arguments = extractArguments(input.substring(prefix.length()), true);
+        return parseFromInput(arguments, context);
+    }
+
+    /**
+     * Returns a stream of suggestions for the argument at the given index.
+     *
+     * @param argumentInput the already provided input for the argument at the given index.
+     * @param index         the index of the argument to get suggestions for.
+     * @return a stream of suggestions matching the given input for the argument at the given index.
+     */
+    protected abstract Stream<String> getSuggestions(String argumentInput, int index);
+
+    /**
+     * Parses the already split arguments.
+     *
+     * @param arguments the array of arguments that were split (can be empty).
+     * @param context   the context of this parsing process.
+     * @return the resulting parsed type.
+     * @throws InputParseException if the input couldn't be parsed correctly.
+     */
+    protected abstract E parseFromInput(@NotNull String[] arguments, ParserContext context) throws InputParseException;
+
+    /**
+     * Extracts arguments enclosed by {@code []} into an array.
+     * Example: {@code [Hello][World]} results in a list containing {@code Hello} and {@code World}.
+     *
+     * @param input          the input to extract arguments from.
+     * @param requireClosing whether or not the extraction requires valid bracketing.
+     * @return an array of extracted arguments.
+     * @throws InputParseException if {@code requireClosing == true} and the count of [ != the count of ]
+     */
+    protected String[] extractArguments(String input, boolean requireClosing) throws InputParseException {
+        int open = 0; // the "level"
+        int openIndex = 0;
+        int i = 0;
+        List<String> arguments = new ArrayList<>();
+        for (; i < input.length(); i++) {
+            if (input.charAt(i) == '[') {
+                if (open++ == 0) {
+                    openIndex = i;
+                }
+            }
+            if (input.charAt(i) == ']') {
+                if (--open == 0) {
+                    arguments.add(input.substring(openIndex + 1, i));
+                }
+            }
+        }
+        if (!requireClosing && open > 0) {
+            arguments.add(input.substring(openIndex + 1));
+        }
+        if (requireClosing && open != 0) {
+            throw new InputParseException("Invalid bracketing, are you missing a '[' or ']'?");
+        }
+        return arguments.toArray(new String[0]);
+    }
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java
index 81b1b11e3..66c55be3e 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java
@@ -38,8 +38,9 @@ public class RandomPatternParser extends InputParser<Pattern> {
 
     @Override
     public Stream<String> getSuggestions(String input) {
-        String[] splits = input.split(",", -1);
-        List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);
+        List<String> patterns = StringUtil.split(input, ',', '[', ']');
+        /*String[] splits = input.split(",", -1);
+        List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);*/
         if (patterns.size() == 1) {
             return Stream.empty();
         }
@@ -63,8 +64,9 @@ public class RandomPatternParser extends InputParser<Pattern> {
     public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
         RandomPattern randomPattern = new RandomPattern();
 
-        String[] splits = input.split(",", -1);
-        List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);
+        List<String> patterns = StringUtil.split(input, ',', '[', ']');
+        /*String[] splits = input.split(",", -1);
+        List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);*/
         if (patterns.size() == 1) {
             return null; // let a 'single'-pattern parser handle it
         }
@@ -74,7 +76,7 @@ public class RandomPatternParser extends InputParser<Pattern> {
 
             // Parse special percentage syntax
             if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
-                String[] p = token.split("%");
+                String[] p = token.split("%", 2);
 
                 if (p.length < 2) {
                     throw new InputParseException("Missing the type after the % symbol for '" + input + "'");
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SimplexPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SimplexPatternParser.java
new file mode 100644
index 000000000..547f96390
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SimplexPatternParser.java
@@ -0,0 +1,75 @@
+package com.sk89q.worldedit.extension.factory.parser.pattern;
+
+import com.boydti.fawe.object.random.SimplexRandom;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.extension.factory.parser.RichParser;
+import com.sk89q.worldedit.extension.input.InputParseException;
+import com.sk89q.worldedit.extension.input.ParserContext;
+import com.sk89q.worldedit.function.pattern.Pattern;
+import com.sk89q.worldedit.function.pattern.RandomPattern;
+import com.sk89q.worldedit.world.block.BlockStateHolder;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.stream.Stream;
+
+public class SimplexPatternParser extends RichParser<Pattern> {
+    private static final String SIMPLEX_PREFIX = "#simplex";
+
+    public SimplexPatternParser(WorldEdit worldEdit) {
+        super(worldEdit, SIMPLEX_PREFIX);
+    }
+
+    @Override
+    protected Stream<String> getSuggestions(String argumentInput, int index) {
+        if (index == 0) {
+            if (argumentInput.isEmpty()) {
+                return Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9");
+            }
+            // if already a valid number, suggest more digits
+            if (isDouble(argumentInput)) {
+                Stream<String> numbers = Stream.of("", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
+                if (argumentInput.indexOf('.') == -1) {
+                    numbers = Stream.concat(numbers, Stream.of("."));
+                }
+                return numbers.map(s -> argumentInput + s);
+            }
+            // no valid input anymore
+            return Stream.empty();
+        }
+        if (index == 1) {
+            return worldEdit.getPatternFactory().getSuggestions(argumentInput).stream();
+        }
+        return Stream.empty();
+    }
+
+    @Override
+    protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) {
+        if (arguments.length != 2) {
+            throw new InputParseException("Simplex requires a scale and a pattern, e.g. #simplex[5][dirt,stone]");
+        }
+        double scale = Double.parseDouble(arguments[0]);
+        scale = 1d / Math.max(1, scale);
+        Pattern inner = worldEdit.getPatternFactory().parseFromInput(arguments[1], context);
+        if (inner instanceof RandomPattern) {
+            return new RandomPattern(new SimplexRandom(scale), (RandomPattern) inner);
+        } else if (inner instanceof BlockStateHolder) {
+            return inner; // single blocks won't have any impact on how simplex behaves
+        } else {
+            throw new InputParseException("Pattern " + inner.getClass().getSimpleName() + " cannot be used with #simplex");
+        }
+    }
+
+    private static boolean isDouble(String input) {
+        boolean point = false;
+        for (char c : input.toCharArray()) {
+            if (!Character.isDigit(c)) {
+                if (c == '.' && !point) {
+                    point = true;
+                } else {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java
index d5ecc2b44..3e02cdfaa 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java
@@ -52,6 +52,19 @@ public class RandomPattern extends AbstractPattern {
         this.random = random;
     }
 
+    /**
+     * Create a random pattern from an existing one but with a different random.
+     *
+     * @param random the new random to use.
+     * @param parent the existing random pattern.
+     */
+    public RandomPattern(SimpleRandom random, RandomPattern parent) {
+        this.random = random;
+        this.weights = parent.weights;
+        this.collection = RandomCollection.of(weights, random);
+        this.patterns = parent.patterns;
+    }
+
     /**
      * Add a pattern to the weight list of patterns.
      *