--- a/net/minecraft/world/level/block/BlockFire.java
+++ b/net/minecraft/world/level/block/BlockFire.java
@@ -29,6 +29,15 @@
 import net.minecraft.world.phys.shapes.VoxelShapeCollision;
 import net.minecraft.world.phys.shapes.VoxelShapes;
 
+// CraftBukkit start
+import net.minecraft.world.item.context.ItemActionContext;
+import org.bukkit.craftbukkit.block.CraftBlockState;
+import org.bukkit.craftbukkit.block.CraftBlockStates;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.event.block.BlockBurnEvent;
+import org.bukkit.event.block.BlockFadeEvent;
+// CraftBukkit end
+
 public class BlockFire extends BlockFireAbstract {
 
     public static final MapCodec<BlockFire> CODEC = simpleCodec(BlockFire::new);
@@ -100,7 +109,24 @@
 
     @Override
     protected IBlockData updateShape(IBlockData iblockdata, IWorldReader iworldreader, ScheduledTickAccess scheduledtickaccess, BlockPosition blockposition, EnumDirection enumdirection, BlockPosition blockposition1, IBlockData iblockdata1, RandomSource randomsource) {
-        return this.canSurvive(iblockdata, iworldreader, blockposition) ? this.getStateWithAge(iworldreader, blockposition, (Integer) iblockdata.getValue(BlockFire.AGE)) : Blocks.AIR.defaultBlockState();
+        // CraftBukkit start
+        if (!this.canSurvive(iblockdata, iworldreader, blockposition)) {
+            // Suppress during worldgen
+            if (!(iworldreader instanceof World world)) {
+                return Blocks.AIR.defaultBlockState();
+            }
+            CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockposition);
+            blockState.setData(Blocks.AIR.defaultBlockState());
+
+            BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState);
+            world.getCraftServer().getPluginManager().callEvent(event);
+
+            if (!event.isCancelled()) {
+                return blockState.getHandle();
+            }
+        }
+        return this.getStateWithAge(iworldreader, blockposition, (Integer) iblockdata.getValue(BlockFire.AGE));
+        // CraftBukkit end
     }
 
     @Override
@@ -149,7 +175,7 @@
         worldserver.scheduleTick(blockposition, (Block) this, getFireTickDelay(worldserver.random));
         if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
             if (!iblockdata.canSurvive(worldserver, blockposition)) {
-                worldserver.removeBlock(blockposition, false);
+                fireExtinguished(worldserver, blockposition); // CraftBukkit - invalid place location
             }
 
             IBlockData iblockdata1 = worldserver.getBlockState(blockposition.below());
@@ -157,7 +183,7 @@
             int i = (Integer) iblockdata.getValue(BlockFire.AGE);
 
             if (!flag && worldserver.isRaining() && this.isNearRain(worldserver, blockposition) && randomsource.nextFloat() < 0.2F + (float) i * 0.03F) {
-                worldserver.removeBlock(blockposition, false);
+                fireExtinguished(worldserver, blockposition); // CraftBukkit - extinguished by rain
             } else {
                 int j = Math.min(15, i + randomsource.nextInt(3) / 2);
 
@@ -171,14 +197,14 @@
                         BlockPosition blockposition1 = blockposition.below();
 
                         if (!worldserver.getBlockState(blockposition1).isFaceSturdy(worldserver, blockposition1, EnumDirection.UP) || i > 3) {
-                            worldserver.removeBlock(blockposition, false);
+                            fireExtinguished(worldserver, blockposition); // CraftBukkit
                         }
 
                         return;
                     }
 
                     if (i == 15 && randomsource.nextInt(4) == 0 && !this.canBurn(worldserver.getBlockState(blockposition.below()))) {
-                        worldserver.removeBlock(blockposition, false);
+                        fireExtinguished(worldserver, blockposition); // CraftBukkit
                         return;
                     }
                 }
@@ -186,12 +212,14 @@
                 boolean flag1 = worldserver.getBiome(blockposition).is(BiomeTags.INCREASED_FIRE_BURNOUT);
                 int k = flag1 ? -50 : 0;
 
