diff --git a/patches/server/Delay-Chunk-Unloads-based-on-Player-Movement.patch b/patches/server/Delay-Chunk-Unloads-based-on-Player-Movement.patch
new file mode 100644
index 0000000000..a1e384563c
--- /dev/null
+++ b/patches/server/Delay-Chunk-Unloads-based-on-Player-Movement.patch
@@ -0,0 +1,109 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <aikar@aikar.co>
+Date: Sat, 18 Jun 2016 23:22:12 -0400
+Subject: [PATCH] Delay Chunk Unloads based on Player Movement
+
+When players are moving in the world, doing things such as building or exploring,
+they will commonly go back and forth in a small area. This causes a ton of chunk load
+and unload activity on the edge chunks of their view distance.
+
+A simple back and forth movement in 6 blocks could spam a chunk to thrash a
+loading and unload cycle over and over again.
+
+This is very wasteful. This system introduces a delay of inactivity on a chunk
+before it actually unloads, which will be handled by the ticket expiry process.
+
+This allows servers with smaller worlds who do less long distance exploring to stop
+wasting cpu cycles on saving/unloading/reloading chunks repeatedly.
+
+diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+@@ -0,0 +0,0 @@ public class PaperWorldConfig {
+         lightQueueSize = getInt("light-queue-size", lightQueueSize);
+     }
+ 
++    public long delayChunkUnloadsBy;
++    private void delayChunkUnloadsBy() {
++        delayChunkUnloadsBy = PaperConfig.getSeconds(getString("delay-chunk-unloads-by", "10s"));
++        if (delayChunkUnloadsBy > 0) {
++            log("Delaying chunk unloads by " + delayChunkUnloadsBy + " seconds");
++            delayChunkUnloadsBy *= 20;
++        }
++    }
++
+     public boolean altItemDespawnRateEnabled;
+     public java.util.Map<org.bukkit.Material, Integer> altItemDespawnRateMap;
+     private void altItemDespawnRate() {
+diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
+@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+         boolean removed = false; // CraftBukkit
+         if (arraysetsorted.remove(ticket)) {
+             removed = true; // CraftBukkit
++            // Paper start - delay chunk unloads for player tickets
++            long delayChunkUnloadsBy = chunkMap.level.paperConfig.delayChunkUnloadsBy;
++            if (ticket.getType() == TicketType.PLAYER && delayChunkUnloadsBy > 0) {
++                boolean hasPlayer = false;
++                for (Ticket<?> ticket1 : arraysetsorted) {
++                    if (ticket1.getType() == TicketType.PLAYER) {
++                        hasPlayer = true;
++                        break;
++                    }
++                }
++                ChunkHolder playerChunk = chunkMap.getUpdatingChunkIfPresent(i);
++                if (!hasPlayer && playerChunk != null && playerChunk.isFullChunkReady()) {
++                    Ticket<Long> delayUnload = new Ticket<Long>(TicketType.DELAY_UNLOAD, 33, i);
++                    delayUnload.delayUnloadBy = delayChunkUnloadsBy;
++                    delayUnload.setCreatedTick(this.ticketTickCounter);
++                    arraysetsorted.remove(delayUnload);
++                    // refresh ticket
++                    arraysetsorted.add(delayUnload);
++                }
++            }
++            // Paper end
+         }
+ 
+         if (arraysetsorted.isEmpty()) {
+diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/Ticket.java
++++ b/src/main/java/net/minecraft/server/level/Ticket.java
+@@ -0,0 +0,0 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
+     private final int ticketLevel;
+     public final T key;
+     public long createdTick;
++    public long delayUnloadBy; // Paper
+ 
+     protected Ticket(TicketType<T> type, int level, T argument) {
+         this.type = type;
+         this.ticketLevel = level;
+         this.key = argument;
++        this.delayUnloadBy = type.timeout; // Paper
+     }
+ 
+     @Override
+@@ -0,0 +0,0 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
+     }
+ 
+     protected boolean timedOut(long currentTick) {
+-        long l = this.type.timeout();
++        long l = delayUnloadBy; // Paper
+         return l != 0L && currentTick - this.createdTick > l;
+     }
+ }
+diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/TicketType.java
++++ b/src/main/java/net/minecraft/server/level/TicketType.java
+@@ -0,0 +0,0 @@ public class TicketType<T> {
+     public static final TicketType<ChunkPos> UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
+     public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
+     public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
++    public static final TicketType<Long> DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper
+ 
+     public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
+         return new TicketType<>(name, argumentComparator, 0L);
diff --git a/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
index 220b687582..e4ccb6d6f8 100644
--- a/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
+++ b/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
@@ -1107,9 +1107,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/net/minecraft/server/level/Ticket.java
 +++ b/src/main/java/net/minecraft/server/level/Ticket.java
 @@ -0,0 +0,0 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
-     private final int ticketLevel;
      public final T key;
      public long createdTick;
+     public long delayUnloadBy; // Paper
 +    public int priority; // Paper - Chunk priority
  
      protected Ticket(TicketType<T> type, int level, T argument) {
diff --git a/patches/server/Improve-Chunk-Status-Transition-Speed.patch b/patches/server/Improve-Chunk-Status-Transition-Speed.patch
new file mode 100644
index 0000000000..98a4256fff
--- /dev/null
+++ b/patches/server/Improve-Chunk-Status-Transition-Speed.patch
@@ -0,0 +1,81 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <aikar@aikar.co>
+Date: Fri, 29 May 2020 23:32:14 -0400
+Subject: [PATCH] Improve Chunk Status Transition Speed
+
+When a chunk is loaded from disk that has already been generated,
+the server has to promote the chunk through the system to reach
+it's current desired status level.
+
+This results in every single status transition going from the main thread
+to the world gen threads, only to discover it has no work it actually
+needs to do.... and then it returns back to main.
+
+This back and forth costs a lot of time and can really delay chunk loads
+when the server is under high TPS due to their being a lot of time in
+between chunk load times, as well as hogs up the chunk threads from doing
+actual generation and light work.
+
+Additionally, the whole task system uses a lot of CPU on the server threads anyways.
+
+So by optimizing status transitions for status's that are already complete,
+we can run them to the desired level while on main thread (where it has
+to happen anyways) instead of ever jumping to world gen thread.
+
+This will improve chunk loading effeciency to be reduced down to the following
+scenario / path:
+
+1) MAIN: Chunk Requested, Load Request sent to ChunkTaskManager / IO Queue
+2) IO: Once position in queue comes, submit read IO data and schedule to chunk task thread
+3) CHUNK: Once IO is loaded and position in queue comes, deserialize the chunk data, process conversions, submit to main queue
+4) MAIN: next Chunk Task process (Mid Tick or End Of Tick), load chunk data into world (POI, main thread tasks)
+5) MAIN: process status transitions all the way to LIGHT, light schedules Threaded task
+6) SERVER: Light tasks register light enablement for chunk and any lighting needing to be done
+7) MAIN: Task returns to main, finish processing to FULL/TICKING status
+
+Previously would have hopped to SERVER around 12+ times there extra.
+
+diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+@@ -0,0 +0,0 @@ public class ChunkHolder {
+         this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+     }
+     // Paper end - optimise isOutsideOfRange
++    // Paper start - optimize chunk status progression without jumping through thread pool
++    public boolean canAdvanceStatus() {
++        ChunkStatus status = getChunkHolderStatus();
++        ChunkAccess chunk = getAvailableChunkNow();
++        return chunk != null && (status == null || chunk.getStatus().isOrAfter(getNextStatus(status)));
++    }
++    // Paper end
+ 
+     public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
+         this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
+diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+             return either.mapLeft((list) -> {
+                 return (LevelChunk) list.get(list.size() / 2);
+             });
+-        }, this.mainThreadExecutor);
++        }, this.mainInvokingExecutor); // Paper
+     }
+ 
+     @Nullable
+@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+             return "chunkGenerate " + requiredStatus.getName();
+         });
+         Executor executor = (runnable) -> {
++            // Paper start - optimize chunk status progression without jumping through thread pool
++            if (holder.canAdvanceStatus()) {
++                this.mainInvokingExecutor.execute(runnable);
++                return;
++            }
++            // Paper end
+             this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
+         };
+ 
diff --git a/patches/server/Incremental-player-saving.patch b/patches/server/Incremental-player-saving.patch
new file mode 100644
index 0000000000..5540f59735
--- /dev/null
+++ b/patches/server/Incremental-player-saving.patch
@@ -0,0 +1,126 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <aikar@aikar.co>
+Date: Sun, 9 Aug 2020 08:59:25 +0300
+Subject: [PATCH] Incremental player saving
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
+@@ -0,0 +0,0 @@ public class PaperConfig {
+         config.set("settings.unsupported-settings.allow-headless-pistons-readme", "This setting controls if players should be able to create headless pistons.");
+         allowHeadlessPistons = getBoolean("settings.unsupported-settings.allow-headless-pistons", false);
+     }
++
++    public static int playerAutoSaveRate = -1;
++    public static int maxPlayerAutoSavePerTick = 10;
++    private static void playerAutoSaveRate() {
++        playerAutoSaveRate = getInt("settings.player-auto-save-rate", -1);
++        maxPlayerAutoSavePerTick = getInt("settings.max-player-auto-save-per-tick", -1);
++        if (maxPlayerAutoSavePerTick == -1) { // -1 Automatic / "Recommended"
++            // 10 should be safe for everyone unless you mass spamming player auto save
++            maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20;
++        }
++    }
+ }
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         return flag3;
+     }
+ 
++    // Paper start
+     public boolean saveEverything(boolean suppressLogs, boolean flush, boolean force) {
++        return saveEverything(suppressLogs, flush, force, -1);
++    }
++    public boolean saveEverything(boolean suppressLogs, boolean flush, boolean force, int playerSaveInterval) {
++        // Paper end
+         boolean flag3;
+ 
+         try {
+             this.isSaving = true;
+-            this.getPlayerList().saveAll();
++            this.getPlayerList().saveAll(playerSaveInterval); // Paper
+             flag3 = this.saveAllChunks(suppressLogs, flush, force);
+         } finally {
+             this.isSaving = false;
+@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         this.isSaving = true;
+         if (this.playerList != null) {
+             MinecraftServer.LOGGER.info("Saving players");
+-            this.playerList.saveAll();
+             this.playerList.removeAll(this.isRestarting); // Paper
+             try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets
+         }
+@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+             }
+         }
+ 
++        // Paper start
++        int playerSaveInterval = com.destroystokyo.paper.PaperConfig.playerAutoSaveRate;
++        if (playerSaveInterval < 0) {
++            playerSaveInterval = autosavePeriod;
++        }
++        // Paper end
+         if (this.autosavePeriod > 0 && this.tickCount % this.autosavePeriod == 0) { // CraftBukkit
+             MinecraftServer.LOGGER.debug("Autosave started");
+             this.profiler.push("save");
+-            this.saveEverything(true, false, false);
++            this.saveEverything(true, false, false, playerSaveInterval); // Paper
+             this.profiler.pop();
+             MinecraftServer.LOGGER.debug("Autosave finished");
+-        }
++        } else this.getPlayerList().saveAll(playerSaveInterval); // Paper
+         io.papermc.paper.util.CachedLists.reset(); // Paper
+         // Paper start - move executeAll() into full server tick timing
+         try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) {
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
+     public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder
+ 
+     private static final Logger LOGGER = LogManager.getLogger();
++    public long lastSave = MinecraftServer.currentTick; // Paper
+     private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
+     private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
+     public ServerGamePacketListenerImpl connection;
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -0,0 +0,0 @@ public abstract class PlayerList {
+     protected void save(ServerPlayer player) {
+         if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
+         if (!player.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug)
++        player.lastSave = MinecraftServer.currentTick; // Paper
+         this.playerIo.save(player);
+         ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit
+ 
+@@ -0,0 +0,0 @@ public abstract class PlayerList {
+     }
+ 
+     public void saveAll() {
++        // Paper start - incremental player saving
++        saveAll(-1);
++    }
++    public void saveAll(int interval) {
+         net.minecraft.server.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
+         MinecraftTimings.savePlayers.startTiming(); // Paper
++        int numSaved = 0;
++        long now = MinecraftServer.currentTick;
+         for (int i = 0; i < this.players.size(); ++i) {
+-            this.save(this.players.get(i));
++            ServerPlayer entityplayer = this.players.get(i);
++            if (interval != -1 || now - entityplayer.lastSave >= interval) {
++                this.save(entityplayer);
++                if (interval != -1 && ++numSaved <= com.destroystokyo.paper.PaperConfig.maxPlayerAutoSavePerTick) { break; }
++            }
++            // Paper end
+         }
+         MinecraftTimings.savePlayers.stopTiming(); // Paper
+         return null; }); // Paper - ensure main
diff --git a/patches/server/Make-item-validations-configurable.patch b/patches/server/Make-item-validations-configurable.patch
index ea9b4d0284..75ca412237 100644
--- a/patches/server/Make-item-validations-configurable.patch
+++ b/patches/server/Make-item-validations-configurable.patch
@@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
 +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
 @@ -0,0 +0,0 @@ public class PaperConfig {
-         config.set("settings.unsupported-settings.allow-headless-pistons-readme", "This setting controls if players should be able to create headless pistons.");
-         allowHeadlessPistons = getBoolean("settings.unsupported-settings.allow-headless-pistons", false);
+             maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20;
+         }
      }
 +
 +    public static int itemValidationDisplayNameLength = 8192;
diff --git a/patches/server/Optimise-nearby-player-lookups.patch b/patches/server/Optimise-nearby-player-lookups.patch
index 461a019f57..131025b266 100644
--- a/patches/server/Optimise-nearby-player-lookups.patch
+++ b/patches/server/Optimise-nearby-player-lookups.patch
@@ -24,7 +24,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - optimise checkDespawn
      }
      // Paper end - optimise isOutsideOfRange
- 
+     // Paper start - optimize chunk status progression without jumping through thread pool
 diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java