From 43227a418f28b863d4fa33311bc25a17c3437a1c Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 13 Jun 2021 11:41:07 +0200
Subject: [PATCH] Work

---
 build.gradle.kts                              |   2 +-
 .../Add-GS4-Query-event.patch                 |   0
 .../Add-PlayerPostRespawnEvent.patch          |   0
 .../Annotation-Test-changes.patch             |   0
 .../Entity-getEntitySpawnReason.patch         |   6 +-
 .../Allow-Saving-of-Oversized-Chunks.patch    | 271 ------------------
 .../Fire-event-on-GS4-query.patch             | 265 -----------------
 ...ity-Metadata-for-all-tracked-players.patch |  43 ---
 ...oggleEvent-when-whitelist-is-toggled.patch |   0
 .../Entity-getEntitySpawnReason.patch         |  42 +--
 patches/server/Fire-event-on-GS4-query.patch  | 209 ++++++++++++++
 ...le-Oversized-Tile-Entities-in-chunks.patch |  46 ++-
 .../Implement-PlayerPostRespawnEvent.patch    |   2 +-
 ...0-Fix-Whitelist-On-Off-inconsistency.patch |  15 +-
 ...-Manager-and-add-advanced-packet-sup.patch | 213 ++++----------
 ...st-tick-at-start-of-drowning-process.patch |   0
 ...ity-Metadata-for-all-tracked-players.patch |  76 +++++
 ...ength-when-serialising-BungeeCord-te.patch |   8 +-
 settings.gradle.kts                           |   1 +
 19 files changed, 399 insertions(+), 800 deletions(-)
 rename patches/{api-unmapped => api}/Add-GS4-Query-event.patch (100%)
 rename patches/{api-unmapped => api}/Add-PlayerPostRespawnEvent.patch (100%)
 rename patches/{api-unmapped => api}/Annotation-Test-changes.patch (100%)
 rename patches/{api-unmapped => api}/Entity-getEntitySpawnReason.patch (92%)
 delete mode 100644 patches/server-remapped/Allow-Saving-of-Oversized-Chunks.patch
 delete mode 100644 patches/server-remapped/Fire-event-on-GS4-query.patch
 delete mode 100644 patches/server-remapped/Update-entity-Metadata-for-all-tracked-players.patch
 rename patches/{server-remapped => server}/Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch (100%)
 rename patches/{server-remapped => server}/Entity-getEntitySpawnReason.patch (81%)
 create mode 100644 patches/server/Fire-event-on-GS4-query.patch
 rename patches/{server-remapped => server}/Handle-Oversized-Tile-Entities-in-chunks.patch (53%)
 rename patches/{server-remapped => server}/Implement-PlayerPostRespawnEvent.patch (95%)
 rename patches/{server-remapped => server}/MC-145260-Fix-Whitelist-On-Off-inconsistency.patch (70%)
 rename patches/{server-remapped => server}/Optimize-Network-Manager-and-add-advanced-packet-sup.patch (55%)
 rename patches/{server-remapped => server}/Set-Zombie-last-tick-at-start-of-drowning-process.patch (100%)
 create mode 100644 patches/server/Update-entity-Metadata-for-all-tracked-players.patch
 rename patches/{server-remapped => server}/Use-proper-max-length-when-serialising-BungeeCord-te.patch (81%)

diff --git a/build.gradle.kts b/build.gradle.kts
index 407f27e9d2..101bc900a8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,7 +1,7 @@
 plugins {
     java
     id("com.github.johnrengelman.shadow") version "7.0.0" apply false
-    id("io.papermc.paperweight.core") version "1.0.0-SNAPSHOT"
+    id("io.papermc.paperweight.core") version "1.0.0-LOCAL-SNAPSHOT"
 }
 
 val mcVersion = providers.gradleProperty("mcVersion")
diff --git a/patches/api-unmapped/Add-GS4-Query-event.patch b/patches/api/Add-GS4-Query-event.patch
similarity index 100%
rename from patches/api-unmapped/Add-GS4-Query-event.patch
rename to patches/api/Add-GS4-Query-event.patch
diff --git a/patches/api-unmapped/Add-PlayerPostRespawnEvent.patch b/patches/api/Add-PlayerPostRespawnEvent.patch
similarity index 100%
rename from patches/api-unmapped/Add-PlayerPostRespawnEvent.patch
rename to patches/api/Add-PlayerPostRespawnEvent.patch
diff --git a/patches/api-unmapped/Annotation-Test-changes.patch b/patches/api/Annotation-Test-changes.patch
similarity index 100%
rename from patches/api-unmapped/Annotation-Test-changes.patch
rename to patches/api/Annotation-Test-changes.patch
diff --git a/patches/api-unmapped/Entity-getEntitySpawnReason.patch b/patches/api/Entity-getEntitySpawnReason.patch
similarity index 92%
rename from patches/api-unmapped/Entity-getEntitySpawnReason.patch
rename to patches/api/Entity-getEntitySpawnReason.patch
index 0008e705be..01af933d1f 100644
--- a/patches/api-unmapped/Entity-getEntitySpawnReason.patch
+++ b/patches/api/Entity-getEntitySpawnReason.patch
@@ -14,9 +14,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/org/bukkit/entity/Entity.java
 +++ b/src/main/java/org/bukkit/entity/Entity.java
 @@ -0,0 +0,0 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
