--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -28,6 +_,12 @@
 import net.minecraft.world.level.storage.ValueInput;
 import net.minecraft.world.level.storage.ValueOutput;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.event.entity.EntityRemoveEvent;
+import org.bukkit.event.entity.ExplosionPrimeEvent;
+// CraftBukkit end
+
 public class PrimedTnt extends Entity implements TraceableEntity {
     private static final EntityDataAccessor<Integer> DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT);
     private static final EntityDataAccessor<BlockState> DATA_BLOCK_STATE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.BLOCK_STATE);
@@ -52,6 +_,7 @@
     public EntityReference<LivingEntity> owner;
     private boolean usedPortal;
     public float explosionPower = 4.0F;
+    public boolean isIncendiary = false; // CraftBukkit
 
     public PrimedTnt(EntityType<? extends PrimedTnt> entityType, Level level) {
         super(entityType, level);
@@ -61,7 +_,7 @@
     public PrimedTnt(Level level, double x, double y, double z, @Nullable LivingEntity owner) {
         this(EntityType.TNT, level);
         this.setPos(x, y, z);
-        double d = level.random.nextDouble() * (float) (Math.PI * 2);
+        double d = this.random.nextDouble() * (float) (Math.PI * 2); // Paper - Don't use level random in entity constructors
         this.setDeltaMovement(-Math.sin(d) * 0.02, 0.2F, -Math.cos(d) * 0.02);
         this.setFuse(80);
         this.xo = x;
@@ -93,10 +_,17 @@
 
     @Override
     public void tick() {
+        if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot
         this.handlePortal();
         this.applyGravity();
         this.move(MoverType.SELF, this.getDeltaMovement());
         this.applyEffectsFromBlocks();
+        // Paper start - Configurable TNT height nerf
+        if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) {
+            this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD);
+            return;
+        }
+        // Paper end - Configurable TNT height nerf
         this.setDeltaMovement(this.getDeltaMovement().scale(0.98));
         if (this.onGround()) {
             this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7));
@@ -105,20 +_,35 @@
         int i = this.getFuse() - 1;
         this.setFuse(i);
         if (i <= 0) {
-            this.discard();
+            // CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event
+            //this.discard();
             if (!this.level().isClientSide) {
                 this.explode();
             }
+            this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
+            // CraftBukkit end
         } else {
             this.updateInWaterStateAndDoFluidPushing();
             if (this.level().isClientSide) {
                 this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5, this.getZ(), 0.0, 0.0, 0.0);
             }
         }
+        // Paper start - Option to prevent TNT from moving in water
+        if (!this.isRemoved() && this.wasTouchingWater && this.level().paperConfig().fixes.preventTntFromMovingInWater) {
+            this.hurtMarked = true;
+            this.hasImpulse = true;
+        }
+        // Paper end - Option to prevent TNT from moving in water
     }
 
     private void explode() {
         if (this.level() instanceof ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_TNT_EXPLODES)) {
+            // CraftBukkit start
+            ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
+            if (event.isCancelled()) {
+                return;
+            }
+            // CraftBukkit end
             this.level()
                 .explode(
                     this,
@@ -127,8 +_,8 @@
                     this.getX(),
                     this.getY(0.0625),
                     this.getZ(),
-                    this.explosionPower,
-                    false,
+                    event.getRadius(), // CraftBukkit
+                    event.getFire(), // CraftBukkit
                     Level.ExplosionInteraction.TNT
                 );
         }
@@ -202,4 +_,11 @@
     public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
         return false;
     }
+
+    // Paper start - Option to prevent TNT from moving in water
+    @Override
+    public boolean isPushedByFluid() {
+        return !this.level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
+    }
+    // Paper end - Option to prevent TNT from moving in water
 }
