--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -1,10 +1,14 @@
 package net.minecraft.world.level.chunk;
 
 import com.google.common.collect.ImmutableList;
+import com.destroystokyo.paper.exception.ServerInternalException;
 import com.google.common.collect.Maps;
+import com.google.common.collect.UnmodifiableIterator;
 import com.mojang.logging.LogUtils;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.shorts.ShortListIterator;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.function.Consumer;
@@ -51,11 +55,11 @@
 import org.slf4j.Logger;
 
 public class LevelChunk extends ChunkAccess {
+
     static final Logger LOGGER = LogUtils.getLogger();
     private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() {
         @Override
-        public void tick() {
-        }
+        public void tick() {}
 
         @Override
         public boolean isRemoved() {
@@ -72,9 +76,9 @@
             return "<null>";
         }
     };
-    private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel = Maps.newHashMap();
+    private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel;
     public boolean loaded;
-    public final Level level;
+    public final ServerLevel level; // CraftBukkit - type
     @Nullable
     private Supplier<FullChunkStatus> fullStatus;
     @Nullable
@@ -82,29 +86,25 @@
     private final Int2ObjectMap<GameEventListenerRegistry> gameEventListenerRegistrySections;
     private final LevelChunkTicks<Block> blockTicks;
     private final LevelChunkTicks<Fluid> fluidTicks;
+    public volatile FullChunkStatus chunkStatus = FullChunkStatus.INACCESSIBLE; // Paper - rewrite chunk system
 
     public LevelChunk(Level world, ChunkPos pos) {
-        this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
+        this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null);
     }
 
-    public LevelChunk(
-        Level world,
-        ChunkPos pos,
-        UpgradeData upgradeData,
-        LevelChunkTicks<Block> blockTickScheduler,
-        LevelChunkTicks<Fluid> fluidTickScheduler,
-        long inhabitedTime,
-        @Nullable LevelChunkSection[] sectionArrayInitializer,
-        @Nullable LevelChunk.PostLoadProcessor entityLoader,
-        @Nullable BlendingData blendingData
-    ) {
-        super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData);
-        this.level = world;
-        this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>();
+    public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<Block> blockTickScheduler, LevelChunkTicks<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) {
+        super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry
+        this.tickersInLevel = Maps.newHashMap();
+        this.level = (ServerLevel) world; // CraftBukkit - type
+        this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap();
+        Heightmap.Types[] aheightmap_type = Heightmap.Types.values();
+        int j = aheightmap_type.length;
 
-        for (Heightmap.Types types : Heightmap.Types.values()) {
-            if (ChunkStatus.FULL.heightmapsAfter().contains(types)) {
-                this.heightmaps.put(types, new Heightmap(this, types));
+        for (int k = 0; k < j; ++k) {
+            Heightmap.Types heightmap_type = aheightmap_type[k];
+
+            if (ChunkStatus.FULL.heightmapsAfter().contains(heightmap_type)) {
+                this.heightmaps.put(heightmap_type, new Heightmap(this, heightmap_type));
             }
         }
 
@@ -113,41 +113,155 @@
         this.fluidTicks = fluidTickScheduler;
     }
 
+    // CraftBukkit start
+    public boolean mustNotSave;
+    public boolean needsDecoration;
+    // CraftBukkit end
+
+    // Paper start
+    public @Nullable net.minecraft.server.level.ChunkHolder playerChunk;
+
+    static final int NEIGHBOUR_CACHE_RADIUS = 3;
+    public static int getNeighbourCacheRadius() {
+        return NEIGHBOUR_CACHE_RADIUS;
+    }
+
+    boolean loadedTicketLevel;
+    private long neighbourChunksLoadedBitset;
+    private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)];
+
+    private static int getNeighbourIndex(final int relativeX, final int relativeZ) {
+        // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)
+        // optimised variant of the above by moving some of the ops to compile time
+        return relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1)));
+    }
+
+    public final LevelChunk getRelativeNeighbourIfLoaded(final int relativeX, final int relativeZ) {
+        return this.loadedNeighbourChunks[getNeighbourIndex(relativeX, relativeZ)];
+    }
+
+    public final boolean isNeighbourLoaded(final int relativeX, final int relativeZ) {
+        return (this.neighbourChunksLoadedBitset & (1L << getNeighbourIndex(relativeX, relativeZ))) != 0;
+    }
+
+    public final void setNeighbourLoaded(final int relativeX, final int relativeZ, final LevelChunk chunk) {
+        if (chunk == null) {
+            throw new IllegalArgumentException("Chunk must be non-null, neighbour: (" + relativeX + "," + relativeZ + "), chunk: " + this.chunkPos);
+        }
+        final long before = this.neighbourChunksLoadedBitset;
+        final int index = getNeighbourIndex(relativeX, relativeZ);
+        this.loadedNeighbourChunks[index] = chunk;
+        this.neighbourChunksLoadedBitset |= (1L << index);
+        this.onNeighbourChange(before, this.neighbourChunksLoadedBitset);
+    }
+
+    public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) {
+        final long before = this.neighbourChunksLoadedBitset;
+        final int index = getNeighbourIndex(relativeX, relativeZ);
+        this.loadedNeighbourChunks[index] = null;
+        this.neighbourChunksLoadedBitset &= ~(1L << index);
+        this.onNeighbourChange(before, this.neighbourChunksLoadedBitset);
+    }
+
+    public final void resetNeighbours() {
+        final long before = this.neighbourChunksLoadedBitset;
+        this.neighbourChunksLoadedBitset = 0L;
+        java.util.Arrays.fill(this.loadedNeighbourChunks, null);
+        this.onNeighbourChange(before, 0L);
+    }
+
+    protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
+
+    }
+
+    public final boolean isAnyNeighborsLoaded() {
+        return neighbourChunksLoadedBitset != 0;
+    }
+    public final boolean areNeighboursLoaded(final int radius) {
+        return LevelChunk.areNeighboursLoaded(this.neighbourChunksLoadedBitset, radius);
+    }
+
+    public static boolean areNeighboursLoaded(final long bitset, final int radius) {
+        // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1)))
+        switch (radius) {
+            case 0: {
+                return (bitset & (1L << getNeighbourIndex(0, 0))) != 0;
+            }
+            case 1: {
+                long mask = 0L;
+                for (int dx = -1; dx <= 1; ++dx) {
+                    for (int dz = -1; dz <= 1; ++dz) {
+                        mask |= (1L << getNeighbourIndex(dx, dz));
+                    }
+                }
+                return (bitset & mask) == mask;
+            }
+            case 2: {
+                long mask = 0L;
+                for (int dx = -2; dx <= 2; ++dx) {
+                    for (int dz = -2; dz <= 2; ++dz) {
+                        mask |= (1L << getNeighbourIndex(dx, dz));
+                    }
+                }
+                return (bitset & mask) == mask;
+            }
+            case 3: {
+                long mask = 0L;
+                for (int dx = -3; dx <= 3; ++dx) {
+                    for (int dz = -3; dz <= 3; ++dz) {
+                        mask |= (1L << getNeighbourIndex(dx, dz));
+                    }
+                }
+                return (bitset & mask) == mask;
+            }
+
+            default:
+                throw new IllegalArgumentException("Radius not recognized: " + radius);
+        }
+    }
+    // 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()
-        );
+        this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
+        // Paper start - rewrite light engine
+        this.setBlockNibbles(protoChunk.getBlockNibbles());
+        this.setSkyNibbles(protoChunk.getSkyNibbles());
+        this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap());
+        this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap());
+        // Paper end - rewrite light engine
+        Iterator iterator = protoChunk.getBlockEntities().values().iterator();
 
