From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Mon, 28 Mar 2016 20:55:47 -0400
Subject: [PATCH] MC Utils

== AT ==
public net.minecraft.server.level.ServerChunkCache mainThread
public net.minecraft.server.level.ServerLevel chunkSource
public org.bukkit.craftbukkit.inventory.CraftItemStack handle
public net.minecraft.server.level.ChunkMap getVisibleChunkIfPresent(J)Lnet/minecraft/server/level/ChunkHolder;
public net.minecraft.server.level.ServerChunkCache mainThreadProcessor
public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor
public net.minecraft.world.level.chunk.LevelChunkSection states

diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.maplist;
+
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import net.minecraft.world.level.chunk.LevelChunk;
+
+// list with O(1) remove & contains
+/**
+ * @author Spottedleaf
+ */
+public final class ChunkList implements Iterable<LevelChunk> {
+
+    protected final Long2IntOpenHashMap chunkToIndex = new Long2IntOpenHashMap(2, 0.8f);
+    {
+        this.chunkToIndex.defaultReturnValue(Integer.MIN_VALUE);
+    }
+
+    protected static final LevelChunk[] EMPTY_LIST = new LevelChunk[0];
+
+    protected LevelChunk[] chunks = EMPTY_LIST;
+    protected int count;
+
+    public int size() {
+        return this.count;
+    }
+
+    public boolean contains(final LevelChunk chunk) {
+        return this.chunkToIndex.containsKey(chunk.coordinateKey);
+    }
+
+    public boolean remove(final LevelChunk chunk) {
+        final int index = this.chunkToIndex.remove(chunk.coordinateKey);
+        if (index == Integer.MIN_VALUE) {
+            return false;
+        }
+
+        // move the entity at the end to this index
+        final int endIndex = --this.count;
+        final LevelChunk end = this.chunks[endIndex];
+        if (index != endIndex) {
+            // not empty after this call
+            this.chunkToIndex.put(end.coordinateKey, index); // update index
+        }
+        this.chunks[index] = end;
+        this.chunks[endIndex] = null;
+
+        return true;
+    }
+
+    public boolean add(final LevelChunk chunk) {
+        final int count = this.count;
+        final int currIndex = this.chunkToIndex.putIfAbsent(chunk.coordinateKey, count);
+
+        if (currIndex != Integer.MIN_VALUE) {
+            return false; // already in this list
+        }
+
+        LevelChunk[] list = this.chunks;
+
+        if (list.length == count) {
+            // resize required
+            list = this.chunks = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
+        }
+
+        list[count] = chunk;
+        this.count = count + 1;
+
+        return true;
+    }
+
+    public LevelChunk getChecked(final int index) {
+        if (index < 0 || index >= this.count) {
+            throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count);
+        }
+        return this.chunks[index];
+    }
+
+    public LevelChunk getUnchecked(final int index) {
+        return this.chunks[index];
+    }
+
+    public LevelChunk[] getRawData() {
+        return this.chunks;
+    }
+
+    public void clear() {
+        this.chunkToIndex.clear();
+        Arrays.fill(this.chunks, 0, this.count, null);
+        this.count = 0;
+    }
+
+    @Override
+    public Iterator<LevelChunk> iterator() {
+        return new Iterator<LevelChunk>() {
+
+            LevelChunk lastRet;
+            int current;
+
+            @Override
+            public boolean hasNext() {
+                return this.current < ChunkList.this.count;
+            }
+
+            @Override
+            public LevelChunk next() {
+                if (this.current >= ChunkList.this.count) {
+                    throw new NoSuchElementException();
+                }
+                return this.lastRet = ChunkList.this.chunks[this.current++];
+            }
+
+            @Override
+            public void remove() {
+                final LevelChunk lastRet = this.lastRet;
+
+                if (lastRet == null) {
+                    throw new IllegalStateException();
+                }
+                this.lastRet = null;
+
+                ChunkList.this.remove(lastRet);
+                --this.current;
+            }
+        };
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.maplist;
+
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import net.minecraft.world.entity.Entity;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+// list with O(1) remove & contains
+/**
+ * @author Spottedleaf
+ */
+public final class EntityList implements Iterable<Entity> {
+
+    protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
+    {
+        this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
+    }
+
+    protected static final Entity[] EMPTY_LIST = new Entity[0];
+
+    protected Entity[] entities = EMPTY_LIST;
+    protected int count;
+
+    public int size() {
+        return this.count;
+    }
+
+    public boolean contains(final Entity entity) {
+        return this.entityToIndex.containsKey(entity.getId());
+    }
+
+    public boolean remove(final Entity entity) {
+        final int index = this.entityToIndex.remove(entity.getId());
+        if (index == Integer.MIN_VALUE) {
+            return false;
+        }
+
+        // move the entity at the end to this index
+        final int endIndex = --this.count;
+        final Entity end = this.entities[endIndex];
+        if (index != endIndex) {
+            // not empty after this call
+            this.entityToIndex.put(end.getId(), index); // update index
+        }
+        this.entities[index] = end;
+        this.entities[endIndex] = null;
+
+        return true;
+    }
+
+    public boolean add(final Entity entity) {
+        final int count = this.count;
+        final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count);
+
+        if (currIndex != Integer.MIN_VALUE) {
+            return false; // already in this list
+        }
+
+        Entity[] list = this.entities;
+
+        if (list.length == count) {
+            // resize required
+            list = this.entities = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
+        }
+
+        list[count] = entity;
+        this.count = count + 1;
+
+        return true;
+    }
+
+    public Entity getChecked(final int index) {
+        if (index < 0 || index >= this.count) {
+            throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count);
+        }
+        return this.entities[index];
+    }
+
+    public Entity getUnchecked(final int index) {
+        return this.entities[index];
+    }
+
+    public Entity[] getRawData() {
+        return this.entities;
+    }
+
+    public void clear() {
+        this.entityToIndex.clear();
+        Arrays.fill(this.entities, 0, this.count, null);
+        this.count = 0;
+    }
+
+    @Override
+    public Iterator<Entity> iterator() {
+        return new Iterator<Entity>() {
+
+            Entity lastRet;
+            int current;
+
+            @Override
+            public boolean hasNext() {
+                return this.current < EntityList.this.count;
+            }
+
+            @Override
+            public Entity next() {
+                if (this.current >= EntityList.this.count) {
+                    throw new NoSuchElementException();
+                }
+                return this.lastRet = EntityList.this.entities[this.current++];
+            }
+
+            @Override
+            public void remove() {
+                final Entity lastRet = this.lastRet;
+
+                if (lastRet == null) {
+                    throw new IllegalStateException();
+                }
+                this.lastRet = null;
+
+                EntityList.this.remove(lastRet);
+                --this.current;
+            }
+        };
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.maplist;
+
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap;
+import java.util.Arrays;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.GlobalPalette;
+
+/**
+ * @author Spottedleaf
+ */
+public final class IBlockDataList {
+
+    static final GlobalPalette<BlockState> GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY);
+
+    // map of location -> (index | (location << 16) | (palette id << 32))
+    private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f);
+    {
+        this.map.defaultReturnValue(Long.MAX_VALUE);
+    }
+
+    private static final long[] EMPTY_LIST = new long[0];
+
+    private long[] byIndex = EMPTY_LIST;
+    private int size;
+
+    public static int getLocationKey(final int x, final int y, final int z) {
+        return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4));
+    }
+
+    public static BlockState getBlockDataFromRaw(final long raw) {
+        return GLOBAL_PALETTE.valueFor((int)(raw >>> 32));
+    }
+
+    public static int getIndexFromRaw(final long raw) {
+        return (int)(raw & 0xFFFF);
+    }
+
+    public static int getLocationFromRaw(final long raw) {
+        return (int)((raw >>> 16) & 0xFFFF);
+    }
+
+    public static long getRawFromValues(final int index, final int location, final BlockState data) {
+        return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32);
+    }
+
+    public static long setIndexRawValues(final long value, final int index) {
+        return value & ~(0xFFFF) | (index);
+    }
+
+    public long add(final int x, final int y, final int z, final BlockState data) {
+        return this.add(getLocationKey(x, y, z), data);
+    }
+
+    public long add(final int location, final BlockState data) {
+        final long curr = this.map.get((short)location);
+
+        if (curr == Long.MAX_VALUE) {
+            final int index = this.size++;
+            final long raw = getRawFromValues(index, location, data);
+            this.map.put((short)location, raw);
+
+            if (index >= this.byIndex.length) {
+                this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L));
+            }
+
+            this.byIndex[index] = raw;
+            return raw;
+        } else {
+            final int index = getIndexFromRaw(curr);
+            final long raw = this.byIndex[index] = getRawFromValues(index, location, data);
+
+            this.map.put((short)location, raw);
+
+            return raw;
+        }
+    }
+
+    public long remove(final int x, final int y, final int z) {
+        return this.remove(getLocationKey(x, y, z));
+    }
+
+    public long remove(final int location) {
+        final long ret = this.map.remove((short)location);
+        final int index = getIndexFromRaw(ret);
+        if (ret == Long.MAX_VALUE) {
+            return ret;
+        }
+
+        // move the entry at the end to this index
+        final int endIndex = --this.size;
+        final long end = this.byIndex[endIndex];
+        if (index != endIndex) {
+            // not empty after this call
+            this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index));
+        }
+        this.byIndex[index] = end;
+        this.byIndex[endIndex] = 0L;
+
+        return ret;
+    }
+
+    public int size() {
+        return this.size;
+    }
+
+    public long getRaw(final int index) {
+        return this.byIndex[index];
+    }
+
+    public int getLocation(final int index) {
+        return getLocationFromRaw(this.getRaw(index));
+    }
+
+    public BlockState getData(final int index) {
+        return getBlockDataFromRaw(this.getRaw(index));
+    }
+
+    public void clear() {
+        this.size = 0;
+        this.map.clear();
+    }
+
+    public LongIterator getRawIterator() {
+        return this.map.values().iterator();
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/ReferenceList.java b/src/main/java/com/destroystokyo/paper/util/maplist/ReferenceList.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/maplist/ReferenceList.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.maplist;
+
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * @author Spottedleaf
+ */
+public final class ReferenceList<E> implements Iterable<E> {
+
+    protected final Reference2IntOpenHashMap<E> referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f);
+    {
+        this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE);
+    }
+
+    protected static final Object[] EMPTY_LIST = new Object[0];
+
+    protected Object[] references = EMPTY_LIST;
+    protected int count;
+
+    public int size() {
+        return this.count;
+    }
+
+    public boolean contains(final E obj) {
+        return this.referenceToIndex.containsKey(obj);
+    }
+
+    public boolean remove(final E obj) {
+        final int index = this.referenceToIndex.removeInt(obj);
+        if (index == Integer.MIN_VALUE) {
+            return false;
+        }
+
+        // move the object at the end to this index
+        final int endIndex = --this.count;
+        final E end = (E)this.references[endIndex];
+        if (index != endIndex) {
+            // not empty after this call
+            this.referenceToIndex.put(end, index); // update index
+        }
+        this.references[index] = end;
+        this.references[endIndex] = null;
+
+        return true;
+    }
+
+    public boolean add(final E obj) {
+        final int count = this.count;
+        final int currIndex = this.referenceToIndex.putIfAbsent(obj, count);
+
+        if (currIndex != Integer.MIN_VALUE) {
+            return false; // already in this list
+        }
+
+        Object[] list = this.references;
+
+        if (list.length == count) {
+            // resize required
+            list = this.references = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
+        }
+
+        list[count] = obj;
+        this.count = count + 1;
+
+        return true;
+    }
+
+    public E getChecked(final int index) {
+        if (index < 0 || index >= this.count) {
+            throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count);
+        }
+        return (E)this.references[index];
+    }
+
+    public E getUnchecked(final int index) {
+        return (E)this.references[index];
+    }
+
+    public Object[] getRawData() {
+        return this.references;
+    }
+
+    public void clear() {
+        this.referenceToIndex.clear();
+        Arrays.fill(this.references, 0, this.count, null);
+        this.count = 0;
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return new Iterator<>() {
+            private E lastRet;
+            private int current;
+
+            @Override
+            public boolean hasNext() {
+                return this.current < ReferenceList.this.count;
+            }
+
+            @Override
+            public E next() {
+                if (this.current >= ReferenceList.this.count) {
+                    throw new NoSuchElementException();
+                }
+                return this.lastRet = (E)ReferenceList.this.references[this.current++];
+            }
+
+            @Override
+            public void remove() {
+                final E lastRet = this.lastRet;
+
+                if (lastRet == null) {
+                    throw new IllegalStateException();
+                }
+                this.lastRet = null;
+
+                ReferenceList.this.remove(lastRet);
+                --this.current;
+            }
+        };
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.misc;
+
+import io.papermc.paper.util.IntegerUtil;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
+import io.papermc.paper.util.MCUtil;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.ChunkPos;
+import javax.annotation.Nullable;
+import java.util.Iterator;
+
+/** @author Spottedleaf */
+public abstract class AreaMap<E> {
+
+    /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */
+
+    protected final Object2LongOpenHashMap<E> objectToLastCoordinate = new Object2LongOpenHashMap<>();
+    protected final Object2IntOpenHashMap<E> objectToViewDistance = new Object2IntOpenHashMap<>();
+
+    {
+        this.objectToViewDistance.defaultReturnValue(-1);
+        this.objectToLastCoordinate.defaultReturnValue(Long.MIN_VALUE);
+    }
+
+    // we use linked for better iteration.
+    // map of: coordinate to set of objects in coordinate
+    protected final Long2ObjectOpenHashMap<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f);
+    protected final PooledLinkedHashSets<E> pooledHashSets;
+
+    protected final ChangeCallback<E> addCallback;
+    protected final ChangeCallback<E> removeCallback;
+    protected final ChangeSourceCallback<E> changeSourceCallback;
+
+    public AreaMap() {
+        this(new PooledLinkedHashSets<>());
+    }
+
+    // let users define a "global" or "shared" pooled sets if they wish
+    public AreaMap(final PooledLinkedHashSets<E> pooledHashSets) {
+        this(pooledHashSets, null, null);
+    }
+
+    public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback) {
+        this(pooledHashSets, addCallback, removeCallback, null);
+    }
+    public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback, final ChangeSourceCallback<E> changeSourceCallback) {
+        this.pooledHashSets = pooledHashSets;
+        this.addCallback = addCallback;
+        this.removeCallback = removeCallback;
+        this.changeSourceCallback = changeSourceCallback;
+    }
+
+    @Nullable
+    public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final long key) {
+        return this.areaMap.get(key);
+    }
+
+    @Nullable
+    public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final ChunkPos chunkPos) {
+        return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos));
+    }
+
+    @Nullable
+    public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final int chunkX, final int chunkZ) {
+        return this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ));
+    }
+
+    // Long.MIN_VALUE indicates the object is not mapped
+    public final long getLastCoordinate(final E object) {
+        return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE);
+    }
+
+    // -1 indicates the object is not mapped
+    public final int getLastViewDistance(final E object) {
+        return this.objectToViewDistance.getOrDefault(object, -1);
+    }
+
+    // returns the total number of mapped chunks
+    public final int size() {
+        return this.areaMap.size();
+    }
+
+    public final void addOrUpdate(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
+        final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance);
+        final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ);
+        final long oldPos = this.objectToLastCoordinate.put(object, newPos);
+
+        if (oldViewDistance == -1) {
+            this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance);
+            this.addObjectCallback(object, chunkX, chunkZ, viewDistance);
+        } else {
+            this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance);
+            this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance);
+        }
+        //this.validate(object, viewDistance);
+    }
+
+    public final boolean update(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
+        final int oldViewDistance = this.objectToViewDistance.replace(object, viewDistance);
+        if (oldViewDistance == -1) {
+            return false;
+        } else {
+            final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ);
+            final long oldPos = this.objectToLastCoordinate.put(object, newPos);
+            this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance);
+            this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance);
+        }
+        //this.validate(object, viewDistance);
+        return true;
+    }
+
+    // called after the distance map updates
+    protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {
+        if (newPosition != oldPosition && this.changeSourceCallback != null) {
+            this.changeSourceCallback.accept(Object, oldPosition, newPosition);
+        }
+    }
+
+    public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
+        final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance);
+        if (oldViewDistance != -1) {
+            return false;
+        }
+
+        final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ);
+        this.objectToLastCoordinate.put(object, newPos);
+        this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance);
+        this.addObjectCallback(object, chunkX, chunkZ, viewDistance);
+
+        //this.validate(object, viewDistance);
+
+        return true;
+    }
+
+    // called after the distance map updates
+    protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {}
+
+    public final boolean remove(final E object) {
+        final long position = this.objectToLastCoordinate.removeLong(object);
+        final int viewDistance = this.objectToViewDistance.removeInt(object);
+
+        if (viewDistance == -1) {
+            return false;
+        }
+
+        final int currentX = MCUtil.getCoordinateX(position);
+        final int currentZ = MCUtil.getCoordinateZ(position);
+
+        this.removeObject(object, currentX, currentZ, currentX, currentZ, viewDistance);
+        this.removeObjectCallback(object, currentX, currentZ, viewDistance);
+        //this.validate(object, -1);
+        return true;
+    }
+
+    // called after the distance map updates
+    protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {}
+
+    protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getEmptySetFor(final E object);
+
+    // expensive op, only for debug
+    protected void validate(final E object, final int viewDistance) {
+        int entiesGot = 0;
+        int expectedEntries = (2 * viewDistance + 1);
+        expectedEntries *= expectedEntries;
+        if (viewDistance < 0) {
+            expectedEntries = 0;
+        }
+
+        final long currPosition = this.objectToLastCoordinate.getLong(object);
+
+        final int centerX = MCUtil.getCoordinateX(currPosition);
+        final int centerZ = MCUtil.getCoordinateZ(currPosition);
+
+        for (Iterator<Long2ObjectLinkedOpenHashMap.Entry<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>>> iterator = this.areaMap.long2ObjectEntrySet().fastIterator();
+             iterator.hasNext();) {
+
+            final Long2ObjectLinkedOpenHashMap.Entry<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> entry = iterator.next();
+            final long key = entry.getLongKey();
+            final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> map = entry.getValue();
+
+            if (map.referenceCount == 0) {
+                throw new IllegalStateException("Invalid map");
+            }
+
+            if (map.contains(object)) {
+                ++entiesGot;
+
+                final int chunkX = MCUtil.getCoordinateX(key);
+                final int chunkZ = MCUtil.getCoordinateZ(key);
+
+                final int dist = Math.max(IntegerUtil.branchlessAbs(chunkX - centerX), IntegerUtil.branchlessAbs(chunkZ - centerZ));
+
+                if (dist > viewDistance) {
+                    throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist);
+                }
+            }
+        }
+
+        if (entiesGot != expectedEntries) {
+            throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot);
+        }
+    }
+
+    private void addObjectTo(final E object, final int chunkX, final int chunkZ, final int currChunkX,
+                             final int currChunkZ, final int prevChunkX, final int prevChunkZ) {
+        final long key = MCUtil.getCoordinateKey(chunkX, chunkZ);
+
+        PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> empty = this.getEmptySetFor(object);
+        PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> current = this.areaMap.putIfAbsent(key, empty);
+
+        if (current != null) {
+            PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> next = this.pooledHashSets.findMapWith(current, object);
+            if (next == current) {
+                throw new IllegalStateException("Expected different map: got " + next.toString());
+            }
+            this.areaMap.put(key, next);
+
+            current = next;
+            // fall through to callback
+        } else {
+            current = empty;
+        }
+
+        if (this.addCallback != null) {
+            try {
+                this.addCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, current);
+            } catch (final Throwable ex) {
+                if (ex instanceof ThreadDeath) {
+                    throw (ThreadDeath)ex;
+                }
+                MinecraftServer.LOGGER.error("Add callback for map threw exception ", ex);
+            }
+        }
+    }
+
+    private void removeObjectFrom(final E object, final int chunkX, final int chunkZ, final int currChunkX,
+                                  final int currChunkZ, final int prevChunkX, final int prevChunkZ) {
+        final long key = MCUtil.getCoordinateKey(chunkX, chunkZ);
+
+        PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> current = this.areaMap.get(key);
+
+        if (current == null) {
+            throw new IllegalStateException("Current map may not be null for " + object + ", (" + chunkX + "," + chunkZ + ")");
+        }
+
+        PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> next = this.pooledHashSets.findMapWithout(current, object);
+
+        if (next == current) {
+            throw new IllegalStateException("Current map [" + next.toString() + "] should have contained " + object + ", (" + chunkX + "," + chunkZ + ")");
+        }
+
+        if (next != null) {
+            this.areaMap.put(key, next);
+        } else {
+            this.areaMap.remove(key);
+        }
+
+        if (this.removeCallback != null) {
+            try {
+                this.removeCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, next);
+            } catch (final Throwable ex) {
+                if (ex instanceof ThreadDeath) {
+                    throw (ThreadDeath)ex;
+                }
+                MinecraftServer.LOGGER.error("Remove callback for map threw exception ", ex);
+            }
+        }
+    }
+
+    private void addObject(final E object, final int chunkX, final int chunkZ, final int prevChunkX, final int prevChunkZ, final int viewDistance) {
+        final int maxX = chunkX + viewDistance;
+        final int maxZ = chunkZ + viewDistance;
+        final int minX = chunkX - viewDistance;
+        final int minZ = chunkZ - viewDistance;
+        for (int x = minX; x <= maxX; ++x) {
+            for (int z = minZ; z <= maxZ; ++z) {
+                this.addObjectTo(object, x, z, chunkX, chunkZ, prevChunkX, prevChunkZ);
+            }
+        }
+    }
+
+    private void removeObject(final E object, final int chunkX, final int chunkZ, final int currentChunkX, final int currentChunkZ, final int viewDistance) {
+        final int maxX = chunkX + viewDistance;
+        final int maxZ = chunkZ + viewDistance;
+        final int minX = chunkX - viewDistance;
+        final int minZ = chunkZ - viewDistance;
+        for (int x = minX; x <= maxX; ++x) {
+            for (int z = minZ; z <= maxZ; ++z) {
+                this.removeObjectFrom(object, x, z, currentChunkX, currentChunkZ, chunkX, chunkZ);
+            }
+        }
+    }
+
+    /* math sign function except 0 returns 1 */
+    protected static int sign(int val) {
+        return 1 | (val >> (Integer.SIZE - 1));
+    }
+
+    private void updateObject(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {
+        final int toX = MCUtil.getCoordinateX(newPosition);
+        final int toZ = MCUtil.getCoordinateZ(newPosition);
+        final int fromX = MCUtil.getCoordinateX(oldPosition);
+        final int fromZ = MCUtil.getCoordinateZ(oldPosition);
+
+        final int dx = toX - fromX;
+        final int dz = toZ - fromZ;
+
+        final int totalX = IntegerUtil.branchlessAbs(fromX - toX);
+        final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ);
+
+        if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) {
+            // teleported?
+            this.removeObject(object, fromX, fromZ, fromX, fromZ, oldViewDistance);
+            this.addObject(object, toX, toZ, fromX, fromZ, newViewDistance);
+            return;
+        }
+
+        if (oldViewDistance != newViewDistance) {
+            // remove loop
+
+            final int oldMinX = fromX - oldViewDistance;
+            final int oldMinZ = fromZ - oldViewDistance;
+            final int oldMaxX = fromX + oldViewDistance;
+            final int oldMaxZ = fromZ + oldViewDistance;
+            for (int currX = oldMinX; currX <= oldMaxX; ++currX) {
+                for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) {
+
+                    // only remove if we're outside the new view distance...
+                    if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) {
+                        this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ);
+                    }
+                }
+            }
+
+            // add loop
+
+            final int newMinX = toX - newViewDistance;
+            final int newMinZ = toZ - newViewDistance;
+            final int newMaxX = toX + newViewDistance;
+            final int newMaxZ = toZ + newViewDistance;
+            for (int currX = newMinX; currX <= newMaxX; ++currX) {
+                for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) {
+
+                    // only add if we're outside the old view distance...
+                    if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) {
+                        this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ);
+                    }
+                }
+            }
+
+            return;
+        }
+
+        // x axis is width
+        // z axis is height
+        // right refers to the x axis of where we moved
+        // top refers to the z axis of where we moved
+
+        // same view distance
+
+        // used for relative positioning
+        final int up = sign(dz); // 1 if dz >= 0, -1 otherwise
+        final int right = sign(dx); // 1 if dx >= 0, -1 otherwise
+
+        // The area excluded by overlapping the two view distance squares creates four rectangles:
+        // Two on the left, and two on the right. The ones on the left we consider the "removed" section
+        // and on the right the "added" section.
+        // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
+        // exclusive to the regions they surround.
+
+        // 4 points of the rectangle
+        int maxX; // exclusive
+        int minX; // inclusive
+        int maxZ; // exclusive
+        int minZ; // inclusive
+
+        if (dx != 0) {
+            // handle right addition
+
+            maxX = toX + (oldViewDistance * right) + right; // exclusive
+            minX = fromX + (oldViewDistance * right) + right; // inclusive
+            maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+            minZ = toZ - (oldViewDistance * up); // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ);
+                }
+            }
+        }
+
+        if (dz != 0) {
+            // handle up addition
+
+            maxX = toX + (oldViewDistance * right) + right; // exclusive
+            minX = toX - (oldViewDistance * right); // inclusive
+            maxZ = toZ + (oldViewDistance * up) + up; // exclusive
+            minZ = fromZ + (oldViewDistance * up) + up; // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ);
+                }
+            }
+        }
+
+        if (dx != 0) {
+            // handle left removal
+
+            maxX = toX - (oldViewDistance * right); // exclusive
+            minX = fromX - (oldViewDistance * right); // inclusive
+            maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+            minZ = toZ - (oldViewDistance * up); // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ);
+                }
+            }
+        }
+
+        if (dz != 0) {
+            // handle down removal
+
+            maxX = fromX + (oldViewDistance * right) + right; // exclusive
+            minX = fromX - (oldViewDistance * right); // inclusive
+            maxZ = toZ - (oldViewDistance * up); // exclusive
+            minZ = fromZ - (oldViewDistance * up); // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ);
+                }
+            }
+        }
+    }
+
+    @FunctionalInterface
+    public static interface ChangeCallback<E> {
+
+        // if there is no previous position, then prevPos = Integer.MIN_VALUE
+        void accept(final E object, final int rangeX, final int rangeZ, final int currPosX, final int currPosZ, final int prevPosX, final int prevPosZ,
+                    final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> newState);
+
+    }
+
+    @FunctionalInterface
+    public static interface ChangeSourceCallback<E> {
+        void accept(final E object, final long prevPos, final long newPos);
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.misc;
+
+import io.papermc.paper.util.IntegerUtil;
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import io.papermc.paper.util.MCUtil;
+import net.minecraft.world.level.ChunkPos;
+
+/** @author Spottedleaf */
+public abstract class DistanceTrackingAreaMap<E> extends AreaMap<E> {
+
+    // use this map only if you need distance tracking, the tracking here is obviously going to hit harder.
+
+    protected final Long2IntOpenHashMap chunkToNearestDistance = new Long2IntOpenHashMap(1024, 0.7f);
+    {
+        this.chunkToNearestDistance.defaultReturnValue(-1);
+    }
+
+    protected final DistanceChangeCallback<E> distanceChangeCallback;
+
+    public DistanceTrackingAreaMap() {
+        this(new PooledLinkedHashSets<>());
+    }
+
+    // let users define a "global" or "shared" pooled sets if they wish
+    public DistanceTrackingAreaMap(final PooledLinkedHashSets<E> pooledHashSets) {
+        this(pooledHashSets, null, null, null);
+    }
+
+    public DistanceTrackingAreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback,
+                                   final DistanceChangeCallback<E> distanceChangeCallback) {
+        super(pooledHashSets, addCallback, removeCallback);
+        this.distanceChangeCallback = distanceChangeCallback;
+    }
+
+    // ret -1 if there is nothing mapped
+    public final int getNearestObjectDistance(final long key) {
+        return this.chunkToNearestDistance.get(key);
+    }
+
+    // ret -1 if there is nothing mapped
+    public final int getNearestObjectDistance(final ChunkPos chunkPos) {
+        return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkPos));
+    }
+
+    // ret -1 if there is nothing mapped
+    public final int getNearestObjectDistance(final int chunkX, final int chunkZ) {
+        return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkX, chunkZ));
+    }
+
+    protected final void recalculateDistance(final int chunkX, final int chunkZ) {
+        final long key = MCUtil.getCoordinateKey(chunkX, chunkZ);
+        final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> state = this.areaMap.get(key);
+        if (state == null) {
+            final int oldDistance = this.chunkToNearestDistance.remove(key);
+            // nothing here.
+            if (oldDistance == -1) {
+                // nothing was here previously
+                return;
+            }
+            if (this.distanceChangeCallback != null) {
+                this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, -1, null);
+            }
+            return;
+        }
+
+        int newDistance = Integer.MAX_VALUE;
+
+        final Object[] rawData = state.getBackingSet();
+        for (int i = 0, len = rawData.length; i < len; ++i) {
+            final Object raw = rawData[i];
+
+            if (raw == null) {
+                continue;
+            }
+
+            final E object = (E)raw;
+            final long location = this.objectToLastCoordinate.getLong(object);
+
+            final int distance = Math.max(IntegerUtil.branchlessAbs(chunkX - MCUtil.getCoordinateX(location)), IntegerUtil.branchlessAbs(chunkZ - MCUtil.getCoordinateZ(location)));
+
+            if (distance < newDistance) {
+                newDistance = distance;
+            }
+        }
+
+        final int oldDistance = this.chunkToNearestDistance.put(key, newDistance);
+
+        if (oldDistance != newDistance) {
+            if (this.distanceChangeCallback != null) {
+                this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, newDistance, state);
+            }
+        }
+    }
+
+    @Override
+    protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
+        final int maxX = chunkX + viewDistance;
+        final int maxZ = chunkZ + viewDistance;
+        final int minX = chunkX - viewDistance;
+        final int minZ = chunkZ - viewDistance;
+        for (int x = minX; x <= maxX; ++x) {
+            for (int z = minZ; z <= maxZ; ++z) {
+                this.recalculateDistance(x, z);
+            }
+        }
+    }
+
+    @Override
+    protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
+        final int maxX = chunkX + viewDistance;
+        final int maxZ = chunkZ + viewDistance;
+        final int minX = chunkX - viewDistance;
+        final int minZ = chunkZ - viewDistance;
+        for (int x = minX; x <= maxX; ++x) {
+            for (int z = minZ; z <= maxZ; ++z) {
+                this.recalculateDistance(x, z);
+            }
+        }
+    }
+
+    @Override
+    protected void updateObjectCallback(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {
+        if (oldPosition == newPosition && newViewDistance == oldViewDistance) {
+            return;
+        }
+
+        final int toX = MCUtil.getCoordinateX(newPosition);
+        final int toZ = MCUtil.getCoordinateZ(newPosition);
+        final int fromX = MCUtil.getCoordinateX(oldPosition);
+        final int fromZ = MCUtil.getCoordinateZ(oldPosition);
+
+        final int totalX = IntegerUtil.branchlessAbs(fromX - toX);
+        final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ);
+
+        if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) {
+            // teleported?
+            this.removeObjectCallback(object, fromX, fromZ, oldViewDistance);
+            this.addObjectCallback(object, toX, toZ, newViewDistance);
+            return;
+        }
+
+        final int minX = Math.min(fromX - oldViewDistance, toX - newViewDistance);
+        final int maxX = Math.max(fromX + oldViewDistance, toX + newViewDistance);
+        final int minZ = Math.min(fromZ - oldViewDistance, toZ - newViewDistance);
+        final int maxZ = Math.max(fromZ + oldViewDistance, toZ + newViewDistance);
+
+        for (int x = minX; x <= maxX; ++x) {
+            for (int z = minZ; z <= maxZ; ++z) {
+                final int distXOld = IntegerUtil.branchlessAbs(x - fromX);
+                final int distZOld = IntegerUtil.branchlessAbs(z - fromZ);
+
+                if (Math.max(distXOld, distZOld) <= oldViewDistance) {
+                    this.recalculateDistance(x, z);
+                    continue;
+                }
+
+                final int distXNew = IntegerUtil.branchlessAbs(x - toX);
+                final int distZNew = IntegerUtil.branchlessAbs(z - toZ);
+
+                if (Math.max(distXNew, distZNew) <= newViewDistance) {
+                    this.recalculateDistance(x, z);
+                    continue;
+                }
+            }
+        }
+    }
+
+    @FunctionalInterface
+    public static interface DistanceChangeCallback<E> {
+
+        void accept(final int posX, final int posZ, final int oldNearestDistance, final int newNearestDistance,
+                    final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> state);
+
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.misc;
+
+import net.minecraft.server.level.ServerPlayer;
+
+/**
+ * @author Spottedleaf
+ */
+public final class PlayerAreaMap extends AreaMap<ServerPlayer> {
+
+    public PlayerAreaMap() {
+        super();
+    }
+
+    public PlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets) {
+        super(pooledHashSets);
+    }
+
+    public PlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets, final ChangeCallback<ServerPlayer> addCallback,
+                         final ChangeCallback<ServerPlayer> removeCallback) {
+        this(pooledHashSets, addCallback, removeCallback, null);
+    }
+
+    public PlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets, final ChangeCallback<ServerPlayer> addCallback,
+                         final ChangeCallback<ServerPlayer> removeCallback, final ChangeSourceCallback<ServerPlayer> changeSourceCallback) {
+        super(pooledHashSets, addCallback, removeCallback, changeSourceCallback);
+    }
+
+    @Override
+    protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getEmptySetFor(final ServerPlayer player) {
+        return player.cachedSingleHashSet;
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.misc;
+
+import net.minecraft.server.level.ServerPlayer;
+
+public class PlayerDistanceTrackingAreaMap extends DistanceTrackingAreaMap<ServerPlayer> {
+
+    public PlayerDistanceTrackingAreaMap() {
+        super();
+    }
+
+    public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets) {
+        super(pooledHashSets);
+    }
+
+    public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets, final ChangeCallback<ServerPlayer> addCallback,
+                                         final ChangeCallback<ServerPlayer> removeCallback, final DistanceChangeCallback<ServerPlayer> distanceChangeCallback) {
+        super(pooledHashSets, addCallback, removeCallback, distanceChangeCallback);
+    }
+
+    @Override
+    protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getEmptySetFor(final ServerPlayer player) {
+        return player.cachedSingleHashSet;
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.misc;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import java.lang.ref.WeakReference;
+
+/** @author Spottedleaf */
+public class PooledLinkedHashSets<E> {
+
+    /* Tested via https://gist.github.com/Spottedleaf/a93bb7a8993d6ce142d3efc5932bf573 */
+
+    // we really want to avoid that equals() check as much as possible...
+    protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(128, 0.25f);
+
+    protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> current) {
+        if (current.referenceCount == 0) {
+            throw new IllegalStateException("Cannot decrement reference count for " + current);
+        }
+        if (current.referenceCount == -1 || --current.referenceCount > 0) {
+            return;
+        }
+
+        this.mapPool.remove(current);
+        return;
+    }
+
+    public PooledObjectLinkedOpenHashSet<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+        final PooledObjectLinkedOpenHashSet<E> cached = current.getAddCache(object);
+
+        if (cached != null) {
+            decrementReferenceCount(current);
+
+            if (cached.referenceCount == 0) {
+                // bring the map back from the dead
+                PooledObjectLinkedOpenHashSet<E> contending = this.mapPool.putIfAbsent(cached, cached);
+                if (contending != null) {
+                    // a map already exists with the elements we want
+                    if (contending.referenceCount != -1) {
+                        ++contending.referenceCount;
+                    }
+                    current.updateAddCache(object, contending);
+                    return contending;
+                }
+
+                cached.referenceCount = 1;
+            } else if (cached.referenceCount != -1) {
+                ++cached.referenceCount;
+            }
+
+            return cached;
+        }
+
+        if (!current.add(object)) {
+            return current;
+        }
+
+        // we use get/put since we use a different key on put
+        PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
+
+        if (ret == null) {
+            ret = new PooledObjectLinkedOpenHashSet<>(current);
+            current.remove(object);
+            this.mapPool.put(ret, ret);
+            ret.referenceCount = 1;
+        } else {
+            if (ret.referenceCount != -1) {
+                ++ret.referenceCount;
+            }
+            current.remove(object);
+        }
+
+        current.updateAddCache(object, ret);
+
+        decrementReferenceCount(current);
+        return ret;
+    }
+
+    // rets null if current.size() == 1
+    public PooledObjectLinkedOpenHashSet<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+        if (current.set.size() == 1) {
+            decrementReferenceCount(current);
+            return null;
+        }
+
+        final PooledObjectLinkedOpenHashSet<E> cached = current.getRemoveCache(object);
+
+        if (cached != null) {
+            decrementReferenceCount(current);
+
+            if (cached.referenceCount == 0) {
+                // bring the map back from the dead
+                PooledObjectLinkedOpenHashSet<E> contending = this.mapPool.putIfAbsent(cached, cached);
+                if (contending != null) {
+                    // a map already exists with the elements we want
+                    if (contending.referenceCount != -1) {
+                        ++contending.referenceCount;
+                    }
+                    current.updateRemoveCache(object, contending);
+                    return contending;
+                }
+
+                cached.referenceCount = 1;
+            } else if (cached.referenceCount != -1) {
+                ++cached.referenceCount;
+            }
+
+            return cached;
+        }
+
+        if (!current.remove(object)) {
+            return current;
+        }
+
+        // we use get/put since we use a different key on put
+        PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
+
+        if (ret == null) {
+            ret = new PooledObjectLinkedOpenHashSet<>(current);
+            current.add(object);
+            this.mapPool.put(ret, ret);
+            ret.referenceCount = 1;
+        } else {
+            if (ret.referenceCount != -1) {
+                ++ret.referenceCount;
+            }
+            current.add(object);
+        }
+
+        current.updateRemoveCache(object, ret);
+
+        decrementReferenceCount(current);
+        return ret;
+    }
+
+    static final class RawSetObjectLinkedOpenHashSet<E> extends ObjectOpenHashSet<E> {
+
+        public RawSetObjectLinkedOpenHashSet() {
+            super();
+        }
+
+        public RawSetObjectLinkedOpenHashSet(final int capacity) {
+            super(capacity);
+        }
+
+        public RawSetObjectLinkedOpenHashSet(final int capacity, final float loadFactor) {
+            super(capacity, loadFactor);
+        }
+
+        @Override
+        public RawSetObjectLinkedOpenHashSet<E> clone() {
+            return (RawSetObjectLinkedOpenHashSet<E>)super.clone();
+        }
+
+        public E[] getRawSet() {
+            return this.key;
+        }
+    }
+
+    public static final class PooledObjectLinkedOpenHashSet<E> {
+
+        private static final WeakReference NULL_REFERENCE = new WeakReference<>(null);
+
+        final RawSetObjectLinkedOpenHashSet<E> set;
+        int referenceCount; // -1 if special
+        int hash; // optimize hashcode
+
+        // add cache
+        WeakReference<E> lastAddObject = NULL_REFERENCE;
+        WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
+
+        // remove cache
+        WeakReference<E> lastRemoveObject = NULL_REFERENCE;
+        WeakReference<PooledObjectLinkedOpenHashSet<E>> lastRemoveMap = NULL_REFERENCE;
+
+        public PooledObjectLinkedOpenHashSet(final PooledLinkedHashSets<E> pooledSets) {
+            this.set = new RawSetObjectLinkedOpenHashSet<>(2, 0.8f);
+        }
+
+        public PooledObjectLinkedOpenHashSet(final E single) {
+            this((PooledLinkedHashSets<E>)null);
+            this.referenceCount = -1;
+            this.add(single);
+        }
+
+        public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet<E> other) {
+            this.set = other.set.clone();
+            this.hash = other.hash;
+        }
+
+        // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
+        // generated by https://github.com/skeeto/hash-prospector
+        private static int hash0(int x) {
+            x *= 0x36935555;
+            x ^= x >>> 16;
+            return x;
+        }
+
+        PooledObjectLinkedOpenHashSet<E> getAddCache(final E element) {
+            final E currentAdd = this.lastAddObject.get();
+
+            if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) {
+                return null;
+            }
+
+            return this.lastAddMap.get();
+        }
+
+        PooledObjectLinkedOpenHashSet<E> getRemoveCache(final E element) {
+            final E currentRemove = this.lastRemoveObject.get();
+
+            if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) {
+                return null;
+            }
+
+            return this.lastRemoveMap.get();
+        }
+
+        void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
+            this.lastAddObject = new WeakReference<>(element);
+            this.lastAddMap = new WeakReference<>(map);
+        }
+
+        void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
+            this.lastRemoveObject = new WeakReference<>(element);
+            this.lastRemoveMap = new WeakReference<>(map);
+        }
+
+        boolean add(final E element) {
+            boolean added =  this.set.add(element);
+
+            if (added) {
+                this.hash += hash0(element.hashCode());
+            }
+
+            return added;
+        }
+
+        boolean remove(Object element) {
+            boolean removed = this.set.remove(element);
+
+            if (removed) {
+                this.hash -= hash0(element.hashCode());
+            }
+
+            return removed;
+        }
+
+        public boolean contains(final Object element) {
+            return this.set.contains(element);
+        }
+
+        public E[] getBackingSet() {
+            return this.set.getRawSet();
+        }
+
+        public int size() {
+            return this.set.size();
+        }
+
+        @Override
+        public int hashCode() {
+            return this.hash;
+        }
+
+        @Override
+        public boolean equals(final Object other) {
+            if (!(other instanceof PooledObjectLinkedOpenHashSet)) {
+                return false;
+            }
+            if (this.referenceCount == 0) {
+                return other == this;
+            } else {
+                if (other == this) {
+                    // Unfortunately we are never equal to our own instance while in use!
+                    return false;
+                }
+                return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " +
+                this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString();
+        }
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.pooled;
+
+import io.papermc.paper.util.MCUtil;
+import org.apache.commons.lang3.mutable.MutableInt;
+
+import java.util.ArrayDeque;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public final class PooledObjects<E> {
+
+    /**
+     * Wrapper for an object that will be have a cleaner registered for it, and may be automatically returned to pool.
+     */
+    public class AutoReleased {
+        private final E object;
+        private final Runnable cleaner;
+
+        public AutoReleased(E object, Runnable cleaner) {
+            this.object = object;
+            this.cleaner = cleaner;
+        }
+
+        public final E getObject() {
+            return object;
+        }
+
+        public final Runnable getCleaner() {
+            return cleaner;
+        }
+    }
+
+    public static final PooledObjects<MutableInt> POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024);
+
+    private final Supplier<E> creator;
+    private final Consumer<E> releaser;
+    private final int maxPoolSize;
+    private final ArrayDeque<E> queue;
+
+    public PooledObjects(final Supplier<E> creator, int maxPoolSize) {
+        this(creator, maxPoolSize, null);
+    }
+    public PooledObjects(final Supplier<E> creator, int maxPoolSize, Consumer<E> releaser) {
+        if (creator == null) {
+            throw new NullPointerException("Creator must not be null");
+        }
+        if (maxPoolSize <= 0) {
+            throw new IllegalArgumentException("Max pool size must be greater-than 0");
+        }
+
+        this.queue = new ArrayDeque<>(maxPoolSize);
+        this.maxPoolSize = maxPoolSize;
+        this.creator = creator;
+        this.releaser = releaser;
+    }
+
+    public AutoReleased acquireCleaner(Object holder) {
+        return acquireCleaner(holder, this::release);
+    }
+
+    public AutoReleased acquireCleaner(Object holder, Consumer<E> releaser) {
+        E resource = acquire();
+        Runnable cleaner = MCUtil.registerCleaner(holder, resource, releaser);
+        return new AutoReleased(resource, cleaner);
+    }
+
+    public final E acquire() {
+        E value;
+        synchronized (queue) {
+            value = this.queue.pollLast();
+        }
+        return value != null ? value : this.creator.get();
+    }
+
+    public final void release(final E value) {
+        if (this.releaser != null) {
+            this.releaser.accept(value);
+        }
+        synchronized (this.queue) {
+            if (queue.size() < this.maxPoolSize) {
+                this.queue.addLast(value);
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.set;
+
+import java.util.Collection;
+
+/**
+ * @author Spottedleaf &lt;Spottedleaf@users.noreply.github.com>
+ */
+public final class OptimizedSmallEnumSet<E extends Enum<E>> {
+
+    private final Class<E> enumClass;
+    private long backingSet;
+
+    public OptimizedSmallEnumSet(final Class<E> clazz) {
+        if (clazz == null) {
+            throw new IllegalArgumentException("Null class");
+        }
+        if (!clazz.isEnum()) {
+            throw new IllegalArgumentException("Class must be enum, not " + clazz.getCanonicalName());
+        }
+        this.enumClass = clazz;
+    }
+
+    public boolean add(final E element) {
+        final int ordinal = element.ordinal();
+        final long key = 1L << ordinal;
+
+        final long prev = this.backingSet;
+        this.backingSet = prev | key;
+
+        return (prev & key) == 0;
+    }
+
+    public boolean remove(final E element) {
+        final int ordinal = element.ordinal();
+        final long key = 1L << ordinal;
+
+        final long prev = this.backingSet;
+        this.backingSet = prev & ~key;
+
+        return (prev & key) != 0;
+    }
+
+    public void clear() {
+        this.backingSet = 0L;
+    }
+
+    public int size() {
+        return Long.bitCount(this.backingSet);
+    }
+
+    public void addAll(final Collection<E> enums) {
+        for (final E element : enums) {
+            if (element == null) {
+                throw new NullPointerException("Null element");
+            }
+            this.backingSet |= (1L << element.ordinal());
+        }
+    }
+
+    public long getBackingSet() {
+        return this.backingSet;
+    }
+
+    public boolean hasCommonElements(final OptimizedSmallEnumSet<E> other) {
+        return (other.backingSet & this.backingSet) != 0;
+    }
+
+    public boolean contains(final E element) {
+        return (this.backingSet & (1L << element.ordinal())) != 0;
+    }
+}
diff --git a/src/main/java/com/mojang/logging/LogUtils.java b/src/main/java/com/mojang/logging/LogUtils.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/mojang/logging/LogUtils.java
+++ b/src/main/java/com/mojang/logging/LogUtils.java
@@ -0,0 +0,0 @@ public class LogUtils {
     public static Logger getLogger() {
         return LoggerFactory.getLogger(STACK_WALKER.getCallerClass());
     }
+    // Paper start
+    public static Logger getClassLogger() {
+        return LoggerFactory.getLogger(STACK_WALKER.getCallerClass().getSimpleName());
+    }
+    // Paper end
 }
diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.chunk.system;
+
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
+import com.destroystokyo.paper.util.SneakyThrow;
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.util.CoordinateUtils;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.ChunkMap;
+import net.minecraft.server.level.ChunkResult;
+import net.minecraft.server.level.FullChunkStatus;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.TicketType;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.status.ChunkPyramid;
+import net.minecraft.world.level.chunk.status.ChunkStatus;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.status.ChunkStep;
+import org.bukkit.Bukkit;
+import org.slf4j.Logger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+public final class ChunkSystem {
+
+    private static final Logger LOGGER = LogUtils.getLogger();
+    private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
+
+    public static int getDistance(final ChunkStatus status) {
+        return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status);
+    }
+
+    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
+        scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL);
+    }
+
+    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) {
+        level.chunkSource.mainThreadProcessor.execute(run);
+    }
+
+    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
+                                         final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority,
+                                         final Consumer<ChunkAccess> onComplete) {
+        if (gen) {
+            scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+            return;
+        }
+        scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
+            if (chunk == null) {
+                if (onComplete != null) {
+                    onComplete.accept(null);
+                }
+            } else {
+                if (chunk.getPersistedStatus().isOrAfter(toStatus)) {
+                    scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+                } else {
+                    if (onComplete != null) {
+                        onComplete.accept(null);
+                    }
+                }
+            }
+        });
+    }
+
+    static final TicketType<Long> CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo);
+
+    private static long chunkLoadCounter = 0L;
+    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
+                                         final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
+        if (!Bukkit.isPrimaryThread()) {
+            scheduleChunkTask(level, chunkX, chunkZ, () -> {
+                scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+            }, priority);
+            return;
+        }
+
+        final int minLevel = 33 + ChunkSystem.getDistance(toStatus);
+        final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
+        final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+
+        if (addTicket) {
+            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+        }
+        level.chunkSource.runDistanceManagerUpdates();
+
+        final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
+            try {
+                if (onComplete != null) {
+                    onComplete.accept(chunk);
+                }
+            } catch (final Throwable thr) {
+                LOGGER.error("Exception handling chunk load callback", thr);
+                SneakyThrow.sneaky(thr);
+            } finally {
+                if (addTicket) {
+                    level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
+                    level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+                }
+            }
+        };
+
+        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+
+        if (holder == null || holder.getTicketLevel() > minLevel) {
+            loadCallback.accept(null);
+            return;
+        }
+
+        final CompletableFuture<ChunkResult<ChunkAccess>> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap);
+
+        if (loadFuture.isDone()) {
+            loadCallback.accept(loadFuture.join().orElse(null));
+            return;
+        }
+
+        loadFuture.whenCompleteAsync((final ChunkResult<ChunkAccess> result, final Throwable thr) -> {
+            if (thr != null) {
+                loadCallback.accept(null);
+                return;
+            }
+            loadCallback.accept(result.orElse(null));
+        }, (final Runnable r) -> {
+            scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
+        });
+    }
+
+    public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
+                                            final FullChunkStatus toStatus, final boolean addTicket,
+                                            final PrioritisedExecutor.Priority priority, final Consumer<LevelChunk> onComplete) {
+        // This method goes unused until the chunk system rewrite
+        if (toStatus == FullChunkStatus.INACCESSIBLE) {
+            throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
+        }
+
+        if (!Bukkit.isPrimaryThread()) {
+            scheduleChunkTask(level, chunkX, chunkZ, () -> {
+                scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+            }, priority);
+            return;
+        }
+
+        final int minLevel = 33 - (toStatus.ordinal() - 1);
+        final int radius = toStatus.ordinal() - 1;
+        final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
+        final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+
+        if (addTicket) {
+            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+        }
+        level.chunkSource.runDistanceManagerUpdates();
+
+        final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
+            try {
+                if (onComplete != null) {
+                    onComplete.accept(chunk);
+                }
+            } catch (final Throwable thr) {
+                LOGGER.error("Exception handling chunk load callback", thr);
+                SneakyThrow.sneaky(thr);
+            } finally {
+                if (addTicket) {
+                    level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
+                    level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+                }
+            }
+        };
+
+        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+
+        if (holder == null || holder.getTicketLevel() > minLevel) {
+            loadCallback.accept(null);
+            return;
+        }
+
+        final CompletableFuture<ChunkResult<LevelChunk>> tickingState;
+        switch (toStatus) {
+            case FULL: {
+                tickingState = holder.getFullChunkFuture();
+                break;
+            }
+            case BLOCK_TICKING: {
+                tickingState = holder.getTickingChunkFuture();
+                break;
+            }
+            case ENTITY_TICKING: {
+                tickingState = holder.getEntityTickingChunkFuture();
+                break;
+            }
+            default: {
+                throw new IllegalStateException("Cannot reach here");
+            }
+        }
+
+        if (tickingState.isDone()) {
+            loadCallback.accept(tickingState.join().orElse(null));
+            return;
+        }
+
+        tickingState.whenCompleteAsync((final ChunkResult<LevelChunk> result, final Throwable thr) -> {
+            if (thr != null) {
+                loadCallback.accept(null);
+                return;
+            }
+            loadCallback.accept(result.orElse(null));
+        }, (final Runnable r) -> {
+            scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
+        });
+    }
+
+    public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
+        return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
+    }
+
+    public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
+        return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
+    }
+
+    public static int getVisibleChunkHolderCount(final ServerLevel level) {
+        return level.chunkSource.chunkMap.visibleChunkMap.size();
+    }
+
+    public static int getUpdatingChunkHolderCount(final ServerLevel level) {
+        return level.chunkSource.chunkMap.updatingChunkMap.size();
+    }
+
+    public static boolean hasAnyChunkHolders(final ServerLevel level) {
+        return getUpdatingChunkHolderCount(level) != 0;
+    }
+
+    public static void onEntityPreAdd(final ServerLevel level, final Entity entity) {
+
+    }
+
+    public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
+
+    }
+
+    public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
+
+    }
+
+    public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
+
+    }
+
+    public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
+
+    }
+
+    public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
+        chunk.level.getChunkSource().tickingChunks.add(chunk);
+    }
+
+    public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
+        chunk.level.getChunkSource().tickingChunks.remove(chunk);
+    }
+
+    public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+        chunk.level.getChunkSource().entityTickingChunks.add(chunk);
+    }
+
+    public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+        chunk.level.getChunkSource().entityTickingChunks.remove(chunk);
+    }
+
+    public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
+        return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
+    }
+
+    public static int getSendViewDistance(final ServerPlayer player) {
+        return getLoadViewDistance(player);
+    }
+
+    public static int getLoadViewDistance(final ServerPlayer player) {
+        final ServerLevel level = player.serverLevel();
+        if (level == null) {
+            return Bukkit.getViewDistance();
+        }
+        return level.chunkSource.chunkMap.getPlayerViewDistance(player);
+    }
+
+    public static int getTickViewDistance(final ServerPlayer player) {
+        final ServerLevel level = player.serverLevel();
+        if (level == null) {
+            return Bukkit.getSimulationDistance();
+        }
+        return level.chunkSource.chunkMap.distanceManager.simulationDistance;
+    }
+
+    private ChunkSystem() {
+        throw new RuntimeException();
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/CachedLists.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+public final class CachedLists {
+
+    public static void reset() {
+
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/CoordinateUtils.java b/src/main/java/io/papermc/paper/util/CoordinateUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/CoordinateUtils.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.SectionPos;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.ChunkPos;
+
+public final class CoordinateUtils {
+
+    // dx, dz are relative to the target chunk
+    // dx, dz in [-radius, radius]
+    public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) {
+        return (dx + radius) + (2 * radius + 1)*(dz + radius);
+    }
+
+    // the chunk keys are compatible with vanilla
+
+    public static long getChunkKey(final BlockPos pos) {
+        return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL);
+    }
+
+    public static long getChunkKey(final Entity entity) {
+        return ((Mth.lfloor(entity.getZ()) >> 4) << 32) | ((Mth.lfloor(entity.getX()) >> 4) & 0xFFFFFFFFL);
+    }
+
+    public static long getChunkKey(final ChunkPos pos) {
+        return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL);
+    }
+
+    public static long getChunkKey(final SectionPos pos) {
+        return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL);
+    }
+
+    public static long getChunkKey(final int x, final int z) {
+        return ((long)z << 32) | (x & 0xFFFFFFFFL);
+    }
+
+    public static int getChunkX(final long chunkKey) {
+        return (int)chunkKey;
+    }
+
+    public static int getChunkZ(final long chunkKey) {
+        return (int)(chunkKey >>> 32);
+    }
+
+    public static int getChunkCoordinate(final double blockCoordinate) {
+        return Mth.floor(blockCoordinate) >> 4;
+    }
+
+    // the section keys are compatible with vanilla's
+
+    static final int SECTION_X_BITS = 22;
+    static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1;
+    static final int SECTION_Y_BITS = 20;
+    static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1;
+    static final int SECTION_Z_BITS = 22;
+    static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1;
+    // format is y,z,x (in order of LSB to MSB)
+    static final int SECTION_Y_SHIFT = 0;
+    static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS;
+    static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS;
+    static final int SECTION_TO_BLOCK_SHIFT = 4;
+
+    public static long getChunkSectionKey(final int x, final int y, final int z) {
+        return ((x & SECTION_X_MASK) << SECTION_X_SHIFT)
+                | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+                | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+    }
+
+    public static long getChunkSectionKey(final SectionPos pos) {
+        return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT)
+                | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+                | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+    }
+
+    public static long getChunkSectionKey(final ChunkPos pos, final int y) {
+        return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT)
+                | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+                | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+    }
+
+    public static long getChunkSectionKey(final BlockPos pos) {
+        return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
+                ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
+                (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
+    }
+
+    public static long getChunkSectionKey(final Entity entity) {
+        return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
+                ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
+                ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
+    }
+
+    public static int getChunkSectionX(final long key) {
+        return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS));
+    }
+
+    public static int getChunkSectionY(final long key) {
+        return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS));
+    }
+
+    public static int getChunkSectionZ(final long key) {
+        return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS));
+    }
+
+    // the block coordinates are not necessarily compatible with vanilla's
+
+    public static int getBlockCoordinate(final double blockCoordinate) {
+        return Mth.floor(blockCoordinate);
+    }
+
+    public static long getBlockKey(final int x, final int y, final int z) {
+        return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54);
+    }
+
+    public static long getBlockKey(final BlockPos pos) {
+        return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54);
+    }
+
+    public static long getBlockKey(final Entity entity) {
+        return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54);
+    }
+
+    private CoordinateUtils() {
+        throw new RuntimeException();
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/IntegerUtil.java b/src/main/java/io/papermc/paper/util/IntegerUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/IntegerUtil.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+public final class IntegerUtil {
+
+    public static final int HIGH_BIT_U32 = Integer.MIN_VALUE;
+    public static final long HIGH_BIT_U64 = Long.MIN_VALUE;
+
+    public static int ceilLog2(final int value) {
+        return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros
+    }
+
+    public static long ceilLog2(final long value) {
+        return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros
+    }
+
+    public static int floorLog2(final int value) {
+        // xor is optimized subtract for 2^n -1
+        // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1)
+        return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros
+    }
+
+    public static int floorLog2(final long value) {
+        // xor is optimized subtract for 2^n -1
+        // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1)
+        return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros
+    }
+
+    public static int roundCeilLog2(final int value) {
+        // optimized variant of 1 << (32 - leading(val - 1))
+        // given
+        // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32)
+        // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1)))
+        // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1)))
+        // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1))
+        // HIGH_BIT_32 >>> (-1 + leading(val - 1))
+        return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1);
+    }
+
+    public static long roundCeilLog2(final long value) {
+        // see logic documented above
+        return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1);
+    }
+
+    public static int roundFloorLog2(final int value) {
+        // optimized variant of 1 << (31 - leading(val))
+        // given
+        // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32)
+        // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val)))
+        // HIGH_BIT_32 >> (31 - (31 - leading(val)))
+        // HIGH_BIT_32 >> (31 - 31 + leading(val))
+        return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value);
+    }
+
+    public static long roundFloorLog2(final long value) {
+        // see logic documented above
+        return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value);
+    }
+
+    public static boolean isPowerOfTwo(final int n) {
+        // 2^n has one bit
+        // note: this rets true for 0 still
+        return IntegerUtil.getTrailingBit(n) == n;
+    }
+
+    public static boolean isPowerOfTwo(final long n) {
+        // 2^n has one bit
+        // note: this rets true for 0 still
+        return IntegerUtil.getTrailingBit(n) == n;
+    }
+
+    public static int getTrailingBit(final int n) {
+        return -n & n;
+    }
+
+    public static long getTrailingBit(final long n) {
+        return -n & n;
+    }
+
+    public static int trailingZeros(final int n) {
+        return Integer.numberOfTrailingZeros(n);
+    }
+
+    public static int trailingZeros(final long n) {
+        return Long.numberOfTrailingZeros(n);
+    }
+
+    // from hacker's delight (signed division magic value)
+    public static int getDivisorMultiple(final long numbers) {
+        return (int)(numbers >>> 32);
+    }
+
+    // from hacker's delight (signed division magic value)
+    public static int getDivisorShift(final long numbers) {
+        return (int)numbers;
+    }
+
+    // copied from hacker's delight (signed division magic value)
+    // http://www.hackersdelight.org/hdcodetxt/magic.c.txt
+    public static long getDivisorNumbers(final int d) {
+        final int ad = branchlessAbs(d);
+
+        if (ad < 2) {
+            throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d);
+        }
+
+        final int two31 = 0x80000000;
+        final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour
+
+        /*
+         Signed usage:
+         int number;
+         long magic = getDivisorNumbers(div);
+         long mul = magic >>> 32;
+         int sign = number >> 31;
+         int result = (int)(((long)number * mul) >>> magic) - sign;
+         */
+        /*
+         Unsigned usage:
+         int number;
+         long magic = getDivisorNumbers(div);
+         long mul = magic >>> 32;
+         int result = (int)(((long)number * mul) >>> magic);
+         */
+
+        int p = 31;
+
+        // all these variables are UNSIGNED!
+        int t = two31 + (d >>> 31);
+        int anc = t - 1 - (int)((t & mask)%ad);
+        int q1 = (int)((two31 & mask)/(anc & mask));
+        int r1 = two31 - q1*anc;
+        int q2 = (int)((two31 & mask)/(ad & mask));
+        int r2 = two31 - q2*ad;
+        int delta;
+
+        do {
+            p = p + 1;
+            q1 = 2*q1;                        // Update q1 = 2**p/|nc|.
+            r1 = 2*r1;                        // Update r1 = rem(2**p, |nc|).
+            if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here)
+                q1 = q1 + 1;
+                r1 = r1 - anc;
+            }
+            q2 = 2*q2;                       // Update q2 = 2**p/|d|.
+            r2 = 2*r2;                       // Update r2 = rem(2**p, |d|).
+            if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here)
+                q2 = q2 + 1;
+                r2 = r2 - ad;
+            }
+            delta = ad - r2;
+        } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0));
+
+        int magicNum = q2 + 1;
+        if (d < 0) {
+            magicNum = -magicNum;
+        }
+        int shift = p;
+        return ((long)magicNum << 32) | shift;
+    }
+
+    public static int branchlessAbs(final int val) {
+        // -n = -1 ^ n + 1
+        final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0
+        return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1
+    }
+
+    public static long branchlessAbs(final long val) {
+        // -n = -1 ^ n + 1
+        final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0
+        return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1
+    }
+
+    //https://github.com/skeeto/hash-prospector for hash functions
+
+    //score = ~590.47984224483832
+    public static int hash0(int x) {
+        x *= 0x36935555;
+        x ^= x >>> 16;
+        return x;
+    }
+
+    //score = ~310.01596637036749
+    public static int hash1(int x) {
+        x ^= x >>> 15;
+        x *= 0x356aaaad;
+        x ^= x >>> 17;
+        return x;
+    }
+
+    public static int hash2(int x) {
+        x ^= x >>> 16;
+        x *= 0x7feb352d;
+        x ^= x >>> 15;
+        x *= 0x846ca68b;
+        x ^= x >>> 16;
+        return x;
+    }
+
+    public static int hash3(int x) {
+        x ^= x >>> 17;
+        x *= 0xed5ad4bb;
+        x ^= x >>> 11;
+        x *= 0xac4c1b51;
+        x ^= x >>> 15;
+        x *= 0x31848bab;
+        x ^= x >>> 14;
+        return x;
+    }
+
+    //score = ~365.79959673201887
+    public static long hash1(long x) {
+        x ^= x >>> 27;
+        x *= 0xb24924b71d2d354bL;
+        x ^= x >>> 28;
+        return x;
+    }
+
+    //h2 hash
+    public static long hash2(long x) {
+        x ^= x >>> 32;
+        x *= 0xd6e8feb86659fd93L;
+        x ^= x >>> 32;
+        x *= 0xd6e8feb86659fd93L;
+        x ^= x >>> 32;
+        return x;
+    }
+
+    public static long hash3(long x) {
+        x ^= x >>> 45;
+        x *= 0xc161abe5704b6c79L;
+        x ^= x >>> 41;
+        x *= 0xe3e5389aedbc90f7L;
+        x ^= x >>> 56;
+        x *= 0x1f9aba75a52db073L;
+        x ^= x >>> 53;
+        return x;
+    }
+
+    private IntegerUtil() {
+        throw new RuntimeException();
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+public final class IntervalledCounter {
+
+    private static final int INITIAL_SIZE = 8;
+
+    protected long[] times;
+    protected long[] counts;
+    protected final long interval;
+    protected long minTime;
+    protected long sum;
+    protected int head; // inclusive
+    protected int tail; // exclusive
+
+    public IntervalledCounter(final long interval) {
+        this.times = new long[INITIAL_SIZE];
+        this.counts = new long[INITIAL_SIZE];
+        this.interval = interval;
+    }
+
+    public void updateCurrentTime() {
+        this.updateCurrentTime(System.nanoTime());
+    }
+
+    public void updateCurrentTime(final long currentTime) {
+        long sum = this.sum;
+        int head = this.head;
+        final int tail = this.tail;
+        final long minTime = currentTime - this.interval;
+
+        final int arrayLen = this.times.length;
+
+        // guard against overflow by using subtraction
+        while (head != tail && this.times[head] - minTime < 0) {
+            sum -= this.counts[head];
+            // there are two ways we can do this:
+            // 1. free the count when adding
+            // 2. free it now
+            // option #2
+            this.counts[head] = 0;
+            if (++head >= arrayLen) {
+                head = 0;
+            }
+        }
+
+        this.sum = sum;
+        this.head = head;
+        this.minTime = minTime;
+    }
+
+    public void addTime(final long currTime) {
+        this.addTime(currTime, 1L);
+    }
+
+    public void addTime(final long currTime, final long count) {
+        // guard against overflow by using subtraction
+        if (currTime - this.minTime < 0) {
+            return;
+        }
+        int nextTail = (this.tail + 1) % this.times.length;
+        if (nextTail == this.head) {
+            this.resize();
+            nextTail = (this.tail + 1) % this.times.length;
+        }
+
+        this.times[this.tail] = currTime;
+        this.counts[this.tail] += count;
+        this.sum += count;
+        this.tail = nextTail;
+    }
+
+    public void updateAndAdd(final long count) {
+        final long currTime = System.nanoTime();
+        this.updateCurrentTime(currTime);
+        this.addTime(currTime, count);
+    }
+
+    public void updateAndAdd(final long count, final long currTime) {
+        this.updateCurrentTime(currTime);
+        this.addTime(currTime, count);
+    }
+
+    private void resize() {
+        final long[] oldElements = this.times;
+        final long[] oldCounts = this.counts;
+        final long[] newElements = new long[this.times.length * 2];
+        final long[] newCounts = new long[this.times.length * 2];
+        this.times = newElements;
+        this.counts = newCounts;
+
+        final int head = this.head;
+        final int tail = this.tail;
+        final int size = tail >= head ? (tail - head) : (tail + (oldElements.length - head));
+        this.head = 0;
+        this.tail = size;
+
+        if (tail >= head) {
+            // sequentially ordered from [head, tail)
+            System.arraycopy(oldElements, head, newElements, 0, size);
+            System.arraycopy(oldCounts, head, newCounts, 0, size);
+        } else {
+            // ordered from [head, length)
+            // then followed by [0, tail)
+            System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
+            System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
+
+            System.arraycopy(oldCounts, head, newCounts, 0, oldCounts.length - head);
+            System.arraycopy(oldCounts, 0, newCounts, oldCounts.length - head, tail);
+        }
+    }
+
+    // returns in units per second
+    public double getRate() {
+        return (double)this.sum / ((double)this.interval * 1.0E-9);
+    }
+
+    public long getInterval() {
+        return this.interval;
+    }
+
+    public long getSum() {
+        return this.sum;
+    }
+
+    public int totalDataPoints() {
+        return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head));
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/MCUtil.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import io.papermc.paper.math.BlockPosition;
+import io.papermc.paper.math.FinePosition;
+import io.papermc.paper.math.Position;
+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
+import java.lang.ref.Cleaner;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.core.Vec3i;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.ClipContext;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.bukkit.Location;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.util.Waitable;
+import org.jetbrains.annotations.NotNull;
+import org.spigotmc.AsyncCatcher;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+public final class MCUtil {
+    public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor(
+        0, 2, 60L, TimeUnit.SECONDS,
+        new LinkedBlockingQueue<>(),
+        new ThreadFactoryBuilder()
+            .setNameFormat("Paper Async Task Handler Thread - %1$d")
+            .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER))
+            .build()
+    );
+    public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor(
+        1, 1, 0L, TimeUnit.SECONDS,
+        new LinkedBlockingQueue<>(),
+        new ThreadFactoryBuilder()
+            .setNameFormat("Paper Object Cleaner")
+            .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER))
+            .build()
+    );
+
+    public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+
+    public static Runnable once(Runnable run) {
+        AtomicBoolean ran = new AtomicBoolean(false);
+        return () -> {
+            if (ran.compareAndSet(false, true)) {
+                run.run();
+            }
+        };
+    }
+
+    public static <T> Runnable once(List<T> list, Consumer<T> cb) {
+        return once(() -> {
+            list.forEach(cb);
+        });
+    }
+
+    private static Runnable makeCleanerCallback(Runnable run) {
+        return once(() -> cleanerExecutor.execute(run));
+    }
+
+    /**
+     * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
+     * @param obj
+     * @param run
+     * @return
+     */
+    public static Runnable registerCleaner(Object obj, Runnable run) {
+        // Wrap callback in its own method above or the lambda will leak object
+        Runnable cleaner = makeCleanerCallback(run);
+        CleanerHolder.CLEANER.register(obj, cleaner);
+        return cleaner;
+    }
+
+    private static final class CleanerHolder {
+        private static final Cleaner CLEANER = Cleaner.create();
+    }
+
+    /**
+     * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
+     * @param obj
+     * @param list
+     * @param cleaner
+     * @param <T>
+     * @return
+     */
+    public static <T> Runnable registerListCleaner(Object obj, List<T> list, Consumer<T> cleaner) {
+        return registerCleaner(obj, () -> {
+            list.forEach(cleaner);
+            list.clear();
+        });
+    }
+
+    /**
+     * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
+     * @param obj
+     * @param resource
+     * @param cleaner
+     * @param <T>
+     * @return
+     */
+    public static <T> Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer<T> cleaner) {
+        return registerCleaner(obj, () -> cleaner.accept(resource));
+    }
+
+    public static List<ChunkPos> getSpiralOutChunks(BlockPos blockposition, int radius) {
+        List<ChunkPos> list = com.google.common.collect.Lists.newArrayList();
+
+        list.add(new ChunkPos(blockposition.getX() >> 4, blockposition.getZ() >> 4));
+        for (int r = 1; r <= radius; r++) {
+            int x = -r;
+            int z = r;
+
+            // Iterates the edge of half of the box; then negates for other half.
+            while (x <= r && z > -r) {
+                list.add(new ChunkPos((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4));
+                list.add(new ChunkPos((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4));
+
+                if (x < r) {
+                    x++;
+                } else {
+                    z--;
+                }
+            }
+        }
+        return list;
+    }
+
+    public static int fastFloor(double x) {
+        int truncated = (int)x;
+        return x < (double)truncated ? truncated - 1 : truncated;
+    }
+
+    public static int fastFloor(float x) {
+        int truncated = (int)x;
+        return x < (double)truncated ? truncated - 1 : truncated;
+    }
+
+    public static float normalizeYaw(float f) {
+        float f1 = f % 360.0F;
+
+        if (f1 >= 180.0F) {
+            f1 -= 360.0F;
+        }
+
+        if (f1 < -180.0F) {
+            f1 += 360.0F;
+        }
+
+        return f1;
+    }
+
+    /**
+     * Quickly generate a stack trace for current location
+     *
+     * @return Stacktrace
+     */
+    public static String stack() {
+        return ExceptionUtils.getFullStackTrace(new Throwable());
+    }
+
+    /**
+     * Quickly generate a stack trace for current location with message
+     *
+     * @param str
+     * @return Stacktrace
+     */
+    public static String stack(String str) {
+        return ExceptionUtils.getFullStackTrace(new Throwable(str));
+    }
+
+    public static long getCoordinateKey(final BlockPos blockPos) {
+        return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL);
+    }
+
+    public static long getCoordinateKey(final Entity entity) {
+        return ((long)(MCUtil.fastFloor(entity.getZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.getX()) >> 4) & 0xFFFFFFFFL);
+    }
+
+    public static long getCoordinateKey(final ChunkPos pair) {
+        return ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL);
+    }
+
+    public static long getCoordinateKey(final int x, final int z) {
+        return ((long)z << 32) | (x & 0xFFFFFFFFL);
+    }
+
+    public static int getCoordinateX(final long key) {
+        return (int)key;
+    }
+
+    public static int getCoordinateZ(final long key) {
+        return (int)(key >>> 32);
+    }
+
+    public static int getChunkCoordinate(final double coordinate) {
+        return MCUtil.fastFloor(coordinate) >> 4;
+    }
+
+    public static int getBlockCoordinate(final double coordinate) {
+        return MCUtil.fastFloor(coordinate);
+    }
+
+    public static long getBlockKey(final int x, final int y, final int z) {
+        return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54);
+    }
+
+    public static long getBlockKey(final BlockPos pos) {
+        return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54);
+    }
+
+    public static long getBlockKey(final Entity entity) {
+        return getBlockKey(getBlockCoordinate(entity.getX()), getBlockCoordinate(entity.getY()), getBlockCoordinate(entity.getZ()));
+    }
+
+    // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable
+    public static <T> void mergeSortedSets(final java.util.function.Consumer<T> consumer, final java.util.Comparator<? super T> comparator, final java.util.SortedSet<T>...sets) {
+        final ObjectRBTreeSet<T> all = new ObjectRBTreeSet<>(comparator);
+        // note: this is done in log(n!) ~ nlogn time. It could be improved if it were to mimic what mergesort does.
+        for (java.util.SortedSet<T> set : sets) {
+            if (set != null) {
+                all.addAll(set);
+            }
+        }
+        all.forEach(consumer);
+    }
+
+    private MCUtil() {}
+
+    public static final java.util.concurrent.Executor MAIN_EXECUTOR = (run) -> {
+        if (!isMainThread()) {
+            MinecraftServer.getServer().execute(run);
+        } else {
+            run.run();
+        }
+    };
+
+    public static <T> CompletableFuture<T> ensureMain(CompletableFuture<T> future) {
+        return future.thenApplyAsync(r -> r, MAIN_EXECUTOR);
+    }
+
+    public static <T> void thenOnMain(CompletableFuture<T> future, Consumer<T> consumer) {
+        future.thenAcceptAsync(consumer, MAIN_EXECUTOR);
+    }
+    public static <T> void thenOnMain(CompletableFuture<T> future, BiConsumer<T, Throwable> consumer) {
+        future.whenCompleteAsync(consumer, MAIN_EXECUTOR);
+    }
+
+    public static boolean isMainThread() {
+        return MinecraftServer.getServer().isSameThread();
+    }
+
+    public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable) {
+        return scheduleTask(ticks, runnable, null);
+    }
+
+    public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable, String taskName) {
+        return MinecraftServer.getServer().server.getScheduler().scheduleInternalTask(runnable, ticks, taskName);
+    }
+
+    public static void processQueue() {
+        Runnable runnable;
+        Queue<Runnable> processQueue = getProcessQueue();
+        while ((runnable = processQueue.poll()) != null) {
+            try {
+                runnable.run();
+            } catch (Exception e) {
+                MinecraftServer.LOGGER.error("Error executing task", e);
+            }
+        }
+    }
+    public static <T> T processQueueWhileWaiting(CompletableFuture <T> future) {
+        try {
+            if (isMainThread()) {
+                while (!future.isDone()) {
+                    try {
+                        return future.get(1, TimeUnit.MILLISECONDS);
+                    } catch (TimeoutException ignored) {
+                        processQueue();
+                    }
+                }
+            }
+            return future.get();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void ensureMain(Runnable run) {
+        ensureMain(null, run);
+    }
+    /**
+     * Ensures the target code is running on the main thread
+     * @param reason
+     * @param run
+     */
+    public static void ensureMain(String reason, Runnable run) {
+        if (!isMainThread()) {
+            if (reason != null) {
+                MinecraftServer.LOGGER.warn("Asynchronous " + reason + "!", new IllegalStateException());
+            }
+            getProcessQueue().add(run);
+            return;
+        }
+        run.run();
+    }
+
+    private static Queue<Runnable> getProcessQueue() {
+        return MinecraftServer.getServer().processQueue;
+    }
+
+    public static <T> T ensureMain(Supplier<T> run) {
+        return ensureMain(null, run);
+    }
+    /**
+     * Ensures the target code is running on the main thread
+     * @param reason
+     * @param run
+     * @param <T>
+     * @return
+     */
+    public static <T> T ensureMain(String reason, Supplier<T> run) {
+        if (!isMainThread()) {
+            if (reason != null) {
+                MinecraftServer.LOGGER.warn("Asynchronous " + reason + "! Blocking thread until it returns ", new IllegalStateException());
+            }
+            Waitable<T> wait = new Waitable<T>() {
+                @Override
+                protected T evaluate() {
+                    return run.get();
+                }
+            };
+            getProcessQueue().add(wait);
+            try {
+                return wait.get();
+            } catch (InterruptedException | ExecutionException e) {
+                MinecraftServer.LOGGER.warn("Encountered exception", e);
+            }
+            return null;
+        }
+        return run.get();
+    }
+
+    /**
+     * Calculates distance between 2 entities
+     * @param e1
+     * @param e2
+     * @return
+     */
+    public static double distance(Entity e1, Entity e2) {
+        return Math.sqrt(distanceSq(e1, e2));
+    }
+
+
+    /**
+     * Calculates distance between 2 block positions
+     * @param e1
+     * @param e2
+     * @return
+     */
+    public static double distance(BlockPos e1, BlockPos e2) {
+        return Math.sqrt(distanceSq(e1, e2));
+    }
+
+    /**
+     * Gets the distance between 2 positions
+     * @param x1
+     * @param y1
+     * @param z1
+     * @param x2
+     * @param y2
+     * @param z2
+     * @return
+     */
+    public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) {
+        return Math.sqrt(distanceSq(x1, y1, z1, x2, y2, z2));
+    }
+
+    /**
+     * Get's the distance squared between 2 entities
+     * @param e1
+     * @param e2
+     * @return
+     */
+    public static double distanceSq(Entity e1, Entity e2) {
+        return distanceSq(e1.getX(),e1.getY(),e1.getZ(), e2.getX(),e2.getY(),e2.getZ());
+    }
+
+    /**
+     * Gets the distance sqaured between 2 block positions
+     * @param pos1
+     * @param pos2
+     * @return
+     */
+    public static double distanceSq(BlockPos pos1, BlockPos pos2) {
+        return distanceSq(pos1.getX(), pos1.getY(), pos1.getZ(), pos2.getX(), pos2.getY(), pos2.getZ());
+    }
+
+    /**
+     * Gets the distance squared between 2 positions
+     * @param x1
+     * @param y1
+     * @param z1
+     * @param x2
+     * @param y2
+     * @param z2
+     * @return
+     */
+    public static double distanceSq(double x1, double y1, double z1, double x2, double y2, double z2) {
+        return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2);
+    }
+
+    /**
+     * Converts a NMS World/BlockPosition to Bukkit Location
+     * @param world
+     * @param x
+     * @param y
+     * @param z
+     * @return
+     */
+    public static Location toLocation(Level world, double x, double y, double z) {
+        return new Location(world.getWorld(), x, y, z);
+    }
+
+    /**
+     * Converts a NMS World/BlockPosition to Bukkit Location
+     * @param world
+     * @param pos
+     * @return
+     */
+    public static Location toLocation(Level world, BlockPos pos) {
+        return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ());
+    }
+
+    /**
+     * Converts an NMS entity's current location to a Bukkit Location
+     * @param entity
+     * @return
+     */
+    public static Location toLocation(Entity entity) {
+        return new Location(entity.getCommandSenderWorld().getWorld(), entity.getX(), entity.getY(), entity.getZ());
+    }
+
+    public static BlockPos toBlockPosition(Location loc) {
+        return new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
+    }
+
+    public static BlockPos toBlockPos(Position pos) {
+        return new BlockPos(pos.blockX(), pos.blockY(), pos.blockZ());
+    }
+
+    public static FinePosition toPosition(Vec3 vector) {
+        return Position.fine(vector.x, vector.y, vector.z);
+    }
+
+    public static BlockPosition toPosition(Vec3i vector) {
+        return Position.block(vector.getX(), vector.getY(), vector.getZ());
+    }
+
+    public static Vec3 toVec3(Position position) {
+        return new Vec3(position.x(), position.y(), position.z());
+    }
+
+    public static boolean isEdgeOfChunk(BlockPos pos) {
+        final int modX = pos.getX() & 15;
+        final int modZ = pos.getZ() & 15;
+        return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15);
+    }
+
+    /**
+     * Posts a task to be executed asynchronously
+     * @param run
+     */
+    public static void scheduleAsyncTask(Runnable run) {
+        asyncExecutor.execute(run);
+    }
+
+    @Nonnull
+    public static ServerLevel getNMSWorld(@Nonnull org.bukkit.World world) {
+        return ((CraftWorld) world).getHandle();
+    }
+
+    public static ServerLevel getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) {
+        return getNMSWorld(entity.getWorld());
+    }
+
+    public static BlockFace toBukkitBlockFace(Direction enumDirection) {
+        switch (enumDirection) {
+            case DOWN:
+                return BlockFace.DOWN;
+            case UP:
+                return BlockFace.UP;
+            case NORTH:
+                return BlockFace.NORTH;
+            case SOUTH:
+                return BlockFace.SOUTH;
+            case WEST:
+                return BlockFace.WEST;
+            case EAST:
+                return BlockFace.EAST;
+            default:
+                return null;
+        }
+    }
+
+    @NotNull
+    public static <T> List<T> copyListAndAdd(@NotNull final List<T> original,
+                                             @NotNull final T newElement) {
+        return ImmutableList.<T>builderWithExpectedSize(original.size() + 1)
+            .addAll(original)
+            .add(newElement)
+            .build();
+    }
+
+    @NotNull
+    public static <T> List<T> copyListAndRemoveIf(@NotNull final List<T> original,
+                                                  @NotNull final Predicate<T> removalPredicate) {
+        final ImmutableList.Builder<T> builder = ImmutableList.builderWithExpectedSize(original.size());
+        for (int i = 0; i < original.size(); i++) {
+            final T value = original.get(i);
+            if (removalPredicate.test(value)) continue;
+
+            builder.add(value);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/StackWalkerUtil.java b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.plugin.java.PluginClassLoader;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Optional;
+
+public class StackWalkerUtil {
+
+    @Nullable
+    public static JavaPlugin getFirstPluginCaller() {
+        Optional<JavaPlugin> foundFrame = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
+            .walk(stream -> stream
+                .filter(frame -> frame.getDeclaringClass().getClassLoader() instanceof PluginClassLoader)
+                .map((frame) -> {
+                    PluginClassLoader classLoader = (PluginClassLoader) frame.getDeclaringClass().getClassLoader();
+                    return classLoader.getPlugin();
+                })
+                .findFirst());
+
+        return foundFrame.orElse(null);
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/WorldUtil.java b/src/main/java/io/papermc/paper/util/WorldUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/WorldUtil.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import net.minecraft.world.level.LevelHeightAccessor;
+
+public final class WorldUtil {
+
+    // min, max are inclusive
+
+    public static int getMaxSection(final LevelHeightAccessor world) {
+        return world.getMaxSection() - 1; // getMaxSection() is exclusive
+    }
+
+    public static int getMinSection(final LevelHeightAccessor world) {
+        return world.getMinSection();
+    }
+
+    public static int getMaxLightSection(final LevelHeightAccessor world) {
+        return getMaxSection(world) + 1;
+    }
+
+    public static int getMinLightSection(final LevelHeightAccessor world) {
+        return getMinSection(world) - 1;
+    }
+
+
+
+    public static int getTotalSections(final LevelHeightAccessor world) {
+        return getMaxSection(world) - getMinSection(world) + 1;
+    }
+
+    public static int getTotalLightSections(final LevelHeightAccessor world) {
+        return getMaxLightSection(world) - getMinLightSection(world) + 1;
+    }
+
+    public static int getMinBlockY(final LevelHeightAccessor world) {
+        return getMinSection(world) << 4;
+    }
+
+    public static int getMaxBlockY(final LevelHeightAccessor world) {
+        return (getMaxSection(world) << 4) | 15;
+    }
+
+    private WorldUtil() {
+        throw new RuntimeException();
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util.maplist;
+
+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import org.bukkit.Bukkit;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+
+public final class IteratorSafeOrderedReferenceSet<E> {
+
+    public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1 << 0;
+
+    protected final Reference2IntLinkedOpenHashMap<E> indexMap;
+    protected int firstInvalidIndex = -1;
+
+    /* list impl */
+    protected E[] listElements;
+    protected int listSize;
+
+    protected final double maxFragFactor;
+
+    protected int iteratorCount;
+
+    private final boolean threadRestricted;
+
+    public IteratorSafeOrderedReferenceSet() {
+        this(16, 0.75f, 16, 0.2);
+    }
+
+    public IteratorSafeOrderedReferenceSet(final boolean threadRestricted) {
+        this(16, 0.75f, 16, 0.2, threadRestricted);
+    }
+
+    public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
+                                           final double maxFragFactor) {
+        this(setCapacity, setLoadFactor, arrayCapacity, maxFragFactor, false);
+    }
+    public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
+                                           final double maxFragFactor, final boolean threadRestricted) {
+        this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor);
+        this.indexMap.defaultReturnValue(-1);
+        this.maxFragFactor = maxFragFactor;
+        this.listElements = (E[])new Object[arrayCapacity];
+        this.threadRestricted = threadRestricted;
+    }
+
+    /*
+    public void check() {
+        int iterated = 0;
+        ReferenceOpenHashSet<E> check = new ReferenceOpenHashSet<>();
+        if (this.listElements != null) {
+            for (int i = 0; i < this.listSize; ++i) {
+                Object obj = this.listElements[i];
+                if (obj != null) {
+                    iterated++;
+                    if (!check.add((E)obj)) {
+                        throw new IllegalStateException("contains duplicate");
+                    }
+                    if (!this.contains((E)obj)) {
+                        throw new IllegalStateException("desync");
+                    }
+                }
+            }
+        }
+
+        if (iterated != this.size()) {
+            throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size());
+        }
+
+        check.clear();
+        iterated = 0;
+        for (final java.util.Iterator<E> iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+            final E element = iterator.next();
+            iterated++;
+            if (!check.add(element)) {
+                throw new IllegalStateException("contains duplicate (iterator is wrong)");
+            }
+            if (!this.contains(element)) {
+                throw new IllegalStateException("desync (iterator is wrong)");
+            }
+        }
+
+        if (iterated != this.size()) {
+            throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size());
+        }
+    }
+    */
+
+    protected final boolean allowSafeIteration() {
+        return !this.threadRestricted || Bukkit.isPrimaryThread();
+    }
+
+    protected final double getFragFactor() {
+        return 1.0 - ((double)this.indexMap.size() / (double)this.listSize);
+    }
+
+    public int createRawIterator() {
+        if (this.allowSafeIteration()) {
+            ++this.iteratorCount;
+        }
+        if (this.indexMap.isEmpty()) {
+            return -1;
+        } else {
+            return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0;
+        }
+    }
+
+    public int advanceRawIterator(final int index) {
+        final E[] elements = this.listElements;
+        int ret = index + 1;
+        for (int len = this.listSize; ret < len; ++ret) {
+            if (elements[ret] != null) {
+                return ret;
+            }
+        }
+
+        return -1;
+    }
+
+    public void finishRawIterator() {
+        if (this.allowSafeIteration() && --this.iteratorCount == 0) {
+            if (this.getFragFactor() >= this.maxFragFactor) {
+                this.defrag();
+            }
+        }
+    }
+
+    public boolean remove(final E element) {
+        final int index = this.indexMap.removeInt(element);
+        if (index >= 0) {
+            if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) {
+                this.firstInvalidIndex = index;
+            }
+            if (this.listElements[index] != element) {
+                throw new IllegalStateException();
+            }
+            this.listElements[index] = null;
+            if (this.allowSafeIteration() && this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) {
+                this.defrag();
+            }
+            //this.check();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean contains(final E element) {
+        return this.indexMap.containsKey(element);
+    }
+
+    public boolean add(final E element) {
+        final int listSize = this.listSize;
+
+        final int previous = this.indexMap.putIfAbsent(element, listSize);
+        if (previous != -1) {
+            return false;
+        }
+
+        if (listSize >= this.listElements.length) {
+            this.listElements = Arrays.copyOf(this.listElements, listSize * 2);
+        }
+        this.listElements[listSize] = element;
+        this.listSize = listSize + 1;
+
+        //this.check();
+        return true;
+    }
+
+    protected void defrag() {
+        if (this.firstInvalidIndex < 0) {
+            return; // nothing to do
+        }
+
+        if (this.indexMap.isEmpty()) {
+            Arrays.fill(this.listElements, 0, this.listSize, null);
+            this.listSize = 0;
+            this.firstInvalidIndex = -1;
+            //this.check();
+            return;
+        }
+
+        final E[] backingArray = this.listElements;
+
+        int lastValidIndex;
+        java.util.Iterator<Reference2IntMap.Entry<E>> iterator;
+
+        if (this.firstInvalidIndex == 0) {
+            iterator = this.indexMap.reference2IntEntrySet().fastIterator();
+            lastValidIndex = 0;
+        } else {
+            lastValidIndex = this.firstInvalidIndex;
+            final E key = backingArray[lastValidIndex - 1];
+            iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry<E>() {
+                @Override
+                public int getIntValue() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public int setValue(int i) {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public E getKey() {
+                    return key;
+                }
+            });
+        }
+
+        while (iterator.hasNext()) {
+            final Reference2IntMap.Entry<E> entry = iterator.next();
+
+            final int newIndex = lastValidIndex++;
+            backingArray[newIndex] = entry.getKey();
+            entry.setValue(newIndex);
+        }
+
+        // cleanup end
+        Arrays.fill(backingArray, lastValidIndex, this.listSize, null);
+        this.listSize = lastValidIndex;
+        this.firstInvalidIndex = -1;
+        //this.check();
+    }
+
+    public E rawGet(final int index) {
+        return this.listElements[index];
+    }
+
+    public int size() {
+        // always returns the correct amount - listSize can be different
+        return this.indexMap.size();
+    }
+
+    public IteratorSafeOrderedReferenceSet.Iterator<E> iterator() {
+        return this.iterator(0);
+    }
+
+    public IteratorSafeOrderedReferenceSet.Iterator<E> iterator(final int flags) {
+        if (this.allowSafeIteration()) {
+            ++this.iteratorCount;
+        }
+        return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
+    }
+
+    public java.util.Iterator<E> unsafeIterator() {
+        return this.unsafeIterator(0);
+    }
+    public java.util.Iterator<E> unsafeIterator(final int flags) {
+        return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
+    }
+
+    public static interface Iterator<E> extends java.util.Iterator<E> {
+
+        public void finishedIterating();
+
+    }
+
+    protected static final class BaseIterator<E> implements IteratorSafeOrderedReferenceSet.Iterator<E> {
+
+        protected final IteratorSafeOrderedReferenceSet<E> set;
+        protected final boolean canFinish;
+        protected final int maxIndex;
+        protected int nextIndex;
+        protected E pendingValue;
+        protected boolean finished;
+        protected E lastReturned;
+
+        protected BaseIterator(final IteratorSafeOrderedReferenceSet<E> set, final boolean canFinish, final int maxIndex) {
+            this.set = set;
+            this.canFinish = canFinish;
+            this.maxIndex = maxIndex;
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (this.finished) {
+                return false;
+            }
+            if (this.pendingValue != null) {
+                return true;
+            }
+
+            final E[] elements = this.set.listElements;
+            int index, len;
+            for (index = this.nextIndex, len = Math.min(this.maxIndex, this.set.listSize); index < len; ++index) {
+                final E element = elements[index];
+                if (element != null) {
+                    this.pendingValue = element;
+                    this.nextIndex = index + 1;
+                    return true;
+                }
+            }
+
+            this.nextIndex = index;
+            return false;
+        }
+
+        @Override
+        public E next() {
+            if (!this.hasNext()) {
+                throw new NoSuchElementException();
+            }
+            final E ret = this.pendingValue;
+
+            this.pendingValue = null;
+            this.lastReturned = ret;
+
+            return ret;
+        }
+
+        @Override
+        public void remove() {
+            final E lastReturned = this.lastReturned;
+            if (lastReturned == null) {
+                throw new IllegalStateException();
+            }
+            this.lastReturned = null;
+            this.set.remove(lastReturned);
+        }
+
+        @Override
+        public void finishedIterating() {
+            if (this.finished || !this.canFinish) {
+                throw new IllegalStateException();
+            }
+            this.lastReturned = null;
+            this.finished = true;
+            if (this.set.allowSafeIteration()) {
+                this.set.finishRawIterator();
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util.player;
+
+import com.destroystokyo.paper.util.maplist.ReferenceList;
+import io.papermc.paper.chunk.system.ChunkSystem;
+import io.papermc.paper.util.CoordinateUtils;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.ChunkPos;
+
+public final class NearbyPlayers {
+
+    public static enum NearbyMapType {
+        GENERAL,
+        GENERAL_SMALL,
+        GENERAL_REALLY_SMALL,
+        TICK_VIEW_DISTANCE,
+        VIEW_DISTANCE;
+    }
+
+    private static final NearbyMapType[] MOB_TYPES = NearbyMapType.values();
+    public static final int TOTAL_MAP_TYPES = MOB_TYPES.length;
+
+    private static final int GENERAL_AREA_VIEW_DISTANCE = 33;
+    private static final int GENERAL_SMALL_VIEW_DISTANCE = 10;
+    private static final int GENERAL_REALLY_SMALL_VIEW_DISTANCE = 3;
+
+    public static final int GENERAL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_AREA_VIEW_DISTANCE << 4);
+    public static final int GENERAL_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_SMALL_VIEW_DISTANCE << 4);
+    public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4);
+
+    private final ServerLevel world;
+    private final Reference2ReferenceOpenHashMap<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
+    private final Long2ReferenceOpenHashMap<TrackedChunk> byChunk = new Long2ReferenceOpenHashMap<>();
+
+    public NearbyPlayers(final ServerLevel world) {
+        this.world = world;
+    }
+
+    public void addPlayer(final ServerPlayer player) {
+        final TrackedPlayer[] newTrackers = new TrackedPlayer[TOTAL_MAP_TYPES];
+        if (this.players.putIfAbsent(player, newTrackers) != null) {
+            throw new IllegalStateException("Already have player " + player);
+        }
+
+        final ChunkPos chunk = player.chunkPosition();
+
+        for (int i = 0; i < TOTAL_MAP_TYPES; ++i) {
+            // use 0 for default, will be updated by tickPlayer
+            (newTrackers[i] = new TrackedPlayer(player, MOB_TYPES[i])).add(chunk.x, chunk.z, 0);
+        }
+
+        // update view distances
+        this.tickPlayer(player);
+    }
+
+    public void removePlayer(final ServerPlayer player) {
+        final TrackedPlayer[] players = this.players.remove(player);
+        if (players == null) {
+            return; // May be called during teleportation before the player is actually placed
+        }
+
+        for (final TrackedPlayer tracker : players) {
+            tracker.remove();
+        }
+    }
+
+    public void tickPlayer(final ServerPlayer player) {
+        final TrackedPlayer[] players = this.players.get(player);
+        if (players == null) {
+            throw new IllegalStateException("Don't have player " + player);
+        }
+
+        final ChunkPos chunk = player.chunkPosition();
+
+        players[NearbyMapType.GENERAL.ordinal()].update(chunk.x, chunk.z, GENERAL_AREA_VIEW_DISTANCE);
+        players[NearbyMapType.GENERAL_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_SMALL_VIEW_DISTANCE);
+        players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE);
+        players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player));
+        players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player));
+    }
+
+    public TrackedChunk getChunk(final ChunkPos pos) {
+        return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
+    }
+
+    public TrackedChunk getChunk(final BlockPos pos) {
+        return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
+    }
+
+    public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
+        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
+
+        return chunk == null ? null : chunk.players[type.ordinal()];
+    }
+
+    public ReferenceList<ServerPlayer> getPlayers(final ChunkPos pos, final NearbyMapType type) {
+        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
+
+        return chunk == null ? null : chunk.players[type.ordinal()];
+    }
+
+    public ReferenceList<ServerPlayer> getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) {
+        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+
+        return chunk == null ? null : chunk.players[type.ordinal()];
+    }
+
+    public ReferenceList<ServerPlayer> getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) {
+        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
+
+        return chunk == null ? null : chunk.players[type.ordinal()];
+    }
+
+    public static final class TrackedChunk {
+
+        public final ReferenceList<ServerPlayer>[] players = new ReferenceList[TOTAL_MAP_TYPES];
+        private int nonEmptyLists;
+        private int updateCount;
+
+        public boolean isEmpty() {
+            return this.nonEmptyLists == 0;
+        }
+
+        public int getUpdateCount() {
+            return this.updateCount;
+        }
+
+        public ReferenceList<ServerPlayer> getPlayers(final NearbyMapType type) {
+            return this.players[type.ordinal()];
+        }
+
+        public void addPlayer(final ServerPlayer player, final NearbyMapType type) {
+            ++this.updateCount;
+            final int idx = type.ordinal();
+            final ReferenceList<ServerPlayer> list = this.players[idx];
+            if (list == null) {
+                ++this.nonEmptyLists;
+                (this.players[idx] = new ReferenceList<>()).add(player);
+                return;
+            }
+
+            if (!list.add(player)) {
+                throw new IllegalStateException("Already contains player " + player);
+            }
+        }
+
+        public void removePlayer(final ServerPlayer player, final NearbyMapType type) {
+            ++this.updateCount;
+            final int idx = type.ordinal();
+            final ReferenceList<ServerPlayer> list = this.players[idx];
+            if (list == null) {
+                throw new IllegalStateException("Does not contain player " + player);
+            }
+
+            if (!list.remove(player)) {
+                throw new IllegalStateException("Does not contain player " + player);
+            }
+
+            if (list.size() == 0) {
+                this.players[idx] = null;
+                --this.nonEmptyLists;
+            }
+        }
+    }
+
+    private final class TrackedPlayer extends SingleUserAreaMap<ServerPlayer> {
+
+        final NearbyMapType type;
+
+        public TrackedPlayer(final ServerPlayer player, final NearbyMapType type) {
+            super(player);
+            this.type = type;
+        }
+
+        @Override
+        protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
+            final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
+
+            NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> {
+                return new TrackedChunk();
+            }).addPlayer(parameter, this.type);
+        }
+
+        @Override
+        protected void removeCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
+            final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
+
+            final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey);
+            if (chunk == null) {
+                throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey));
+            }
+
+            chunk.removePlayer(parameter, this.type);
+
+            if (chunk.isEmpty()) {
+                NearbyPlayers.this.byChunk.remove(chunkKey);
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/papermc/paper/util/player/SingleUserAreaMap.java b/src/main/java/io/papermc/paper/util/player/SingleUserAreaMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/player/SingleUserAreaMap.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util.player;
+
+import io.papermc.paper.util.IntegerUtil;
+
+public abstract class SingleUserAreaMap<T> {
+
+    private static final int NOT_SET = Integer.MIN_VALUE;
+
+    private final T parameter;
+    private int lastChunkX = NOT_SET;
+    private int lastChunkZ = NOT_SET;
+    private int distance = NOT_SET;
+
+    public SingleUserAreaMap(final T parameter) {
+        this.parameter = parameter;
+    }
+
+    /* math sign function except 0 returns 1 */
+    protected static int sign(int val) {
+        return 1 | (val >> (Integer.SIZE - 1));
+    }
+
+    protected abstract void addCallback(final T parameter, final int chunkX, final int chunkZ);
+
+    protected abstract void removeCallback(final T parameter, final int chunkX, final int chunkZ);
+
+    private void addToNew(final T parameter, final int chunkX, final int chunkZ, final int distance) {
+        final int maxX = chunkX + distance;
+        final int maxZ = chunkZ + distance;
+
+        for (int cx = chunkX - distance; cx <= maxX; ++cx) {
+            for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
+                this.addCallback(parameter, cx, cz);
+            }
+        }
+    }
+
+    private void removeFromOld(final T parameter, final int chunkX, final int chunkZ, final int distance) {
+        final int maxX = chunkX + distance;
+        final int maxZ = chunkZ + distance;
+
+        for (int cx = chunkX - distance; cx <= maxX; ++cx) {
+            for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
+                this.removeCallback(parameter, cx, cz);
+            }
+        }
+    }
+
+    public final boolean add(final int chunkX, final int chunkZ, final int distance) {
+        if (distance < 0) {
+            throw new IllegalArgumentException(Integer.toString(distance));
+        }
+        if (this.lastChunkX != NOT_SET) {
+            return false;
+        }
+        this.lastChunkX = chunkX;
+        this.lastChunkZ = chunkZ;
+        this.distance = distance;
+
+        this.addToNew(this.parameter, chunkX, chunkZ, distance);
+
+        return true;
+    }
+
+    public final boolean update(final int toX, final int toZ, final int newViewDistance) {
+        if (newViewDistance < 0) {
+            throw new IllegalArgumentException(Integer.toString(newViewDistance));
+        }
+        final int fromX = this.lastChunkX;
+        final int fromZ = this.lastChunkZ;
+        final int oldViewDistance = this.distance;
+        if (fromX == NOT_SET) {
+            return false;
+        }
+
+        this.lastChunkX = toX;
+        this.lastChunkZ = toZ;
+        this.distance = newViewDistance;
+
+        final T parameter = this.parameter;
+
+
+        final int dx = toX - fromX;
+        final int dz = toZ - fromZ;
+
+        final int totalX = IntegerUtil.branchlessAbs(fromX - toX);
+        final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ);
+
+        if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) {
+            // teleported?
+            this.removeFromOld(parameter, fromX, fromZ, oldViewDistance);
+            this.addToNew(parameter, toX, toZ, newViewDistance);
+            return true;
+        }
+
+        if (oldViewDistance != newViewDistance) {
+            // remove loop
+
+            final int oldMinX = fromX - oldViewDistance;
+            final int oldMinZ = fromZ - oldViewDistance;
+            final int oldMaxX = fromX + oldViewDistance;
+            final int oldMaxZ = fromZ + oldViewDistance;
+            for (int currX = oldMinX; currX <= oldMaxX; ++currX) {
+                for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) {
+
+                    // only remove if we're outside the new view distance...
+                    if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) {
+                        this.removeCallback(parameter, currX, currZ);
+                    }
+                }
+            }
+
+            // add loop
+
+            final int newMinX = toX - newViewDistance;
+            final int newMinZ = toZ - newViewDistance;
+            final int newMaxX = toX + newViewDistance;
+            final int newMaxZ = toZ + newViewDistance;
+            for (int currX = newMinX; currX <= newMaxX; ++currX) {
+                for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) {
+
+                    // only add if we're outside the old view distance...
+                    if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) {
+                        this.addCallback(parameter, currX, currZ);
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        // x axis is width
+        // z axis is height
+        // right refers to the x axis of where we moved
+        // top refers to the z axis of where we moved
+
+        // same view distance
+
+        // used for relative positioning
+        final int up = sign(dz); // 1 if dz >= 0, -1 otherwise
+        final int right = sign(dx); // 1 if dx >= 0, -1 otherwise
+
+        // The area excluded by overlapping the two view distance squares creates four rectangles:
+        // Two on the left, and two on the right. The ones on the left we consider the "removed" section
+        // and on the right the "added" section.
+        // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
+        // exclusive to the regions they surround.
+
+        // 4 points of the rectangle
+        int maxX; // exclusive
+        int minX; // inclusive
+        int maxZ; // exclusive
+        int minZ; // inclusive
+
+        if (dx != 0) {
+            // handle right addition
+
+            maxX = toX + (oldViewDistance * right) + right; // exclusive
+            minX = fromX + (oldViewDistance * right) + right; // inclusive
+            maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+            minZ = toZ - (oldViewDistance * up); // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.addCallback(parameter, currX, currZ);
+                }
+            }
+        }
+
+        if (dz != 0) {
+            // handle up addition
+
+            maxX = toX + (oldViewDistance * right) + right; // exclusive
+            minX = toX - (oldViewDistance * right); // inclusive
+            maxZ = toZ + (oldViewDistance * up) + up; // exclusive
+            minZ = fromZ + (oldViewDistance * up) + up; // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.addCallback(parameter, currX, currZ);
+                }
+            }
+        }
+
+        if (dx != 0) {
+            // handle left removal
+
+            maxX = toX - (oldViewDistance * right); // exclusive
+            minX = fromX - (oldViewDistance * right); // inclusive
+            maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+            minZ = toZ - (oldViewDistance * up); // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.removeCallback(parameter, currX, currZ);
+                }
+            }
+        }
+
+        if (dz != 0) {
+            // handle down removal
+
+            maxX = fromX + (oldViewDistance * right) + right; // exclusive
+            minX = fromX - (oldViewDistance * right); // inclusive
+            maxZ = toZ - (oldViewDistance * up); // exclusive
+            minZ = fromZ - (oldViewDistance * up); // inclusive
+
+            for (int currX = minX; currX != maxX; currX += right) {
+                for (int currZ = minZ; currZ != maxZ; currZ += up) {
+                    this.removeCallback(parameter, currX, currZ);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public final boolean remove() {
+        final int chunkX = this.lastChunkX;
+        final int chunkZ = this.lastChunkZ;
+        final int distance = this.distance;
+        if (chunkX == NOT_SET) {
+            return false;
+        }
+
+        this.lastChunkX = this.lastChunkZ = this.distance = NOT_SET;
+
+        this.removeFromOld(this.parameter, chunkX, chunkZ, distance);
+
+        return true;
+    }
+}
diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/Util.java
+++ b/src/main/java/net/minecraft/Util.java
@@ -0,0 +0,0 @@ public class Util {
     }
 
     public static long getNanos() {
-        return timeSource.getAsLong();
+        return System.nanoTime(); // Paper
     }
 
     public static long getEpochMillis() {
diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/nbt/CompoundTag.java
+++ b/src/main/java/net/minecraft/nbt/CompoundTag.java
@@ -0,0 +0,0 @@ public class CompoundTag implements Tag {
             return "TAG_Compound";
         }
     };
-    private final Map<String, Tag> tags;
+    public final Map<String, Tag> tags; // Paper
 
     protected CompoundTag(Map<String, Tag> entries) {
         this.tags = entries;
@@ -0,0 +0,0 @@ public class CompoundTag implements Tag {
         this.tags.put(key, NbtUtils.createUUID(value));
     }
 
+
+    /**
+     * You must use {@link #hasUUID(String)} before or else it <b>will</b> throw an NPE.
+     */
     public UUID getUUID(String key) {
         return NbtUtils.loadUUID(this.get(key));
     }
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.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 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
     BandwidthDebugMonitor bandwidthDebugMonitor;
     public String hostname = ""; // CraftBukkit - add field
 
+    // Paper start - add utility methods
+    public final net.minecraft.server.level.ServerPlayer getPlayer() {
+        if (this.packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl impl) {
+            return impl.player;
+        } else if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) {
+            org.bukkit.craftbukkit.entity.CraftPlayer player = impl.getCraftPlayer();
+            return player == null ? null : player.getHandle();
+        }
+        return null;
+    }
+    // Paper end - add utility methods
+
     public Connection(PacketFlow side) {
         this.receiving = side;
     }
diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/PacketEncoder.java
+++ b/src/main/java/net/minecraft/network/PacketEncoder.java
@@ -0,0 +0,0 @@ public class PacketEncoder<T extends PacketListener> extends MessageToByteEncode
 
             JvmProfiler.INSTANCE.onPacketSent(this.protocolInfo.id(), packetType, channelHandlerContext.channel().remoteAddress(), i);
         } catch (Throwable var9) {
-            LOGGER.error("Error sending packet {}", packetType, var9);
+            LOGGER.error("Error sending packet {} (skippable? {})", packetType, packet.isSkippable(), var9);
             if (packet.isSkippable()) {
                 throw new SkipPacketException(var9);
             }
diff --git a/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java b/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
+++ b/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
@@ -0,0 +0,0 @@ public record ClientboundCustomQueryPacket(int transactionId, CustomQueryPayload
     public void handle(ClientLoginPacketListener listener) {
         listener.handleCustomQuery(this);
     }
+
+    // Paper start - MC Utils - default query payloads
+    public static record PlayerInfoChannelPayload(ResourceLocation id, FriendlyByteBuf buffer) implements CustomQueryPayload {
+
+        @Override
+        public void write(final FriendlyByteBuf buf) {
+            buf.writeBytes(this.buffer.copy());
+        }
+    }
+    // Paper end - MC Utils - default query payloads
 }
diff --git a/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java b/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
+++ b/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
@@ -0,0 +0,0 @@ public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable Cu
     }
 
     private static CustomQueryAnswerPayload readPayload(int queryId, FriendlyByteBuf buf) {
-        return readUnknownPayload(buf);
+        // Paper start - MC Utils - default query payloads
+        FriendlyByteBuf buffer = buf.readNullable((buf2) -> {
+            int i = buf2.readableBytes();
+            if (i >= 0 && i <= MAX_PAYLOAD_SIZE) {
+                return new FriendlyByteBuf(buf2.readBytes(i));
+            } else {
+                throw new IllegalArgumentException("Payload may not be larger than " + MAX_PAYLOAD_SIZE + " bytes");
+            }
+        });
+        return buffer == null ? null : new net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket.QueryAnswerPayload(buffer);
+        // Paper end - MC Utils - default query payloads
     }
 
     private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf buf) {
@@ -0,0 +0,0 @@ public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable Cu
     public void handle(ServerLoginPacketListener listener) {
         listener.handleCustomQueryPacket(this);
     }
+
+    // Paper start - MC Utils - default query payloads
+    public static final class QueryAnswerPayload implements CustomQueryAnswerPayload {
+
+        public final FriendlyByteBuf buffer;
+
+        public QueryAnswerPayload(final net.minecraft.network.FriendlyByteBuf buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public void write(final net.minecraft.network.FriendlyByteBuf buf) {
+            buf.writeBytes(this.buffer.copy());
+        }
+    }
+    // Paper end - MC Utils - default query payloads
+
 }
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
     public final double[] recentTps = new double[ 3 ];
     // Spigot end
     public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
+    public static long currentTickLong = 0L; // Paper - track current tick as a long
 
     public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
         AtomicReference<S> atomicreference = new AtomicReference();
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
             MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1);
         }
         // Spigot start