-                this.checkBurnOut(worldserver, blockposition.east(), 300 + k, randomsource, i);
-                this.checkBurnOut(worldserver, blockposition.west(), 300 + k, randomsource, i);
-                this.checkBurnOut(worldserver, blockposition.below(), 250 + k, randomsource, i);
-                this.checkBurnOut(worldserver, blockposition.above(), 250 + k, randomsource, i);
-                this.checkBurnOut(worldserver, blockposition.north(), 300 + k, randomsource, i);
-                this.checkBurnOut(worldserver, blockposition.south(), 300 + k, randomsource, i);
+                // CraftBukkit start - add source blockposition to burn calls
+                this.trySpread(worldserver, blockposition.east(), 300 + k, randomsource, i, blockposition);
+                this.trySpread(worldserver, blockposition.west(), 300 + k, randomsource, i, blockposition);
+                this.trySpread(worldserver, blockposition.below(), 250 + k, randomsource, i, blockposition);
+                this.trySpread(worldserver, blockposition.above(), 250 + k, randomsource, i, blockposition);
+                this.trySpread(worldserver, blockposition.north(), 300 + k, randomsource, i, blockposition);
+                this.trySpread(worldserver, blockposition.south(), 300 + k, randomsource, i, blockposition);
+                // CraftBukkit end
                 BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition();
 
                 for (int l = -1; l <= 1; ++l) {
@@ -217,7 +245,15 @@
                                     if (i2 > 0 && randomsource.nextInt(k1) <= i2 && (!worldserver.isRaining() || !this.isNearRain(worldserver, blockposition_mutableblockposition))) {
                                         int j2 = Math.min(15, i + randomsource.nextInt(5) / 4);
 
-                                        worldserver.setBlock(blockposition_mutableblockposition, this.getStateWithAge(worldserver, blockposition_mutableblockposition, j2), 3);
+                                        // CraftBukkit start - Call to stop spread of fire
+                                        if (worldserver.getBlockState(blockposition_mutableblockposition).getBlock() != Blocks.FIRE) {
+                                            if (CraftEventFactory.callBlockIgniteEvent(worldserver, blockposition_mutableblockposition, blockposition).isCancelled()) {
+                                                continue;
+                                            }
+
+                                            CraftEventFactory.handleBlockSpreadEvent(worldserver, blockposition, blockposition_mutableblockposition, this.getStateWithAge(worldserver, blockposition_mutableblockposition, j2), 3); // CraftBukkit
+                                        }
+                                        // CraftBukkit end
                                     }
                                 }
                             }
@@ -241,12 +277,28 @@
         return iblockdata.hasProperty(BlockProperties.WATERLOGGED) && (Boolean) iblockdata.getValue(BlockProperties.WATERLOGGED) ? 0 : this.igniteOdds.getInt(iblockdata.getBlock());
     }
 
-    private void checkBurnOut(World world, BlockPosition blockposition, int i, RandomSource randomsource, int j) {
+    private void trySpread(World world, BlockPosition blockposition, int i, RandomSource randomsource, int j, BlockPosition sourceposition) { // CraftBukkit add sourceposition
         int k = this.getBurnOdds(world.getBlockState(blockposition));
 
         if (randomsource.nextInt(i) < k) {
             IBlockData iblockdata = world.getBlockState(blockposition);
 
+            // CraftBukkit start
+            org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
+            org.bukkit.block.Block sourceBlock = world.getWorld().getBlockAt(sourceposition.getX(), sourceposition.getY(), sourceposition.getZ());
+
+            BlockBurnEvent event = new BlockBurnEvent(theBlock, sourceBlock);
+            world.getCraftServer().getPluginManager().callEvent(event);
+
+            if (event.isCancelled()) {
+                return;
+            }
+
+            if (iblockdata.getBlock() instanceof BlockTNT && !CraftEventFactory.callTNTPrimeEvent(world, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.FIRE, null, sourceposition)) {
+                return;
+            }
+            // CraftBukkit end
+
             if (randomsource.nextInt(j + 10) < 5 && !world.isRainingAt(blockposition)) {
                 int l = Math.min(j + randomsource.nextInt(5) / 4, 15);
 
@@ -310,8 +362,10 @@
     }
 
     @Override
-    protected void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) {
-        super.onPlace(iblockdata, world, blockposition, iblockdata1, flag);
+    // CraftBukkit start - context
+    protected void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag, ItemActionContext context) {
+        super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, context);
+        // CraftBukkit end
         world.scheduleTick(blockposition, (Block) this, getFireTickDelay(world.random));
     }
 
