--- a/net/minecraft/world/level/chunk/Chunk.java
+++ b/net/minecraft/world/level/chunk/Chunk.java
@@ -92,7 +92,7 @@
     private final Map<BlockPosition, NBTTagCompound> pendingBlockEntities;
     private final Map<BlockPosition, Chunk.c> tickersInLevel;
     public boolean loaded;
-    public final World level;
+    public final WorldServer level; // CraftBukkit - type
     public final Map<HeightMap.Type, HeightMap> heightmaps;
     private final ChunkConverter upgradeData;
     public final Map<BlockPosition, TileEntity> blockEntities;
@@ -122,7 +122,7 @@
         this.blockEntities = Maps.newHashMap();
         this.structureStarts = Maps.newHashMap();
         this.structuresRefences = Maps.newHashMap();
-        this.level = world;
+        this.level = (WorldServer) world; // CraftBukkit - type
         this.chunkPos = chunkcoordintpair;
         this.upgradeData = chunkconverter;
         this.gameEventDispatcherSections = new Int2ObjectOpenHashMap();
@@ -152,8 +152,22 @@
         }
 
         this.postProcessing = new ShortList[world.getSectionsCount()];
+        // CraftBukkit start
+        this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
     }
 
+    public org.bukkit.Chunk bukkitChunk;
+    public org.bukkit.Chunk getBukkitChunk() {
+        return bukkitChunk;
+    }
+
+    public boolean mustNotSave;
+    public boolean needsDecoration;
+
+    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
+    public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY);
+    // CraftBukkit end
+
     public Chunk(WorldServer worldserver, ProtoChunk protochunk, @Nullable Consumer<Chunk> consumer) {
         this(worldserver, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.q(), protochunk.o(), protochunk.p(), protochunk.getInhabitedTime(), protochunk.getSections(), consumer);
         Iterator iterator = protochunk.y().values().iterator();
@@ -184,6 +198,7 @@
 
         this.b(protochunk.s());
         this.unsaved = true;
+        this.needsDecoration = true; // CraftBukkit
     }
 
     @Override
@@ -285,9 +300,16 @@
         }
     }
 
+    // CraftBukkit start
     @Nullable
     @Override
     public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag) {
+        return this.setType(blockposition, iblockdata, flag, true);
+    }
+
+    @Nullable
+    public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag, boolean doPlace) {
+        // CraftBukkit end
         int i = blockposition.getY();
         int j = this.getSectionIndex(i);
         ChunkSection chunksection = this.sections[j];
@@ -333,7 +355,8 @@
             if (!chunksection.getType(k, l, i1).a(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);
                 }
 
@@ -401,7 +424,12 @@
 
     @Nullable
     public TileEntity a(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);
@@ -460,6 +488,13 @@
                 tileentity1.aa_();
             }
 
+            // CraftBukkit start
+        } else {
+            System.out.println("Attempted to place a tile entity (" + tileentity + ") at " + tileentity.getPosition().getX() + "," + tileentity.getPosition().getY() + "," + tileentity.getPosition().getZ()
+                + " (" + getType(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
         }
     }
 
@@ -541,6 +576,50 @@
 
     }
 
+    // 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.
+             */
+            server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(this.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.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isNeedsSaving());
+        server.getPluginManager().callEvent(unloadEvent);
+        // note: saving can be prevented, but not forced if no saving is actually required
+        this.mustNotSave = !unloadEvent.isSaveChunk();
+    }
+    // CraftBukkit end
+
     public void markDirty() {
         this.unsaved = true;
     }
@@ -664,7 +743,7 @@
 
     @Override
     public boolean isNeedsSaving() {
-        return this.unsaved;
+        return this.unsaved && !this.mustNotSave; // CraftBukkit
     }
 
     @Nullable
@@ -795,7 +874,7 @@
 
     public void B() {
         if (this.blockTicks instanceof ProtoChunkTickList) {
-            ((ProtoChunkTickList) this.blockTicks).a(this.level.getBlockTickList(), (blockposition) -> {
+            ((ProtoChunkTickList<Block>) this.blockTicks).a(this.level.getBlockTickList(), (blockposition) -> { // CraftBukkit - decompile error
                 return this.getType(blockposition).getBlock();
             });
             this.blockTicks = TickListEmpty.b();
@@ -805,7 +884,7 @@
         }
 
         if (this.liquidTicks instanceof ProtoChunkTickList) {
-            ((ProtoChunkTickList) this.liquidTicks).a(this.level.getFluidTickList(), (blockposition) -> {
+            ((ProtoChunkTickList<FluidType>) this.liquidTicks).a(this.level.getFluidTickList(), (blockposition) -> { // CraftBukkit - decompile error
                 return this.getFluid(blockposition).getType();
             });
             this.liquidTicks = TickListEmpty.b();
@@ -819,14 +898,14 @@
     public void a(WorldServer worldserver) {
         RegistryBlocks registryblocks;
 
-        if (this.blockTicks == TickListEmpty.b()) {
+        if (this.blockTicks == TickListEmpty.<Block>b()) { // CraftBukkit - decompile error
             registryblocks = IRegistry.BLOCK;
             Objects.requireNonNull(registryblocks);
             this.blockTicks = new TickListChunk<>(registryblocks::getKey, worldserver.getBlockTickList().a(this.chunkPos, true, false), worldserver.getTime());
             this.setNeedsSaving(true);
         }
 
-        if (this.liquidTicks == TickListEmpty.b()) {
+        if (this.liquidTicks == TickListEmpty.<FluidType>b()) { // CraftBukkit - decompile error
             registryblocks = IRegistry.FLUID;
             Objects.requireNonNull(registryblocks);
             this.liquidTicks = new TickListChunk<>(registryblocks::getKey, worldserver.getFluidTickList().a(this.chunkPos, true, false), worldserver.getTime());
@@ -899,7 +978,7 @@
 
     private <T extends TileEntity> void f(T t0) {
         IBlockData iblockdata = t0.getBlock();
-        BlockEntityTicker<T> blockentityticker = iblockdata.a(this.level, t0.getTileType());
+        BlockEntityTicker<T> blockentityticker = iblockdata.a(this.level, (TileEntityTypes<T>) t0.getTileType()); // CraftBukkit - decompile error
 
         if (blockentityticker == null) {
             this.l(t0.getPosition());
@@ -978,7 +1057,7 @@
         private boolean loggedInvalidBlockState;
 
         a(TileEntity tileentity, BlockEntityTicker blockentityticker) {
-            this.blockEntity = tileentity;
+            this.blockEntity = (T) tileentity; // CraftBukkit - decompile error
             this.ticker = blockentityticker;
         }
 
@@ -1001,7 +1080,7 @@
                             this.loggedInvalidBlockState = true;
                             Chunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new org.apache.logging.log4j.util.Supplier[]{this::d, this::c, () -> {
                                         return iblockdata;
-                                    }});
+                                    }}); // CraftBukkit - squelch checkstyle
                         }
 
                         gameprofilerfiller.exit();