+        io.papermc.paper.util.MCUtil.asyncExecutor.shutdown(); // Paper
+        try { io.papermc.paper.util.MCUtil.asyncExecutor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
+        } catch (java.lang.InterruptedException ignored) {} // Paper
         if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
             MinecraftServer.LOGGER.info("Saving usercache.json");
             this.getProfileCache().save();
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                     }
                 }
                 // Spigot start
+                ++MinecraftServer.currentTickLong; // Paper - track current tick as a long
                 if ( tickCount++ % MinecraftServer.SAMPLE_INTERVAL == 0 )
                 {
                     long curTime = Util.getMillis();
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
             MinecraftServer.LOGGER.debug("Autosave finished");
             SpigotTimings.worldSaveTimer.stopTiming(); // Spigot
         }
-
+        io.papermc.paper.util.CachedLists.reset(); // Paper
         this.profiler.push("tallying");
         long j = Util.getNanos() - i;
         int k = this.tickCount % 100;
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 extends GenerationChunkHolder {
     public static final ChunkResult<LevelChunk> UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk");
     private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
     private final LevelHeightAccessor levelHeightAccessor;
-    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture;
-    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture;
-    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture;
+    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
+    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
+    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
     public int oldTicketLevel;
     private int ticketLevel;
     private int queueLevel;
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
     private CompletableFuture<?> sendSync;
     private CompletableFuture<?> saveSync;
 
+    private final ChunkMap chunkMap; // Paper
+
+    // Paper start
+    public void onChunkAdd() {
+
+    }
+
+    public void onChunkRemove() {
+
+    }
+    // Paper end
+
     public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
         super(pos);
         this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
         this.queueLevel = this.oldTicketLevel;
         this.setTicketLevel(level);
         this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
+        this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper
     }
 
