diff --git a/patches/api/PreSpawnerSpawnEvent.patch b/patches/api/PreSpawnerSpawnEvent.patch
new file mode 100644
index 0000000000..391d4675fa
--- /dev/null
+++ b/patches/api/PreSpawnerSpawnEvent.patch
@@ -0,0 +1,47 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Phoenix616 <mail@moep.tv>
+Date: Tue, 18 Sep 2018 23:50:10 +0100
+Subject: [PATCH] PreSpawnerSpawnEvent
+
+This adds a separate event before an entity is spawned by a spawner
+which contains the location of the spawner too similarly to how the
+SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for
+spawners.
+
+Dropped as it does not apply due to the earlier PreCreatureSpawnEvent patch not being applied
+
+diff --git a/src/main/java/com/destroystokyo/paper/event/entity/PreSpawnerSpawnEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/PreSpawnerSpawnEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/event/entity/PreSpawnerSpawnEvent.java
+@@ -0,0 +0,0 @@
++package com.destroystokyo.paper.event.entity;
++
++
++import com.google.common.base.Preconditions;
++import org.bukkit.Location;
++import org.bukkit.entity.EntityType;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called before an entity is spawned into a world by a spawner.
++ *
++ * This only includes the spawner's location and not the full BlockState snapshot for performance reasons.
++ * If you really need it you have to get the spawner yourself.
++ */
++
++public class PreSpawnerSpawnEvent extends PreCreatureSpawnEvent {
++    @NotNull private final Location spawnerLocation;
++
++    public PreSpawnerSpawnEvent(@NotNull Location location, @NotNull EntityType type, @NotNull Location spawnerLocation) {
++        super(location, type, CreatureSpawnEvent.SpawnReason.SPAWNER);
++        this.spawnerLocation = Preconditions.checkNotNull(spawnerLocation, "Spawner location may not be null");
++    }
++
++    @NotNull
++    public Location getSpawnerLocation() {
++        return spawnerLocation;
++    }
++}
diff --git a/patches/server/Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server/Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch
new file mode 100644
index 0000000000..ebf886f2c3
--- /dev/null
+++ b/patches/server/Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <aikar@aikar.co>
+Date: Wed, 12 Sep 2018 21:47:01 -0400
+Subject: [PATCH] Optimize Biome Mob Lookups for Mob Spawning
+
+Uses an EnumMap as well as a Set paired List for O(1) contains calls.
+
+diff --git a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java
++++ b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java
+@@ -0,0 +0,0 @@ public class MobSpawnSettings {
+     }
+ 
+     public static class Builder {
+-        private final Map<MobCategory, List<MobSpawnSettings.SpawnerData>> spawners = Stream.of(MobCategory.values()).collect(ImmutableMap.toImmutableMap((mobCategory) -> {
++        // Paper start - keep track of data in a pair set to give O(1) contains calls - we have to hook removals incase plugins mess with it
++        public static class MobList extends java.util.ArrayList<MobSpawnSettings.SpawnerData> {
++            java.util.Set<MobSpawnSettings.SpawnerData> biomes = new java.util.HashSet<>();
++
++            @Override
++            public boolean contains(Object o) {
++                return biomes.contains(o);
++            }
++
++            @Override
++            public boolean add(MobSpawnSettings.SpawnerData BiomeSettingsMobs) {
++                biomes.add(BiomeSettingsMobs);
++                return super.add(BiomeSettingsMobs);
++            }
++
++            @Override
++            public MobSpawnSettings.SpawnerData remove(int index) {
++                MobSpawnSettings.SpawnerData removed = super.remove(index);
++                if (removed != null) {
++                    biomes.remove(removed);
++                }
++                return removed;
++            }
++
++            @Override
++            public void clear() {
++                biomes.clear();
++                super.clear();
++            }
++        }
++        // use toImmutableEnumMap collector
++        private final Map<MobCategory, List<MobSpawnSettings.SpawnerData>> spawners = (Map) Stream.of(MobCategory.values()).collect(Maps.toImmutableEnumMap((mobCategory) -> {
+             return mobCategory;
+         }, (mobCategory) -> {
+-            return Lists.newArrayList();
++            return new MobList(); // Use MobList instead of ArrayList
+         }));
++        // Paper end
+         private final Map<EntityType<?>, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap();
+         private float creatureGenerationProbability = 0.1F;
+         private boolean playerCanSpawn;
diff --git a/patches/server/PreSpawnerSpawnEvent.patch b/patches/server/PreSpawnerSpawnEvent.patch
new file mode 100644
index 0000000000..a31a073edb
--- /dev/null
+++ b/patches/server/PreSpawnerSpawnEvent.patch
@@ -0,0 +1,29 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Phoenix616 <mail@moep.tv>
+Date: Tue, 18 Sep 2018 23:53:23 +0100
+Subject: [PATCH] PreSpawnerSpawnEvent
+
+This adds a separate event before an entity is spawned by a spawner
+which contains the location of the spawner too similarly to how the
+SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for
+spawners.
+
+diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java
++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java
+@@ -0,0 +0,0 @@ public abstract class BaseSpawner {
+ 
+                         org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key);
+                         if (type != null) {
+-                            com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event;
+-                            event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
++                            com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event;
++                            event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent(
+                                 net.minecraft.server.MCUtil.toLocation(world, d0, d1, d2),
+                                 type,
+-                                org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER
++                                net.minecraft.server.MCUtil.toLocation(world, pos)
+                             );
+                             if (!event.callEvent()) {
+                                 flag = true;
diff --git a/patches/server/Synchronize-PalettedContainer-instead-of-ReentrantLoc.patch b/patches/server/Synchronize-PalettedContainer-instead-of-ReentrantLoc.patch
new file mode 100644
index 0000000000..43b02bbc14
--- /dev/null
+++ b/patches/server/Synchronize-PalettedContainer-instead-of-ReentrantLoc.patch
@@ -0,0 +1,92 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <aikar@aikar.co>
+Date: Fri, 29 May 2020 20:29:02 -0400
+Subject: [PATCH] Synchronize PalettedContainer instead of ReentrantLock
+
+Mojang has flaws in their logic about chunks being concurrently
+wrote to. So we constantly see crashes around multiple threads writing.
+
+Additionally, java has optimized synchronization so well that its
+in many times faster than trying to manage read wrote locks for low
+contention situations.
+
+And this is extremely a low contention situation.
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
+     private final DebugBuffer<Pair<Thread, StackTraceElement[]>> traces = null;
+ 
+     public void acquire() {
++        /* // Paper start - disable this - use proper synchronization
+         if (this.traces != null) {
+             Thread thread = Thread.currentThread();
+             this.traces.push(Pair.of(thread, thread.getStackTrace()));
+         }
+ 
+         ThreadingDetector.checkAndLock(this.lock, this.traces, "PalettedContainer");
++        */ // Paper end
+     }
+ 
+     public void release() {
+-        this.lock.release();
++        //this.lock.release(); // Paper - disable this
+     }
+ 
+     public PalettedContainer(Palette<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> elementSerializer, T defaultElement) {
+@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
+         return this.palette.idFor(objectAdded);
+     }
+ 
+-    public T getAndSet(int x, int y, int z, T value) {
++    public synchronized T getAndSet(int x, int y, int z, T value) { // Paper - synchronize
+         Object var6;
+         try {
+             this.acquire();
+@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
+         return (T)(object == null ? this.defaultValue : object);
+     }
+ 
+-    public void set(int i, int j, int k, T object) {
++    public synchronized void set(int i, int j, int k, T object) { // Paper - synchronize
+         try {
+             this.acquire();
+             this.set(getIndex(i, j, k), object);
+@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
+         return (T)(object == null ? this.defaultValue : object);
+     }
+ 
+-    public void read(FriendlyByteBuf buf) {
++    public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize
+         try {
+             this.acquire();
+             int i = buf.readByte();
+@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
+     }
+ 
+     public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER
+-    public void write(FriendlyByteBuf buf) {
++    public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize
+         try {
+             this.acquire();
+             buf.writeByte(this.bits);
+@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
+ 
+     }
+ 
+-    public void read(ListTag paletteNbt, long[] data) {
++    public synchronized void read(ListTag paletteNbt, long[] data) { // Paper - synchronize
+         try {
+             this.acquire();
+             int i = Math.max(4, Mth.ceillog2(paletteNbt.size()));
+@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
+ 
+     }
+ 
+-    public void write(CompoundTag nbt, String paletteKey, String dataKey) {
++    public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey) { // Paper - synchronize
+         try {
+             this.acquire();
+             HashMapPalette<T> hashMapPalette = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer);