--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -5,6 +5,7 @@
 import java.io.IOException;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -24,8 +25,10 @@
 import net.minecraft.network.protocol.Packet;
 import net.minecraft.resources.ResourceKey;
 import net.minecraft.resources.ResourceLocation;
+import io.papermc.paper.util.MCUtil;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.level.FullChunkStatus;
+import net.minecraft.server.level.ServerLevel;
 import net.minecraft.sounds.SoundEvent;
 import net.minecraft.sounds.SoundEvents;
 import net.minecraft.sounds.SoundSource;
@@ -41,6 +44,7 @@
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.boss.EnderDragonPart;
 import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.item.ItemEntity;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.alchemy.PotionBrewing;
@@ -54,11 +58,14 @@
 import net.minecraft.world.level.block.entity.BlockEntity;
 import net.minecraft.world.level.block.entity.TickingBlockEntity;
 import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.border.BorderChangeListener;
 import net.minecraft.world.level.border.WorldBorder;
 import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkSource;
 import net.minecraft.world.level.chunk.LevelChunk;
 import net.minecraft.world.level.chunk.status.ChunkStatus;
 import net.minecraft.world.level.dimension.DimensionType;
+import net.minecraft.world.level.dimension.LevelStem;
 import net.minecraft.world.level.entity.EntityTypeTest;
 import net.minecraft.world.level.entity.LevelEntityGetter;
 import net.minecraft.world.level.gameevent.GameEvent;
@@ -76,7 +83,27 @@
 import net.minecraft.world.phys.Vec3;
 import net.minecraft.world.scores.Scoreboard;
 
-public abstract class Level implements LevelAccessor, AutoCloseable {
+// CraftBukkit start
+import java.util.HashMap;
+import java.util.Map;
+import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
+import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.CraftServer;
+import org.bukkit.craftbukkit.CraftWorld;
+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;
+import org.bukkit.event.block.BlockPhysicsEvent;
+// CraftBukkit end
+
+public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel, ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter { // Paper - rewrite chunk system
+
     public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
     public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld"));
     public static final ResourceKey<Level> NETHER = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("the_nether"));
@@ -88,7 +115,7 @@
     public static final int TICKS_PER_DAY = 24000;
     public static final int MAX_ENTITY_SPAWN_Y = 20000000;
     public static final int MIN_ENTITY_SPAWN_Y = -20000000;
-    protected final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList();
+    public final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList(); // Paper - public
     protected final NeighborUpdater neighborUpdater;
     private final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();
     private boolean tickingBlockEntities;
@@ -102,6 +129,7 @@
     protected float oThunderLevel;
     public float thunderLevel;
     public final RandomSource random = RandomSource.create();
+    /** @deprecated */
     @Deprecated
     private final RandomSource threadSafeRandom = RandomSource.createThreadSafe();
     private final Holder<DimensionType> dimensionTypeRegistration;
@@ -115,33 +143,152 @@
     private final DamageSources damageSources;
     private long subTickCount;
 
-    protected Level(
-        WritableLevelData properties,
-        ResourceKey<Level> registryRef,
-        RegistryAccess registryManager,
-        Holder<DimensionType> dimensionEntry,
-        Supplier<ProfilerFiller> profiler,
-        boolean isClient,
-        boolean debugWorld,
-        long biomeAccess,
-        int maxChainedNeighborUpdates
-    ) {
-        this.profiler = profiler;
-        this.levelData = properties;
-        this.dimensionTypeRegistration = dimensionEntry;
-        final DimensionType dimensionType = dimensionEntry.value();
-        this.dimension = registryRef;
-        this.isClientSide = isClient;
-        if (dimensionType.coordinateScale() != 1.0) {
-            this.worldBorder = new WorldBorder() {
+    // CraftBukkit start Added the following
+    private final CraftWorld world;
+    public boolean pvpMode;
+    public org.bukkit.generator.ChunkGenerator generator;
+
+    public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710
+    public boolean captureBlockStates = false;
+    public boolean captureTreeGeneration = false;
+    public boolean isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
+    public Map<BlockPos, org.bukkit.craftbukkit.block.CraftBlockState> capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper
+    public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates
+    public List<ItemEntity> captureDrops;
+    public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
+    // Paper start
+    public int wakeupInactiveRemainingAnimals;
+    public int wakeupInactiveRemainingFlying;
+    public int wakeupInactiveRemainingMonsters;
+    public int wakeupInactiveRemainingVillagers;
+    // Paper end
+    public boolean populating;
+    public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
+    // Paper start - add paper world config
+    private final io.papermc.paper.configuration.WorldConfiguration paperConfig;
+    public io.papermc.paper.configuration.WorldConfiguration paperConfig() {
+        return this.paperConfig;
+    }
+    // Paper end - add paper world config
+
+    public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
+    public final co.aikar.timings.WorldTimingsHandler timings; // Paper
+    public static BlockPos lastPhysicsProblem; // Spigot
+    private org.spigotmc.TickLimiter entityLimiter;
+    private org.spigotmc.TickLimiter tileLimiter;
+    private int tileTickPosition;
+    public final Map<Explosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
+    public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
+
+    public CraftWorld getWorld() {
+        return this.world;
+    }
+
+    public CraftServer getCraftServer() {
+        return (CraftServer) Bukkit.getServer();
+    }
+
+    // Paper start - Use getChunkIfLoadedImmediately
+    @Override
+    public boolean hasChunk(int chunkX, int chunkZ) {
+        return this.getChunkIfLoaded(chunkX, chunkZ) != null;
+    }
+    // Paper end - Use getChunkIfLoadedImmediately
+    // Paper start - per world ticks per spawn
+    private int getTicksPerSpawn(SpawnCategory spawnCategory) {
+        final int perWorld = this.paperConfig().entities.spawning.ticksPerSpawn.getInt(CraftSpawnCategory.toNMS(spawnCategory));
+        if (perWorld >= 0) {
+            return perWorld;
+        }
+        return this.getCraftServer().getTicksPerSpawns(spawnCategory);
+    }
+    // Paper end
+
+    public abstract ResourceKey<LevelStem> getTypeKey();
+
+    // Paper start - rewrite chunk system
+    private ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup entityLookup;
+
+    @Override
+    public final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup moonrise$getEntityLookup() {
+        return this.entityLookup;
+    }
+
+    @Override
+    public void moonrise$setEntityLookup(final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup entityLookup) {
+        if (this.entityLookup != null && !(this.entityLookup instanceof ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup)) {
+            throw new IllegalStateException("Entity lookup already initialised");
+        }
+        this.entityLookup = entityLookup;
+    }
+
+    @Override
+    public final <T extends Entity> List<T> getEntitiesOfClass(final Class<T> entityClass, final AABB boundingBox, final Predicate<? super T> predicate) {
+        this.getProfiler().incrementCounter("getEntities");
+        final List<T> ret = new java.util.ArrayList<>();
+
+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entityClass, null, boundingBox, ret, predicate);
+
+        return ret;
+    }
+
+    @Override
+    public final List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
+        this.getProfiler().incrementCounter("getEntities");
+        final List<Entity> ret = new java.util.ArrayList<>();
+
+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getHardCollidingEntities(entity, box, ret, predicate);
+
+        return ret;
+    }
+
+    @Override
+    public LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) {
+        return this.getChunkSource().getChunk(chunkX, chunkZ, false);
+    }
+
+    @Override
+    public ChunkAccess moonrise$getAnyChunkIfLoaded(final int chunkX, final int chunkZ) {
+        return this.getChunkSource().getChunk(chunkX, chunkZ, ChunkStatus.EMPTY, false);
+    }
+
+    @Override
+    public ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus) {
+        return this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, false);
+    }
+    // Paper end - rewrite chunk system
+
+    protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config & Anti-Xray
+        this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
+        this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+        this.generator = gen;
+        this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
+
+        // CraftBukkit Ticks things
+        for (SpawnCategory spawnCategory : SpawnCategory.values()) {
+            if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
+                this.ticksPerSpawnCategory.put(spawnCategory, this.getTicksPerSpawn(spawnCategory)); // Paper
+            }
+        }
+
+        // CraftBukkit end
+        this.profiler = supplier;
+        this.levelData = worlddatamutable;
+        this.dimensionTypeRegistration = holder;
+        final DimensionType dimensionmanager = (DimensionType) holder.value();
+
+        this.dimension = resourcekey;
+        this.isClientSide = flag;
+        if (dimensionmanager.coordinateScale() != 1.0D) {
+            this.worldBorder = new WorldBorder() { // CraftBukkit - decompile error
                 @Override
                 public double getCenterX() {
-                    return super.getCenterX() / dimensionType.coordinateScale();
+                    return super.getCenterX(); // CraftBukkit
                 }
 
                 @Override
                 public double getCenterZ() {
-                    return super.getCenterZ() / dimensionType.coordinateScale();
+                    return super.getCenterZ(); // CraftBukkit
                 }
             };
         } else {
@@ -149,13 +296,93 @@
         }
 
         this.thread = Thread.currentThread();