+    // Paper start
+    public @Nullable ChunkAccess getAvailableChunkNow() {
+        // TODO can we just getStatusFuture(EMPTY)?
+        for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
+            ChunkAccess chunkAccess = this.getChunkIfPresentUnchecked(curr);
+            if (chunkAccess == null) {
+                continue;
+            }
+            return chunkAccess;
+        }
+        return null;
+    }
+    // Paper end
     // CraftBukkit start
     public LevelChunk getFullChunkNow() {
         // Note: We use the oldTicketLevel for isLoaded checks.
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
     }
     // CraftBukkit end
 
-    public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
+    public final CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() { // Paper - final for inline
         return this.tickingChunkFuture;
     }
 
-    public CompletableFuture<ChunkResult<LevelChunk>> getEntityTickingChunkFuture() {
+    public final CompletableFuture<ChunkResult<LevelChunk>> getEntityTickingChunkFuture() { // Paper - final for inline
         return this.entityTickingChunkFuture;
     }
 
-    public CompletableFuture<ChunkResult<LevelChunk>> getFullChunkFuture() {
+    public final CompletableFuture<ChunkResult<LevelChunk>> getFullChunkFuture() { // Paper - final for inline
         return this.fullChunkFuture;
     }
 
     @Nullable
-    public LevelChunk getTickingChunk() {
+    public final LevelChunk getTickingChunk() { // Paper - final for inline
         return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error
     }
 
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
 
     }
 
+    // Paper start
+    public @Nullable ChunkStatus getChunkHolderStatus() {
+        for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
+            ChunkAccess chunkAccess = this.getChunkIfPresentUnchecked(curr);
+            if (chunkAccess == null) {
+                continue;
+            }
+            return curr;
+        }
+
+        return null;
+    }
+    // Paper end
+
     public CompletableFuture<?> getSaveSyncFuture() {
         return this.saveSync;
     }
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
     }
 
     @Override
-    public int getTicketLevel() {
+    public final int getTicketLevel() { // Paper - final for inline
         return this.ticketLevel;
     }
 
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
 
         this.wasAccessibleSinceLastSave |= flag1;
         if (!flag && flag1) {
+            int expectCreateCount = ++this.fullChunkCreateCount; // Paper
             this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this);
             this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL);
+            // Paper start - cache ticking ready status
+            this.fullChunkFuture.thenAccept(chunkResult -> {
+                chunkResult.ifSuccess(chunk -> {
+                    if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
+                        ChunkHolder.this.isFullChunkReady = true;
+                        io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(chunk, this);
+                    }
+                });
+            });
+            // Paper end - cache ticking ready status
             this.addSaveDependency(this.fullChunkFuture);
         }
 
         if (flag && !flag1) {
+            // Paper start
+            if (this.isFullChunkReady) {
+                io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
+            }
+            // Paper end
             this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
             this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
         }
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
         if (!flag2 && flag3) {
             this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this);
             this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
+            // Paper start - cache ticking ready status
+            this.tickingChunkFuture.thenAccept(chunkResult -> {
+                chunkResult.ifSuccess(chunk -> {
+                    // note: Here is a very good place to add callbacks to logic waiting on this.
+                    ChunkHolder.this.isTickingReady = true;
+                    io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(chunk, this);
+                });
+            });
+            // Paper end
             this.addSaveDependency(this.tickingChunkFuture);
         }
 
         if (flag2 && !flag3) {
-            this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+            // Paper start
+            if (this.isTickingReady) {
+                io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
+            }
+            // Paper end
+            this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
             this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
         }
 
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
 
             this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this);
             this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
+            // Paper start - cache ticking ready status
+            this.entityTickingChunkFuture.thenAccept(chunkResult -> {
+                chunkResult.ifSuccess(chunk -> {
+                    ChunkHolder.this.isEntityTickingReady = true;
+                    io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(chunk, this);
+                });
+            });
+            // Paper end
             this.addSaveDependency(this.entityTickingChunkFuture);
         }
 
         if (flag4 && !flag5) {
-            this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+            // Paper start
+            if (this.isEntityTickingReady) {
+                io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
+            }
+            // Paper end
+            this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
             this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
         }
 
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
 
         List<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge);
     }
+
+    // Paper start
+    public final boolean isEntityTickingReady() {
+        return this.isEntityTickingReady;
+    }
+
+    public final boolean isTickingReady() {
+        return this.isTickingReady;
+    }
+
+    public final boolean isFullChunkReady() {
+        return this.isFullChunkReady;
+    }
+    // Paper end
 }
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
     };
     // CraftBukkit end
 
+    // Paper start - distance maps
+    private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+
+    void addPlayerToDistanceMaps(ServerPlayer player) {
+        int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
+        int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
+        // Note: players need to be explicitly added to distance maps before they can be updated
+        this.nearbyPlayers.addPlayer(player);
+    }
+
+    void removePlayerFromDistanceMaps(ServerPlayer player) {
+        int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
+        int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
+        // Note: players need to be explicitly added to distance maps before they can be updated
+        this.nearbyPlayers.removePlayer(player);
+    }
+
+    void updateMaps(ServerPlayer player) {
+        int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
+        int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
+        // Note: players need to be explicitly added to distance maps before they can be updated
+        this.nearbyPlayers.tickPlayer(player);
+    }
+    // Paper end
+    // Paper start
+    public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
+        return this.pendingUnloads.get(io.papermc.paper.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
+    }
+    public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers;
+    // Paper end
+
     public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
         super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
         this.visibleChunkMap = this.updatingChunkMap.clone();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         this.poiManager = new PoiManager(new RegionStorageInfo(session.getLevelId(), world.dimension(), "poi"), path.resolve("poi"), dataFixer, dsync, iregistrycustom, world.getServer(), world);
         this.setServerViewDistance(viewDistance);
         this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, this.mainThreadMailbox);