-        for (BlockEntity blockEntity : protoChunk.getBlockEntities().values()) {
-            this.setBlockEntity(blockEntity);
+        while (iterator.hasNext()) {
+            BlockEntity tileentity = (BlockEntity) iterator.next();
+
+            this.setBlockEntity(tileentity);
         }
 
         this.pendingBlockEntities.putAll(protoChunk.getBlockEntityNbts());
 
-        for (int i = 0; i < protoChunk.getPostProcessing().length; i++) {
+        for (int i = 0; i < protoChunk.getPostProcessing().length; ++i) {
             this.postProcessing[i] = protoChunk.getPostProcessing()[i];
         }
 
         this.setAllStarts(protoChunk.getAllStarts());
         this.setAllReferences(protoChunk.getAllReferences());
+        iterator = protoChunk.getHeightmaps().iterator();
+
+        while (iterator.hasNext()) {
+            Entry<Heightmap.Types, Heightmap> entry = (Entry) iterator.next();
 
-        for (Entry<Heightmap.Types, Heightmap> entry : protoChunk.getHeightmaps()) {
             if (ChunkStatus.FULL.heightmapsAfter().contains(entry.getKey())) {
-                this.setHeightmap(entry.getKey(), entry.getValue().getRawData());
+                this.setHeightmap((Heightmap.Types) entry.getKey(), ((Heightmap) entry.getValue()).getRawData());
             }
         }
 
-        this.skyLightSources = protoChunk.skyLightSources;
+        // Paper - starlight - remove skyLightSources
         this.setLightCorrect(protoChunk.isLightCorrect());
         this.unsaved = true;
+        this.needsDecoration = true; // CraftBukkit
+        // CraftBukkit start
+        this.persistentDataContainer = protoChunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
+        // CraftBukkit end
     }
 
     @Override
@@ -165,154 +279,224 @@
         return new ChunkAccess.TicksToSave(this.blockTicks, this.fluidTicks);
     }
 
+    // Paper start
+    @Override
+    public long getInhabitedTime() {
+        return this.level.paperConfig().chunks.fixedChunkInhabitedTime < 0 ? super.getInhabitedTime() : this.level.paperConfig().chunks.fixedChunkInhabitedTime;
+    }
+    // Paper end
+
     @Override
     public GameEventListenerRegistry getListenerRegistry(int ySectionCoord) {
-        return this.level instanceof ServerLevel serverLevel
-            ? this.gameEventListenerRegistrySections
-                .computeIfAbsent(
-                    ySectionCoord, sectionCoord -> new EuclideanGameEventListenerRegistry(serverLevel, ySectionCoord, this::removeGameEventListenerRegistry)
-                )
-            : super.getListenerRegistry(ySectionCoord);
+        Level world = this.level;
+
+        if (world instanceof ServerLevel worldserver) {
+            return (GameEventListenerRegistry) this.gameEventListenerRegistrySections.computeIfAbsent(ySectionCoord, (j) -> {
+                return new EuclideanGameEventListenerRegistry(worldserver, ySectionCoord, this::removeGameEventListenerRegistry);
+            });
+        } else {
+            return super.getListenerRegistry(ySectionCoord);
+        }
     }
 
+    // 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();
+
         if (this.level.isDebug()) {
-            BlockState blockState = null;
+            BlockState iblockdata = null;
+
             if (j == 60) {
-                blockState = Blocks.BARRIER.defaultBlockState();
+                iblockdata = Blocks.BARRIER.defaultBlockState();
             }
 
             if (j == 70) {
-                blockState = DebugLevelSource.getBlockStateFor(i, k);
+                iblockdata = DebugLevelSource.getBlockStateFor(i, k);
             }
 
-            return blockState == null ? Blocks.AIR.defaultBlockState() : blockState;
+            return iblockdata == null ? Blocks.AIR.defaultBlockState() : iblockdata;
         } else {
             try {
                 int l = this.getSectionIndex(j);
+
                 if (l >= 0 && l < this.sections.length) {
-                    LevelChunkSection levelChunkSection = this.sections[l];
-                    if (!levelChunkSection.hasOnlyAir()) {
-                        return levelChunkSection.getBlockState(i & 15, j & 15, k & 15);
+                    LevelChunkSection chunksection = this.sections[l];
+
+                    if (!chunksection.hasOnlyAir()) {
+                        return chunksection.getBlockState(i & 15, j & 15, k & 15);
                     }
                 }
 
                 return Blocks.AIR.defaultBlockState();
-            } catch (Throwable var8) {
-                CrashReport crashReport = CrashReport.forThrowable(var8, "Getting block state");
-                CrashReportCategory crashReportCategory = crashReport.addCategory("Block being got");
-                crashReportCategory.setDetail("Location", () -> CrashReportCategory.formatLocation(this, i, j, k));
-                throw new ReportedException(crashReport);
+            } catch (Throwable throwable) {
+                CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting block state");
+                CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got");
+
+                crashreportsystemdetails.setDetail("Location", () -> {
+                    return CrashReportCategory.formatLocation(this, i, j, k);
+                });
+                throw new ReportedException(crashreport);
             }
         }
     }
 
+    // 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());
     }
 
     public FluidState getFluidState(int x, int y, int z) {
-        try {
-            int i = this.getSectionIndex(y);
-            if (i >= 0 && i < this.sections.length) {
-                LevelChunkSection levelChunkSection = this.sections[i];
-                if (!levelChunkSection.hasOnlyAir()) {
-                    return levelChunkSection.getFluidState(x & 15, y & 15, z & 15);
+        // Paper start - Perf: Optimise Chunk#getFluid
+        // try {  // Remove try catch
+        int index = this.getSectionIndex(y);
+            if (index >= 0 && index < this.sections.length) {
+                LevelChunkSection chunksection = this.sections[index];
+
+                if (!chunksection.hasOnlyAir()) {
+                    return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState();
+                    // Paper end - Perf: Optimise Chunk#getFluid
                 }
             }
 
             return Fluids.EMPTY.defaultFluidState();
-        } catch (Throwable var7) {
-            CrashReport crashReport = CrashReport.forThrowable(var7, "Getting fluid state");
-            CrashReportCategory crashReportCategory = crashReport.addCategory("Block being got");
-            crashReportCategory.setDetail("Location", () -> CrashReportCategory.formatLocation(this, x, y, z));
-            throw new ReportedException(crashReport);
+        /* // Paper - Perf: Optimise Chunk#getFluid
+        } catch (Throwable throwable) {
+            CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state");
+            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got");
+
+            crashreportsystemdetails.setDetail("Location", () -> {
+                return CrashReportCategory.formatLocation(this, x, y, z);
+            });
+            throw new ReportedException(crashreport);
         }
+         */  // Paper - Perf: Optimise Chunk#getFluid
     }
 
+    // CraftBukkit start
     @Nullable
     @Override
     public BlockState setBlockState(BlockPos pos, BlockState state, boolean moved) {
-        int i = pos.getY();
-        LevelChunkSection levelChunkSection = this.getSection(this.getSectionIndex(i));
-        boolean bl = levelChunkSection.hasOnlyAir();
-        if (bl && state.isAir()) {
+        return this.setBlockState(pos, state, moved, true);
+    }
+
+    @Nullable
+    public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) {
+        // CraftBukkit end
+        int i = blockposition.getY();
+        LevelChunkSection chunksection = this.getSection(this.getSectionIndex(i));
+        boolean flag1 = chunksection.hasOnlyAir();
+
+        if (flag1 && iblockdata.isAir()) {
             return null;
         } else {
-            int j = pos.getX() & 15;
+            int j = blockposition.getX() & 15;
             int k = i & 15;
-            int l = pos.getZ() & 15;
-            BlockState blockState = levelChunkSection.setBlockState(j, k, l, state);
-            if (blockState == state) {
+            int l = blockposition.getZ() & 15;
+            BlockState iblockdata1 = chunksection.setBlockState(j, k, l, iblockdata);
+
+            if (iblockdata1 == iblockdata) {
                 return null;
             } else {
-                Block block = state.getBlock();
-                this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(j, i, l, state);
-                this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(j, i, l, state);
-                this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR).update(j, i, l, state);
-                this.heightmaps.get(Heightmap.Types.WORLD_SURFACE).update(j, i, l, state);
-                boolean bl2 = levelChunkSection.hasOnlyAir();
-                if (bl != bl2) {
-                    this.level.getChunkSource().getLightEngine().updateSectionStatus(pos, bl2);
+                Block block = iblockdata.getBlock();
+
+                ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING)).update(j, i, l, iblockdata);
+                ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES)).update(j, i, l, iblockdata);
+                ((Heightmap) this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR)).update(j, i, l, iblockdata);
+                ((Heightmap) this.heightmaps.get(Heightmap.Types.WORLD_SURFACE)).update(j, i, l, iblockdata);
+                boolean flag2 = chunksection.hasOnlyAir();
+
+                if (flag1 != flag2) {
+                    this.level.getChunkSource().getLightEngine().updateSectionStatus(blockposition, flag2);
                 }
 
-                if (LightEngine.hasDifferentLightProperties(this, pos, blockState, state)) {
-                    ProfilerFiller profilerFiller = this.level.getProfiler();
-                    profilerFiller.push("updateSkyLightSources");
-                    this.skyLightSources.update(this, j, i, l);
-                    profilerFiller.popPush("queueCheckLight");
-                    this.level.getChunkSource().getLightEngine().checkBlock(pos);
-                    profilerFiller.pop();
+                if (LightEngine.hasDifferentLightProperties(this, blockposition, iblockdata1, iblockdata)) {
+                    ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+                    gameprofilerfiller.push("updateSkyLightSources");
+                    // Paper - starlight - remove skyLightSources
+                    gameprofilerfiller.popPush("queueCheckLight");
+                    this.level.getChunkSource().getLightEngine().checkBlock(blockposition);
+                    gameprofilerfiller.pop();
                 }
 
-                boolean bl3 = blockState.hasBlockEntity();
-                if (!this.level.isClientSide) {
-                    blockState.onRemove(this.level, pos, state, moved);
-                } else if (!blockState.is(block) && bl3) {
-                    this.removeBlockEntity(pos);
+                boolean flag3 = iblockdata1.hasBlockEntity();
+
+                if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
+                    iblockdata1.onRemove(this.level, blockposition, iblockdata, flag);
+                } else if (!iblockdata1.is(block) && flag3) {
+                    this.removeBlockEntity(blockposition);
                 }
 
-                if (!levelChunkSection.getBlockState(j, k, l).is(block)) {
+                if (!chunksection.getBlockState(j, k, l).is(block)) {
                     return null;
                 } else {
-                    if (!this.level.isClientSide) {
-                        state.onPlace(this.level, pos, blockState, moved);
+                    // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
+                    if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) {
+                        iblockdata.onPlace(this.level, blockposition, iblockdata1, flag);
                     }
 
-                    if (state.hasBlockEntity()) {
-                        BlockEntity blockEntity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK);
-                        if (blockEntity == null) {
-                            blockEntity = ((EntityBlock)block).newBlockEntity(pos, state);
-                            if (blockEntity != null) {
-                                this.addAndRegisterBlockEntity(blockEntity);
+                    if (iblockdata.hasBlockEntity()) {
+                        BlockEntity tileentity = this.getBlockEntity(blockposition, LevelChunk.EntityCreationType.CHECK);
+
+                        if (tileentity == null) {
+                            tileentity = ((EntityBlock) block).newBlockEntity(blockposition, iblockdata);
+                            if (tileentity != null) {
+                                this.addAndRegisterBlockEntity(tileentity);
                             }
                         } else {
-                            blockEntity.setBlockState(state);
-                            this.updateBlockEntityTicker(blockEntity);
+                            tileentity.setBlockState(iblockdata);
+                            this.updateBlockEntityTicker(tileentity);
                         }
                     }
 
                     this.unsaved = true;
-                    return blockState;
+                    return iblockdata1;
                 }
             }
         }
     }
 
+    /** @deprecated */
     @Deprecated
     @Override
-    public void addEntity(Entity entity) {
-    }
+    public void addEntity(Entity entity) {}
 
     @Nullable
     private BlockEntity createBlockEntity(BlockPos pos) {
-        BlockState blockState = this.getBlockState(pos);
-        return !blockState.hasBlockEntity() ? null : ((EntityBlock)blockState.getBlock()).newBlockEntity(pos, blockState);
+        BlockState iblockdata = this.getBlockState(pos);
+
+        return !iblockdata.hasBlockEntity() ? null : ((EntityBlock) iblockdata.getBlock()).newBlockEntity(pos, iblockdata);
     }
 
     @Nullable
@@ -323,41 +507,54 @@
 
     @Nullable
     public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) {
-        BlockEntity blockEntity = this.blockEntities.get(pos);
-        if (blockEntity == null) {
-            CompoundTag compoundTag = this.pendingBlockEntities.remove(pos);
-            if (compoundTag != null) {
-                BlockEntity blockEntity2 = this.promotePendingBlockEntity(pos, compoundTag);
-                if (blockEntity2 != null) {
-                    return blockEntity2;
+        // CraftBukkit start
+        BlockEntity tileentity = this.level.capturedTileEntities.get(pos);
+        if (tileentity == null) {
+            tileentity = (BlockEntity) this.blockEntities.get(pos);
+        }
+        // CraftBukkit end
+
+        if (tileentity == null) {
+            CompoundTag nbttagcompound = (CompoundTag) this.pendingBlockEntities.remove(pos);
+
+            if (nbttagcompound != null) {
+                BlockEntity tileentity1 = this.promotePendingBlockEntity(pos, nbttagcompound);
+
+                if (tileentity1 != null) {
+                    return tileentity1;
                 }
             }
         }
 
-        if (blockEntity == null) {
+        if (tileentity == null) {
             if (creationType == LevelChunk.EntityCreationType.IMMEDIATE) {
-                blockEntity = this.createBlockEntity(pos);
-                if (blockEntity != null) {
-                    this.addAndRegisterBlockEntity(blockEntity);
+                tileentity = this.createBlockEntity(pos);
+                if (tileentity != null) {
+                    this.addAndRegisterBlockEntity(tileentity);
                 }
             }
-        } else if (blockEntity.isRemoved()) {
+        } else if (tileentity.isRemoved()) {
             this.blockEntities.remove(pos);
             return null;
         }
 
-        return blockEntity;
+        return tileentity;
     }
 
     public void addAndRegisterBlockEntity(BlockEntity blockEntity) {
         this.setBlockEntity(blockEntity);
         if (this.isInLevel()) {
-            if (this.level instanceof ServerLevel serverLevel) {
-                this.addGameEventListener(blockEntity, serverLevel);
+            Level world = this.level;
+
+            if (world instanceof ServerLevel) {
+                ServerLevel worldserver = (ServerLevel) world;
+
+                this.addGameEventListener(blockEntity, worldserver);
             }
 
             this.updateBlockEntityTicker(blockEntity);
         }
+
     }
 
     private boolean isInLevel() {
@@ -365,55 +562,92 @@
     }
 
     boolean isTicking(BlockPos pos) {
-        return this.level.getWorldBorder().isWithinBounds(pos)
-            && (
-                !(this.level instanceof ServerLevel serverLevel)
-                    || this.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING) && serverLevel.areEntitiesLoaded(ChunkPos.asLong(pos))
-            );
+        if (!this.level.getWorldBorder().isWithinBounds(pos)) {
+            return false;
+        } else {
+            Level world = this.level;
+
+            if (!(world instanceof ServerLevel)) {
+                return true;
+            } else {
+                ServerLevel worldserver = (ServerLevel) world;
+
+                return this.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING) && worldserver.areEntitiesLoaded(ChunkPos.asLong(pos));
+            }
+        }
     }
 
     @Override
     public void setBlockEntity(BlockEntity blockEntity) {
-        BlockPos blockPos = blockEntity.getBlockPos();
-        if (this.getBlockState(blockPos).hasBlockEntity()) {
+        BlockPos blockposition = blockEntity.getBlockPos();
+
+        if (this.getBlockState(blockposition).hasBlockEntity()) {
             blockEntity.setLevel(this.level);
             blockEntity.clearRemoved();
-            BlockEntity blockEntity2 = this.blockEntities.put(blockPos.immutable(), blockEntity);
-            if (blockEntity2 != null && blockEntity2 != blockEntity) {
-                blockEntity2.setRemoved();
+            BlockEntity tileentity1 = (BlockEntity) this.blockEntities.put(blockposition.immutable(), blockEntity);
+
+            if (tileentity1 != null && tileentity1 != blockEntity) {
+                tileentity1.setRemoved();
             }
+
+            // CraftBukkit start
+        } else {
+            // Paper start - ServerExceptionEvent
+            ServerInternalException e = new ServerInternalException(
+                "Attempted to place a tile entity (" + blockEntity + ") at " + blockEntity.getBlockPos().getX() + ","
+                    + blockEntity.getBlockPos().getY() + "," + blockEntity.getBlockPos().getZ()
+                    + " (" + getBlockState(blockposition) + ") where there was no entity tile!\n" +
+                    "Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16) +
+                    "\nWorld: " + level.getLevel().dimension().location());
+            e.printStackTrace();
+            ServerInternalException.reportInternalException(e);
+            // Paper end - ServerExceptionEvent
+            // CraftBukkit end
         }
     }
 
     @Nullable
     @Override
     public CompoundTag getBlockEntityNbtForSaving(BlockPos pos, HolderLookup.Provider registryLookup) {
-        BlockEntity blockEntity = this.getBlockEntity(pos);
-        if (blockEntity != null && !blockEntity.isRemoved()) {
-            CompoundTag compoundTag = blockEntity.saveWithFullMetadata(this.level.registryAccess());
-            compoundTag.putBoolean("keepPacked", false);
-            return compoundTag;
+        BlockEntity tileentity = this.getBlockEntity(pos);
+        CompoundTag nbttagcompound;
+
+        if (tileentity != null && !tileentity.isRemoved()) {
+            nbttagcompound = tileentity.saveWithFullMetadata(this.level.registryAccess());
+            nbttagcompound.putBoolean("keepPacked", false);
+            return nbttagcompound;
         } else {
-            CompoundTag compoundTag2 = this.pendingBlockEntities.get(pos);
-            if (compoundTag2 != null) {
-                compoundTag2 = compoundTag2.copy();
-                compoundTag2.putBoolean("keepPacked", true);
+            nbttagcompound = (CompoundTag) this.pendingBlockEntities.get(pos);
+            if (nbttagcompound != null) {
+                nbttagcompound = nbttagcompound.copy();
+                nbttagcompound.putBoolean("keepPacked", true);
             }
 
-            return compoundTag2;
+            return nbttagcompound;
         }
     }
 
     @Override
     public void removeBlockEntity(BlockPos pos) {
         if (this.isInLevel()) {
-            BlockEntity blockEntity = this.blockEntities.remove(pos);
-            if (blockEntity != null) {
-                if (this.level instanceof ServerLevel serverLevel) {
-                    this.removeGameEventListener(blockEntity, serverLevel);
+            BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos);
+
+            // CraftBukkit start - SPIGOT-5561: Also remove from pending map
+            if (!this.pendingBlockEntities.isEmpty()) {
+                this.pendingBlockEntities.remove(pos);
+            }
+            // CraftBukkit end
+
+            if (tileentity != null) {
+                Level world = this.level;
+
+                if (world instanceof ServerLevel) {
+                    ServerLevel worldserver = (ServerLevel) world;
+
+                    this.removeGameEventListener(tileentity, worldserver);
                 }
 
-                blockEntity.setRemoved();
+                tileentity.setRemoved();
             }
         }
 
@@ -422,14 +656,18 @@
 
     private <T extends BlockEntity> void removeGameEventListener(T blockEntity, ServerLevel world) {
         Block block = blockEntity.getBlockState().getBlock();
+
         if (block instanceof EntityBlock) {
-            GameEventListener gameEventListener = ((EntityBlock)block).getListener(world, blockEntity);
-            if (gameEventListener != null) {
+            GameEventListener gameeventlistener = ((EntityBlock) block).getListener(world, blockEntity);
+
+            if (gameeventlistener != null) {
                 int i = SectionPos.blockToSectionCoord(blockEntity.getBlockPos().getY());
-                GameEventListenerRegistry gameEventListenerRegistry = this.getListenerRegistry(i);
-                gameEventListenerRegistry.unregister(gameEventListener);
+                GameEventListenerRegistry gameeventlistenerregistry = this.getListenerRegistry(i);
+
+                gameeventlistenerregistry.unregister(gameeventlistener);
             }
         }
+
     }
 
     private void removeGameEventListenerRegistry(int ySectionCoord) {
@@ -437,10 +675,12 @@
     }
 
     private void removeBlockEntityTicker(BlockPos pos) {
-        LevelChunk.RebindableTickingBlockEntityWrapper rebindableTickingBlockEntityWrapper = this.tickersInLevel.remove(pos);
-        if (rebindableTickingBlockEntityWrapper != null) {
-            rebindableTickingBlockEntityWrapper.rebind(NULL_TICKER);
+        LevelChunk.RebindableTickingBlockEntityWrapper chunk_d = (LevelChunk.RebindableTickingBlockEntityWrapper) this.tickersInLevel.remove(pos);
+
+        if (chunk_d != null) {
+            chunk_d.rebind(LevelChunk.NULL_TICKER);
         }
+
     }
 
     public void runPostLoad() {
@@ -448,39 +688,229 @@
             this.postLoad.run(this);
             this.postLoad = null;
         }
+
+    }
+
+    // Paper start - new load callbacks
+    private io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder;
+    public io.papermc.paper.chunk.system.scheduling.NewChunkHolder getChunkHolder() {
+        return this.chunkHolder;
+    }
+
+    public void setChunkHolder(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) {
+        if (chunkHolder == null) {
+            throw new NullPointerException("Chunkholder cannot be null");
+        }
+        if (this.chunkHolder != null) {
+            throw new IllegalStateException("Already have chunkholder: " + this.chunkHolder + ", cannot replace with " + chunkHolder);
+        }
+        this.chunkHolder = chunkHolder;
+        this.playerChunk = chunkHolder.vanillaChunkHolder;
+    }
+
+    /* Note: We skip the light neighbour chunk loading done for the vanilla full chunk */
+    /* Starlight does not need these chunks for lighting purposes because of edge checks */
+    public void pushChunkIntoLoadedMap() {
+        int chunkX = this.chunkPos.x;
+        int chunkZ = this.chunkPos.z;
+        net.minecraft.server.level.ServerChunkCache chunkProvider = this.level.getChunkSource();
+        for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) {
+            for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) {
+                LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz);
+                if (neighbour != null) {
+                    neighbour.setNeighbourLoaded(-dx, -dz, this);
+                    // should be in cached already
+                    this.setNeighbourLoaded(dx, dz, neighbour);
+                }
+            }
+        }
+        this.setNeighbourLoaded(0, 0, this);
+        this.level.getChunkSource().addLoadedChunk(this);
+    }
+
+    public void onChunkLoad(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) {
+        // figure out how this should interface with:
+        // the entity chunk load event // -> moved to the FULL status
+        // the chunk load event // -> stays here
+        // any entity add to world events // -> in FULL status
+        this.loadCallback();
+        io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(this, chunkHolder.vanillaChunkHolder);
+    }
+
+    public void onChunkUnload(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) {
+        // figure out how this should interface with:
+        // the entity chunk load event // -> moved to chunk unload to disk (not written yet)
+        // the chunk load event // -> stays here
+        // any entity add to world events // -> goes into the unload logic, it will completely explode
+        // etc later
+        this.unloadCallback();
+        io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this, chunkHolder.vanillaChunkHolder);
+    }
+
+    public void onChunkTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) {
+        this.postProcessGeneration();
+        this.level.startTickingChunk(this);
+        io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(this, chunkHolder.vanillaChunkHolder);
+    }
+
+    public void onChunkNotTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) {
+        io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this, chunkHolder.vanillaChunkHolder);
+    }
+
+    public void onChunkEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) {
+        io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(this, chunkHolder.vanillaChunkHolder);
     }
 
+    public void onChunkNotEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) {
+        io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this, chunkHolder.vanillaChunkHolder);
+    }
+    // Paper end - new load callbacks
+
+    // CraftBukkit start
+    public void loadCallback() {
+        if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper
+        // Paper - rewrite chunk system - move into separate callback
+        this.loadedTicketLevel = true;
+        // Paper - rewrite chunk system - move into separate callback
+        org.bukkit.Server server = this.level.getCraftServer();
+        // Paper - rewrite chunk system - move into separate callback
+        if (server != null) {
+            /*
+             * If it's a new world, the first few chunks are generated inside
+             * the World constructor. We can't reliably alter that, so we have
+             * no way of creating a CraftWorld/CraftServer at that point.
+             */
+            org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
+            server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
+            this.chunkHolder.getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system
+
+            if (this.needsDecoration) {
+                try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper
+                this.needsDecoration = false;
+                java.util.Random random = new java.util.Random();
+                random.setSeed(this.level.getSeed());
+                long xRand = random.nextLong() / 2L * 2L + 1L;
+                long zRand = random.nextLong() / 2L * 2L + 1L;
+                random.setSeed((long) this.chunkPos.x * xRand + (long) this.chunkPos.z * zRand ^ this.level.getSeed());
+
+                org.bukkit.World world = this.level.getWorld();
+                if (world != null) {
+                    this.level.populating = true;
+                    try {
+                        for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
+                            populator.populate(world, random, bukkitChunk);
+                        }
+                    } finally {
+                        this.level.populating = false;
+                    }
+                }
+                server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
+                } // Paper
+            }
+        }
+    }
+
+    public void unloadCallback() {
+        if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper
+        org.bukkit.Server server = this.level.getCraftServer();
+        this.chunkHolder.getEntityChunk().callEntitiesUnloadEvent(); // Paper - rewrite chunk system
+        org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
+        org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, true); // Paper - rewrite chunk system - force save to true so that mustNotSave is correctly set below
+        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 - neighbour cache
+        int chunkX = this.chunkPos.x;
+        int chunkZ = this.chunkPos.z;
+        net.minecraft.server.level.ServerChunkCache chunkProvider = this.level.getChunkSource();
+        for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) {
+            for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) {
+                LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz);
+                if (neighbour != null) {
+                    neighbour.setNeighbourUnloaded(-dx, -dz);
+                }
+            }
+        }
+        this.loadedTicketLevel = false;
+        this.resetNeighbours();
+        // Paper end
+    }
+
+    // Paper start - add dirty system to tick lists
+    @Override
+    public void setUnsaved(boolean needsSaving) {
+        if (!needsSaving) {
+            this.blockTicks.clearDirty();
+            this.fluidTicks.clearDirty();
+        }
+        super.setUnsaved(needsSaving);
+    }
+    // Paper end - add dirty system to tick lists
+
+    @Override
+    public boolean isUnsaved() {
+        // Paper start - add dirty system to tick lists
+        long gameTime = this.level.getLevelData().getGameTime();
+        if (this.blockTicks.isDirty(gameTime) || this.fluidTicks.isDirty(gameTime)) {
+            return true;
+        }
+        // Paper end - add dirty system to tick lists
+        return super.isUnsaved(); // Paper - rewrite chunk system - do NOT clobber the dirty flag
+    }
+    // CraftBukkit end
+
     public boolean isEmpty() {
         return false;
     }
 
     public void replaceWithPacketData(FriendlyByteBuf buf, CompoundTag nbt, Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> consumer) {
         this.clearAllBlockEntities();
+        LevelChunkSection[] achunksection = this.sections;
+        int i = achunksection.length;
 
-        for (LevelChunkSection levelChunkSection : this.sections) {
-            levelChunkSection.read(buf);
+        int j;
+
+        for (j = 0; j < i; ++j) {
+            LevelChunkSection chunksection = achunksection[j];
+
+            chunksection.read(buf);
         }
 
-        for (Heightmap.Types types : Heightmap.Types.values()) {
-            String string = types.getSerializationKey();
-            if (nbt.contains(string, 12)) {
-                this.setHeightmap(types, nbt.getLongArray(string));
+        Heightmap.Types[] aheightmap_type = Heightmap.Types.values();
+
+        i = aheightmap_type.length;
+
+        for (j = 0; j < i; ++j) {
+            Heightmap.Types heightmap_type = aheightmap_type[j];
+            String s = heightmap_type.getSerializationKey();
+
+            if (nbt.contains(s, 12)) {
+                this.setHeightmap(heightmap_type, nbt.getLongArray(s));
             }
         }
 
         this.initializeLightSources();
-        consumer.accept((pos, blockEntityType, nbtx) -> {
-            BlockEntity blockEntity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE);
-            if (blockEntity != null && nbtx != null && blockEntity.getType() == blockEntityType) {
-                blockEntity.loadWithComponents(nbtx, this.level.registryAccess());
+        consumer.accept((blockposition, tileentitytypes, nbttagcompound1) -> {
+            BlockEntity tileentity = this.getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE);
+
+            if (tileentity != null && nbttagcompound1 != null && tileentity.getType() == tileentitytypes) {
+                tileentity.loadWithComponents(nbttagcompound1, this.level.registryAccess());
             }
+
         });
     }
 
     public void replaceBiomes(FriendlyByteBuf buf) {
-        for (LevelChunkSection levelChunkSection : this.sections) {
-            levelChunkSection.readBiomes(buf);
+        LevelChunkSection[] achunksection = this.sections;
+        int i = achunksection.length;
+
+        for (int j = 0; j < i; ++j) {
+            LevelChunkSection chunksection = achunksection[j];
+
+            chunksection.readBiomes(buf);
         }
+
     }
 
     public void setLoaded(boolean loadedToWorld) {
@@ -495,22 +925,30 @@
         return this.blockEntities;
     }
 
+    public boolean isPostProcessingDone; // Paper - replace chunk loader system
     public void postProcessGeneration() {
-        ChunkPos chunkPos = this.getPos();
+        try { // Paper - replace chunk loader system
+        ChunkPos chunkcoordintpair = this.getPos();
 
-        for (int i = 0; i < this.postProcessing.length; i++) {
+        for (int i = 0; i < this.postProcessing.length; ++i) {
             if (this.postProcessing[i] != null) {
-                for (Short short_ : this.postProcessing[i]) {
-                    BlockPos blockPos = ProtoChunk.unpackOffsetCoordinates(short_, this.getSectionYFromSectionIndex(i), chunkPos);
-                    BlockState blockState = this.getBlockState(blockPos);
-                    FluidState fluidState = blockState.getFluidState();
-                    if (!fluidState.isEmpty()) {
-                        fluidState.tick(this.level, blockPos);
+                ShortListIterator shortlistiterator = this.postProcessing[i].iterator();
+
+                while (shortlistiterator.hasNext()) {
+                    Short oshort = (Short) shortlistiterator.next();
+                    BlockPos blockposition = ProtoChunk.unpackOffsetCoordinates(oshort, this.getSectionYFromSectionIndex(i), chunkcoordintpair);
+                    BlockState iblockdata = this.getBlockState(blockposition);
+                    FluidState fluid = iblockdata.getFluidState();
+
+                    if (!fluid.isEmpty()) {
+                        fluid.tick(this.level, blockposition);
                     }
 
-                    if (!(blockState.getBlock() instanceof LiquidBlock)) {
-                        BlockState blockState2 = Block.updateFromNeighbourShapes(blockState, this.level, blockPos);
-                        this.level.setBlock(blockPos, blockState2, 20);
+                    if (!(iblockdata.getBlock() instanceof LiquidBlock)) {
+                        BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, this.level, blockposition);
+
+                        this.level.setBlock(blockposition, iblockdata1, 20);
+                        if (iblockdata1 != iblockdata) this.level.chunkSource.blockChanged(blockposition); // Paper - replace player chunk loader - notify since we send before processing full updates
                     }
                 }
 
@@ -518,37 +956,46 @@
             }
         }
 
-        for (BlockPos blockPos2 : ImmutableList.copyOf(this.pendingBlockEntities.keySet())) {
-            this.getBlockEntity(blockPos2);
+        UnmodifiableIterator unmodifiableiterator = ImmutableList.copyOf(this.pendingBlockEntities.keySet()).iterator();
+
+        while (unmodifiableiterator.hasNext()) {
+            BlockPos blockposition1 = (BlockPos) unmodifiableiterator.next();
+
+            this.getBlockEntity(blockposition1);
         }
 
         this.pendingBlockEntities.clear();
         this.upgradeData.upgrade(this);
+        } finally { // Paper start - replace chunk loader system
+            this.isPostProcessingDone = true;
+        }
+        // Paper end - replace chunk loader system
     }
 
     @Nullable
     private BlockEntity promotePendingBlockEntity(BlockPos pos, CompoundTag nbt) {
-        BlockState blockState = this.getBlockState(pos);
-        BlockEntity blockEntity;
+        BlockState iblockdata = this.getBlockState(pos);
+        BlockEntity tileentity;
+
         if ("DUMMY".equals(nbt.getString("id"))) {
-            if (blockState.hasBlockEntity()) {
-                blockEntity = ((EntityBlock)blockState.getBlock()).newBlockEntity(pos, blockState);
+            if (iblockdata.hasBlockEntity()) {
+                tileentity = ((EntityBlock) iblockdata.getBlock()).newBlockEntity(pos, iblockdata);
             } else {
-                blockEntity = null;
-                LOGGER.warn("Tried to load a DUMMY block entity @ {} but found not block entity block {} at location", pos, blockState);
+                tileentity = null;
+                LevelChunk.LOGGER.warn("Tried to load a DUMMY block entity @ {} but found not block entity block {} at location", pos, iblockdata);
             }
         } else {
-            blockEntity = BlockEntity.loadStatic(pos, blockState, nbt, this.level.registryAccess());
+            tileentity = BlockEntity.loadStatic(pos, iblockdata, nbt, this.level.registryAccess());
         }
 
-        if (blockEntity != null) {
-            blockEntity.setLevel(this.level);
-            this.addAndRegisterBlockEntity(blockEntity);
+        if (tileentity != null) {
+            tileentity.setLevel(this.level);
+            this.addAndRegisterBlockEntity(tileentity);
         } else {
-            LOGGER.warn("Tried to load a block entity for block {} but failed at location {}", blockState, pos);
+            LevelChunk.LOGGER.warn("Tried to load a block entity for block {} but failed at location {}", iblockdata, pos);
         }
 
-        return blockEntity;
+        return tileentity;
     }
 
     public void unpackTicks(long time) {
@@ -572,7 +1019,7 @@
     }
 
     public FullChunkStatus getFullStatus() {
-        return this.fullStatus == null ? FullChunkStatus.FULL : this.fullStatus.get();
+        return this.chunkHolder == null ? FullChunkStatus.INACCESSIBLE : this.chunkHolder.getChunkStatus(); // Paper - rewrite chunk system
     }
 
     public void setFullStatus(Supplier<FullChunkStatus> levelTypeProvider) {
@@ -582,172 +1029,192 @@
     public void clearAllBlockEntities() {
         this.blockEntities.values().forEach(BlockEntity::setRemoved);
         this.blockEntities.clear();
-        this.tickersInLevel.values().forEach(ticker -> ticker.rebind(NULL_TICKER));
+        this.tickersInLevel.values().forEach((chunk_d) -> {
+            chunk_d.rebind(LevelChunk.NULL_TICKER);
+        });
         this.tickersInLevel.clear();
     }
 
     public void registerAllBlockEntitiesAfterLevelLoad() {
-        this.blockEntities.values().forEach(blockEntity -> {
-            if (this.level instanceof ServerLevel serverLevel) {
-                this.addGameEventListener(blockEntity, serverLevel);
+        this.blockEntities.values().forEach((tileentity) -> {
+            Level world = this.level;
+
+            if (world instanceof ServerLevel worldserver) {
+                this.addGameEventListener(tileentity, worldserver);
             }
 
-            this.updateBlockEntityTicker(blockEntity);
+            this.updateBlockEntityTicker(tileentity);
         });
     }
 
     private <T extends BlockEntity> void addGameEventListener(T blockEntity, ServerLevel world) {
         Block block = blockEntity.getBlockState().getBlock();
+
         if (block instanceof EntityBlock) {
-            GameEventListener gameEventListener = ((EntityBlock)block).getListener(world, blockEntity);
-            if (gameEventListener != null) {
-                this.getListenerRegistry(SectionPos.blockToSectionCoord(blockEntity.getBlockPos().getY())).register(gameEventListener);
+            GameEventListener gameeventlistener = ((EntityBlock) block).getListener(world, blockEntity);
+
+            if (gameeventlistener != null) {
+                this.getListenerRegistry(SectionPos.blockToSectionCoord(blockEntity.getBlockPos().getY())).register(gameeventlistener);
             }
         }
+
     }
 
     private <T extends BlockEntity> void updateBlockEntityTicker(T blockEntity) {
-        BlockState blockState = blockEntity.getBlockState();
-        BlockEntityTicker<T> blockEntityTicker = blockState.getTicker(this.level, (BlockEntityType<T>)blockEntity.getType());
-        if (blockEntityTicker == null) {
+        BlockState iblockdata = blockEntity.getBlockState();
+        BlockEntityTicker<T> blockentityticker = iblockdata.getTicker(this.level, (BlockEntityType<T>) blockEntity.getType()); // CraftBukkit - decompile error
+
+        if (blockentityticker == null) {
             this.removeBlockEntityTicker(blockEntity.getBlockPos());
         } else {
-            this.tickersInLevel
-                .compute(
-                    blockEntity.getBlockPos(),
-                    (pos, ticker) -> {
-                        TickingBlockEntity tickingBlockEntity = this.createTicker(blockEntity, blockEntityTicker);
-                        if (ticker != null) {
-                            ticker.rebind(tickingBlockEntity);
-                            return (LevelChunk.RebindableTickingBlockEntityWrapper)ticker;
-                        } else if (this.isInLevel()) {
-                            LevelChunk.RebindableTickingBlockEntityWrapper rebindableTickingBlockEntityWrapper = new LevelChunk.RebindableTickingBlockEntityWrapper(
-                                tickingBlockEntity
-                            );
-                            this.level.addBlockEntityTicker(rebindableTickingBlockEntityWrapper);
-                            return rebindableTickingBlockEntityWrapper;
-                        } else {
-                            return null;
-                        }
-                    }
-                );
+            this.tickersInLevel.compute(blockEntity.getBlockPos(), (blockposition, chunk_d) -> {
+                TickingBlockEntity tickingblockentity = this.createTicker(blockEntity, blockentityticker);
+
+                if (chunk_d != null) {
+                    chunk_d.rebind(tickingblockentity);
+                    return chunk_d;
+                } else if (this.isInLevel()) {
+                    LevelChunk.RebindableTickingBlockEntityWrapper chunk_d1 = new LevelChunk.RebindableTickingBlockEntityWrapper(this, tickingblockentity);
+
+                    this.level.addBlockEntityTicker(chunk_d1);
+                    return chunk_d1;
+                } else {
+                    return null;
+                }
+            });
         }
+
     }
 
     private <T extends BlockEntity> TickingBlockEntity createTicker(T blockEntity, BlockEntityTicker<T> blockEntityTicker) {
         return new LevelChunk.BoundTickingBlockEntity<>(blockEntity, blockEntityTicker);
     }
 
-    class BoundTickingBlockEntity<T extends BlockEntity> implements TickingBlockEntity {
-        private final T blockEntity;
-        private final BlockEntityTicker<T> ticker;
-        private boolean loggedInvalidBlockState;
+    @FunctionalInterface
+    public interface PostLoadProcessor {
 
-        BoundTickingBlockEntity(final T blockEntity, final BlockEntityTicker<T> ticker) {
-            this.blockEntity = blockEntity;
-            this.ticker = ticker;
+        void run(LevelChunk chunk);
+    }
+
+    public static enum EntityCreationType {
+
+        IMMEDIATE, QUEUED, CHECK;
+
+        private EntityCreationType() {}
+    }
+
+    private class RebindableTickingBlockEntityWrapper implements TickingBlockEntity {
+
+        private TickingBlockEntity ticker;
+
+        RebindableTickingBlockEntityWrapper(final LevelChunk wrapped, final TickingBlockEntity tickingblockentity) {
+            this.ticker = tickingblockentity;
+        }
+
+        void rebind(TickingBlockEntity wrapped) {
+            this.ticker = wrapped;
         }
 
         @Override
         public void tick() {
-            if (!this.blockEntity.isRemoved() && this.blockEntity.hasLevel()) {
-                BlockPos blockPos = this.blockEntity.getBlockPos();
-                if (LevelChunk.this.isTicking(blockPos)) {
-                    try {
-                        ProfilerFiller profilerFiller = LevelChunk.this.level.getProfiler();
-                        profilerFiller.push(this::getType);
-                        BlockState blockState = LevelChunk.this.getBlockState(blockPos);
-                        if (this.blockEntity.getType().isValid(blockState)) {
-                            this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity);
-                            this.loggedInvalidBlockState = false;
-                        } else if (!this.loggedInvalidBlockState) {
-                            this.loggedInvalidBlockState = true;
-                            LevelChunk.LOGGER
-                                .warn(
-                                    "Block entity {} @ {} state {} invalid for ticking:",
-                                    LogUtils.defer(this::getType),
-                                    LogUtils.defer(this::getPos),
-                                    blockState
-                                );
-                        }
-
-                        profilerFiller.pop();
-                    } catch (Throwable var5) {
-                        CrashReport crashReport = CrashReport.forThrowable(var5, "Ticking block entity");
-                        CrashReportCategory crashReportCategory = crashReport.addCategory("Block entity being ticked");
-                        this.blockEntity.fillCrashReportCategory(crashReportCategory);
-                        throw new ReportedException(crashReport);
-                    }
-                }
-            }
+            this.ticker.tick();
         }
 
         @Override
         public boolean isRemoved() {
-            return this.blockEntity.isRemoved();
+            return this.ticker.isRemoved();
         }
 
         @Override
         public BlockPos getPos() {
-            return this.blockEntity.getBlockPos();
+            return this.ticker.getPos();
         }
 
         @Override
         public String getType() {
-            return BlockEntityType.getKey(this.blockEntity.getType()).toString();
+            return this.ticker.getType();
         }
 
-        @Override
         public String toString() {
-            return "Level ticker for " + this.getType() + "@" + this.getPos();
+            return String.valueOf(this.ticker) + " <wrapped>";
         }
     }
 
-    public static enum EntityCreationType {
-        IMMEDIATE,
-        QUEUED,
-        CHECK;
-    }
-
-    @FunctionalInterface
-    public interface PostLoadProcessor {
-        void run(LevelChunk chunk);
-    }
-
-    class RebindableTickingBlockEntityWrapper implements TickingBlockEntity {
-        private TickingBlockEntity ticker;
+    private class BoundTickingBlockEntity<T extends BlockEntity> implements TickingBlockEntity {
 
-        RebindableTickingBlockEntityWrapper(final TickingBlockEntity wrapped) {
-            this.ticker = wrapped;
-        }
+        private final T blockEntity;
+        private final BlockEntityTicker<T> ticker;
+        private boolean loggedInvalidBlockState;
 
-        void rebind(TickingBlockEntity wrapped) {
-            this.ticker = wrapped;
+        BoundTickingBlockEntity(final BlockEntity tileentity, final BlockEntityTicker blockentityticker) {
+            this.blockEntity = (T) tileentity; // CraftBukkit - decompile error
+            this.ticker = blockentityticker;
         }
 
         @Override
         public void tick() {
-            this.ticker.tick();
+            if (!this.blockEntity.isRemoved() && this.blockEntity.hasLevel()) {
+                BlockPos blockposition = this.blockEntity.getBlockPos();
+
+                if (LevelChunk.this.isTicking(blockposition)) {
+                    try {
+                        ProfilerFiller gameprofilerfiller = LevelChunk.this.level.getProfiler();
+
+                        gameprofilerfiller.push(this::getType);
+                        this.blockEntity.tickTimer.startTiming(); // Spigot
+                        BlockState iblockdata = LevelChunk.this.getBlockState(blockposition);
+
+                        if (this.blockEntity.getType().isValid(iblockdata)) {
+                            this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), iblockdata, this.blockEntity);
+                            this.loggedInvalidBlockState = false;
+                        // Paper start - Remove the Block Entity if it's invalid
+                        } else {
+                            LevelChunk.this.removeBlockEntity(this.getPos());
+                            if (!this.loggedInvalidBlockState) {
+                                this.loggedInvalidBlockState = true;
+                                LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata});
+                            }
+                            // Paper end - Remove the Block Entity if it's invalid
+                        }
+
+                        gameprofilerfiller.pop();
+                    } catch (Throwable throwable) {
+                        if (throwable instanceof ThreadDeath) throw throwable; // Paper
+                        // Paper start - Prevent block entity and entity crashes
+                        final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
+                        net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
+                        net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
+                        LevelChunk.this.removeBlockEntity(this.getPos());
+                        // Paper end - Prevent block entity and entity crashes
+                        // Spigot start
+                    } finally {
+                        this.blockEntity.tickTimer.stopTiming();
+                        // Spigot end
+                    }
+                }
+            }
+
         }
 
         @Override
         public boolean isRemoved() {
-            return this.ticker.isRemoved();
+            return this.blockEntity.isRemoved();
         }
 
         @Override
         public BlockPos getPos() {
-            return this.ticker.getPos();
+            return this.blockEntity.getBlockPos();
         }
 
         @Override
         public String getType() {
-            return this.ticker.getType();
+            return BlockEntityType.getKey(this.blockEntity.getType()).toString();
         }
 
-        @Override
         public String toString() {
-            return this.ticker + " <wrapped>";
+            String s = this.getType();
+
+            return "Level ticker for " + s + "@" + String.valueOf(this.getPos());
         }
     }
 }