-        this.biomeManager = new BiomeManager(this, biomeAccess);
-        this.isDebug = debugWorld;
-        this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates);
-        this.registryAccess = registryManager;
-        this.damageSources = new DamageSources(registryManager);
+        this.biomeManager = new BiomeManager(this, i);
+        this.isDebug = flag1;
+        this.neighborUpdater = new CollectingNeighborUpdater(this, j);
+        this.registryAccess = iregistrycustom;
+        this.damageSources = new DamageSources(iregistrycustom);
+        // CraftBukkit start
+        this.getWorldBorder().world = (ServerLevel) this;
+        // From PlayerList.setPlayerFileData
+        this.getWorldBorder().addListener(new BorderChangeListener() {
+            @Override
+            public void onBorderSizeSet(WorldBorder border, double size) {
+                Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderSizePacket(border), border.world);
+            }
+
+            @Override
+            public void onBorderSizeLerping(WorldBorder border, double fromSize, double toSize, long time) {
+                Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world);
+            }
+
+            @Override
+            public void onBorderCenterSet(WorldBorder border, double centerX, double centerZ) {
+                Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world);
+            }
+
+            @Override
+            public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
+                Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world);
+            }
+
+            @Override
+            public void onBorderSetWarningBlocks(WorldBorder border, int warningBlockDistance) {
+                Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world);
+            }
+
+            @Override
+            public void onBorderSetDamagePerBlock(WorldBorder border, double damagePerBlock) {}
+
+            @Override
+            public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {}
+        });
+        // CraftBukkit end
+        this.timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings
+        this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
+        this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
+        this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system
+        this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
     }
 
+    // Paper start - Cancel hit for vanished players
+    // ret true if no collision
+    public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision,
+                                              BlockPos position, boolean checkCanSee) {
+        // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape)
+        net.minecraft.world.phys.shapes.VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision);
+        if (voxelshape.isEmpty()) {
+            return true;
+        }
+
+        voxelshape = voxelshape.move((double) position.getX(), (double) position.getY(), (double) position.getZ());
+        if (voxelshape.isEmpty()) {
+            return true;
+        }
+
+        List<Entity> entities = this.getEntities(null, voxelshape.bounds());
+        for (int i = 0, len = entities.size(); i < len; ++i) {
+            Entity entity = entities.get(i);
+
+            if (checkCanSee && source instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer
+                && !((net.minecraft.server.level.ServerPlayer) source).getBukkitEntity().canSee(((net.minecraft.server.level.ServerPlayer) entity).getBukkitEntity())) {
+                continue;
+            }
+
+            // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity));
+            // elide the last check since vanilla calls with entity = null
+            // only we care about the source for the canSee check
+            if (entity.isRemoved() || !entity.blocksBuilding) {
+                continue;
+            }
+
+            if (net.minecraft.world.phys.shapes.Shapes.joinIsNotEmpty(voxelshape, net.minecraft.world.phys.shapes.Shapes.create(entity.getBoundingBox()), net.minecraft.world.phys.shapes.BooleanOp.AND)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+    // Paper end - Cancel hit for vanished players
     @Override
     public boolean isClientSide() {
         return this.isClientSide;
@@ -167,130 +394,397 @@
         return null;
     }
 
+    // Paper start - Broken down method of raytracing for EntityLiving#hasLineOfSight, replaces BlockGetter#clip(CollisionContext)
+    public net.minecraft.world.phys.BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, net.minecraft.world.phys.shapes.CollisionContext context) {
+        // most of this code comes from BlockGetter#clip(CollisionContext, BiFunction, Function), but removes the needless functions
+        if (start.equals(end)) {
+            return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+        }
+
+        final double endX = Mth.lerp(-1.0E-7D, end.x, start.x);
+        final double endY = Mth.lerp(-1.0E-7D, end.y, start.y);
+        final double endZ = Mth.lerp(-1.0E-7D, end.z, start.z);
+
+        final double startX = Mth.lerp(-1.0E-7D, start.x, end.x);
+        final double startY = Mth.lerp(-1.0E-7D, start.y, end.y);
+        final double startZ = Mth.lerp(-1.0E-7D, start.z, end.z);
+
+        int currentX = Mth.floor(startX);
+        int currentY = Mth.floor(startY);
+        int currentZ = Mth.floor(startZ);
+
+        final BlockPos.MutableBlockPos currentBlock = new BlockPos.MutableBlockPos(currentX, currentY, currentZ);
+
+        LevelChunk chunk = this.getChunkIfLoaded(currentBlock);
+        if (chunk == null) {
+            return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+        }
+
+        final net.minecraft.world.phys.BlockHitResult.Type initialCheck = this.clipDirect(start, end, currentBlock, chunk.getBlockState(currentBlock), context);
+        if (initialCheck != null) {
+            return initialCheck;
+        }
+
+        final double diffX = endX - startX;
+        final double diffY = endY - startY;
+        final double diffZ = endZ - startZ;
+
+        final int xDirection = Mth.sign(diffX);
+        final int yDirection = Mth.sign(diffY);
+        final int zDirection = Mth.sign(diffZ);
+
+        final double normalizedX = xDirection == 0 ? Double.MAX_VALUE : (double) xDirection / diffX;
+        final double normalizedY = yDirection == 0 ? Double.MAX_VALUE : (double) yDirection / diffY;
+        final double normalizedZ = zDirection == 0 ? Double.MAX_VALUE : (double) zDirection / diffZ;
+
+        double normalizedXDirection = normalizedX * (xDirection > 0 ? 1.0D - Mth.frac(startX) : Mth.frac(startX));
+        double normalizedYDirection = normalizedY * (yDirection > 0 ? 1.0D - Mth.frac(startY) : Mth.frac(startY));
+        double normalizedZDirection = normalizedZ * (zDirection > 0 ? 1.0D - Mth.frac(startZ) : Mth.frac(startZ));
+
+        net.minecraft.world.phys.BlockHitResult.Type result;
+
+        do {
+            if (normalizedXDirection > 1.0D && normalizedYDirection > 1.0D && normalizedZDirection > 1.0D) {
+                return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+            }
+
+            if (normalizedXDirection < normalizedYDirection) {
+                if (normalizedXDirection < normalizedZDirection) {
+                    currentX += xDirection;
+                    normalizedXDirection += normalizedX;
+                } else {
+                    currentZ += zDirection;
+                    normalizedZDirection += normalizedZ;
+                }
+            } else if (normalizedYDirection < normalizedZDirection) {
+                currentY += yDirection;
+                normalizedYDirection += normalizedY;
+            } else {
+                currentZ += zDirection;
+                normalizedZDirection += normalizedZ;
+            }
+
+            currentBlock.set(currentX, currentY, currentZ);
+            if (chunk.getPos().x != currentBlock.getX() >> 4 || chunk.getPos().z != currentBlock.getZ() >> 4) {
+                chunk = this.getChunkIfLoaded(currentBlock);
+                if (chunk == null) {
+                    return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+                }
+            }
+            result = this.clipDirect(start, end, currentBlock, chunk.getBlockState(currentBlock), context);
+        } while (result == null);
+
+        return result;
+    }
+    // Paper end
+
     public boolean isInWorldBounds(BlockPos pos) {
-        return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
+        return pos.isInsideBuildHeightAndWorldBoundsHorizontal(this); // Paper - use better/optimized check
     }
 
     public static boolean isInSpawnableBounds(BlockPos pos) {
-        return !isOutsideSpawnableHeight(pos.getY()) && isInWorldBoundsHorizontal(pos);
+        return !Level.isOutsideSpawnableHeight(pos.getY()) && Level.isInWorldBoundsHorizontal(pos);
     }
 
     private static boolean isInWorldBoundsHorizontal(BlockPos pos) {
-        return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000;
+        return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk()
     }
 
     private static boolean isOutsideSpawnableHeight(int y) {
         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 i, int j) {
-        return (LevelChunk)this.getChunk(i, j, ChunkStatus.FULL);
+    public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
+        // Paper start - Perf: make sure loaded chunks get the inlined variant of this function
+        net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource();
+        LevelChunk ifLoaded = cps.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
+        if (ifLoaded != null) {
+            return ifLoaded;
+        }
+        return (LevelChunk) cps.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
+        // Paper end - Perf: make sure loaded chunks get the inlined variant of this function
     }
 
+    // Paper start - if loaded
     @Nullable
     @Override
-    public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
-        ChunkAccess chunkAccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create);
-        if (chunkAccess == null && create) {
+    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 final boolean hasChunkAt(BlockPos pos) {
+        return getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4) != null; // Paper - Perf: Optimize Level.hasChunkAt(BlockPosition)Z
+    }
+
+    public final boolean isLoadedAndInBounds(BlockPos blockposition) { // Paper - final for inline
+        return getWorldBorder().isWithinBounds(blockposition) && getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null;
+    }
+
+    public @Nullable LevelChunk getChunkIfLoaded(int x, int z) { // Overridden in WorldServer for ABI compat which has final
+        return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(x, z);
+    }
+    public final @Nullable LevelChunk getChunkIfLoaded(BlockPos blockposition) {
+        return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+    }
+
+    //  reduces need to do isLoaded before getType
+    public final @Nullable BlockState getBlockStateIfLoadedAndInBounds(BlockPos blockposition) {
+        return getWorldBorder().isWithinBounds(blockposition) ? getBlockStateIfLoaded(blockposition) : null;
+    }
+
+    @Override
+    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) {
             throw new IllegalStateException("Should always be able to create a chunk!");
         } else {
-            return chunkAccess;
+            return ichunkaccess;
         }
     }
 
     @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);
     }
 
     @Override
     public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