+        // Paper start
+        this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level);
+        // Paper end
+    }
+
+    // Paper start
+    // always use accessor, so folia can override
+    public final io.papermc.paper.util.player.NearbyPlayers getNearbyPlayers() {
+        return this.nearbyPlayers;
     }
 
+    public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
+        return -1;
+    }
+    // Paper end
+
     protected ChunkGenerator generator() {
         return this.worldGenContext.generator();
     }
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         };
 
         stringbuilder.append("Updating:").append(System.lineSeparator());
-        this.updatingChunkMap.values().forEach(consumer);
+        io.papermc.paper.chunk.system.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
         stringbuilder.append("Visible:").append(System.lineSeparator());
-        this.visibleChunkMap.values().forEach(consumer);
+        io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
         CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
         CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
 
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                     holder.setTicketLevel(level);
                 } else {
                     holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
+                    // Paper start
+                    io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderCreate(this.level, holder);
+                    // Paper end
                 }
 
+                // Paper start
+                holder.onChunkAdd();
+                // Paper end
                 this.updatingChunkMap.put(pos, holder);
                 this.modified = true;
             }
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 
     protected void saveAllChunks(boolean flush) {
         if (flush) {
-            List<ChunkHolder> list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList();
+            List<ChunkHolder> list = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper
             MutableBoolean mutableboolean = new MutableBoolean();
 
             do {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
             });
             this.flushWorker();
         } else {
-            this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded);
+            io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded);
         }
 
     }
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
     }
 
     public boolean hasWork() {
-        return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets();
+        return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || io.papermc.paper.chunk.system.ChunkSystem.hasAnyChunkHolders(this.level) || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper
     }
 
     private void processUnloads(BooleanSupplier shouldKeepTicking) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                 }
 
                 this.updatingChunkMap.remove(j);
