--- a/net/minecraft/world/level/block/FireBlock.java
+++ b/net/minecraft/world/level/block/FireBlock.java
@@ -57,7 +_,7 @@
     private static final int BURN_EASY = 60;
     private static final int BURN_MEDIUM = 20;
     private static final int BURN_HARD = 5;
-    private final Object2IntMap<Block> igniteOdds = new Object2IntOpenHashMap<>();
+    public final Object2IntMap<Block> igniteOdds = new Object2IntOpenHashMap<>();
     private final Object2IntMap<Block> burnOdds = new Object2IntOpenHashMap<>();
 
     @Override
@@ -122,7 +_,25 @@
         BlockState neighborState,
         RandomSource random
     ) {
-        return this.canSurvive(state, level, pos) ? this.getStateWithAge(level, pos, state.getValue(AGE)) : Blocks.AIR.defaultBlockState();
+        // CraftBukkit start
+        if (!(level instanceof ServerLevel)) return this.canSurvive(state, level, pos) ? (BlockState) this.getStateWithAge(level, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation
+        if (!this.canSurvive(state, level, pos)) {
+            // Suppress during worldgen
+            if (!(level instanceof Level world1)) {
+                return Blocks.AIR.defaultBlockState();
+            }
+            org.bukkit.craftbukkit.block.CraftBlockState blockState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world1, pos);
+            blockState.setData(Blocks.AIR.defaultBlockState());
+
+            org.bukkit.event.block.BlockFadeEvent event = new org.bukkit.event.block.BlockFadeEvent(blockState.getBlock(), blockState);
+            world1.getCraftServer().getPluginManager().callEvent(event);
+
+            if (!event.isCancelled()) {
+                return blockState.getHandle();
+            }
+        }
+        return this.getStateWithAge(level, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation"
+        // CraftBukkit end
     }
 
     @Override
@@ -162,10 +_,10 @@
 
     @Override
     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
-        level.scheduleTick(pos, this, getFireTickDelay(level.random));
+        level.scheduleTick(pos, this, getFireTickDelay(level)); // Paper - Add fire-tick-delay option
         if (level.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
             if (!state.canSurvive(level, pos)) {
-                level.removeBlock(pos, false);
+                this.fireExtinguished(level, pos); // CraftBukkit - invalid place location
             }
 
             BlockState blockState = level.getBlockState(pos.below());
@@ -184,26 +_,28 @@
                     if (!this.isValidFireLocation(level, pos)) {
                         BlockPos blockPos = pos.below();
                         if (!level.getBlockState(blockPos).isFaceSturdy(level, blockPos, Direction.UP) || ageValue > 3) {
-                            level.removeBlock(pos, false);
+                            this.fireExtinguished(level, pos); // CraftBukkit - extinguished by rain
                         }
 
                         return;
                     }
 
                     if (ageValue == 15 && random.nextInt(4) == 0 && !this.canBurn(level.getBlockState(pos.below()))) {
-                        level.removeBlock(pos, false);
+                        this.fireExtinguished(level, pos); // CraftBukkit
                         return;
                     }
                 }
 
                 boolean isIncreasedFireBurnout = level.getBiome(pos).is(BiomeTags.INCREASED_FIRE_BURNOUT);
                 int i = isIncreasedFireBurnout ? -50 : 0;
-                this.checkBurnOut(level, pos.east(), 300 + i, random, ageValue);
-                this.checkBurnOut(level, pos.west(), 300 + i, random, ageValue);
-                this.checkBurnOut(level, pos.below(), 250 + i, random, ageValue);
-                this.checkBurnOut(level, pos.above(), 250 + i, random, ageValue);
-                this.checkBurnOut(level, pos.north(), 300 + i, random, ageValue);
-                this.checkBurnOut(level, pos.south(), 300 + i, random, ageValue);
+                // CraftBukkit start - add source blockposition to burn calls
+                this.checkBurnOut(level, pos.east(), 300 + i, random, ageValue, pos);
+                this.checkBurnOut(level, pos.west(), 300 + i, random, ageValue, pos);
+                this.checkBurnOut(level, pos.below(), 250 + i, random, ageValue, pos);
+                this.checkBurnOut(level, pos.above(), 250 + i, random, ageValue, pos);
+                this.checkBurnOut(level, pos.north(), 300 + i, random, ageValue, pos);
+                this.checkBurnOut(level, pos.south(), 300 + i, random, ageValue, pos);
+                // CraftBukkit end
                 BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
 
                 for (int i1 = -1; i1 <= 1; i1++) {
@@ -225,7 +_,15 @@
 
                                     if (i5 > 0 && random.nextInt(i4) <= i5 && (!level.isRaining() || !this.isNearRain(level, mutableBlockPos))) {
                                         int min1 = Math.min(15, ageValue + random.nextInt(5) / 4);
-                                        level.setBlock(mutableBlockPos, this.getStateWithAge(level, mutableBlockPos, min1), 3);
+                                        // CraftBukkit start - Call to stop spread of fire
+                                        if (level.getBlockState(mutableBlockPos).getBlock() != Blocks.FIRE) {
+                                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, mutableBlockPos, pos).isCancelled()) {
+                                                continue;
+                                            }
+
+                                            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, mutableBlockPos, this.getStateWithAge(level, mutableBlockPos, min1), 3); // CraftBukkit
+                                        }
+                                        // CraftBukkit end
                                     }
                                 }
                             }