+        // CraftBukkit start - tree generation
+        if (this.captureTreeGeneration) {
+            // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
+            BlockState type = getBlockState(pos);
+            if (!type.isDestroyable()) return false;
+            // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
+            CraftBlockState blockstate = this.capturedBlockStates.get(pos);
+            if (blockstate == null) {
+                blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags);
+                this.capturedBlockStates.put(pos.immutable(), blockstate);
+            }
+            blockstate.setData(state);
+            blockstate.setFlag(flags);
+            return true;
+        }
+        // CraftBukkit end
         if (this.isOutsideBuildHeight(pos)) {
             return false;
         } else if (!this.isClientSide && this.isDebug()) {
             return false;
         } else {
-            LevelChunk levelChunk = this.getChunkAt(pos);
+            LevelChunk chunk = this.getChunkAt(pos);
             Block block = state.getBlock();
-            BlockState blockState = levelChunk.setBlockState(pos, state, (flags & 64) != 0);
-            if (blockState == null) {
+
+            // CraftBukkit start - capture blockstates
+            boolean captured = false;
+            if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) {
+                CraftBlockState blockstate = (CraftBlockState) world.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot
+                blockstate.setFlag(flags); // Paper - set flag
+                this.capturedBlockStates.put(pos.immutable(), blockstate);
+                captured = true;
+            }
+            // CraftBukkit end
+
+            BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
+            this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray
+
+            if (iblockdata1 == null) {
+                // CraftBukkit start - remove blockstate if failed (or the same)
+                if (this.captureBlockStates && captured) {
+                    this.capturedBlockStates.remove(pos);
+                }
+                // CraftBukkit end
                 return false;
             } else {
-                BlockState blockState2 = this.getBlockState(pos);
-                if (blockState2 == state) {
-                    if (blockState != blockState2) {
-                        this.setBlocksDirty(pos, blockState, blockState2);
+                BlockState iblockdata2 = this.getBlockState(pos);
+
+                /*
+                if (iblockdata2 == iblockdata) {
+                    if (iblockdata1 != iblockdata2) {
+                        this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
                     }
 
-                    if ((flags & 2) != 0
-                        && (!this.isClientSide || (flags & 4) == 0)
-                        && (this.isClientSide || levelChunk.getFullStatus() != null && levelChunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) {
-                        this.sendBlockUpdated(pos, blockState, state, flags);
+                    if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) {
+                        this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
                     }
 
-                    if ((flags & 1) != 0) {
-                        this.blockUpdated(pos, blockState.getBlock());
-                        if (!this.isClientSide && state.hasAnalogOutputSignal()) {
-                            this.updateNeighbourForOutputSignal(pos, block);
+                    if ((i & 1) != 0) {
+                        this.blockUpdated(blockposition, iblockdata1.getBlock());
+                        if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) {
+                            this.updateNeighbourForOutputSignal(blockposition, block);
                         }
                     }
 
-                    if ((flags & 16) == 0 && maxUpdateDepth > 0) {
-                        int i = flags & -34;
-                        blockState.updateIndirectNeighbourShapes(this, pos, i, maxUpdateDepth - 1);
-                        state.updateNeighbourShapes(this, pos, i, maxUpdateDepth - 1);
-                        state.updateIndirectNeighbourShapes(this, pos, i, maxUpdateDepth - 1);
+                    if ((i & 16) == 0 && j > 0) {
+                        int k = i & -34;
+
+                        iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
+                        iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
+                        iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
                     }
 
-                    this.onBlockStateChange(pos, blockState, blockState2);
+                    this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
+                }
+                */
+
+                // CraftBukkit start
+                if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates
+                    // Modularize client and physic updates
+                    // Spigot start
+                    try {
+                        this.notifyAndUpdatePhysics(pos, chunk, iblockdata1, state, iblockdata2, flags, maxUpdateDepth);
+                    } catch (StackOverflowError ex) {
+                        Level.lastPhysicsProblem = new BlockPos(pos);
+                    }
+                    // Spigot end
                 }
+                // CraftBukkit end
 
                 return true;
             }
         }
     }
 
-    public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {
+    // CraftBukkit start - Split off from above in order to directly send client and physic updates
+    public void notifyAndUpdatePhysics(BlockPos blockposition, LevelChunk chunk, BlockState oldBlock, BlockState newBlock, BlockState actualBlock, int i, int j) {
+        BlockState iblockdata = newBlock;
+        BlockState iblockdata1 = oldBlock;
+        BlockState iblockdata2 = actualBlock;
+        if (iblockdata2 == iblockdata) {
+            if (iblockdata1 != iblockdata2) {
+                this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
+            }
+
+            if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.FULL)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - rewrite chunk system - change from ticking to full
+                this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
+            }
+
+            if ((i & 1) != 0) {
+                this.blockUpdated(blockposition, iblockdata1.getBlock());
+                if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) {
+                    this.updateNeighbourForOutputSignal(blockposition, newBlock.getBlock());
+                }
+            }
+
+            if ((i & 16) == 0 && j > 0) {
+                int k = i & -34;
+
+                // CraftBukkit start
+                iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam
+                CraftWorld world = ((ServerLevel) this).getWorld();
+                boolean cancelledUpdates = false; // Paper - Fix block place logic
+                if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent
+                    BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata));
+                    this.getCraftServer().getPluginManager().callEvent(event);
+
+                    cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic
+                }
+                // CraftBukkit end
+                if (!cancelledUpdates) { // Paper - Fix block place logic
+                iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
+                iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
+                } // Paper - Fix block place logic
+            }
+
+            // CraftBukkit start - SPIGOT-5710
+            if (!this.preventPoiUpdated) {
+                this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
+            }
+            // CraftBukkit end
+        }
     }