+                playerchunk.onChunkRemove(); // Paper
                 this.pendingUnloads.put(j, playerchunk);
                 this.modified = true;
                 ++i;
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         }
 
         int l = 0;
-        ObjectIterator<ChunkHolder> objectiterator = this.visibleChunkMap.values().iterator();
+        Iterator<ChunkHolder> objectiterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
 
         while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) {
             if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
             } else {
                 ChunkAccess ichunkaccess = holder.getLatestChunk();
 
-                if (this.pendingUnloads.remove(pos, holder) && ichunkaccess != null) {
+                // Paper start
+                boolean removed;
+                if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
+                    io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder);
+                    // Paper end
                     LevelChunk chunk;
 
                     if (ichunkaccess instanceof LevelChunk) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                     this.lightEngine.tryScheduleUpdate();
                     this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
                     this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong());
-                }
+                } else if (removed) { // Paper start
+                    io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder);
+                } // Paper end
 
             }
         };
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         }
     }
 
-    protected void setServerViewDistance(int watchDistance) {
+    public void setServerViewDistance(int watchDistance) { // Paper - public
         int j = Mth.clamp(watchDistance, 2, 32);
 
         if (j != this.serverViewDistance) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 
     }
 
-    int getPlayerViewDistance(ServerPlayer player) {
+    public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
         return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
     }
 
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
     }
 
     public int size() {
-        return this.visibleChunkMap.size();
+        return io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
     }
 
     public DistanceManager getDistanceManager() {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
     }
 
     protected Iterable<ChunkHolder> getChunks() {
-        return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
+        return Iterables.unmodifiableIterable(io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
     }
 
     void dumpChunks(Writer writer) throws IOException {
         CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
         TickingTracker tickingtracker = this.distanceManager.tickingTracker();
-        ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
+        Iterator<ChunkHolder> objectbidirectionaliterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
 
         while (objectbidirectionaliterator.hasNext()) {
-            Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
-            long i = entry.getLongKey();
+            ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper
+            long i = playerchunk.pos.toLong(); // Paper
             ChunkPos chunkcoordintpair = new ChunkPos(i);
-            ChunkHolder playerchunk = (ChunkHolder) entry.getValue();
+            // Paper - move up
             Optional<ChunkAccess> optional = Optional.ofNullable(playerchunk.getLatestChunk());
             Optional<LevelChunk> optional1 = optional.flatMap((ichunkaccess) -> {
                 return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 
             player.setChunkTrackingView(ChunkTrackingView.EMPTY);
             this.updateChunkTracking(player);
+            this.addPlayerToDistanceMaps(player); // Paper - distance maps
         } else {
             SectionPos sectionposition = player.getLastSectionPos();
 
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                 this.distanceManager.removePlayer(sectionposition, player);
             }
 
+            this.removePlayerFromDistanceMaps(player); // Paper - distance maps
             this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY);
         }
 
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
             this.updateChunkTracking(player);
         }
 
+        this.updateMaps(player); // Paper - distance maps
     }
 
     private void updateChunkTracking(ServerPlayer player) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         });
     }
 
-    private class ChunkDistanceManager extends DistanceManager {
+    public class ChunkDistanceManager extends DistanceManager { // Paper - public
 
         protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
-            super(workerExecutor, mainThreadExecutor);
+            super(workerExecutor, mainThreadExecutor, ChunkMap.this); // Paper
         }
 
         @Override
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 {
     final Executor mainThreadExecutor;
     private long ticketTickCounter;
     public int simulationDistance = 10;
+    private final ChunkMap chunkMap; // Paper
 
-    protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor) {
+    protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor, ChunkMap chunkMap) {
         Objects.requireNonNull(mainThreadExecutor);
         ProcessorHandle<Runnable> mailbox = ProcessorHandle.of("player ticket throttler", mainThreadExecutor::execute);
         ChunkTaskPriorityQueueSorter chunktaskqueuesorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(mailbox), workerExecutor, 4);
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
         this.ticketThrottlerInput = chunktaskqueuesorter.getProcessor(mailbox, true);
         this.ticketThrottlerReleaser = chunktaskqueuesorter.getReleaseProcessor(mailbox);
         this.mainThreadExecutor = mainThreadExecutor;
+        this.chunkMap = chunkMap; // Paper
     }
 
     protected void purgeStaleTickets() {
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
     }
 
     public void removeTicketsOnClosing() {
-        ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT);
+        ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
         ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
 
         while (objectiterator.hasNext()) {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.storage.LevelStorageSource;
 
 public class ServerChunkCache extends ChunkSource {
 
+    public static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
     private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList();
     private final DistanceManager distanceManager;
     final ServerLevel level;
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
     @Nullable
     @VisibleForDebug
     private NaturalSpawner.SpawnState lastSpawnState;
+    // Paper start
+    public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> tickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+    public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+    private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
+    long chunkFutureAwaitCounter;
+    // Paper end
 
     public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
         this.level = world;
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
         return chunk.getFullChunkNow() != null;
     }
     // CraftBukkit end
+    // Paper start
+    public void addLoadedChunk(LevelChunk chunk) {
+        this.fullChunks.put(chunk.coordinateKey, chunk);
+    }
+
+    public void removeLoadedChunk(LevelChunk chunk) {
+        this.fullChunks.remove(chunk.coordinateKey);
+    }
+
+    @Nullable
+    public ChunkAccess getChunkAtImmediately(int x, int z) {
+        ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
+        if (holder == null) {
+            return null;
+        }
+
+        return holder.getLatestChunk();
+    }
+
+    public <T> void addTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
+        this.distanceManager.addTicket(ticketType, chunkPos, ticketLevel, identifier);
+    }
+
+    public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
+        this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier);
+    }
+
+    // "real" get chunk if loaded
+    // Note: Partially copied from the getChunkAt method below
+    @Nullable
+    public LevelChunk getChunkAtIfCachedImmediately(int x, int z) {
+        long k = ChunkPos.asLong(x, z);
+
+        // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe
+
+        ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k);
+        if (playerChunk == null) {
+            return null;
+        }
+
+        return playerChunk.getFullChunkNowUnchecked();
+    }
+
+    @Nullable
+    public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) {
+        return this.fullChunks.get(ChunkPos.asLong(x, z));
+    }
+    // Paper end
 
     @Override
     public ThreadedLevelLightEngine getLightEngine() {
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
         return this.mainThreadProcessor.pollTask();
     }
 
-    boolean runDistanceManagerUpdates() {
+    public boolean runDistanceManagerUpdates() { // Paper - public
         boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
         boolean flag1 = this.chunkMap.promoteChunkMap();
 
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
         }
     }
 
+    // Paper start
+    public boolean isPositionTicking(Entity entity) {
+        return this.isPositionTicking(ChunkPos.asLong(net.minecraft.util.Mth.floor(entity.getX()) >> 4, net.minecraft.util.Mth.floor(entity.getZ()) >> 4));
+    }
+    // Paper end
+
     public boolean isPositionTicking(long pos) {
         ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
 
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
         return this.convertable.dimensionType;
     }
 
+    // Paper start
+    public final boolean areChunksLoadedForMove(AABB axisalignedbb) {
+        // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override
+        // ICollisionAccess methods for VoxelShapes)
+        // be more strict too, add a block (dumb plugins in move events?)
+        int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
+        int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
+
+        int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
+        int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
+
+        int minChunkX = minBlockX >> 4;
+        int maxChunkX = maxBlockX >> 4;
+
+        int minChunkZ = minBlockZ >> 4;
+        int maxChunkZ = maxBlockZ >> 4;
+
+        ServerChunkCache chunkProvider = this.getChunkSource();
+
+        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
+            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
+                if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority,
+                                             java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
+        if (Thread.currentThread() != this.thread) {
+            this.getChunkSource().mainThreadProcessor.execute(() -> {
+                this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad);
+            });
+            return;
+        }
+        List<net.minecraft.world.level.chunk.ChunkAccess> ret = new java.util.ArrayList<>();
+        it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList();
+
+        int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
+        int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
+
+        int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
+        int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
+
+        int minChunkX = minBlockX >> 4;
+        int maxChunkX = maxBlockX >> 4;
+
+        int minChunkZ = minBlockZ >> 4;
+        int maxChunkZ = maxBlockZ >> 4;
+
+        ServerChunkCache chunkProvider = this.getChunkSource();
+
+        int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
+        int[] loadedChunks = new int[1];
+
+        Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
+
+        java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> {
+            if (chunk != null) {
+                int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel());
+                ret.add(chunk);
+                ticketLevels.add(ticketLevel);
+                chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier);
+            }
+            if (++loadedChunks[0] == requiredChunks) {
+                try {
+                    onLoad.accept(java.util.Collections.unmodifiableList(ret));
+                } finally {
+                    for (int i = 0, len = ret.size(); i < len; ++i) {
+                        ChunkPos chunkPos = ret.get(i).getPos();
+                        int ticketLevel = ticketLevels.getInt(i);
+
+                        chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
+                        chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier);
+                    }
+                }
+            }
+        };
+
+        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
+            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
+                io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(
+                    this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer
+                );
+            }
+        }
+    }
+    // Paper end
+
     // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
     public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
         // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error
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 net.minecraft.world.entity.player.Player {
     public boolean sentListPacket = false;
     public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent
     // CraftBukkit end
+    public boolean isRealPlayer; // Paper
+    public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
 
     public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
         super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
@@ -0,0 +0,0 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
         this.updateOptions(clientOptions);
         this.object = null;
 
+        this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
+
         // CraftBukkit start
         this.displayName = this.getScoreboardName();
         this.bukkitPickUpLoot = true;
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 @@ import net.minecraft.util.Unit;
 import net.minecraft.world.level.ChunkPos;
 
 public class TicketType<T> {
+    public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
 
     private final String name;
     private final Comparator<T> comparator;
diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
@@ -0,0 +0,0 @@ public class WorldGenRegion implements WorldGenLevel {
         return k < this.generatingStep.directDependencies().size();
     }
 
+    // Paper start - if loaded util
+    @Nullable
+    @Override
+    public ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
+        return this.getChunk(x, z, ChunkStatus.FULL, false);
+    }
+
+    @Override
+    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+        ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+        return chunk == null ? null : chunk.getBlockState(blockposition);
+    }
+
+    @Override
+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+        ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+        return chunk == null ? null : chunk.getFluidState(blockposition);
+    }
+    // Paper end
+
     @Override
     public BlockState getBlockState(BlockPos pos) {
         return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos);
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 {
     }
 
     public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
+        player.isRealPlayer = true; // Paper
         GameProfile gameprofile = player.getGameProfile();
         GameProfileCache usercache = this.server.getProfileCache();
         // Optional optional; // CraftBukkit - decompile error
diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
@@ -0,0 +0,0 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
             runnable.run();
         }
     }
+    // Paper start
+    public void scheduleOnMain(Runnable r0) {
+        // postToMainThread does not work the same as older versions of mc
+        // This method is actually used to create a TickTask, which can then be posted onto main
+        this.tell(this.wrapRunnable(r0));
+    }
+    // Paper end
 
     @Override
     public void tell(R runnable) {
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 SyncedDataHolder, Nameable, EntityAccess
         return this.level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4);
     }
     // CraftBukkit end
+    // Paper start
+    public final AABB getBoundingBoxAt(double x, double y, double z) {
+        return this.dimensions.makeBoundingBox(x, y, z);
+    }
+    // Paper end
 
     public Entity(EntityType<?> type, Level world) {
         this.id = Entity.ENTITY_COUNTER.incrementAndGet();
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
     public boolean collides = true;
     public Set<UUID> collidableExemptions = new HashSet<>();
     public boolean bukkitPickUpLoot;
+    public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
 
     @Override
     public float getBukkitYaw() {
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -0,0 +0,0 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
         return this.target;
     }
 
+    public org.bukkit.craftbukkit.entity.CraftMob getBukkitMob() { return (org.bukkit.craftbukkit.entity.CraftMob) super.getBukkitEntity(); } // Paper
+
     @Nullable
     protected final LivingEntity getTargetFromBrain() {
         return (LivingEntity) this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); // CraftBukkit - decompile error
diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java
+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java
@@ -0,0 +0,0 @@ public abstract class PathfinderMob extends Mob {
         super(type, world);
     }
 
+    public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper
+
     public float getWalkTargetValue(BlockPos pos) {
         return this.getWalkTargetValue(pos, this.level());
     }
diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Monster.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.ServerLevelAccessor;
 import net.minecraft.world.level.dimension.DimensionType;
 
 public abstract class Monster extends PathfinderMob implements Enemy {
+    public org.bukkit.craftbukkit.entity.CraftMonster getBukkitMonster() { return (org.bukkit.craftbukkit.entity.CraftMonster) super.getBukkitEntity(); } // Paper
     protected Monster(EntityType<? extends Monster> type, Level world) {
         super(type, world);
         this.xpReward = 5;
diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/item/ItemStack.java
+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
@@ -0,0 +0,0 @@ public final class ItemStack implements DataComponentHolder {
         }
     }
 
+    // Paper start - (this is just a good no conflict location)
+    public org.bukkit.inventory.ItemStack asBukkitMirror() {
+        return CraftItemStack.asCraftMirror(this);
+    }
+    public org.bukkit.inventory.ItemStack asBukkitCopy() {
+        return CraftItemStack.asCraftMirror(this.copy());
+    }
+    public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
+        return CraftItemStack.asNMSCopy(itemstack);
+    }
+    private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
+    public org.bukkit.inventory.ItemStack getBukkitStack() {
+        if (bukkitStack == null || bukkitStack.handle != this) {
+            bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
+        }
+        return bukkitStack;
+    }
+    // Paper end
+
     public void applyComponents(DataComponentPatch changes) {
         this.components.applyPatch(changes);
         this.getItem().verifyComponentsAfterLoad(this);
@@ -0,0 +0,0 @@ public final class ItemStack implements DataComponentHolder {
     // CraftBukkit start
     @Deprecated
     public void setItem(Item item) {
+        this.bukkitStack = null; // Paper
         this.item = item;
     }
     // CraftBukkit end
diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Direction;
 import net.minecraft.util.Mth;
+import net.minecraft.world.level.block.Block;
 import net.minecraft.world.level.block.entity.BlockEntity;
 import net.minecraft.world.level.block.entity.BlockEntityType;
 import net.minecraft.world.level.block.state.BlockState;
@@ -0,0 +0,0 @@ public interface BlockGetter extends LevelHeightAccessor {
     }
 
     BlockState getBlockState(BlockPos pos);
+    // Paper start - if loaded util
+    @Nullable BlockState getBlockStateIfLoaded(BlockPos blockposition);
+
+    default @Nullable Block getBlockIfLoaded(BlockPos blockposition) {
+        BlockState type = this.getBlockStateIfLoaded(blockposition);
+        return type == null ? null : type.getBlock();
+    }
+    @Nullable FluidState getFluidIfLoaded(BlockPos blockposition);
+    // Paper end
 
     FluidState getFluidState(BlockPos pos);
 
diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/ChunkPos.java
+++ b/src/main/java/net/minecraft/world/level/ChunkPos.java
@@ -0,0 +0,0 @@ public class ChunkPos {
     public static final int REGION_MAX_INDEX = 31;
     public final int x;
     public final int z;
+    public final long longKey; // Paper
     private static final int HASH_A = 1664525;
     private static final int HASH_C = 1013904223;
     private static final int HASH_Z_XOR = -559038737;
@@ -0,0 +0,0 @@ public class ChunkPos {
     public ChunkPos(int x, int z) {
         this.x = x;
         this.z = z;
+        this.longKey = asLong(this.x, this.z); // Paper
     }
 
     public ChunkPos(BlockPos pos) {
         this.x = SectionPos.blockToSectionCoord(pos.getX());
         this.z = SectionPos.blockToSectionCoord(pos.getZ());
+        this.longKey = asLong(this.x, this.z); // Paper
     }
 
     public ChunkPos(long pos) {
         this.x = (int)pos;
         this.z = (int)(pos >> 32);
+        this.longKey = asLong(this.x, this.z); // Paper
     }
 
     public static ChunkPos minFromRegion(int x, int z) {
@@ -0,0 +0,0 @@ public class ChunkPos {
     }
 
     public long toLong() {
-        return asLong(this.x, this.z);
+        return longKey; // Paper
     }
 
     public static long asLong(int chunkX, int chunkZ) {
diff --git a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java
@@ -0,0 +0,0 @@ public enum EmptyBlockGetter implements BlockGetter {
         return null;
     }
 
+    // Paper start - If loaded util
+    @Override
+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+        return Fluids.EMPTY.defaultFluidState();
+    }
+
+    @Override
+    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+        return Blocks.AIR.defaultBlockState();
+    }
+    // Paper end
+
     @Override
     public BlockState getBlockState(BlockPos pos) {
         return Blocks.AIR.defaultBlockState();
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.CraftServer;
 import org.bukkit.craftbukkit.CraftWorld;
 import org.bukkit.craftbukkit.SpigotTimings; // Spigot
 import org.bukkit.craftbukkit.block.CapturedBlockState;
+import org.bukkit.craftbukkit.block.CraftBlockState;
 import org.bukkit.craftbukkit.block.data.CraftBlockData;
 import org.bukkit.craftbukkit.util.CraftSpawnCategory;
 import org.bukkit.entity.SpawnCategory;
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
         return null;
     }
 
+    // Paper start
+    public net.minecraft.world.phys.BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, net.minecraft.world.phys.shapes.CollisionContext context) {
+        // To be patched over
+        return this.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, context)).getType();
+    }
+    // Paper end
+
     public boolean isInWorldBounds(BlockPos pos) {
         return !this.isOutsideBuildHeight(pos) && Level.isInWorldBoundsHorizontal(pos);
     }
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
         return y < -20000000 || y >= 20000000;
     }
 
-    public LevelChunk getChunkAt(BlockPos pos) {
+    public final LevelChunk getChunkAt(BlockPos pos) { // Paper - help inline
         return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
     }
 
     @Override
-    public LevelChunk getChunk(int chunkX, int chunkZ) {
-        return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL);
+    public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
+        return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
     }
 
+    // Paper start - if loaded
+    @Nullable
+    @Override
+    public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
+        return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z);
+    }
+
+    @Override
     @Nullable
+    public final BlockState getBlockStateIfLoaded(BlockPos pos) {
+        // CraftBukkit start - tree generation
+        if (this.captureTreeGeneration) {
+            CraftBlockState previous = this.capturedBlockStates.get(pos);
+            if (previous != null) {
+                return previous.getHandle();
+            }
+        }
+        // CraftBukkit end
+        if (this.isOutsideBuildHeight(pos)) {
+            return Blocks.VOID_AIR.defaultBlockState();
+        } else {
+            ChunkAccess chunk = this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);
+
+            return chunk == null ? null : chunk.getBlockState(pos);
+        }
+    }
+
+    @Override
+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+        ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+
+        return chunk == null ? null : chunk.getFluidState(blockposition);
+    }
+
     @Override
-    public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
+    public final ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { // Paper - final for inline
+        // Paper end
         ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create);
 
         if (ichunkaccess == null && create) {
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
     }
 
     @Override