-      */
-     @NotNull
-     Chunk getChunk();
+         // TODO remove impl here
+         return getLocation().getChunk();
+     }
 +
 +    /**
 +     * @return The {@link org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason} that spawned this entity.
diff --git a/patches/server-remapped/Allow-Saving-of-Oversized-Chunks.patch b/patches/server-remapped/Allow-Saving-of-Oversized-Chunks.patch
deleted file mode 100644
index 0b91912ed6..0000000000
--- a/patches/server-remapped/Allow-Saving-of-Oversized-Chunks.patch
+++ /dev/null
@@ -1,271 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <aikar@aikar.co>
-Date: Fri, 15 Feb 2019 01:08:19 -0500
-Subject: [PATCH] Allow Saving of Oversized Chunks
-
-The Minecraft World Region File format has a hard cap of 1MB per chunk.
-This is due to the fact that the header of the file format only allocates
-a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector.
-
-This limit can be reached fairly easily with books, resulting in the chunk being unable
-to save to the world. Worse off, is that nothing printed when this occured, and silently
-performed a chunk rollback on next load.
-
-This leads to security risk with duplication and is being actively exploited.
-
-This patch catches the too large scenario, falls back and moves any large Entity
-or Tile Entity into a new compound, and this compound is saved into a different file.
-
-On Chunk Load, we check for oversized status, and if so, we load the extra file and
-merge the Entities and Tile Entities from the oversized chunk back into the level to
-then be loaded as normal.
-
-Once a chunk is returned back to normal size, the oversized flag will clear, and no
-extra data file will exist.
-
-This fix maintains compatability with all existing Anvil Region Format tools as it
-does not alter the save format. They will just not know about the extra entities.
-
-This fix also maintains compatability if someone switches server jars to one without
-this fix, as the data will remain in the oversized file. Once the server returns
-to a jar with this fix, the data will be restored.
-
-diff --git a/src/main/java/net/minecraft/nbt/NbtIo.java b/src/main/java/net/minecraft/nbt/NbtIo.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/nbt/NbtIo.java
-+++ b/src/main/java/net/minecraft/nbt/NbtIo.java
-@@ -0,0 +0,0 @@ public class NbtIo {
- 
-     }
- 
-+    public static CompoundTag readNBT(DataInput datainput) throws IOException { return read(datainput); } // Paper - OBFHELPER
-     public static CompoundTag read(DataInput input) throws IOException {
-         return read(input, NbtAccounter.UNLIMITED);
-     }
-@@ -0,0 +0,0 @@ public class NbtIo {
-         }
-     }
- 
-+    public static void writeNBT(CompoundTag nbttagcompound, DataOutput dataoutput) throws IOException { write(nbttagcompound, dataoutput); } // Paper - OBFHELPER
-     public static void write(CompoundTag tag, DataOutput output) throws IOException {
-         writeUnnamedTag((Tag) tag, output);
-     }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -0,0 +0,0 @@ import java.nio.file.LinkOption;
- import java.nio.file.Path;
- import java.nio.file.StandardCopyOption;
- import java.nio.file.StandardOpenOption;
-+import java.util.zip.InflaterInputStream; // Paper
-+
- import javax.annotation.Nullable;
- import net.minecraft.Util;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.nbt.NbtIo;
- import net.minecraft.world.level.ChunkPos;
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
-     private final IntBuffer timestamps;
-     @VisibleForTesting
-     protected final RegionBitmap usedSectors;
-+    public final File file; // Paper
- 
-     public RegionFile(File file, File directory, boolean dsync) throws IOException {
-         this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync);
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
- 
-     public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
-         this.header = ByteBuffer.allocateDirect(8192);
-+        this.file = file.toFile(); // Paper
-+        initOversizedState(); // Paper
-         this.usedSectors = new RegionBitmap();
-         this.version = outputChunkStreamVersion;
-         if (!Files.isDirectory(directory, new LinkOption[0])) {
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
-         void run() throws IOException;
-     }
- 
-+    // Paper start
-+    private final byte[] oversized = new byte[1024];
-+    private int oversizedCount = 0;
-+
-+    private synchronized void initOversizedState() throws IOException {
-+        File metaFile = getOversizedMetaFile();
-+        if (metaFile.exists()) {
-+            final byte[] read = java.nio.file.Files.readAllBytes(metaFile.toPath());
-+            System.arraycopy(read, 0, oversized, 0, oversized.length);
-+            for (byte temp : oversized) {
-+                oversizedCount += temp;
-+            }
-+        }
-+    }
-+
-+    private static int getChunkIndex(int x, int z) {
-+        return (x & 31) + (z & 31) * 32;
-+    }
-+    synchronized boolean isOversized(int x, int z) {
-+        return this.oversized[getChunkIndex(x, z)] == 1;
-+    }
-+    synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
-+        final int offset = getChunkIndex(x, z);
-+        boolean previous = this.oversized[offset] == 1;
-+        this.oversized[offset] = (byte) (oversized ? 1 : 0);
-+        if (!previous && oversized) {
-+            oversizedCount++;
-+        } else if (!oversized && previous) {
-+            oversizedCount--;
-+        }
-+        if (previous && !oversized) {
-+            File oversizedFile = getOversizedFile(x, z);
-+            if (oversizedFile.exists()) {
-+                oversizedFile.delete();
-+            }
-+        }
-+        if (oversizedCount > 0) {
-+            if (previous != oversized) {
-+                writeOversizedMeta();
-+            }
-+        } else if (previous) {
-+            File oversizedMetaFile = getOversizedMetaFile();
-+            if (oversizedMetaFile.exists()) {
-+                oversizedMetaFile.delete();
-+            }
-+        }
-+    }
-+
-+    private void writeOversizedMeta() throws IOException {
-+        java.nio.file.Files.write(getOversizedMetaFile().toPath(), oversized);
-+    }
-+
-+    private File getOversizedMetaFile() {
-+        return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + ".oversized.nbt");
-+    }
-+
-+    private File getOversizedFile(int x, int z) {
-+        return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
-+    }
-+
-+    synchronized CompoundTag getOversizedData(int x, int z) throws IOException {
-+        File file = getOversizedFile(x, z);
-+        try (DataInputStream out = new DataInputStream(new BufferedInputStream(new InflaterInputStream(new java.io.FileInputStream(file))))) {
-+            return NbtIo.readNBT((java.io.DataInput) out);
-+        }
-+
-+    }
-+    // Paper end
-     class ChunkBuffer extends ByteArrayOutputStream {
- 
-         private final ChunkPos pos;
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -0,0 +0,0 @@ import java.io.File;
- import java.io.IOException;
- import javax.annotation.Nullable;
- import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.nbt.ListTag;
- import net.minecraft.nbt.NbtIo;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.server.MinecraftServer;
- import net.minecraft.util.ExceptionCollector;
- import net.minecraft.world.level.ChunkPos;
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
-         }
-     }
- 
-+    // Paper start
-+    private static void printOversizedLog(String msg, File file, int x, int z) {
-+        org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.");
-+    }
-+
-+    private static final int DEFAULT_SIZE_THRESHOLD = 1024 * 8;
-+    private static final int OVERZEALOUS_TOTAL_THRESHOLD = 1024 * 64;
-+    private static final int OVERZEALOUS_THRESHOLD = 1024;
-+    private static int SIZE_THRESHOLD = DEFAULT_SIZE_THRESHOLD;
-+    private static void resetFilterThresholds() {
-+        SIZE_THRESHOLD = Math.max(1024 * 4, Integer.getInteger("Paper.FilterThreshhold", DEFAULT_SIZE_THRESHOLD));
-+    }
-+    static {
-+        resetFilterThresholds();
-+    }
-+
-+    static boolean isOverzealous() {
-+        return SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD;
-+    }
-+
-+
-+    private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
-+        synchronized (regionfile) {
-+            try (DataInputStream datainputstream = regionfile.getReadStream(chunkCoordinate)) {
-+                CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
-+                CompoundTag chunk = NbtIo.readNBT((DataInput) datainputstream);
-+                if (oversizedData == null) {
-+                    return chunk;
-+                }
-+                CompoundTag oversizedLevel = oversizedData.getCompound("Level");
-+                CompoundTag level = chunk.getCompound("Level");
-+
-+                mergeChunkList(level, oversizedLevel, "Entities");
-+                mergeChunkList(level, oversizedLevel, "TileEntities");
-+
-+                chunk.put("Level", level);
-+
-+                return chunk;
-+            } catch (Throwable throwable) {
-+                throwable.printStackTrace();
-+                throw throwable;
-+            }
-+        }
-+    }
-+
-+    private static void mergeChunkList(CompoundTag level, CompoundTag oversizedLevel, String key) {
-+        ListTag levelList = level.getList(key, 10);
-+        ListTag oversizedList = oversizedLevel.getList(key, 10);
-+
-+        if (!oversizedList.isEmpty()) {
-+            levelList.addAll(oversizedList);
-+            level.put(key, levelList);
-+        }
-+    }
-+
-+    private static int getNBTSize(Tag nbtBase) {
-+        DataOutputStream test = new DataOutputStream(new org.apache.commons.io.output.NullOutputStream());
-+        try {
-+            nbtBase.write(test);
-+            return test.size();
-+        } catch (IOException e) {
-+            e.printStackTrace();
-+            return 0;
-+        }
-+    }
-+
-+    // Paper End
-+
-     @Nullable
-     public CompoundTag read(ChunkPos pos) throws IOException {
-         // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
-         }
-         // CraftBukkit end
-         DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
-+        // Paper start
-+        if (regionfile.isOversized(pos.x, pos.z)) {
-+            printOversizedLog("Loading Oversized Chunk!", regionfile.file, pos.x, pos.z);
-+            return readOversizedChunk(regionfile, pos);
-+        }
-+        // Paper end
-         Throwable throwable = null;
- 
-         CompoundTag nbttagcompound;
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
- 
-         try {
-             NbtIo.write(tag, (DataOutput) dataoutputstream);
-+            regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
-         } catch (Throwable throwable1) {
-             throwable = throwable1;
-             throw throwable1;
diff --git a/patches/server-remapped/Fire-event-on-GS4-query.patch b/patches/server-remapped/Fire-event-on-GS4-query.patch
deleted file mode 100644
index 5ad8d81824..0000000000
--- a/patches/server-remapped/Fire-event-on-GS4-query.patch
+++ /dev/null
@@ -1,265 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Mark Vainomaa <mikroskeem@mikroskeem.eu>
-Date: Sun, 17 Mar 2019 21:46:56 +0200
-Subject: [PATCH] Fire event on GS4 query
-
-
-diff --git a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
-+++ b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
-@@ -0,0 +0,0 @@ public class NetworkDataOutputStream {
-         this.dataOutputStream.write(abyte, 0, abyte.length);
-     }
- 
-+    public void writeString(String string) throws IOException { this.writeString(string); } // Paper - OBFHELPER
-     public void writeString(String s) throws IOException {
-         this.dataOutputStream.writeBytes(s);
-         this.dataOutputStream.write(0);
-     }
-+    // Paper start - unchecked exception variant to use in Stream API
-+    public void writeStringUnchecked(String string) {
-+        try {
-+            writeString(string);
-+        } catch (IOException e) {
-+            com.destroystokyo.paper.util.SneakyThrow.sneaky(e);
-+        }
-+    }
-+    // Paper end
- 
-+    public void writeInt(int i) throws IOException { this.write(i); } // Paper - OBFHELPER
-     public void write(int i) throws IOException {
-         this.dataOutputStream.write(i);
-     }
- 
-+    public void writeShort(short i) throws IOException { this.writeShort(i); } // Paper - OBFHELPER
-     public void writeShort(short short0) throws IOException {
-         this.dataOutputStream.writeShort(Short.reverseBytes(short0));
-     }
-diff --git a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
-+++ b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
-@@ -0,0 +0,0 @@ import java.util.Random;
- import javax.annotation.Nullable;
- import net.minecraft.Util;
- import net.minecraft.server.ServerInterface;
-+import net.minecraft.server.dedicated.DedicatedServer;
- import net.minecraft.server.rcon.NetworkDataOutputStream;
- import net.minecraft.server.rcon.PktUtils;
- import org.apache.logging.log4j.LogManager;
-@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
-     private static final Logger LOGGER = LogManager.getLogger();
-     private long lastChallengeCheck;
-     private final int port;
--    private final int serverPort;
--    private final int maxPlayers;
--    private final String serverName;
--    private final String worldName;
-+    private final int serverPort; private final int getServerPort() { return this.serverPort; } // Paper - OBFHELPER
-+    private final int maxPlayers; private final int getMaxPlayers() { return this.maxPlayers; } // Paper - OBFHELPER
-+    private final String serverName; private final String getMotd() { return this.serverName; } // Paper - OBFHELPER
-+    private final String worldName; private final String getWorldName() { return this.worldName; } // Paper - OBFHELPER
-     private DatagramSocket socket;
-     private final byte[] buffer = new byte[1460];
--    private String hostIp;
-+    private String hostIp; public final String getServerHost() { return this.hostIp; } // Paper - OBFHELPER
-     private String serverIp;
-     private final Map<SocketAddress, QueryThreadGs4.RequestChallenge> validChallenges;
--    private final NetworkDataOutputStream rulesResponse;
-+    private final NetworkDataOutputStream rulesResponse; private final NetworkDataOutputStream getCachedFullResponse() { return this.rulesResponse; } // Paper - OBFHELPER
-     private long lastRulesResponse;
--    private final ServerInterface serverInterface;
-+    private final ServerInterface serverInterface; private final ServerInterface getServer() { return this.serverInterface; } // Paper - OBFHELPER
- 
-     private QueryThreadGs4(ServerInterface server, int queryPort) {
-         super("Query Listener");
-@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
- 
-                         remotestatusreply.write((int) 0);
-                         remotestatusreply.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
--                        remotestatusreply.writeString(this.serverName);
-+                        /* Paper start - GS4 Query event
-+                        remotestatusreply.a(this.i);
-+                        remotestatusreply.a("SMP");
-+                        remotestatusreply.a(this.j);
-+                        remotestatusreply.a(Integer.toString(this.r.getPlayerCount()));
-+                        remotestatusreply.a(Integer.toString(this.h));
-+                        remotestatusreply.a((short) this.g);
-+                        remotestatusreply.a(this.m);
-+                        */
-+                        com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
-+                            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC;
-+                        com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
-+                            .motd(this.getMotd())
-+                            .map(this.getWorldName())
-+                            .currentPlayers(this.getServer().getPlayerCount())
-+                            .maxPlayers(this.getMaxPlayers())
-+                            .port(this.getServerPort())
-+                            .hostname(this.getServerHost())
-+                            .gameVersion(this.getServer().getServerVersion())
-+                            .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
-+                            .build();
-+                        com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
-+                            new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
-+                        queryEvent.callEvent();
-+                        queryResponse = queryEvent.getResponse();
-+                        remotestatusreply.writeString(queryResponse.getMotd());
-                         remotestatusreply.writeString("SMP");
--                        remotestatusreply.writeString(this.worldName);
--                        remotestatusreply.writeString(Integer.toString(this.serverInterface.getPlayerCount()));
--                        remotestatusreply.writeString(Integer.toString(this.maxPlayers));
--                        remotestatusreply.writeShort((short) this.serverPort);
--                        remotestatusreply.writeString(this.hostIp);
-+                        remotestatusreply.writeString(queryResponse.getMap());
-+                        remotestatusreply.writeString(Integer.toString(queryResponse.getCurrentPlayers()));
-+                        remotestatusreply.writeString(Integer.toString(queryResponse.getMaxPlayers()));
-+                        remotestatusreply.writeShort((short) queryResponse.getPort());
-+                        remotestatusreply.writeString(queryResponse.getHostname());
-+                        // Paper end
-                         this.sendTo(remotestatusreply.toByteArray(), packet);
-                         QueryThreadGs4.LOGGER.debug("Status [{}]", socketaddress);
-                     }
-@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
-             this.rulesResponse.writeString("splitnum");
-             this.rulesResponse.write((int) 128);
-             this.rulesResponse.write((int) 0);
--            this.rulesResponse.writeString("hostname");
--            this.rulesResponse.writeString(this.serverName);
--            this.rulesResponse.writeString("gametype");
--            this.rulesResponse.writeString("SMP");
--            this.rulesResponse.writeString("game_id");
--            this.rulesResponse.writeString("MINECRAFT");
--            this.rulesResponse.writeString("version");
--            this.rulesResponse.writeString(this.serverInterface.getServerVersion());
--            this.rulesResponse.writeString("plugins");
--            this.rulesResponse.writeString(this.serverInterface.getPluginNames());
--            this.rulesResponse.writeString("map");
--            this.rulesResponse.writeString(this.worldName);
--            this.rulesResponse.writeString("numplayers");
--            this.rulesResponse.writeString("" + this.serverInterface.getPlayerCount());
--            this.rulesResponse.writeString("maxplayers");
--            this.rulesResponse.writeString("" + this.maxPlayers);
--            this.rulesResponse.writeString("hostport");
--            this.rulesResponse.writeString("" + this.serverPort);
--            this.rulesResponse.writeString("hostip");
--            this.rulesResponse.writeString(this.hostIp);
--            this.rulesResponse.write((int) 0);
--            this.rulesResponse.write((int) 1);
--            this.rulesResponse.writeString("player_");
--            this.rulesResponse.write((int) 0);
--            String[] astring = this.serverInterface.getPlayerNames();
-+            /* Paper start - GS4 Query event
-+            this.p.a("hostname");
-+            this.p.a(this.i);
-+            this.p.a("gametype");
-+            this.p.a("SMP");
-+            this.p.a("game_id");
-+            this.p.a("MINECRAFT");
-+            this.p.a("version");
-+            this.p.a(this.r.getVersion());
-+            this.p.a("plugins");
-+            this.p.a(this.r.getPlugins());
-+            this.p.a("map");
-+            this.p.a(this.j);
-+            this.p.a("numplayers");
-+            this.p.a("" + this.r.getPlayerCount());
-+            this.p.a("maxplayers");
-+            this.p.a("" + this.h);
-+            this.p.a("hostport");
-+            this.p.a("" + this.g);
-+            this.p.a("hostip");
-+            this.p.a(this.m);
-+            this.p.a((int) 0);
-+            this.p.a((int) 1);
-+            this.p.a("player_");
-+            this.p.a((int) 0);
-+            String[] astring = this.r.getPlayers();
-             String[] astring1 = astring;
-             int j = astring.length;
- 
-             for (int k = 0; k < j; ++k) {
-                 String s = astring1[k];
- 
--                this.rulesResponse.writeString(s);
-+                this.p.a(s);
-             }
- 
--            this.rulesResponse.write((int) 0);
-+            this.p.a((int) 0);
-+            */
-+            // Pack plugins
-+            java.util.List<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> plugins = java.util.Collections.emptyList();
-+            org.bukkit.plugin.Plugin[] bukkitPlugins;
-+            if (((DedicatedServer) this.getServer()).server.getQueryPlugins() && (bukkitPlugins = org.bukkit.Bukkit.getPluginManager().getPlugins()).length > 0) {
-+                plugins = java.util.stream.Stream.of(bukkitPlugins)
-+                    .map(plugin -> com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation.of(plugin.getName(), plugin.getDescription().getVersion()))
-+                    .collect(java.util.stream.Collectors.toList());
-+            }
-+
-+            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
-+                .motd(this.getMotd())
-+                .map(this.getWorldName())
-+                .currentPlayers(this.getServer().getPlayerCount())
-+                .maxPlayers(this.getMaxPlayers())
-+                .port(this.getServerPort())
-+                .hostname(this.getServerHost())
-+                .plugins(plugins)
-+                .players(this.getServer().getPlayerNames())
-+                .gameVersion(this.getServer().getServerVersion())
-+                .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
-+                .build();
-+            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
-+                com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL;
-+            com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
-+                new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
-+            queryEvent.callEvent();
-+            queryResponse = queryEvent.getResponse();
-+            this.getCachedFullResponse().writeString("hostname");
-+            this.getCachedFullResponse().writeString(queryResponse.getMotd());
-+            this.getCachedFullResponse().writeString("gametype");
-+            this.getCachedFullResponse().writeString("SMP");
-+            this.getCachedFullResponse().writeString("game_id");
-+            this.getCachedFullResponse().writeString("MINECRAFT");
-+            this.getCachedFullResponse().writeString("version");
-+            this.getCachedFullResponse().writeString(queryResponse.getGameVersion());
-+            this.getCachedFullResponse().writeString("plugins");
-+            java.lang.StringBuilder pluginsString = new java.lang.StringBuilder();
-+            pluginsString.append(queryResponse.getServerVersion());
-+            if (!queryResponse.getPlugins().isEmpty()) {
-+                pluginsString.append(": ");
-+                java.util.Iterator<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> iter = queryResponse.getPlugins().iterator();
-+                while (iter.hasNext()) {
-+                    com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation info = iter.next();
-+                    pluginsString.append(info.getName());
-+                    if (info.getVersion() != null) {
-+                        pluginsString.append(' ').append(info.getVersion().replace(";", ","));
-+                    }
-+                    if (iter.hasNext()) {
-+                        pluginsString.append(';').append(' ');
-+                    }
-+                }
-+            }
-+            this.getCachedFullResponse().writeString(pluginsString.toString());
-+            this.getCachedFullResponse().writeString("map");
-+            this.getCachedFullResponse().writeString(queryResponse.getMap());
-+            this.getCachedFullResponse().writeString("numplayers");
-+            this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getCurrentPlayers()));
-+            this.getCachedFullResponse().writeString("maxplayers");
-+            this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getMaxPlayers()));
-+            this.getCachedFullResponse().writeString("hostport");
-+            this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getPort()));
-+            this.getCachedFullResponse().writeString("hostip");
-+            this.getCachedFullResponse().writeString(queryResponse.getHostname());
-+            // The "meaningless data" start, copied from above
-+            this.getCachedFullResponse().writeInt(0);
-+            this.getCachedFullResponse().writeInt(1);
-+            this.getCachedFullResponse().writeString("player_");
-+            this.getCachedFullResponse().writeInt(0);
-+            // "Meaningless data" end
-+            queryResponse.getPlayers().forEach(this.getCachedFullResponse()::writeStringUnchecked);
-+            this.getCachedFullResponse().writeInt(0);
-+            // Paper end
-             return this.rulesResponse.toByteArray();
-         }
-     }
diff --git a/patches/server-remapped/Update-entity-Metadata-for-all-tracked-players.patch b/patches/server-remapped/Update-entity-Metadata-for-all-tracked-players.patch
deleted file mode 100644
index 877a6dbf2a..0000000000
--- a/patches/server-remapped/Update-entity-Metadata-for-all-tracked-players.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: AgentTroll <woodyc40@gmail.com>
-Date: Fri, 22 Mar 2019 22:24:03 -0700
-Subject: [PATCH] Update entity Metadata for all tracked players
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerEntity.java
-+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
-@@ -0,0 +0,0 @@ public class ServerEntity {
-         return ClientboundMoveEntityPacket.packetToEntity(this.xp, this.yp, this.zp);
-     }
- 
-+    // Paper start - Add broadcast method
-+    void broadcast(Packet<?> packet) {
-+        this.getPacketConsumer().accept(packet);
-+    }
-+    // Paper end
-+
-     private void broadcastAndSend(Packet<?> packet) {
-         this.broadcast.accept(packet);
-         if (this.entity instanceof ServerPlayer) {
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener {
- 
-                     if (event.isCancelled() || this.player.inventory.getSelected() == null || this.player.inventory.getSelected().getItem() != origItem) {
-                         // Refresh the current entity metadata
--                        this.send(new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true));
-+                        // Paper start - update entity for all players
-+                        ClientboundSetEntityDataPacket packet1 = new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true);
-+                        if (entity.tracker != null) {
-+                            entity.tracker.broadcast(packet1);
-+                        } else {
-+                            this.send(packet1);
-+                        }
-+                        // Paper end
-                     }
- 
-                     if (event.isCancelled()) {
diff --git a/patches/server-remapped/Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch b/patches/server/Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch
similarity index 100%
rename from patches/server-remapped/Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch
rename to patches/server/Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch
diff --git a/patches/server-remapped/Entity-getEntitySpawnReason.patch b/patches/server/Entity-getEntitySpawnReason.patch
similarity index 81%
rename from patches/server-remapped/Entity-getEntitySpawnReason.patch
rename to patches/server/Entity-getEntitySpawnReason.patch
index f322579511..604afd6332 100644
--- a/patches/server-remapped/Entity-getEntitySpawnReason.patch
+++ b/patches/server/Entity-getEntitySpawnReason.patch
@@ -18,9 +18,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
          org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
 +        if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper
-         // Paper start
-         if (entity.valid) {
-             MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable());
+         if (entity.isRemoved()) {
+             // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getName(entity.getEntityType())); // CraftBukkit
+             return false;
 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
@@ -47,30 +47,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import net.minecraft.world.entity.item.ItemEntity;
  import net.minecraft.world.entity.player.Player;
  import net.minecraft.world.entity.vehicle.Boat;
-@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
+@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
+             }
          }
      };