+    // CraftBukkit end
+
+    public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {}
 
     @Override
     public boolean removeBlock(BlockPos pos, boolean move) {
-        FluidState fluidState = this.getFluidState(pos);
-        return this.setBlock(pos, fluidState.createLegacyBlock(), 3 | (move ? 64 : 0));
+        FluidState fluid = this.getFluidState(pos);
+
+        return this.setBlock(pos, fluid.createLegacyBlock(), 3 | (move ? 64 : 0));
     }
 
     @Override
     public boolean destroyBlock(BlockPos pos, boolean drop, @Nullable Entity breakingEntity, int maxUpdateDepth) {
-        BlockState blockState = this.getBlockState(pos);
-        if (blockState.isAir()) {
+        BlockState iblockdata = this.getBlockState(pos);
+
+        if (iblockdata.isAir()) {
             return false;
         } else {
-            FluidState fluidState = this.getFluidState(pos);
-            if (!(blockState.getBlock() instanceof BaseFireBlock)) {
-                this.levelEvent(2001, pos, Block.getId(blockState));
+            FluidState fluid = this.getFluidState(pos);
+            // Paper start - BlockDestroyEvent; while the above setAir method is named same and looks very similar
+            // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent,
+            // it doesn't imply destruction of a block that plays a sound effect / drops an item.
+            boolean playEffect = true;
+            BlockState effectType = iblockdata;
+            int xp = iblockdata.getBlock().getExpDrop(iblockdata, (ServerLevel) this, pos, ItemStack.EMPTY, true);
+            if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) {
+                com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), fluid.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, drop);
+                if (!event.callEvent()) {
+                    return false;
+                }
+                effectType = ((CraftBlockData) event.getEffectBlock()).getState();
+                playEffect = event.playEffect();
+                drop = event.willDrop();
+                xp = event.getExpToDrop();
+            }
+            // Paper end - BlockDestroyEvent
+
+            if (playEffect && !(effectType.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent
+                this.levelEvent(2001, pos, Block.getId(effectType)); // Paper - BlockDestroyEvent
             }
 
             if (drop) {
-                BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null;
-                Block.dropResources(blockState, this, pos, blockEntity, breakingEntity, ItemStack.EMPTY);
+                BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
+
+                Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
+                iblockdata.getBlock().popExperience((ServerLevel) this, pos, xp, breakingEntity); // Paper - Properly handle xp dropping; custom amount
             }
 
-            boolean bl = this.setBlock(pos, fluidState.createLegacyBlock(), 3, maxUpdateDepth);
-            if (bl) {
-                this.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(breakingEntity, blockState));
+            boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth);
+
+            if (flag1) {
+                this.gameEvent((Holder) GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(breakingEntity, iblockdata));
             }
 
-            return bl;
+            return flag1;
         }
     }
 
-    public void addDestroyBlockEffect(BlockPos pos, BlockState state) {
-    }
+    public void addDestroyBlockEffect(BlockPos pos, BlockState state) {}
 
     public boolean setBlockAndUpdate(BlockPos pos, BlockState state) {
         return this.setBlock(pos, state, 3);
@@ -298,20 +792,15 @@
 
     public abstract void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags);
 
-    public void setBlocksDirty(BlockPos pos, BlockState old, BlockState updated) {
-    }
+    public void setBlocksDirty(BlockPos pos, BlockState old, BlockState updated) {}
 
-    public void updateNeighborsAt(BlockPos pos, Block sourceBlock) {
-    }
+    public void updateNeighborsAt(BlockPos pos, Block sourceBlock) {}
 
-    public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block sourceBlock, Direction direction) {
-    }
+    public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block sourceBlock, Direction direction) {}
 
-    public void neighborChanged(BlockPos pos, Block sourceBlock, BlockPos sourcePos) {
-    }
+    public void neighborChanged(BlockPos pos, Block sourceBlock, BlockPos sourcePos) {}
 
-    public void neighborChanged(BlockState state, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {
-    }
+    public void neighborChanged(BlockState state, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {}
 
     @Override
     public void neighborShapeChanged(Direction direction, BlockState neighborState, BlockPos pos, BlockPos neighborPos, int flags, int maxUpdateDepth) {
@@ -320,18 +809,19 @@
 
     @Override
     public int getHeight(Heightmap.Types heightmap, int x, int z) {
-        int j;
+        int k;
+
         if (x >= -30000000 && z >= -30000000 && x < 30000000 && z < 30000000) {
             if (this.hasChunk(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z))) {
-                j = this.getChunk(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z)).getHeight(heightmap, x & 15, z & 15) + 1;
+                k = this.getChunk(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z)).getHeight(heightmap, x & 15, z & 15) + 1;
             } else {
-                j = this.getMinBuildHeight();
+                k = this.getMinBuildHeight();
             }
         } else {
-            j = this.getSeaLevel() + 1;
+            k = this.getSeaLevel() + 1;
         }
 
-        return j;
+        return k;
     }
 
     @Override
@@ -341,11 +831,20 @@
 
     @Override
     public BlockState getBlockState(BlockPos pos) {
+        // CraftBukkit start - tree generation
+        if (this.captureTreeGeneration) {
+            CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper
+            if (previous != null) {
+                return previous.getHandle();
+            }
+        }
+        // CraftBukkit end
         if (this.isOutsideBuildHeight(pos)) {
             return Blocks.VOID_AIR.defaultBlockState();
         } else {
-            LevelChunk levelChunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
-            return levelChunk.getBlockState(pos);
+            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);
         }
     }
 
@@ -354,8 +853,9 @@
         if (this.isOutsideBuildHeight(pos)) {
             return Fluids.EMPTY.defaultFluidState();
         } else {
-            LevelChunk levelChunk = this.getChunkAt(pos);
-            return levelChunk.getFluidState(pos);
+            LevelChunk chunk = this.getChunkAt(pos);
+
+            return chunk.getFluidState(pos);
         }
     }
 
@@ -368,27 +868,29 @@
     }
 
     public void playSound(@Nullable Entity source, BlockPos pos, SoundEvent sound, SoundSource category, float volume, float pitch) {
-        this.playSound(source instanceof Player player ? player : null, pos, sound, category, volume, pitch);
+        Player entityhuman;
+
+        if (source instanceof Player entityhuman1) {
+            entityhuman = entityhuman1;
+        } else {
+            entityhuman = null;
+        }
+
+        this.playSound(entityhuman, pos, sound, category, volume, pitch);
     }
 
     @Override
     public void playSound(@Nullable Player source, BlockPos pos, SoundEvent sound, SoundSource category, float volume, float pitch) {
-        this.playSound(source, (double)pos.getX() + 0.5, (double)pos.getY() + 0.5, (double)pos.getZ() + 0.5, sound, category, volume, pitch);
+        this.playSound(source, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, sound, category, volume, pitch);
     }
 
-    public abstract void playSeededSound(
-        @Nullable Player source, double x, double y, double z, Holder<SoundEvent> sound, SoundSource category, float volume, float pitch, long seed
-    );
+    public abstract void playSeededSound(@Nullable Player source, double x, double y, double z, Holder<SoundEvent> sound, SoundSource category, float volume, float pitch, long seed);
 
