--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -76,7 +_,7 @@
     };
     private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel = Maps.newHashMap();
     public boolean loaded;
-    public final Level level;
+    public final ServerLevel level; // CraftBukkit - type
     @Nullable
     private Supplier<FullChunkStatus> fullStatus;
     @Nullable
@@ -85,6 +_,14 @@
     private final LevelChunkTicks<Block> blockTicks;
     private final LevelChunkTicks<Fluid> fluidTicks;
     private LevelChunk.UnsavedListener unsavedListener = chunkPos -> {};
+    // CraftBukkit start
+    public boolean mustNotSave;
+    public boolean needsDecoration;
+    // CraftBukkit end
+
+    // Paper start
+    boolean loadedTicketLevel;
+    // Paper end
 
     public LevelChunk(Level level, ChunkPos pos) {
         this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
@@ -102,7 +_,7 @@
         @Nullable BlendingData blendingData
     ) {
         super(pos, data, level, level.registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData);
-        this.level = level;
+        this.level = (ServerLevel) level; // CraftBukkit - type
         this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>();
 
         for (Heightmap.Types types : Heightmap.Types.values()) {
@@ -154,6 +_,10 @@
         this.skyLightSources = chunk.skyLightSources;
         this.setLightCorrect(chunk.isLightCorrect());
         this.markUnsaved();
+        this.needsDecoration = true; // CraftBukkit
+        // CraftBukkit start
+        this.persistentDataContainer = chunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
+        // CraftBukkit end
     }
 
     public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) {
@@ -162,6 +_,12 @@
             unsavedListener.setUnsaved(this.chunkPos);
         }
     }
+    // Paper start
+    @Override
+    public long getInhabitedTime() {
+        return this.level.paperConfig().chunks.fixedChunkInhabitedTime < 0 ? super.getInhabitedTime() : this.level.paperConfig().chunks.fixedChunkInhabitedTime;
+    }
+    // Paper end
 
     @Override
     public void markUnsaved() {
@@ -195,8 +_,25 @@
             : super.getListenerRegistry(sectionY);
     }
 
+    // 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 x = pos.getX();
         int y = pos.getY();
         int z = pos.getZ();
@@ -231,33 +_,54 @@
         }
     }
 
+    // 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 {
+        // try { // Paper start - Perf: Optimise Chunk#getFluid
             int sectionIndex = this.getSectionIndex(y);
             if (sectionIndex >= 0 && sectionIndex < this.sections.length) {
                 LevelChunkSection levelChunkSection = this.sections[sectionIndex];
                 if (!levelChunkSection.hasOnlyAir()) {
-                    return levelChunkSection.getFluidState(x & 15, y & 15, z & 15);
+                    return levelChunkSection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid
                 }
             }
 
             return Fluids.EMPTY.defaultFluidState();
+        /* // Paper - Perf: Optimise Chunk#getFluid
         } 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
     }
 
     @Nullable
     @Override
     public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) {
+// CraftBukkit start
+        return this.setBlockState(pos, state, isMoving, true);
+    }
+
+    @Nullable
+    public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) {
+        // CraftBukkit end
         int y = pos.getY();
         LevelChunkSection section = this.getSection(this.getSectionIndex(y));
         boolean hasOnlyAir = section.hasOnlyAir();
@@ -292,7 +_,7 @@
                 }
 
                 boolean hasBlockEntity = blockState.hasBlockEntity();
-                if (!this.level.isClientSide) {
+                if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
                     blockState.onRemove(this.level, pos, state, isMoving);
                 } else if (!blockState.is(block) && hasBlockEntity) {
                     this.removeBlockEntity(pos);
@@ -301,7 +_,7 @@
                 if (!section.getBlockState(i, i1, i2).is(block)) {
                     return null;
                 } else {
-                    if (!this.level.isClientSide) {
+                    if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
                         state.onPlace(this.level, pos, blockState, isMoving);
                     }
 
@@ -355,7 +_,12 @@
 
     @Nullable
     public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) {
-        BlockEntity blockEntity = this.blockEntities.get(pos);
+        // CraftBukkit start
+        BlockEntity blockEntity = this.level.capturedTileEntities.get(pos);
+        if (blockEntity == null) {
+            blockEntity = this.blockEntities.get(pos);
+        }
+        // CraftBukkit end
         if (blockEntity == null) {
             CompoundTag compoundTag = this.pendingBlockEntities.remove(pos);
             if (compoundTag != null) {
@@ -409,7 +_,13 @@
         BlockPos blockPos = blockEntity.getBlockPos();
         BlockState blockState = this.getBlockState(blockPos);
         if (!blockState.hasBlockEntity()) {
-            LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", blockEntity, blockPos, blockState);
+            // Paper start - ServerExceptionEvent
+            com.destroystokyo.paper.exception.ServerInternalException e = new com.destroystokyo.paper.exception.ServerInternalException(
+                "Trying to set block entity %s at position %s, but state %s does not allow it".formatted(blockEntity, blockPos, blockState)
+            );
+            e.printStackTrace();
+            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(e);
+            // Paper end - ServerExceptionEvent
         } else {
             BlockState blockState1 = blockEntity.getBlockState();
             if (blockState != blockState1) {
@@ -457,6 +_,11 @@
     public void removeBlockEntity(BlockPos pos) {
         if (this.isInLevel()) {
             BlockEntity 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 (blockEntity != null) {
                 if (this.level instanceof ServerLevel serverLevel) {
                     this.removeGameEventListener(blockEntity, serverLevel);
@@ -499,6 +_,65 @@
         }
     }
 
+    // CraftBukkit start
+    public void loadCallback() {
+        // Paper start
+        this.loadedTicketLevel = true;
+        // Paper end
+        org.bukkit.Server server = this.level.getCraftServer();
+        this.level.getChunkSource().addLoadedChunk(this); // Paper
+        if (server != null) {
+            /*
+             * If it's a new world, the first few chunks are generated inside
+             * 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));
+
+            if (this.needsDecoration) {
+                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));
+            }
+        }
+    }
+
+    public void unloadCallback() {
+        org.bukkit.Server server = this.level.getCraftServer();
+        org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
+        org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved());
+        server.getPluginManager().callEvent(unloadEvent);
+        // note: saving can be prevented, but not forced if no saving is actually required
+        this.mustNotSave = !unloadEvent.isSaveChunk();
+        this.level.getChunkSource().removeLoadedChunk(this); // Paper
+        // Paper start
+        this.loadedTicketLevel = false;
+        // Paper end
+    }
+
+    @Override
+    public boolean isUnsaved() {
+        return super.isUnsaved() && !this.mustNotSave;
+    }
+    // CraftBukkit end
+
     public boolean isEmpty() {
         return false;
     }
@@ -711,23 +_,24 @@
                         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
-                                );
+                        // 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:", LogUtils.defer(this::getType), LogUtils.defer(this::getPos), blockState);
+                            }
+                            // Paper end - Remove the Block Entity if it's invalid
                         }
 
                         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);
+                        // 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, var5);
+                        net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var5))); // Paper - ServerExceptionEvent
+                        LevelChunk.this.removeBlockEntity(this.getPos());
+                        // Paper end - Prevent block entity and entity crashes
                     }
                 }
             }
