--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -27,6 +_,12 @@
 import net.minecraft.world.level.material.FluidState;
 import net.minecraft.world.level.portal.TeleportTransition;
 
+// 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);
@@ -47,9 +_,10 @@
         }
     };
     @Nullable
-    private LivingEntity owner;
+    public LivingEntity owner;
     private boolean usedPortal;
-    private float explosionPower = 4.0F;
+    public float explosionPower = 4.0F;
+    public boolean isIncendiary = false; // CraftBukkit - add field
 
     public PrimedTnt(EntityType<? extends PrimedTnt> entityType, Level level) {
         super(entityType, level);
@@ -59,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;
@@ -91,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));
@@ -103,19 +_,49 @@
         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) {
+            /*
+             * Author: Jedediah Smith <jedediah@silencegreys.com>
+             */
+            // Send position and velocity updates to nearby players on every tick while the TNT is in water.
+            // This does pretty well at keeping their clients in sync with the server.
+            net.minecraft.server.level.ChunkMap.TrackedEntity ete = ((net.minecraft.server.level.ServerLevel) this.level()).getChunkSource().chunkMap.entityMap.get(this.getId());
+            if (ete != null) {
+                net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this);
+                net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket.teleport(this.getId(), net.minecraft.world.entity.PositionMoveRotation.of(this), java.util.Set.of(), this.onGround);
+
+                ete.seenBy.stream()
+                    .filter(viewer -> (viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16)
+                    .forEach(viewer -> {
+                        viewer.send(velocityPacket);
+                        viewer.send(positionPacket);
+                    });
+            }
+        }
+        // Paper end - Option to prevent TNT from moving in water
     }
 
     private void explode() {
+        // CraftBukkit start
+        ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
+        if (event.isCancelled()) {
+            return;
+        }
+        // CraftBukkit end
         this.level()
             .explode(
                 this,
@@ -124,8 +_,8 @@
                 this.getX(),
                 this.getY(0.0625),
                 this.getZ(),
-                this.explosionPower,
-                false,
+                event.getRadius(), // CraftBukkit
+                event.getFire(), // CraftBukkit
                 Level.ExplosionInteraction.TNT
             );
     }
@@ -200,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
 }