-    public void playSeededSound(
-        @Nullable Player source, double x, double y, double z, SoundEvent sound, SoundSource category, float volume, float pitch, long seed
-    ) {
+    public void playSeededSound(@Nullable Player source, double x, double y, double z, SoundEvent sound, SoundSource category, float volume, float pitch, long seed) {
         this.playSeededSound(source, x, y, z, BuiltInRegistries.SOUND_EVENT.wrapAsHolder(sound), category, volume, pitch, seed);
     }
 
-    public abstract void playSeededSound(
-        @Nullable Player source, Entity entity, Holder<SoundEvent> sound, SoundSource category, float volume, float pitch, long seed
-    );
+    public abstract void playSeededSound(@Nullable Player source, Entity entity, Holder<SoundEvent> sound, SoundSource category, float volume, float pitch, long seed);
 
     public void playSound(@Nullable Player source, double x, double y, double z, SoundEvent sound, SoundSource category) {
         this.playSound(source, x, y, z, sound, category, 1.0F, 1.0F);
@@ -407,33 +909,26 @@
     }
 
     public void playLocalSound(BlockPos pos, SoundEvent sound, SoundSource category, float volume, float pitch, boolean useDistance) {
-        this.playLocalSound((double)pos.getX() + 0.5, (double)pos.getY() + 0.5, (double)pos.getZ() + 0.5, sound, category, volume, pitch, useDistance);
+        this.playLocalSound((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, sound, category, volume, pitch, useDistance);
     }
 
-    public void playLocalSound(Entity entity, SoundEvent sound, SoundSource category, float volume, float pitch) {
-    }
+    public void playLocalSound(Entity entity, SoundEvent sound, SoundSource category, float volume, float pitch) {}
 
-    public void playLocalSound(double x, double y, double z, SoundEvent sound, SoundSource category, float volume, float pitch, boolean useDistance) {
-    }
+    public void playLocalSound(double x, double y, double z, SoundEvent sound, SoundSource category, float volume, float pitch, boolean useDistance) {}
 
     @Override
-    public void addParticle(ParticleOptions parameters, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
-    }
+    public void addParticle(ParticleOptions parameters, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {}
 
-    public void addParticle(ParticleOptions parameters, boolean alwaysSpawn, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
-    }
+    public void addParticle(ParticleOptions parameters, boolean alwaysSpawn, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {}
 
-    public void addAlwaysVisibleParticle(ParticleOptions parameters, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
-    }
+    public void addAlwaysVisibleParticle(ParticleOptions parameters, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {}
 
-    public void addAlwaysVisibleParticle(
-        ParticleOptions parameters, boolean alwaysSpawn, double x, double y, double z, double velocityX, double velocityY, double velocityZ
-    ) {
-    }
+    public void addAlwaysVisibleParticle(ParticleOptions parameters, boolean alwaysSpawn, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {}
 
     public float getSunAngle(float tickDelta) {
-        float f = this.getTimeOfDay(tickDelta);
-        return f * (float) (Math.PI * 2);
+        float f1 = this.getTimeOfDay(tickDelta);
+
+        return f1 * 6.2831855F;
     }
 
     public void addBlockEntityTicker(TickingBlockEntity ticker) {
@@ -441,40 +936,68 @@
     }
 
     protected void tickBlockEntities() {
-        ProfilerFiller profilerFiller = this.getProfiler();
-        profilerFiller.push("blockEntities");
+        ProfilerFiller gameprofilerfiller = this.getProfiler();
+
+        gameprofilerfiller.push("blockEntities");
+        this.timings.tileEntityPending.startTiming(); // Spigot
         this.tickingBlockEntities = true;
         if (!this.pendingBlockEntityTickers.isEmpty()) {
             this.blockEntityTickers.addAll(this.pendingBlockEntityTickers);
             this.pendingBlockEntityTickers.clear();
         }
+        this.timings.tileEntityPending.stopTiming(); // Spigot
 
-        Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
-        boolean bl = this.tickRateManager().runsNormally();
+        this.timings.tileEntityTick.startTiming(); // Spigot
+        // Spigot start
+        // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
+        boolean flag = this.tickRateManager().runsNormally();
 
-        while (iterator.hasNext()) {
-            TickingBlockEntity tickingBlockEntity = iterator.next();
-            if (tickingBlockEntity.isRemoved()) {
-                iterator.remove();
-            } else if (bl && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
-                tickingBlockEntity.tick();
+        int tilesThisCycle = 0;
+        var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
+        toRemove.add(null); // Paper - Fix MC-117075
+        for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters
+            this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0;
+            TickingBlockEntity tickingblockentity = (TickingBlockEntity) this.blockEntityTickers.get(this.tileTickPosition);
+            // Spigot end
+
+            if (tickingblockentity.isRemoved()) {
+                // Spigot start
+                tilesThisCycle--;
+                toRemove.add(tickingblockentity); // Paper - Fix MC-117075; use removeAll
+                // Spigot end
+            } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
+                tickingblockentity.tick();
             }
         }
+        this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
 
+        this.timings.tileEntityTick.stopTiming(); // Spigot
         this.tickingBlockEntities = false;
-        profilerFiller.pop();
+        co.aikar.timings.TimingHistory.tileEntityTicks += this.blockEntityTickers.size(); // Paper
+        gameprofilerfiller.pop();
+        this.spigotConfig.currentPrimedTnt = 0; // Spigot
     }
 
     public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
         try {
             tickConsumer.accept(entity);
-        } catch (Throwable var6) {
-            CrashReport crashReport = CrashReport.forThrowable(var6, "Ticking entity");
-            CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being ticked");
-            entity.fillCrashReportCategory(crashReportCategory);
-            throw new ReportedException(crashReport);
+        } catch (Throwable throwable) {
+            if (throwable instanceof ThreadDeath) throw throwable; // Paper
+            // Paper start - Prevent block entity and entity crashes
+            final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
+            MinecraftServer.LOGGER.error(msg, throwable);
+            getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
+            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+            // Paper end - Prevent block entity and entity crashes
         }
     }
+    // Paper start - Option to prevent armor stands from doing entity lookups
+    @Override
+    public boolean noCollision(@Nullable Entity entity, AABB box) {
+        if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false;
+        return LevelAccessor.super.noCollision(entity, box);
+    }
+    // Paper end - Option to prevent armor stands from doing entity lookups
 
     public boolean shouldTickDeath(Entity entity) {
         return true;
@@ -489,137 +1012,56 @@
     }
 
     public Explosion explode(@Nullable Entity entity, double x, double y, double z, float power, Level.ExplosionInteraction explosionSourceType) {
-        return this.explode(
-            entity,
-            Explosion.getDefaultDamageSource(this, entity),
-            null,
-            x,
-            y,
-            z,
-            power,
-            false,
-            explosionSourceType,
-            ParticleTypes.EXPLOSION,
-            ParticleTypes.EXPLOSION_EMITTER,
-            SoundEvents.GENERIC_EXPLODE
-        );
+        return this.explode(entity, Explosion.getDefaultDamageSource(this, entity), (ExplosionDamageCalculator) null, x, y, z, power, false, explosionSourceType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE);
     }
 
-    public Explosion explode(
-        @Nullable Entity entity, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType
-    ) {
-        return this.explode(
-            entity,
-            Explosion.getDefaultDamageSource(this, entity),
-            null,
-            x,
-            y,
-            z,
-            power,
-            createFire,
-            explosionSourceType,
-            ParticleTypes.EXPLOSION,
-            ParticleTypes.EXPLOSION_EMITTER,
-            SoundEvents.GENERIC_EXPLODE
-        );
+    public Explosion explode(@Nullable Entity entity, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType) {
+        return this.explode(entity, Explosion.getDefaultDamageSource(this, entity), (ExplosionDamageCalculator) null, x, y, z, power, createFire, explosionSourceType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE);
     }
 
-    public Explosion explode(
-        @Nullable Entity entity,
-        @Nullable DamageSource damageSource,
-        @Nullable ExplosionDamageCalculator behavior,
-        Vec3 pos,
-        float power,
-        boolean createFire,
-        Level.ExplosionInteraction explosionSourceType
-    ) {
-        return this.explode(
-            entity,
-            damageSource,
-            behavior,
-            pos.x(),
-            pos.y(),
-            pos.z(),
-            power,
-            createFire,
-            explosionSourceType,
-            ParticleTypes.EXPLOSION,
-            ParticleTypes.EXPLOSION_EMITTER,
-            SoundEvents.GENERIC_EXPLODE
-        );
+    public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType) {
+        return this.explode(entity, damageSource, behavior, pos.x(), pos.y(), pos.z(), power, createFire, explosionSourceType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE);
     }
 
-    public Explosion explode(
-        @Nullable Entity entity,
-        @Nullable DamageSource damageSource,
-        @Nullable ExplosionDamageCalculator behavior,
-        double x,
-        double y,
-        double z,
-        float power,
-        boolean createFire,
-        Level.ExplosionInteraction explosionSourceType
-    ) {
-        return this.explode(
-            entity,
-            damageSource,
-            behavior,
-            x,
-            y,
-            z,
-            power,
-            createFire,
-            explosionSourceType,
-            ParticleTypes.EXPLOSION,
-            ParticleTypes.EXPLOSION_EMITTER,
-            SoundEvents.GENERIC_EXPLODE
-        );
+    public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType) {
+        return this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE);
     }
 
-    public Explosion explode(
-        @Nullable Entity entity,
-        @Nullable DamageSource damageSource,
-        @Nullable ExplosionDamageCalculator behavior,
-        double x,
-        double y,
-        double z,
-        float power,
-        boolean createFire,
-        Level.ExplosionInteraction explosionSourceType,
-        ParticleOptions particle,
-        ParticleOptions emitterParticle,
-        Holder<SoundEvent> soundEvent
-    ) {
+    public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions particle, ParticleOptions emitterParticle, Holder<SoundEvent> soundEvent) {
         return this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, true, particle, emitterParticle, soundEvent);
     }
 
-    public Explosion explode(
-        @Nullable Entity entity,
-        @Nullable DamageSource damageSource,
-        @Nullable ExplosionDamageCalculator behavior,
-        double x,
-        double y,
-        double z,
-        float power,
-        boolean createFire,
-        Level.ExplosionInteraction explosionSourceType,
-        boolean particles,
-        ParticleOptions particle,
-        ParticleOptions emitterParticle,
-        Holder<SoundEvent> soundEvent
-    ) {
-        Explosion.BlockInteraction blockInteraction = switch (explosionSourceType) {
-            case NONE -> Explosion.BlockInteraction.KEEP;
-            case BLOCK -> this.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY);
-            case MOB -> this.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
-            ? this.getDestroyType(GameRules.RULE_MOB_EXPLOSION_DROP_DECAY)
-            : Explosion.BlockInteraction.KEEP;
-            case TNT -> this.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY);
-            case TRIGGER -> Explosion.BlockInteraction.TRIGGER_BLOCK;
-        };
-        Explosion explosion = new Explosion(
-            this, entity, damageSource, behavior, x, y, z, power, createFire, blockInteraction, particle, emitterParticle, soundEvent
-        );
+    public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, boolean particles, ParticleOptions particle, ParticleOptions emitterParticle, Holder<SoundEvent> soundEvent) {
+        Explosion.BlockInteraction explosion_effect;
+
+        switch (explosionSourceType.ordinal()) {
+            case 0:
+                explosion_effect = Explosion.BlockInteraction.KEEP;
+                break;
+            case 1:
+                explosion_effect = this.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY);
+                break;
+            case 2:
+                explosion_effect = this.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? this.getDestroyType(GameRules.RULE_MOB_EXPLOSION_DROP_DECAY) : Explosion.BlockInteraction.KEEP;
+                break;
+            case 3:
+                explosion_effect = this.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY);
+                break;
+            case 4:
+                explosion_effect = Explosion.BlockInteraction.TRIGGER_BLOCK;
+                break;
+            // CraftBukkit start - handle custom explosion type
+            case 5:
+                explosion_effect = Explosion.BlockInteraction.DESTROY;
+                break;
+            // CraftBukkit end
+            default:
+                throw new MatchException((String) null, (Throwable) null);
+        }
+
+        Explosion.BlockInteraction explosion_effect1 = explosion_effect;
+        Explosion explosion = new Explosion(this, entity, damageSource, behavior, x, y, z, power, createFire, explosion_effect1, particle, emitterParticle, soundEvent);
+
         explosion.explode();
         explosion.finalizeExplosion(particles);
         return explosion;
@@ -634,19 +1076,33 @@
     @Nullable
     @Override
     public BlockEntity getBlockEntity(BlockPos pos) {
-        if (this.isOutsideBuildHeight(pos)) {
-            return null;
-        } else {
-            return !this.isClientSide && Thread.currentThread() != this.thread
-                ? null
-                : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE);
+        // CraftBukkit start
+        return this.getBlockEntity(pos, true);
+    }
+
+    @Nullable
+    public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) {
+        // Paper start - Perf: Optimize capturedTileEntities lookup
+        net.minecraft.world.level.block.entity.BlockEntity blockEntity;
+        if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) {
+            return blockEntity;
         }