@@ -256,19 +_,42 @@
             : this.igniteOdds.getInt(state.getBlock());
     }
 
-    private void checkBurnOut(Level level, BlockPos pos, int chance, RandomSource random, int age) {
+    private void checkBurnOut(Level level, BlockPos pos, int chance, RandomSource random, int age, BlockPos sourceposition) { // CraftBukkit add sourceposition
         int burnOdds = this.getBurnOdds(level.getBlockState(pos));
         if (random.nextInt(chance) < burnOdds) {
             BlockState blockState = level.getBlockState(pos);
+
+            // CraftBukkit start
+            org.bukkit.block.Block theBlock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
+            org.bukkit.block.Block sourceBlock = level.getWorld().getBlockAt(sourceposition.getX(), sourceposition.getY(), sourceposition.getZ());
+
+            org.bukkit.event.block.BlockBurnEvent event = new org.bukkit.event.block.BlockBurnEvent(theBlock, sourceBlock);
+            level.getCraftServer().getPluginManager().callEvent(event);
+
+            if (event.isCancelled()) {
+                return;
+            }
+
+            if (blockState.getBlock() instanceof TntBlock && !org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.FIRE, null, sourceposition)) {
+                return;
+            }
+            // CraftBukkit end
             if (random.nextInt(age + 10) < 5 && !level.isRainingAt(pos)) {
                 int min = Math.min(age + random.nextInt(5) / 4, 15);
                 level.setBlock(pos, this.getStateWithAge(level, pos, min), 3);
             } else {
-                level.removeBlock(pos, false);
+                if (!blockState.is(Blocks.TNT)) level.removeBlock(pos, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down
             }
 
             Block block = blockState.getBlock();
             if (block instanceof TntBlock) {
+                // Paper start - TNTPrimeEvent
+                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
+                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) {
+                    return;
+                }
+                level.removeBlock(pos, false);
+                // Paper end - TNTPrimeEvent
                 TntBlock.explode(level, pos);
             }
         }
@@ -310,13 +_,14 @@
     }
 
     @Override
-    protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
-        super.onPlace(state, level, pos, oldState, isMoving);
-        level.scheduleTick(pos, this, getFireTickDelay(level.random));
+    protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving, net.minecraft.world.item.context.UseOnContext context) {
+        super.onPlace(state, level, pos, oldState, isMoving, context);
+        // CraftBukkit end
+        level.scheduleTick(pos, this, FireBlock.getFireTickDelay(level)); // Paper - Add fire-tick-delay option
     }
 
-    private static int getFireTickDelay(RandomSource random) {
-        return 30 + random.nextInt(10);
+    private static int getFireTickDelay(Level level) { // Paper - Add fire-tick-delay option
+        return level.paperConfig().environment.fireTickDelay + level.random.nextInt(10); // Paper - Add fire-tick-delay option
     }
 
     @Override