-    public boolean setBlock(BlockPos pos, BlockState state, int flags) {
+    public final boolean setBlock(BlockPos pos, BlockState state, int flags) { // Paper - final for inline
         return this.setBlock(pos, state, flags, 512);
     }
 
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
         if (this.isOutsideBuildHeight(pos)) {
             return Blocks.VOID_AIR.defaultBlockState();
         } else {
-            LevelChunk chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
+            ChunkAccess chunk = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine
 
             return chunk.getBlockState(pos);
         }
diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/LevelReader.java
+++ b/src/main/java/net/minecraft/world/level/LevelReader.java
@@ -0,0 +0,0 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal
     @Nullable
     ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
 
+    @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading)
+    @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);}
+
     @Deprecated
     boolean hasChunk(int chunkX, int chunkZ);
 
diff --git a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java
+++ b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java
@@ -0,0 +0,0 @@ import net.minecraft.core.Holder;
 import net.minecraft.core.SectionPos;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.util.profiling.ProfilerFiller;
+import net.minecraft.server.level.ServerLevel;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.level.biome.Biome;
 import net.minecraft.world.level.biome.Biomes;
@@ -0,0 +0,0 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter {
     private ChunkAccess getChunk(int chunkX, int chunkZ) {
         int i = chunkX - this.centerX;
         int j = chunkZ - this.centerZ;
-        if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) {
+        if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) { // Paper - if this changes, update getChunkIfLoaded below
             ChunkAccess chunkAccess = this.chunks[i][j];
             return (ChunkAccess)(chunkAccess != null ? chunkAccess : new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), this.plains.get()));
         } else {
@@ -0,0 +0,0 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter {
         }
     }
 
+    // Paper start - if loaded util
+    private @Nullable ChunkAccess getChunkIfLoaded(int x, int z) {
+        // Based on getChunk(int, int)
+        int xx = x - this.centerX;
+        int zz = z - this.centerZ;
+
+        if (xx >= 0 && xx < this.chunks.length && zz >= 0 && zz < this.chunks[xx].length) {
+            return this.chunks[xx][zz];
+        }
+        return null;
+    }
+    @Override
+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+        ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+        return chunk == null ? null : chunk.getFluidState(blockposition);
+    }
+
+    @Override
+    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+        ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+        return chunk == null ? null : chunk.getBlockState(blockposition);
+    }
+    // Paper end
+
     @Override
     public WorldBorder getWorldBorder() {
         return this.level.getWorldBorder();
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
             }
         }
 
+        // Paper start
+        protected boolean shapeExceedsCube = true;
+        public final boolean shapeExceedsCube() {
+            return this.shapeExceedsCube;
+        }
+        // Paper end
+
         public void initCache() {
             this.fluidState = ((Block) this.owner).getFluidState(this.asState());
             this.isRandomlyTicking = ((Block) this.owner).isRandomlyTicking(this.asState());
             if (!this.getBlock().hasDynamicShape()) {
                 this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState());
             }
+            this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
 
             this.legacySolid = this.calculateSolid();
         }
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
             return this.getBlock().getOcclusionShape(this.asState(), world, pos);
         }
 
-        public boolean hasLargeCollisionShape() {
-            return this.cache == null || this.cache.largeCollisionShape;
+        public final boolean hasLargeCollisionShape() { // Paper
+            return this.shapeExceedsCube; // Paper - moved into shape cache init
         }
 
         public boolean useShapeForLightOcclusion() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
     protected final ShortList[] postProcessing;
     protected volatile boolean unsaved;
     private volatile boolean isLightCorrect;
-    protected final ChunkPos chunkPos;
+    protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key
     private long inhabitedTime;
     /** @deprecated */
     @Nullable
@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
     // CraftBukkit end
 
     public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
-        this.chunkPos = pos;
+        this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
+        this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
         this.upgradeData = upgradeData;
         this.levelHeightAccessor = heightLimitView;
         this.sections = new LevelChunkSection[heightLimitView.getSectionsCount()];
diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
@@ -0,0 +0,0 @@ public class EmptyLevelChunk extends LevelChunk {
     public BlockState getBlockState(BlockPos pos) {
         return Blocks.VOID_AIR.defaultBlockState();
     }
+    // Paper start
+    @Override
+    public BlockState getBlockState(final int x, final int y, final int z) {
+        return Blocks.VOID_AIR.defaultBlockState();
+    }
+    // Paper end
 
     @Nullable
     @Override
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
     public boolean needsDecoration;
     // CraftBukkit end
 
+    // Paper start
+    boolean loadedTicketLevel;
+    // Paper end
+
     public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
         this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
         Iterator iterator = protoChunk.getBlockEntities().values().iterator();
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
         }
     }
 
+    // Paper start - Perf: Reduce instructions and provide final method
+    public BlockState getBlockState(final int x, final int y, final int z) {
+        return this.getBlockStateFinal(x, y, z);
+    }
+    public BlockState getBlockStateFinal(final int x, final int y, final int z) {
+        // Copied and modified from below
+        final int sectionIndex = this.getSectionIndex(y);
+        if (sectionIndex < 0 || sectionIndex >= this.sections.length
+            || this.sections[sectionIndex].nonEmptyBlockCount == 0) {
+            return Blocks.AIR.defaultBlockState();
+        }
+        return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15);
+    }
     @Override
     public BlockState getBlockState(BlockPos pos) {
+        if (true) {
+            return this.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ());
+        }
+        // Paper end - Perf: Reduce instructions and provide final method
         int i = pos.getX();
         int j = pos.getY();
         int k = pos.getZ();
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
         }
     }
 
+    // Paper start - If loaded util
+    @Override
+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+        return this.getFluidState(blockposition);
+    }
+
+    @Override
+    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+        return this.getBlockState(blockposition);
+    }
+    // Paper end
+
     @Override
     public FluidState getFluidState(BlockPos pos) {
         return this.getFluidState(pos.getX(), pos.getY(), pos.getZ());
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
 
     // CraftBukkit start
     public void loadCallback() {
+        // Paper start
+        this.loadedTicketLevel = true;
+        // Paper end
         org.bukkit.Server server = this.level.getCraftServer();
+        this.level.getChunkSource().addLoadedChunk(this); // Paper
         if (server != null) {
             /*
              * If it's a new world, the first few chunks are generated inside
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
         server.getPluginManager().callEvent(unloadEvent);
         // note: saving can be prevented, but not forced if no saving is actually required
         this.mustNotSave = !unloadEvent.isSaveChunk();
+        this.level.getChunkSource().removeLoadedChunk(this); // Paper
+        // Paper start
+        this.loadedTicketLevel = false;
+        // Paper end
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -0,0 +0,0 @@ public class LevelChunkSection {
     public static final int SECTION_HEIGHT = 16;
     public static final int SECTION_SIZE = 4096;
     public static final int BIOME_CONTAINER_BITS = 2;
-    private short nonEmptyBlockCount;
+    short nonEmptyBlockCount; // Paper - package private
     private short tickingBlockCount;
     private short tickingFluidCount;
     public final PalettedContainer<BlockState> states;
diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
@@ -0,0 +0,0 @@ public class ProtoChunk extends ChunkAccess {
         return new ChunkAccess.TicksToSave(this.blockTicks, this.fluidTicks);
     }
 
+    // Paper start - If loaded util
+    @Override
+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+        return this.getFluidState(blockposition);
+    }
+
+    @Override
+    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+        return this.getBlockState(blockposition);
+    }
+    // Paper end
+
     @Override
     public BlockState getBlockState(BlockPos pos) {
         int i = pos.getY();
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
     }
 
     private boolean addEntity(T entity, boolean existing) {
+        // Paper start - chunk system hooks
+        if (existing) {
+            // I don't want to know why this is a generic type.
+            Entity entityCasted = (Entity)entity;
+            boolean wasRemoved = entityCasted.isRemoved();
+            io.papermc.paper.chunk.system.ChunkSystem.onEntityPreAdd((net.minecraft.server.level.ServerLevel) entityCasted.level(), entityCasted);
+            if (!wasRemoved && entityCasted.isRemoved()) {
+                // removed by callback
+                return false;
+            }
+        }
+        // Paper end - chunk system hooks
         if (!this.addEntityUuid(entity)) {
             return false;
         } else {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
         return this.spigot;
     }
     // Spigot end
+
+    @Override
+    public double[] getTPS() {
+        return new double[]{0, 0, 0}; // TODO
+    }
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public Chunk[] getLoadedChunks() {
-        Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = this.world.getChunkSource().chunkMap.visibleChunkMap;
-        return chunks.values().stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(CraftChunk::new).toArray(Chunk[]::new);
+        List<ChunkHolder> chunks = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.world); // Paper
+        return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(CraftChunk::new).toArray(Chunk[]::new);
     }
 
     @Override
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public boolean refreshChunk(int x, int z) {
-        ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.visibleChunkMap.get(ChunkPos.asLong(x, z));
+        ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
         if (playerChunk == null) return false;
 
         playerChunk.getTickingChunkFuture().thenAccept(either -> {
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
         return this.spigot;
     }
     // Spigot end
+    // Paper start
+    public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(int x, int z, boolean gen, boolean urgent) {
+        if (Bukkit.isPrimaryThread()) {
+            net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z);
+            if (immediate != null) {
+                return java.util.concurrent.CompletableFuture.completedFuture(new CraftChunk(immediate));
+            }
+        }
+
+        ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority;
+        if (urgent) {
+            priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER;
+        } else {
+            priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL;
+        }
+
+        java.util.concurrent.CompletableFuture<Chunk> ret = new java.util.concurrent.CompletableFuture<>();
+
+        io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> {
+            net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
+                net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c;
+                ret.complete(chunk == null ? null : new CraftChunk(chunk));
+            });
+        });
+
+        return ret;
+    }
+
+    @Override
+    public void setViewDistance(final int viewDistance) {
+        if (viewDistance < 2 || viewDistance > 32) {
+            throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+        }
+        this.getHandle().chunkSource.chunkMap.setServerViewDistance(viewDistance);
+    }
+
+    @Override
+    public void setSimulationDistance(final int simulationDistance) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    @Override
+    public int getSendViewDistance() {
+        return this.getViewDistance();
+    }
+
+    @Override
+    public void setSendViewDistance(final int viewDistance) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+    // Paper end
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
         return this.spigot;
     }
     // Spigot end
+
+    @Override
+    public int getViewDistance() {
+        return io.papermc.paper.chunk.system.ChunkSystem.getLoadViewDistance(this.getHandle());
+    }
+
+    @Override
+    public void setViewDistance(final int viewDistance) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    @Override
+    public int getSimulationDistance() {
+        return io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(this.getHandle());
+    }
+
+    @Override
+    public void setSimulationDistance(final int simulationDistance) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    @Override
+    public int getSendViewDistance() {
+        return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(this.getHandle());
+    }
+
+    @Override
+    public void setSendViewDistance(final int viewDistance) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
@@ -0,0 +0,0 @@ import org.bukkit.material.MaterialData;
 @DelegateDeserialization(ItemStack.class)
 public final class CraftItemStack extends ItemStack {
 
+    // Paper start - MC Utils
+    public static net.minecraft.world.item.ItemStack unwrap(ItemStack bukkit) {
+        if (bukkit instanceof CraftItemStack craftItemStack) {
+            return craftItemStack.handle != null ? craftItemStack.handle : net.minecraft.world.item.ItemStack.EMPTY;
+        } else {
+            return asNMSCopy(bukkit);
+        }
+    }
+
+    public static net.minecraft.world.item.ItemStack getOrCloneOnMutation(ItemStack old, ItemStack newInstance) {
+        return old == newInstance ? unwrap(old) : asNMSCopy(newInstance);
+    }
+    // Paper end - MC Utils
+
     public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) {
         if (original instanceof CraftItemStack) {
             CraftItemStack stack = (CraftItemStack) original;
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
@@ -0,0 +0,0 @@ import org.bukkit.scheduler.BukkitWorker;
  */
 public class CraftScheduler implements BukkitScheduler {
 
+    static Plugin MINECRAFT = new MinecraftInternalPlugin();
     /**
      * The start ID for the counter.
      */
@@ -0,0 +0,0 @@ public class CraftScheduler implements BukkitScheduler {
         this.runTaskTimer(plugin, (Object) task, delay, period);
     }
 
+    public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) {
+        final CraftTask task = new CraftTask(run, nextId(), taskName);
+        return handle(task, delay);
+    }
+
     public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) {
         CraftScheduler.validate(plugin, runnable);
         if (delay < 0L) {
@@ -0,0 +0,0 @@ public class CraftScheduler implements BukkitScheduler {
                     task.run();
                     task.timings.stopTiming(); // Spigot
                 } catch (final Throwable throwable) {
-                    task.getOwner().getLogger().log(
+                    // Paper start
+                    String msg = String.format(
+                        "Task #%s for %s generated an exception",
+                        task.getTaskId(),
+                        task.getOwner().getDescription().getFullName());
+                    if (task.getOwner() == MINECRAFT) {
+                        net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
+                    } else {
+                        task.getOwner().getLogger().log(
                             Level.WARNING,
-                            String.format(
-                                "Task #%s for %s generated an exception",
-                                task.getTaskId(),
-                                task.getOwner().getDescription().getFullName()),
+                            msg,
                             throwable);
+                    }
+                    // Paper end
                 } finally {
                     this.currentTask = null;
                 }
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
@@ -0,0 +0,0 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
     CraftTask(final Object task) {
         this(null, task, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING);
     }
+    // Paper start
+    public String taskName = null;
+    boolean internal = false;
+    CraftTask(final Object task, int id, String taskName) {
+        this.rTask = (Runnable) task;
+        this.cTask = null;
+        this.plugin = CraftScheduler.MINECRAFT;
+        this.taskName = taskName;
+        this.internal = true;
+        this.id = id;
+        this.period = CraftTask.NO_REPEATING;
+        this.taskName = taskName;
+        this.timings = null; // Will be changed in later patch
+    }
+    // Paper end
 
     CraftTask(final Plugin plugin, final Object task, final int id, final long period) {
         this.plugin = plugin;
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
@@ -0,0 +0,0 @@
+package org.bukkit.craftbukkit.scheduler;
+
+
+import org.bukkit.Server;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.generator.BiomeProvider;
+import org.bukkit.generator.ChunkGenerator;
+import org.bukkit.plugin.PluginBase;
+import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.plugin.PluginLoader;
+import org.bukkit.plugin.PluginLogger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+
+public class MinecraftInternalPlugin extends PluginBase {
+    private boolean enabled = true;
+
+    private final String pluginName;
+    private PluginDescriptionFile pdf;
+
+    public MinecraftInternalPlugin() {
+        this.pluginName = "Minecraft";
+        pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    public File getDataFolder() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public PluginDescriptionFile getDescription() {
+        return pdf;
+    }
+
+    @Override
+    public FileConfiguration getConfig() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public InputStream getResource(String filename) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void saveConfig() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void saveDefaultConfig() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void saveResource(String resourcePath, boolean replace) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void reloadConfig() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public PluginLogger getLogger() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public PluginLoader getPluginLoader() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Server getServer() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void onDisable() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void onLoad() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void onEnable() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean isNaggable() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void setNaggable(boolean canNag) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
     private static final Map<Item, Material> ITEM_MATERIAL = new HashMap<>();
     private static final Map<Material, Item> MATERIAL_ITEM = new HashMap<>();
     private static final Map<Material, Block> MATERIAL_BLOCK = new HashMap<>();
+    // Paper start
+    private static final Map<org.bukkit.entity.EntityType, net.minecraft.world.entity.EntityType<?>> ENTITY_TYPE_ENTITY_TYPES = new HashMap<>();
+    private static final Map<net.minecraft.world.entity.EntityType<?>, org.bukkit.entity.EntityType> ENTITY_TYPES_ENTITY_TYPE = new HashMap<>();
 
     static {
+        for (org.bukkit.entity.EntityType type : org.bukkit.entity.EntityType.values()) {
+            if (type == org.bukkit.entity.EntityType.UNKNOWN) continue;
+            ENTITY_TYPE_ENTITY_TYPES.put(type, BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(type.getKey())));
+            ENTITY_TYPES_ENTITY_TYPE.put(BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(type.getKey())), type);
+        }
+        // Paper end
         for (Block block : BuiltInRegistries.BLOCK) {
             BLOCK_MATERIAL.put(block, Material.getMaterial(BuiltInRegistries.BLOCK.getKey(block).getPath().toUpperCase(Locale.ROOT)));
         }
@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
     public static ResourceLocation key(Material mat) {
         return CraftNamespacedKey.toMinecraft(mat.getKey());
     }
+    // Paper start
+    public static net.minecraft.world.entity.EntityType<?> getEntityTypes(org.bukkit.entity.EntityType type) {
+        return ENTITY_TYPE_ENTITY_TYPES.get(type);
+    }
+    public static org.bukkit.entity.EntityType getEntityType(net.minecraft.world.entity.EntityType<?> entityTypes) {
+        return ENTITY_TYPES_ENTITY_TYPE.get(entityTypes);
+    }
+    // Paper end
     // ========================================================================
 
     public static byte toLegacyData(BlockState data) {
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
@@ -0,0 +0,0 @@ import net.minecraft.world.phys.shapes.VoxelShape;
 import net.minecraft.world.ticks.LevelTickAccess;
 import net.minecraft.world.ticks.TickPriority;
 import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.jetbrains.annotations.Nullable;
 
 public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
 
@@ -0,0 +0,0 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
     public int getMoonPhase() {
         return this.handle.getMoonPhase();
     }
+
+    // Paper start
+    @Nullable
+    @Override
+    public BlockState getBlockStateIfLoaded(final BlockPos blockposition) {
+        return this.handle.getBlockStateIfLoaded(blockposition);
+    }
+
+    @Nullable
+    @Override
+    public FluidState getFluidIfLoaded(final BlockPos blockposition) {
+        return this.handle.getFluidIfLoaded(blockposition);
+    }
+
+    @Nullable
+    @Override
+    public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) {
+        return this.handle.getChunkIfLoadedImmediately(x, z);
+    }
+    // Paper end
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
@@ -0,0 +0,0 @@ public class DummyGeneratorAccess implements WorldGenLevel {
     public FluidState getFluidState(BlockPos pos) {
         return Fluids.EMPTY.defaultFluidState(); // SPIGOT-6634
     }
+    // Paper start - if loaded util
+    @javax.annotation.Nullable
+    @Override
+    public ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
 
+    @Override
+    public FluidState getFluidIfLoaded(BlockPos blockposition) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+    // Paper end
     @Override
     public WorldBorder getWorldBorder() {
         throw new UnsupportedOperationException("Not supported yet.");
diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
@@ -0,0 +0,0 @@ public class UnsafeList<E> extends AbstractList<E> implements List<E>, RandomAcc
         return this.indexOf(o) >= 0;
     }
 
+    // Paper start
+    protected transient int maxSize;
+    public void setSize(int size) {
+        if (this.maxSize < this.size) {
+            this.maxSize = this.size;
+        }
+        this.size = size;
+    }
+
+    public void completeReset() {
+        if (this.data != null) {
+            Arrays.fill(this.data, 0, Math.max(this.size, this.maxSize), null);
+        }
+        this.size = 0;
+        this.maxSize = 0;
+        if (this.iterPool != null) {
+            for (Iterator temp : this.iterPool) {
+                if (temp == null) {
+                    continue;
+                }
+                ((Itr)temp).valid = false;
+            }
+        }
+    }
+    // Paper end
+
     @Override
     public void clear() {
         // Create new array to reset memory usage to initial capacity
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -0,0 +0,0 @@ public class ActivationRange
 
     public enum ActivationType
     {
+        WATER, // Paper
+        FLYING_MONSTER, // Paper
+        VILLAGER, // Paper
         MONSTER,
         ANIMAL,
         RAIDER,
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -0,0 +0,0 @@ public class SpigotConfig
                 }
             }
         }
-
+        // Paper start
+        SpigotConfig.save();
+    }
+    public static void save() {
+        // Paper end
         try
         {
             SpigotConfig.config.save( SpigotConfig.CONFIG_FILE );