+        // Paper end - Perf: Optimize capturedTileEntities lookup
+        // CraftBukkit end
+        return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system
     }
 
     public void setBlockEntity(BlockEntity blockEntity) {
-        BlockPos blockPos = blockEntity.getBlockPos();
-        if (!this.isOutsideBuildHeight(blockPos)) {
-            this.getChunkAt(blockPos).addAndRegisterBlockEntity(blockEntity);
+        BlockPos blockposition = blockEntity.getBlockPos();
+
+        if (!this.isOutsideBuildHeight(blockposition)) {
+            // CraftBukkit start
+            if (this.captureBlockStates) {
+                this.capturedTileEntities.put(blockposition.immutable(), blockEntity);
+                return;
+            }
+            // CraftBukkit end
+            this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity);
         }
     }
 
@@ -657,18 +1113,16 @@
     }
 
     public boolean isLoaded(BlockPos pos) {
-        return !this.isOutsideBuildHeight(pos)
-            && this.getChunkSource().hasChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
+        return this.isOutsideBuildHeight(pos) ? false : this.getChunkSource().hasChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
     }
 
     public boolean loadedAndEntityCanStandOnFace(BlockPos pos, Entity entity, Direction direction) {
         if (this.isOutsideBuildHeight(pos)) {
             return false;
         } else {
-            ChunkAccess chunkAccess = this.getChunk(
-                SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false
-            );
-            return chunkAccess != null && chunkAccess.getBlockState(pos).entityCanStandOnFace(this, pos, entity, direction);
+            ChunkAccess ichunkaccess = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false);
+
+            return ichunkaccess == null ? false : ichunkaccess.getBlockState(pos).entityCanStandOnFace(this, pos, entity, direction);
         }
     }
 
@@ -677,10 +1131,11 @@
     }
 
     public void updateSkyBrightness() {
-        double d = 1.0 - (double)(this.getRainLevel(1.0F) * 5.0F) / 16.0;
-        double e = 1.0 - (double)(this.getThunderLevel(1.0F) * 5.0F) / 16.0;
-        double f = 0.5 + 2.0 * Mth.clamp((double)Mth.cos(this.getTimeOfDay(1.0F) * (float) (Math.PI * 2)), -0.25, 0.25);
-        this.skyDarken = (int)((1.0 - f * d * e) * 11.0);
+        double d0 = 1.0D - (double) (this.getRainLevel(1.0F) * 5.0F) / 16.0D;
+        double d1 = 1.0D - (double) (this.getThunderLevel(1.0F) * 5.0F) / 16.0D;
+        double d2 = 0.5D + 2.0D * Mth.clamp((double) Mth.cos(this.getTimeOfDay(1.0F) * 6.2831855F), -0.25D, 0.25D);
+
+        this.skyDarken = (int) ((1.0D - d2 * d0 * d1) * 11.0D);
     }
 
     public void setSpawnSettings(boolean spawnMonsters, boolean spawnAnimals) {
@@ -688,14 +1143,13 @@
     }
 
     public BlockPos getSharedSpawnPos() {
-        BlockPos blockPos = this.levelData.getSpawnPos();
-        if (!this.getWorldBorder().isWithinBounds(blockPos)) {
-            blockPos = this.getHeightmapPos(
-                Heightmap.Types.MOTION_BLOCKING, BlockPos.containing(this.getWorldBorder().getCenterX(), 0.0, this.getWorldBorder().getCenterZ())
-            );
+        BlockPos blockposition = this.levelData.getSpawnPos();
+
+        if (!this.getWorldBorder().isWithinBounds(blockposition)) {
+            blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, BlockPos.containing(this.getWorldBorder().getCenterX(), 0.0D, this.getWorldBorder().getCenterZ()));
         }
 
-        return blockPos;
+        return blockposition;
     }
 
     public float getSharedSpawnAngle() {
@@ -709,9 +1163,9 @@
                 this.thunderLevel = 1.0F;
             }
         }
