--- a/net/minecraft/world/level/chunk/Chunk.java
+++ b/net/minecraft/world/level/chunk/Chunk.java
@@ -75,7 +75,7 @@
     };
     private final Map<BlockPosition, Chunk.d> tickersInLevel;
     public boolean loaded;
-    public final World level;
+    public final WorldServer level; // CraftBukkit - type
     @Nullable
     private Supplier<FullChunkStatus> fullStatus;
     @Nullable
@@ -91,7 +91,7 @@
     public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, LevelChunkTicks<Block> levelchunkticks, LevelChunkTicks<FluidType> levelchunkticks1, long i, @Nullable ChunkSection[] achunksection, @Nullable Chunk.c chunk_c, @Nullable BlendingData blendingdata) {
         super(chunkcoordintpair, chunkconverter, world, world.registryAccess().registryOrThrow(Registries.BIOME), i, achunksection, blendingdata);
         this.tickersInLevel = Maps.newHashMap();
-        this.level = world;
+        this.level = (WorldServer) world; // CraftBukkit - type
         this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap();
         HeightMap.Type[] aheightmap_type = HeightMap.Type.values();
         int j = aheightmap_type.length;
@@ -109,6 +109,11 @@
         this.fluidTicks = levelchunkticks1;
     }
 
+    // CraftBukkit start
+    public boolean mustNotSave;
+    public boolean needsDecoration;
+    // CraftBukkit end
+
     public Chunk(WorldServer worldserver, ProtoChunk protochunk, @Nullable Chunk.c chunk_c) {
         this(worldserver, protochunk.getPos(), protochunk.getUpgradeData(), protochunk.unpackBlockTicks(), protochunk.unpackFluidTicks(), protochunk.getInhabitedTime(), protochunk.getSections(), chunk_c, protochunk.getBlendingData());
         Iterator iterator = protochunk.getBlockEntities().values().iterator();
@@ -140,6 +145,10 @@
         this.skyLightSources = protochunk.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
@@ -244,9 +253,16 @@
         }
     }
 
+    // CraftBukkit start
     @Nullable
     @Override
     public IBlockData setBlockState(BlockPosition blockposition, IBlockData iblockdata, boolean flag) {
+        return this.setBlockState(blockposition, iblockdata, flag, true);
+    }
+
+    @Nullable
+    public IBlockData setBlockState(BlockPosition blockposition, IBlockData iblockdata, boolean flag, boolean doPlace) {
+        // CraftBukkit end
         int i = blockposition.getY();
         ChunkSection chunksection = this.getSection(this.getSectionIndex(i));
         boolean flag1 = chunksection.hasOnlyAir();
@@ -295,7 +311,8 @@
                 if (!chunksection.getBlockState(j, k, l).is(block)) {
                     return null;
                 } else {
-                    if (!this.level.isClientSide) {
+                    // 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.BlockTileEntity)) {
                         iblockdata.onPlace(this.level, blockposition, iblockdata1, flag);
                     }
 
@@ -340,7 +357,12 @@
 
     @Nullable
     public TileEntity getBlockEntity(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) {
-        TileEntity tileentity = (TileEntity) this.blockEntities.get(blockposition);
+        // CraftBukkit start
+        TileEntity tileentity = level.capturedTileEntities.get(blockposition);
+        if (tileentity == null) {
+            tileentity = (TileEntity) this.blockEntities.get(blockposition);
+        }
+        // CraftBukkit end
 
         if (tileentity == null) {
             NBTTagCompound nbttagcompound = (NBTTagCompound) this.pendingBlockEntities.remove(blockposition);
@@ -418,6 +440,13 @@
                 tileentity1.setRemoved();
             }
 
+            // CraftBukkit start
+        } else {
+            System.out.println("Attempted to place a tile entity (" + tileentity + ") at " + tileentity.getBlockPos().getX() + "," + tileentity.getBlockPos().getY() + "," + tileentity.getBlockPos().getZ()
+                + " (" + getBlockState(blockposition) + ") where there was no entity tile!");
+            System.out.println("Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16));
+            new Exception().printStackTrace();
+            // CraftBukkit end
         }
     }
 
@@ -447,6 +476,12 @@
         if (this.isInLevel()) {
             TileEntity tileentity = (TileEntity) this.blockEntities.remove(blockposition);
 
+            // CraftBukkit start - SPIGOT-5561: Also remove from pending map
+            if (!pendingBlockEntities.isEmpty()) {
+                pendingBlockEntities.remove(blockposition);
+            }
+            // CraftBukkit end
+
             if (tileentity != null) {
                 World world = this.level;
 
@@ -500,6 +535,57 @@
 
     }
 
+    // CraftBukkit start
+    public void loadCallback() {
+        org.bukkit.Server server = this.level.getCraftServer();
+        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(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 ^ 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();
+    }
+
+    @Override
+    public boolean isUnsaved() {
+        return super.isUnsaved() && !this.mustNotSave;
+    }
+    // CraftBukkit end
+
     public boolean isEmpty() {
         return false;
     }
@@ -697,7 +783,7 @@
 
     private <T extends TileEntity> void updateBlockEntityTicker(T t0) {
         IBlockData iblockdata = t0.getBlockState();
-        BlockEntityTicker<T> blockentityticker = iblockdata.getTicker(this.level, t0.getType());
+        BlockEntityTicker<T> blockentityticker = iblockdata.getTicker(this.level, (TileEntityTypes<T>) t0.getType()); // CraftBukkit - decompile error
 
         if (blockentityticker == null) {
             this.removeBlockEntityTicker(t0.getBlockPos());
@@ -782,7 +868,7 @@
         private boolean loggedInvalidBlockState;
 
         a(TileEntity tileentity, BlockEntityTicker blockentityticker) {
-            this.blockEntity = tileentity;
+            this.blockEntity = (T) tileentity; // CraftBukkit - decompile error
             this.ticker = blockentityticker;
         }
 