-     public List<Entity> entitySlice = null;
 +    public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason;
      // Paper end
  
      public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
-@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
-                 tag.setUUID("Paper.OriginWorld", origin.getWorld().getUID());
-                 tag.put("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ()));
+@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
+                 nbt.setUUID("Paper.OriginWorld", origin.getWorld().getUID());
+                 nbt.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ()));
              }
 +            if (spawnReason != null) {
-+                tag.putString("Paper.SpawnReason", spawnReason.name());
++                nbt.putString("Paper.SpawnReason", spawnReason.name());
 +            }
              // Save entity's from mob spawner status
              if (spawnedViaMobSpawner) {
-                 tag.putBoolean("Paper.FromMobSpawner", true);
-@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
+                 nbt.putBoolean("Paper.FromMobSpawner", true);
+@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
              }
  
-             spawnedViaMobSpawner = tag.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
-+            if (tag.contains("Paper.SpawnReason")) {
-+                String spawnReasonName = tag.getString("Paper.SpawnReason");
+             spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
++            if (nbt.contains("Paper.SpawnReason")) {
++                String spawnReasonName = nbt.getString("Paper.SpawnReason");
 +                try {
 +                    spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName);
 +                } catch (Exception ignored) {
@@ -81,7 +81,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                if (spawnedViaMobSpawner) {
 +                    spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER;
 +                } else if (this instanceof Mob && (this instanceof Animal || this instanceof AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) {
-+                    if (!tag.getBoolean("PersistenceRequired")) {
++                    if (!nbt.getBoolean("PersistenceRequired")) {
 +                        spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL;
 +                    }
 +                }
@@ -97,13 +97,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- 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 {
-                                 // Spigot End
-                             }
+                             // Spigot End
+                         }
                          entity.spawnedViaMobSpawner = true; // Paper
-+                            entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper
-                             flag = true; // Paper
-                             // Spigot Start
-                             if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) {
++                        entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper
+                         flag = true; // Paper
+                         // Spigot Start
+                         if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) {
 diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
diff --git a/patches/server/Fire-event-on-GS4-query.patch b/patches/server/Fire-event-on-GS4-query.patch
new file mode 100644
index 0000000000..d98be1e2f4
--- /dev/null
+++ b/patches/server/Fire-event-on-GS4-query.patch
@@ -0,0 +1,209 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Mark Vainomaa <mikroskeem@mikroskeem.eu>
+Date: Sun, 17 Mar 2019 21:46:56 +0200
+Subject: [PATCH] Fire event on GS4 query
+
+
+diff --git a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
++++ b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
+@@ -0,0 +0,0 @@ public class NetworkDataOutputStream {
+         this.dataOutputStream.write(0);
+     }
+ 
++    // Paper start - unchecked exception variant to use in Stream API
++    public void writeStringUnchecked(String string) {
++        try {
++            writeString(string);
++        } catch (IOException e) {
++            com.destroystokyo.paper.util.SneakyThrow.sneaky(e);
++        }
++    }
++    // Paper end
++
+     public void write(int value) throws IOException {
+         this.dataOutputStream.write(value);
+     }
+diff --git a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
++++ b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
+@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
+         if (3 <= i && -2 == bs[0] && -3 == bs[1]) {
+             LOGGER.debug("Packet '{}' [{}]", PktUtils.toHexString(bs[2]), socketAddress);
+             switch(bs[2]) {
+-            case 0:
+-                if (!this.validChallenge(packet)) {
+-                    LOGGER.debug("Invalid challenge [{}]", (Object)socketAddress);
+-                    return false;
+-                } else if (15 == i) {
+-                    this.sendTo(this.buildRuleResponse(packet), packet);
+-                    LOGGER.debug("Rules [{}]", (Object)socketAddress);
+-                } else {
+-                    NetworkDataOutputStream networkDataOutputStream = new NetworkDataOutputStream(1460);
+-                    networkDataOutputStream.write(0);
+-                    networkDataOutputStream.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
+-                    networkDataOutputStream.writeString(this.serverName);
+-                    networkDataOutputStream.writeString("SMP");
+-                    networkDataOutputStream.writeString(this.worldName);
+-                    networkDataOutputStream.writeString(Integer.toString(this.serverInterface.getPlayerCount()));
+-                    networkDataOutputStream.writeString(Integer.toString(this.maxPlayers));
+-                    networkDataOutputStream.writeShort((short)this.serverPort);
+-                    networkDataOutputStream.writeString(this.hostIp);
+-                    this.sendTo(networkDataOutputStream.toByteArray(), packet);
+-                    LOGGER.debug("Status [{}]", (Object)socketAddress);
+-                }
+-            default:
+-                return true;
+-            case 9:
+-                this.sendChallenge(packet);
+-                LOGGER.debug("Challenge [{}]", (Object)socketAddress);
+-                return true;
++                case 0:
++                    if (!this.validChallenge(packet)) {
++                        LOGGER.debug("Invalid challenge [{}]", (Object)socketAddress);
++                        return false;
++                    } else if (15 == i) {
++                        this.sendTo(this.buildRuleResponse(packet), packet);
++                        LOGGER.debug("Rules [{}]", (Object)socketAddress);
++                    } else {
++                        NetworkDataOutputStream networkDataOutputStream = new NetworkDataOutputStream(1460);
++                        networkDataOutputStream.write(0);
++                        networkDataOutputStream.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
++
++                        com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
++                            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC;
++                        com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
++                            .motd(this.serverName)
++                            .map(this.worldName)
++                            .currentPlayers(this.serverInterface.getPlayerCount())
++                            .maxPlayers(this.maxPlayers)
++                            .port(this.serverPort)
++                            .hostname(this.hostIp)
++                            .gameVersion(this.serverInterface.getServerVersion())
++                            .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
++                            .build();
++                        com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
++                            new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
++                        queryEvent.callEvent();
++                        queryResponse = queryEvent.getResponse();
++
++                        networkDataOutputStream.writeString(queryResponse.getMotd());
++                        networkDataOutputStream.writeString("SMP");
++                        networkDataOutputStream.writeString(queryResponse.getMap());
++                        networkDataOutputStream.writeString(Integer.toString(queryResponse.getCurrentPlayers()));
++                        networkDataOutputStream.writeString(Integer.toString(queryResponse.getMaxPlayers()));
++                        networkDataOutputStream.writeShort((short) queryResponse.getPort());
++                        networkDataOutputStream.writeString(queryResponse.getHostname());
++                        // Paper end
++                        this.sendTo(networkDataOutputStream.toByteArray(), packet);
++                        LOGGER.debug("Status [{}]", (Object)socketAddress);
++                    }
++                default:
++                    return true;
++                case 9:
++                    this.sendChallenge(packet);
++                    LOGGER.debug("Challenge [{}]", (Object)socketAddress);
++                    return true;
+             }
+         } else {
+             LOGGER.debug("Invalid packet [{}]", (Object)socketAddress);
+@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
+             this.rulesResponse.writeString("splitnum");
+             this.rulesResponse.write(128);
+             this.rulesResponse.write(0);
++            // Paper start
++            // Pack plugins
++            java.util.List<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> plugins = java.util.Collections.emptyList();
++            org.bukkit.plugin.Plugin[] bukkitPlugins;
++            if (((net.minecraft.server.dedicated.DedicatedServer) this.serverInterface).server.getQueryPlugins() && (bukkitPlugins = org.bukkit.Bukkit.getPluginManager().getPlugins()).length > 0) {
++                plugins = java.util.stream.Stream.of(bukkitPlugins)
++                    .map(plugin -> com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation.of(plugin.getName(), plugin.getDescription().getVersion()))
++                    .collect(java.util.stream.Collectors.toList());
++            }
++
++            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
++                .motd(this.serverName)
++                .map(this.worldName)
++                .currentPlayers(this.serverInterface.getPlayerCount())
++                .maxPlayers(this.maxPlayers)
++                .port(this.serverPort)
++                .hostname(this.hostIp)
++                .plugins(plugins)
++                .players(this.serverInterface.getPlayerNames())
++                .gameVersion(this.serverInterface.getServerVersion())
++                .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
++                .build();
++            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
++                com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL;
++            com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
++                new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
++            queryEvent.callEvent();
++            queryResponse = queryEvent.getResponse();
+             this.rulesResponse.writeString("hostname");
+-            this.rulesResponse.writeString(this.serverName);
++            this.rulesResponse.writeString(queryResponse.getMotd());
+             this.rulesResponse.writeString("gametype");
+             this.rulesResponse.writeString("SMP");
+             this.rulesResponse.writeString("game_id");
+             this.rulesResponse.writeString("MINECRAFT");
+             this.rulesResponse.writeString("version");
+-            this.rulesResponse.writeString(this.serverInterface.getServerVersion());
++            this.rulesResponse.writeString(queryResponse.getGameVersion());
+             this.rulesResponse.writeString("plugins");
+-            this.rulesResponse.writeString(this.serverInterface.getPluginNames());
++            java.lang.StringBuilder pluginsString = new java.lang.StringBuilder();
++            pluginsString.append(queryResponse.getServerVersion());
++            if (!queryResponse.getPlugins().isEmpty()) {
++                pluginsString.append(": ");
++                java.util.Iterator<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> iter = queryResponse.getPlugins().iterator();
++                while (iter.hasNext()) {
++                    com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation info = iter.next();
++                    pluginsString.append(info.getName());
++                    if (info.getVersion() != null) {
++                        pluginsString.append(' ').append(info.getVersion().replace(";", ","));
++                    }
++                    if (iter.hasNext()) {
++                        pluginsString.append(';').append(' ');
++                    }
++                }
++            }
++            this.rulesResponse.writeString(pluginsString.toString());
+             this.rulesResponse.writeString("map");
+-            this.rulesResponse.writeString(this.worldName);
++            this.rulesResponse.writeString(queryResponse.getMap());
+             this.rulesResponse.writeString("numplayers");
+-            this.rulesResponse.writeString("" + this.serverInterface.getPlayerCount());
++            this.rulesResponse.writeString(Integer.toString(queryResponse.getCurrentPlayers()));
+             this.rulesResponse.writeString("maxplayers");
+-            this.rulesResponse.writeString("" + this.maxPlayers);
++            this.rulesResponse.writeString(Integer.toString(queryResponse.getMaxPlayers()));
+             this.rulesResponse.writeString("hostport");
+-            this.rulesResponse.writeString("" + this.serverPort);
++            this.rulesResponse.writeString(Integer.toString(queryResponse.getPort()));
+             this.rulesResponse.writeString("hostip");
+-            this.rulesResponse.writeString(this.hostIp);
+-            this.rulesResponse.write(0);
+-            this.rulesResponse.write(1);
++            this.rulesResponse.writeString(queryResponse.getHostname());
++            // The "meaningless data" start, copied from above
++            this.rulesResponse.writeInt(0);
++            this.rulesResponse.writeInt(1);
+             this.rulesResponse.writeString("player_");
+-            this.rulesResponse.write(0);
+-            String[] strings = this.serverInterface.getPlayerNames();
+-
+-            for(String string : strings) {
+-                this.rulesResponse.writeString(string);
+-            }
+-
+-            this.rulesResponse.write(0);
++            this.rulesResponse.writeInt(0);
++            // "Meaningless data" end
++            queryResponse.getPlayers().forEach(this.rulesResponse::writeStringUnchecked);
++            this.rulesResponse.writeInt(0);
++            // Paper end
+             return this.rulesResponse.toByteArray();
+         }
+     }
diff --git a/patches/server-remapped/Handle-Oversized-Tile-Entities-in-chunks.patch b/patches/server/Handle-Oversized-Tile-Entities-in-chunks.patch
similarity index 53%
rename from patches/server-remapped/Handle-Oversized-Tile-Entities-in-chunks.patch
rename to patches/server/Handle-Oversized-Tile-Entities-in-chunks.patch
index 48096b1e53..080cde99cd 100644
--- a/patches/server-remapped/Handle-Oversized-Tile-Entities-in-chunks.patch
+++ b/patches/server/Handle-Oversized-Tile-Entities-in-chunks.patch
@@ -13,42 +13,38 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
 +++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
 @@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
-     private boolean fullChunk;
- 
-     public ClientboundLevelChunkPacket() {}
+     private final int[] biomes;
+     private final byte[] buffer;
+     private final List<CompoundTag> blockEntitiesTags;
 +    // Paper start
 +    private final java.util.List<Packet> extraPackets = new java.util.ArrayList<>();
 +    private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750);
- 
++
 +    @Override
 +    public java.util.List<Packet> getExtraPackets() {
 +        return extraPackets;
 +    }
 +    // Paper end
-     public ClientboundLevelChunkPacket(LevelChunk chunk, int includedSectionsMask) {
-         ChunkPos chunkcoordintpair = chunk.getPos();
  
+     public ClientboundLevelChunkPacket(LevelChunk chunk) {
+         ChunkPos chunkPos = chunk.getPos();
 @@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
-         this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, includedSectionsMask);
+         this.buffer = new byte[this.calculateChunkSize(chunk)];
+         this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk);
          this.blockEntitiesTags = Lists.newArrayList();
-         iterator = chunk.getBlockEntities().entrySet().iterator();
 +        int totalTileEntities = 0; // Paper
  
-         while (iterator.hasNext()) {
-             entry = (Entry) iterator.next();
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
-             int j = blockposition.getY() >> 4;
- 
-             if (this.isFullChunk() || (includedSectionsMask & 1 << j) != 0) {
-+                // Paper start - improve oversized chunk data packet handling
-+                if (++totalTileEntities > TE_LIMIT) {
-+                    ClientboundBlockEntityDataPacket updatePacket = tileentity.getUpdatePacket();
-+                    if (updatePacket != null) {
-+                        this.extraPackets.add(updatePacket);
-+                        continue;
-+                    }
+         for(Entry<BlockPos, BlockEntity> entry2 : chunk.getBlockEntities().entrySet()) {
+             BlockEntity blockEntity = entry2.getValue();
++            // Paper start - improve oversized chunk data packet handling
++            if (++totalTileEntities > TE_LIMIT) {
++                ClientboundBlockEntityDataPacket updatePacket = blockEntity.getUpdatePacket();
++                if (updatePacket != null) {
++                    this.extraPackets.add(updatePacket);
++                    continue;
 +                }
-+                // Paper end
-                 CompoundTag nbttagcompound = tileentity.getUpdateTag();
-                 if (tileentity instanceof SkullBlockEntity) { SkullBlockEntity.sanitizeTileEntityUUID(nbttagcompound); } // Paper
- 
++            }
++            // Paper end
+             CompoundTag compoundTag = blockEntity.getUpdateTag();
+             if (blockEntity instanceof net.minecraft.world.level.block.entity.SkullBlockEntity) { net.minecraft.world.level.block.entity.SkullBlockEntity.sanitizeTileEntityUUID(compoundTag); } // Paper
+             this.blockEntitiesTags.add(compoundTag);
diff --git a/patches/server-remapped/Implement-PlayerPostRespawnEvent.patch b/patches/server/Implement-PlayerPostRespawnEvent.patch
similarity index 95%
rename from patches/server-remapped/Implement-PlayerPostRespawnEvent.patch
rename to patches/server/Implement-PlayerPostRespawnEvent.patch
index 2f89cb7af8..35a98837a2 100644
--- a/patches/server-remapped/Implement-PlayerPostRespawnEvent.patch
+++ b/patches/server/Implement-PlayerPostRespawnEvent.patch
@@ -9,7 +9,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- 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 {
-         // this.a(entityplayer1, entityplayer, worldserver1); // CraftBukkit - removed
+ 
          boolean flag2 = false;
  
 +        // Paper start
diff --git a/patches/server-remapped/MC-145260-Fix-Whitelist-On-Off-inconsistency.patch b/patches/server/MC-145260-Fix-Whitelist-On-Off-inconsistency.patch
similarity index 70%
rename from patches/server-remapped/MC-145260-Fix-Whitelist-On-Off-inconsistency.patch
rename to patches/server/MC-145260-Fix-Whitelist-On-Off-inconsistency.patch
index 9dc0bf6552..fb0b1b1815 100644
--- a/patches/server-remapped/MC-145260-Fix-Whitelist-On-Off-inconsistency.patch
+++ b/patches/server/MC-145260-Fix-Whitelist-On-Off-inconsistency.patch
@@ -27,19 +27,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public boolean isWhitelisted(GameProfile gameprofile, org.bukkit.event.player.PlayerLoginEvent loginEvent) {
          boolean isOp = this.ops.contains(gameprofile);
 -        boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile);
-+        boolean isWhitelisted = !this.isUsingWhitelist() || isOp || this.whitelist.contains(gameprofile);
++        boolean isWhitelisted = !this.isUsingWhitelist() || isOp || this.whitelist.contains(gameprofile); // Paper - use isUsingWhitelist()
          final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event;
--        event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.doWhiteList, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage);
-+        event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.isUsingWhitelist(), isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage);
+-        event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(net.minecraft.server.MCUtil.toBukkit(gameprofile), this.doWhiteList, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage);
++        event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(net.minecraft.server.MCUtil.toBukkit(gameprofile), this.isUsingWhitelist(), isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); // Paper - use isUsingWhitelist()
          event.callEvent();
          if (!event.isWhitelisted()) {
              if (loginEvent != null) {
-@@ -0,0 +0,0 @@ public abstract class PlayerList {
-         MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
-         MinecraftTimings.savePlayers.startTiming(); // Paper
-         for (int i = 0; i < this.players.size(); ++i) {
--            this.savePlayerFile((EntityPlayer) this.players.get(i));
-+            this.save((ServerPlayer) this.players.get(i));
-         }
-         MinecraftTimings.savePlayers.stopTiming(); // Paper
-         return null; }); // Paper - ensure main
diff --git a/patches/server-remapped/Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/patches/server/Optimize-Network-Manager-and-add-advanced-packet-sup.patch
similarity index 55%
rename from patches/server-remapped/Optimize-Network-Manager-and-add-advanced-packet-sup.patch
rename to patches/server/Optimize-Network-Manager-and-add-advanced-packet-sup.patch
index 9d106e5dda..90e3b571dd 100644
--- a/patches/server-remapped/Optimize-Network-Manager-and-add-advanced-packet-sup.patch
+++ b/patches/server/Optimize-Network-Manager-and-add-advanced-packet-sup.patch
@@ -31,22 +31,6 @@ diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/network/Connection.java
 +++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -0,0 +0,0 @@ import net.minecraft.network.chat.Component;
- import net.minecraft.network.chat.TranslatableComponent;
- import net.minecraft.network.protocol.Packet;
- import net.minecraft.network.protocol.PacketFlow;
-+import net.minecraft.network.protocol.game.ClientboundBossEventPacket;
-+import net.minecraft.network.protocol.game.ClientboundChatPacket;
-+import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket;
- import net.minecraft.network.protocol.game.ClientboundDisconnectPacket;
-+import net.minecraft.network.protocol.game.ClientboundKeepAlivePacket;
-+import net.minecraft.network.protocol.game.ClientboundSetTitlesPacket;
-+import net.minecraft.server.MCUtil;
- import net.minecraft.server.RunningOnDifferentThreadException;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.server.network.ServerGamePacketListenerImpl;
- import net.minecraft.server.network.ServerLoginPacketListenerImpl;
- import net.minecraft.util.LazyLoadedValue;
 @@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      public int protocolVersion;
      public java.net.InetSocketAddress virtualHost;
@@ -71,7 +55,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.packetListener = listener;
      }
 +    // Paper start
-+    public ServerPlayer getPlayer() {
++    public net.minecraft.server.level.ServerPlayer getPlayer() {
 +        if (packetListener instanceof ServerGamePacketListenerImpl) {
 +            return ((ServerGamePacketListenerImpl) packetListener).player;
 +        } else {
@@ -101,11 +85,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper start
 +        private static boolean canSendImmediate(Connection networkManager, Packet<?> packet) {
 +            return networkManager.isPending || networkManager.protocol != ConnectionProtocol.PLAY ||
-+                packet instanceof ClientboundKeepAlivePacket ||
-+                packet instanceof ClientboundChatPacket ||
-+                packet instanceof ClientboundCommandSuggestionsPacket ||
-+                packet instanceof ClientboundSetTitlesPacket ||
-+                packet instanceof ClientboundBossEventPacket;
++                packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundChatPacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundClearTitlesPacket ||
++                packet instanceof net.minecraft.network.protocol.game.ClientboundBossEventPacket;
 +        }
 +        // Paper end
 +    }
@@ -128,10 +116,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +        packet.onPacketDispatch(getPlayer());
 +        if (connected && (InnerUtil.canSendImmediate(this, packet) || (
-+            MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() &&
++            net.minecraft.server.MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() &&
 +            (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
 +        ))) {
-+            this.dispatchPacket(packet, callback);
++            this.send(packet, callback);
 +            return;
          }
 +        // write the packets to the queue, then flush - antixray hooks there already
@@ -142,106 +130,67 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        } else {
 +            java.util.List<Connection.PacketHolder> packets = new java.util.ArrayList<>(1 + extraPackets.size());
 +            packets.add(new Connection.PacketHolder(packet, null)); // delay the future listener until the end of the extra packets
-+
+ 
 +            for (int i = 0, len = extraPackets.size(); i < len;) {
 +                Packet extra = extraPackets.get(i);
 +                boolean end = ++i == len;
 +                packets.add(new Connection.PacketHolder(extra, end ? callback : null)); // append listener to the end
 +            }
- 
 +            this.queue.addAll(packets); // atomic
 +        }
-+        this.sendPacketQueue();
++        this.flushQueue();
 +        // Paper end
      }
  
-     private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.sendPacket(packet, genericFutureListener); } // Paper - OBFHELPER
+     private void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
 @@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-             this.channel.config().setAutoRead(false);
+             this.setProtocol(enumprotocol);
          }
  
-+        ServerPlayer player = getPlayer(); // Paper
-         if (this.channel.eventLoop().inEventLoop()) {
-             if (enumprotocol != enumprotocol1) {
-                 this.setProtocol(enumprotocol);
-             }
-+            // Paper start
-+            if (!isConnected()) {
-+                packet.onPacketDispatchFinish(player, null);
-+                return;
-+            }
-+            try {
-+                // Paper end
- 
-             ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
- 
-             if (callback != null) {
-                 channelfuture.addListener(callback);
-             }
-+            // Paper start
-+            if (packet.hasFinishListener()) {
-+                channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
-+            }
-+            // Paper end
- 
-             channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
-+            // Paper start
-+            } catch (Exception e) {
-+                LOGGER.error("NetworkException: " + player, e);
-+                disconnect(new TranslatableComponent("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
-+                packet.onPacketDispatchFinish(player, null);
-+            }
-+            // Paper end
-         } else {
-             this.channel.eventLoop().execute(() -> {
-                 if (enumprotocol != enumprotocol1) {
-                     this.setProtocol(enumprotocol);
-                 }
- 
-+                // Paper start
-+                if (!isConnected()) {
-+                    packet.onPacketDispatchFinish(player, null);
-+                    return;
-+                }
-+                try {
-+                    // Paper end
-                 ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet);
- 
++        // Paper start
++        net.minecraft.server.level.ServerPlayer player = getPlayer();
++        if (!isConnected()) {
++            packet.onPacketDispatchFinish(player, null);
++            return;
++        }
 +
-                 if (callback != null) {
-                     channelfuture1.addListener(callback);
-                 }
-+                // Paper start
-+                if (packet.hasFinishListener()) {
-+                    channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
-+                }
-+                // Paper end
++        try {
++            // Paper end
+         ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
  
-                 channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
-+                // Paper start
-+                } catch (Exception e) {
-+                    LOGGER.error("NetworkException: " + player, e);
-+                    disconnect(new TranslatableComponent("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
-+                    packet.onPacketDispatchFinish(player, null);
-+                }
-+                // Paper end
-             });
+         if (genericfuturelistener != null) {
+             channelfuture.addListener(genericfuturelistener);
          }
++        // Paper start
++        if (packet.hasFinishListener()) {
++            channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
++        }
++        // Paper end
  
+         channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
++        // Paper start
++        } catch (Exception e) {
++            LOGGER.error("NetworkException: " + player, e);
++            disconnect(new net.minecraft.network.chat.TranslatableComponent("disconnect.genericReason", "Internal Exception: " + e.getMessage()));
++            packet.onPacketDispatchFinish(player, null);
++        }
++        // Paper end
+     }
+ 
+     private ConnectionProtocol getCurrentProtocol() {
+         return (ConnectionProtocol) this.channel.attr(Connection.ATTRIBUTE_PROTOCOL).get();
      }
  
--    private void sendPacketQueue() { this.flushQueue(); } // Paper - OBFHELPER
 -    private void flushQueue() {
 -        if (this.channel != null && this.channel.isOpen()) {
 -            Queue queue = this.queue;
 -
 +    // Paper start - rewrite this to be safer if ran off main thread
-+    private boolean sendPacketQueue() { return this.p(); } // OBFHELPER // void -> boolean
-+    private boolean p() { // void -> boolean
++    private boolean flushQueue() { // void -> boolean
 +        if (!isConnected()) {
 +            return true;
 +        }
-+        if (MCUtil.isMainThread()) {
++        if (net.minecraft.server.MCUtil.isMainThread()) {
 +            return processQueue();
 +        } else if (isPending) {
 +            // Should only happen during login/status stages
@@ -262,19 +211,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // But if we are not on main due to login/status, the parent is synchronized on packetQueue
 +        java.util.Iterator<PacketHolder> iterator = this.queue.iterator();
 +        while (iterator.hasNext()) {
-+            Connection.PacketHolder queued = iterator.next(); // poll -> peek
++            PacketHolder queued = iterator.next(); // poll -> peek
 +
 +            // Fix NPE (Spigot bug caused by handleDisconnection())
 +            if (queued == null) {
 +                return true;
 +            }
  
-+            Packet<?> packet = queued.getPacket();
++            Packet<?> packet = queued.packet;
 +            if (!packet.isReady()) {
 +                return false;
 +            } else {
 +                iterator.remove();
-+                this.dispatchPacket(packet, queued.getGenericFutureListener());
++                this.sendPacket(packet, queued.listener);
              }
          }
 +        return true;
@@ -282,26 +231,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end
  
      public void tick() {
--        this.flushQueue();
-+        this.p();
-         if (this.packetListener instanceof ServerLoginPacketListenerImpl) {
-             ((ServerLoginPacketListenerImpl) this.packetListener).tick();
-         }
+         this.flushQueue();
 @@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
          return this.address;
      }
  
 +    // Paper start
 +    public void clearPacketQueue() {
-+        ServerPlayer player = getPlayer();
++        net.minecraft.server.level.ServerPlayer player = getPlayer();
 +        queue.forEach(queuedPacket -> {
-+            Packet<?> packet = queuedPacket.getPacket();
++            Packet<?> packet = queuedPacket.packet;
 +            if (packet.hasFinishListener()) {
 +                packet.onPacketDispatchFinish(player, null);
 +            }
 +        });
 +        queue.clear();
-+    } // Paper end
++    }
++    // Paper end
      public void disconnect(Component disconnectReason) {
          // Spigot Start
          this.preparing = false;
@@ -314,13 +260,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (this.channel != null && !this.channel.isOpen()) {
              if (this.disconnectionHandled) {
 -                Connection.LOGGER.warn("handleDisconnection() called twice");
-+                //NetworkManager.LOGGER.warn("handleDisconnection() called twice"); // Paper - Do not log useless message
++                //Connection.LOGGER.warn("handleDisconnection() called twice"); // Paper - Do not log useless message
              } else {
                  this.disconnectionHandled = true;
                  if (this.getDisconnectedReason() != null) {
 @@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
                  } else if (this.getPacketListener() != null) {
-                     this.getPacketListener().a(new TranslatableComponent("multiplayer.disconnect.generic"));
+                     this.getPacketListener().onDisconnect(new TranslatableComponent("multiplayer.disconnect.generic"));
                  }
 -                this.queue.clear(); // Free up packet queue.
 +                clearPacketQueue(); // Paper
@@ -331,64 +277,23 @@ diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/network/protocol/Packet.java
 +++ b/src/main/java/net/minecraft/network/protocol/Packet.java
-@@ -0,0 +0,0 @@
- package net.minecraft.network.protocol;
- 
-+import io.netty.channel.ChannelFuture; // Paper
- import java.io.IOException;
- import net.minecraft.network.FriendlyByteBuf;
- import net.minecraft.network.PacketListener;
 @@ -0,0 +0,0 @@ public interface Packet<T extends PacketListener> {
      void handle(T listener);
  
      // Paper start
-+
 +    /**
 +     * @param player Null if not at PLAY stage yet
 +     */
-+    default void onPacketDispatch(@javax.annotation.Nullable EntityPlayer player) {}
++    default void onPacketDispatch(@javax.annotation.Nullable net.minecraft.server.level.ServerPlayer player) {}
 +
 +    /**
 +     * @param player Null if not at PLAY stage yet
 +     * @param future Can be null if packet was cancelled
 +     */
-+    default void onPacketDispatchFinish(@javax.annotation.Nullable EntityPlayer player, @javax.annotation.Nullable ChannelFuture future) {}
++    default void onPacketDispatchFinish(@javax.annotation.Nullable net.minecraft.server.level.ServerPlayer player, @javax.annotation.Nullable io.netty.channel.ChannelFuture future) {}
 +    default boolean hasFinishListener() { return false; }
 +    default boolean isReady() { return true; }
 +    default java.util.List<Packet> getExtraPackets() { return null; }
-     default boolean packetTooLarge(NetworkManager manager) {
+     default boolean packetTooLarge(net.minecraft.network.Connection manager) {
          return false;
      }
-diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-@@ -0,0 +0,0 @@ import io.netty.channel.epoll.EpollServerSocketChannel;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.ServerSocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
-+import io.netty.handler.flush.FlushConsolidationHandler; // Paper
- import io.netty.handler.timeout.ReadTimeoutHandler;
- import java.io.IOException;
- import java.net.InetAddress;
-@@ -0,0 +0,0 @@ public class ServerConnectionListener {
-     private final List<Connection> connections = Collections.synchronizedList(Lists.newArrayList());
-     // Paper start - prevent blocking on adding a new network manager while the server is ticking
-     private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
-+    private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper
-     private void addPending() {
-         Connection manager = null;
-         while ((manager = pending.poll()) != null) {
-             connections.add(manager);
-+            manager.isPending = false;
-         }
-     }
-     // Paper end
-@@ -0,0 +0,0 @@ public class ServerConnectionListener {
-                         ;
-                     }
- 
-+                    if (!disableFlushConsolidation) channel.pipeline().addFirst(new FlushConsolidationHandler()); // Paper
-                     channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this)).addLast("splitter", new Varint21FrameDecoder()).addLast("decoder", new PacketDecoder(PacketFlow.SERVERBOUND)).addLast("prepender", new Varint21LengthFieldPrepender()).addLast("encoder", new PacketEncoder(PacketFlow.CLIENTBOUND));
-                     int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
-                     Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND);
diff --git a/patches/server-remapped/Set-Zombie-last-tick-at-start-of-drowning-process.patch b/patches/server/Set-Zombie-last-tick-at-start-of-drowning-process.patch
similarity index 100%
rename from patches/server-remapped/Set-Zombie-last-tick-at-start-of-drowning-process.patch
rename to patches/server/Set-Zombie-last-tick-at-start-of-drowning-process.patch
diff --git a/patches/server/Update-entity-Metadata-for-all-tracked-players.patch b/patches/server/Update-entity-Metadata-for-all-tracked-players.patch
new file mode 100644
index 0000000000..25c1aeaf0a
--- /dev/null
+++ b/patches/server/Update-entity-Metadata-for-all-tracked-players.patch
@@ -0,0 +1,76 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: AgentTroll <woodyc40@gmail.com>
+Date: Fri, 22 Mar 2019 22:24:03 -0700
+Subject: [PATCH] Update entity Metadata for all tracked players
+
+
+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
+                 } else {
+                     ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas());
+ 
++                    entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker
+                     this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
+                     playerchunkmap_entitytracker.updatePlayers(this.level.players());
+                     if (entity instanceof ServerPlayer) {
+@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         if (playerchunkmap_entitytracker1 != null) {
+             playerchunkmap_entitytracker1.broadcastRemoved();
+         }
+-
++        entity.tracker = null; // Paper - We're no longer tracked
+     }
+ 
+     protected void tick() {
+diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
+@@ -0,0 +0,0 @@ public class ServerEntity {
+         return ClientboundMoveEntityPacket.packetToEntity(this.xp, this.yp, this.zp);
+     }
+ 
++    // Paper start - Add broadcast method
++    void broadcast(Packet<?> packet) {
++        this.broadcast.accept(packet);
++    }
++    // Paper end
++
+     private void broadcastAndSend(Packet<?> packet) {
+         this.broadcast.accept(packet);
+         if (this.entity instanceof ServerPlayer) {
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
+ 
+                         if (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem) {
+                             // Refresh the current entity metadata
+-                            ServerGamePacketListenerImpl.this.send(new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true));
++                            // Paper start - update entity for all players
++                            ClientboundSetEntityDataPacket packet1 = new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true);
++                            if (entity.tracker != null) {
++                                entity.tracker.broadcast(packet1);
++                            } else {
++                                ServerGamePacketListenerImpl.this.send(packet1);
++                            }
++                            // Paper end
+                         }
+ 
+                         if (event.isCancelled()) {
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
+     public boolean forceExplosionKnockback; // SPIGOT-949
+     public boolean persistentInvisibility = false;
+     public org.bukkit.Location origin; // Paper
++    public net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper - fast access to tracker
+     // Spigot start
+     public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this);
+     public final boolean defaultActivationState;
diff --git a/patches/server-remapped/Use-proper-max-length-when-serialising-BungeeCord-te.patch b/patches/server/Use-proper-max-length-when-serialising-BungeeCord-te.patch
similarity index 81%
rename from patches/server-remapped/Use-proper-max-length-when-serialising-BungeeCord-te.patch
rename to patches/server/Use-proper-max-length-when-serialising-BungeeCord-te.patch
index 4fe5407cb3..dc7d33890a 100644
--- a/patches/server-remapped/Use-proper-max-length-when-serialising-BungeeCord-te.patch
+++ b/patches/server/Use-proper-max-length-when-serialising-BungeeCord-te.patch
@@ -15,17 +15,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  public class ClientboundChatPacket implements Packet<ClientGamePacketListener> {
 -
 +    private static final int MAX_LENGTH = Short.MAX_VALUE * 8 + 8; // Paper
-     private Component message;
+     private final Component message;
      public net.kyori.adventure.text.Component adventure$message; // Paper
      public net.md_5.bungee.api.chat.BaseComponent[] components; // Spigot
 @@ -0,0 +0,0 @@ public class ClientboundChatPacket implements Packet<ClientGamePacketListener> {
-             //packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); // Paper - comment, replaced with below
+             // buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(components)); // Paper - comment, replaced with below
              // Paper start - don't nest if we don't need to so that we can preserve formatting
              if (this.components.length == 1) {
--                buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0]));
+-                buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0]));
 +                buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0]), MAX_LENGTH); // Paper - use proper max length
              } else {
--                buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components));
+-                buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components));
 +                buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components), MAX_LENGTH); // Paper - use proper max length
              }
              // Paper end
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 54bb957497..302bcb31ab 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,5 +1,6 @@
 pluginManagement {
     repositories {
+        mavenLocal()
         gradlePluginPortal()
         maven("https://wav.jfrog.io/artifactory/repo/")
     }