+
     }
 
-    @Override
     public void close() throws IOException {
         this.getChunkSource().close();
     }
@@ -725,26 +1179,19 @@
     @Override
     public List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
         this.getProfiler().incrementCounter("getEntities");
-        List<Entity> list = Lists.newArrayList();
-        this.getEntities().get(box, entity -> {
-            if (entity != except && predicate.test(entity)) {
-                list.add(entity);
-            }
+        // Paper start - rewrite chunk system
+        final List<Entity> ret = new java.util.ArrayList<>();
 
-            if (entity instanceof EnderDragon) {
-                for (EnderDragonPart enderDragonPart : ((EnderDragon)entity).getSubEntities()) {
-                    if (entity != except && predicate.test(enderDragonPart)) {
-                        list.add(enderDragonPart);
-                    }
-                }
-            }
-        });
-        return list;
+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(except, box, ret, predicate);
+
+        return ret;
+        // Paper end - rewrite chunk system
     }
 
     @Override
     public <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate) {
         List<T> list = Lists.newArrayList();
+
         this.getEntities(filter, box, predicate, list);
         return list;
     }
@@ -753,31 +1200,77 @@
         this.getEntities(filter, box, predicate, result, Integer.MAX_VALUE);
     }
 
-    public <T extends Entity> void getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate, List<? super T> result, int limit) {
+    // Paper start - rewrite chunk system
+    public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
+                                               final AABB boundingBox, final Predicate<? super T> predicate,
+                                               final List<? super T> into, final int maxCount) {
         this.getProfiler().incrementCounter("getEntities");
-        this.getEntities().get(filter, box, entity -> {
-            if (predicate.test(entity)) {
-                result.add(entity);
-                if (result.size() >= limit) {
-                    return AbortableIterationConsumer.Continuation.ABORT;
-                }
+
+        if (entityTypeTest instanceof net.minecraft.world.entity.EntityType<T> byType) {
+            if (maxCount != Integer.MAX_VALUE) {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(byType, boundingBox, into, predicate, maxCount);
+                return;
+            } else {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(byType, boundingBox, into, predicate);
+                return;
             }
+        }
 
-            if (entity instanceof EnderDragon enderDragon) {
-                for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) {
-                    T entity2 = filter.tryCast(enderDragonPart);
-                    if (entity2 != null && predicate.test(entity2)) {
-                        result.add(entity2);
-                        if (result.size() >= limit) {
-                            return AbortableIterationConsumer.Continuation.ABORT;
-                        }
-                    }
+        if (entityTypeTest == null) {
+            if (maxCount != Integer.MAX_VALUE) {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount);
+                return;
+            } else {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate);
+                return;
+            }
+        }
+
+        final Class<? extends Entity> base = entityTypeTest.getBaseClass();
+
+        final Predicate<? super T> modifiedPredicate;
+        if (predicate == null) {
+            modifiedPredicate = (final T obj) -> {
+                return entityTypeTest.tryCast(obj) != null;
+            };
+        } else {
+            modifiedPredicate = (final Entity obj) -> {
+                final T casted = entityTypeTest.tryCast(obj);
+                if (casted == null) {
+                    return false;
                 }
+
+                return predicate.test(casted);
+            };
+        }
+
+        if (base == null || base == Entity.class) {
+            if (maxCount != Integer.MAX_VALUE) {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)modifiedPredicate, maxCount);
+                return;
+            } else {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)modifiedPredicate);
+                return;
+            }
+        } else {
+            if (maxCount != Integer.MAX_VALUE) {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(base, null, boundingBox, (List)into, (Predicate)modifiedPredicate, maxCount);
+                return;
+            } else {
+                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(base, null, boundingBox, (List)into, (Predicate)modifiedPredicate);
+                return;
             }
+        }
+    }
 
-            return AbortableIterationConsumer.Continuation.CONTINUE;
-        });
+    public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) {
+        ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices slices = ((ServerLevel)this).moonrise$getEntityLookup().getChunk(chunkX, chunkZ);
+        if (slices == null) {
+            return new org.bukkit.entity.Entity[0];
+        }
+        return slices.getChunkEntities();
     }
+    // Paper end - rewrite chunk system
 
     @Nullable
     public abstract Entity getEntity(int id);
@@ -786,6 +1279,7 @@
         if (this.hasChunkAt(pos)) {
             this.getChunkAt(pos).setUnsaved(true);
         }
+
     }
 
     @Override
@@ -793,8 +1287,7 @@
         return 63;
     }
 
-    public void disconnect() {
-    }
+    public void disconnect() {}
 
     public long getGameTime() {
         return this.levelData.getGameTime();
@@ -808,11 +1301,9 @@
         return true;
     }
 
-    public void broadcastEntityEvent(Entity entity, byte status) {
-    }
+    public void broadcastEntityEvent(Entity entity, byte status) {}
 
-    public void broadcastDamageEvent(Entity entity, DamageSource damageSource) {
-    }
+    public void broadcastDamageEvent(Entity entity, DamageSource damageSource) {}
 
     public void blockEvent(BlockPos pos, Block block, int type, int data) {
         this.getBlockState(pos).triggerEvent(this, pos, type, data);
@@ -834,9 +1325,10 @@
     }
 
     public void setThunderLevel(float thunderGradient) {
-        float f = Mth.clamp(thunderGradient, 0.0F, 1.0F);
-        this.oThunderLevel = f;
-        this.thunderLevel = f;
+        float f1 = Mth.clamp(thunderGradient, 0.0F, 1.0F);
+
+        this.oThunderLevel = f1;
+        this.thunderLevel = f1;
     }
 
     public float getRainLevel(float delta) {
@@ -844,17 +1336,18 @@
     }
 
     public void setRainLevel(float rainGradient) {
-        float f = Mth.clamp(rainGradient, 0.0F, 1.0F);
-        this.oRainLevel = f;
-        this.rainLevel = f;
+        float f1 = Mth.clamp(rainGradient, 0.0F, 1.0F);
+
+        this.oRainLevel = f1;
+        this.rainLevel = f1;
     }
 
     public boolean isThundering() {
-        return this.dimensionType().hasSkyLight() && !this.dimensionType().hasCeiling() && (double)this.getThunderLevel(1.0F) > 0.9;
+        return this.dimensionType().hasSkyLight() && !this.dimensionType().hasCeiling() ? (double) this.getThunderLevel(1.0F) > 0.9D : false;
     }
 
     public boolean isRaining() {
-        return (double)this.getRainLevel(1.0F) > 0.2;
+        return (double) this.getRainLevel(1.0F) > 0.2D;
     }
 
     public boolean isRainingAt(BlockPos pos) {
@@ -865,8 +1358,9 @@
         } else if (this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos).getY() > pos.getY()) {
             return false;
         } else {
-            Biome biome = this.getBiome(pos).value();
-            return biome.getPrecipitationAt(pos) == Biome.Precipitation.RAIN;
+            Biome biomebase = (Biome) this.getBiome(pos).value();
+
+            return biomebase.getPrecipitationAt(pos) == Biome.Precipitation.RAIN;
         }
     }
 
@@ -877,59 +1371,74 @@
 
     public abstract MapId getFreeMapId();
 
-    public void globalLevelEvent(int eventId, BlockPos pos, int data) {
-    }
+    public void globalLevelEvent(int eventId, BlockPos pos, int data) {}
 
     public CrashReportCategory fillReportDetails(CrashReport report) {
-        CrashReportCategory crashReportCategory = report.addCategory("Affected level", 1);
-        crashReportCategory.setDetail("All players", () -> this.players().size() + " total; " + this.players());
-        crashReportCategory.setDetail("Chunk stats", this.getChunkSource()::gatherStats);
-        crashReportCategory.setDetail("Level dimension", () -> this.dimension().location().toString());
+        CrashReportCategory crashreportsystemdetails = report.addCategory("Affected level", 1);
+
+        crashreportsystemdetails.setDetail("All players", () -> {
+            int i = this.players().size();
+
+            return "" + i + " total; " + String.valueOf(this.players());
+        });
+        ChunkSource ichunkprovider = this.getChunkSource();
+
+        Objects.requireNonNull(ichunkprovider);
+        crashreportsystemdetails.setDetail("Chunk stats", ichunkprovider::gatherStats);
+        crashreportsystemdetails.setDetail("Level dimension", () -> {
+            return this.dimension().location().toString();
+        });
 
         try {
-            this.levelData.fillCrashReportCategory(crashReportCategory, this);
-        } catch (Throwable var4) {
-            crashReportCategory.setDetailError("Level Data Unobtainable", var4);
+            this.levelData.fillCrashReportCategory(crashreportsystemdetails, this);
+        } catch (Throwable throwable) {
+            crashreportsystemdetails.setDetailError("Level Data Unobtainable", throwable);
         }
 
-        return crashReportCategory;
+        return crashreportsystemdetails;
     }
 
     public abstract void destroyBlockProgress(int entityId, BlockPos pos, int progress);
 
-    public void createFireworks(double x, double y, double z, double velocityX, double velocityY, double velocityZ, List<FireworkExplosion> explosions) {
-    }
+    public void createFireworks(double x, double y, double z, double velocityX, double velocityY, double velocityZ, List<FireworkExplosion> explosions) {}
 
     public abstract Scoreboard getScoreboard();
 
     public void updateNeighbourForOutputSignal(BlockPos pos, Block block) {
-        for (Direction direction : Direction.Plane.HORIZONTAL) {
-            BlockPos blockPos = pos.relative(direction);
-            if (this.hasChunkAt(blockPos)) {
-                BlockState blockState = this.getBlockState(blockPos);
-                if (blockState.is(Blocks.COMPARATOR)) {
-                    this.neighborChanged(blockState, blockPos, block, pos, false);
-                } else if (blockState.isRedstoneConductor(this, blockPos)) {
-                    blockPos = blockPos.relative(direction);
-                    blockState = this.getBlockState(blockPos);
-                    if (blockState.is(Blocks.COMPARATOR)) {
-                        this.neighborChanged(blockState, blockPos, block, pos, false);
+        Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
+
+        while (iterator.hasNext()) {
+            Direction enumdirection = (Direction) iterator.next();
+            BlockPos blockposition1 = pos.relative(enumdirection);
+
+            if (this.hasChunkAt(blockposition1)) {
+                BlockState iblockdata = this.getBlockState(blockposition1);
+
+                if (iblockdata.is(Blocks.COMPARATOR)) {
+                    this.neighborChanged(iblockdata, blockposition1, block, pos, false);
+                } else if (iblockdata.isRedstoneConductor(this, blockposition1)) {
+                    blockposition1 = blockposition1.relative(enumdirection);
+                    iblockdata = this.getBlockState(blockposition1);
+                    if (iblockdata.is(Blocks.COMPARATOR)) {
+                        this.neighborChanged(iblockdata, blockposition1, block, pos, false);
                     }
                 }
             }
         }
+
     }
 
     @Override
     public DifficultyInstance getCurrentDifficultyAt(BlockPos pos) {
-        long l = 0L;
+        long i = 0L;
         float f = 0.0F;
+
         if (this.hasChunkAt(pos)) {
             f = this.getMoonBrightness();
-            l = this.getChunkAt(pos).getInhabitedTime();
+            i = this.getChunkAt(pos).getInhabitedTime();
         }
 
-        return new DifficultyInstance(this.getDifficulty(), this.getDayTime(), l, f);
+        return new DifficultyInstance(this.getDifficulty(), this.getDayTime(), i, f);
     }
 
     @Override
@@ -937,8 +1446,7 @@
         return this.skyDarken;
     }
 
-    public void setSkyFlashTime(int lightningTicksLeft) {
-    }
+    public void setSkyFlashTime(int lightningTicksLeft) {}
 
     @Override
     public WorldBorder getWorldBorder() {
@@ -951,7 +1459,7 @@
 
     @Override
     public DimensionType dimensionType() {
-        return this.dimensionTypeRegistration.value();
+        return (DimensionType) this.dimensionTypeRegistration.value();
     }
 
     public Holder<DimensionType> dimensionTypeRegistration() {
@@ -979,10 +1487,11 @@
 
     public abstract RecipeManager getRecipeManager();
 
-    public BlockPos getBlockRandomPos(int x, int y, int z, int i) {
+    public BlockPos getBlockRandomPos(int x, int y, int z, int l) {
         this.randValue = this.randValue * 3 + 1013904223;
-        int j = this.randValue >> 2;
-        return new BlockPos(x + (j & 15), y + (j >> 16 & i), z + (j >> 8 & 15));
+        int i1 = this.randValue >> 2;
+
+        return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15));
     }
 
     public boolean noSave() {
@@ -990,7 +1499,7 @@
     }
 
     public ProfilerFiller getProfiler() {
-        return this.profiler.get();
+        return (ProfilerFiller) this.profiler.get();
     }
 
     public Supplier<ProfilerFiller> getProfilerSupplier() {
@@ -1010,7 +1519,7 @@
 
     @Override
     public long nextSubTickCount() {
-        return this.subTickCount++;
+        return (long) (this.subTickCount++);
     }
 
     @Override
@@ -1025,17 +1534,14 @@
     public abstract PotionBrewing potionBrewing();
 
     public static enum ExplosionInteraction implements StringRepresentable {
-        NONE("none"),
-        BLOCK("block"),
-        MOB("mob"),
-        TNT("tnt"),
-        TRIGGER("trigger");
+
+        NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), TRIGGER("trigger"), STANDARD("standard"); // CraftBukkit - Add STANDARD which will always use Explosion.Effect.DESTROY
 
         public static final Codec<Level.ExplosionInteraction> CODEC = StringRepresentable.fromEnum(Level.ExplosionInteraction::values);
         private final String id;
 
-        private ExplosionInteraction(String id) {
-            this.id = id;
+        private ExplosionInteraction(String s) {
+            this.id = s;
         }
 
         @Override
@@ -1043,4 +1549,34 @@
             return this.id;
         }
     }
+    // Paper start - respect global sound events gamerule
+    public List<net.minecraft.server.level.ServerPlayer> getPlayersForGlobalSoundGamerule() {
+        return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players();
+    }
+
+    public double getGlobalSoundRangeSquared(java.util.function.Function<org.spigotmc.SpigotWorldConfig, Integer> rangeFunction) {
+        final double range = rangeFunction.apply(this.spigotConfig);
+        return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent
+    }
+    // Paper end - respect global sound events gamerule
+    // Paper start - notify observers even if grow failed
+    public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final CraftBlockState craftBlockState) {
+        // notify observers if the block state is the same and the Y level equals the original y level (for mega trees)
+        // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the
+        // tree grew or not
+        if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) {
+            this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512);
+        }
+    }
+    // Paper end - notify observers even if grow failed
+    // Paper start - optimize redstone (Alternate Current)
+    public alternate.current.wire.WireHandler getWireHandler() {
+        // This method is overridden in ServerLevel.
+        // Since Paper is a server platform there is no risk
+        // of this implementation being called. It is here
+        // only so this method can be called without casting
+        // an instance of Level to ServerLevel.
+        return null;
+    }
+    // Paper end - optimize redstone (Alternate Current)
 }
