--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -16,8 +16,14 @@
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.Blocks;
 import net.minecraft.world.level.block.state.BlockState;
+// 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);
     private static final int DEFAULT_FUSE_TIME = 80;
@@ -25,6 +31,8 @@
     public static final String TAG_FUSE = "fuse";
     @Nullable
     public LivingEntity owner;
+    public float yield = 4; // CraftBukkit - add field
+    public boolean isIncendiary = false; // CraftBukkit - add field
 
     public PrimedTnt(EntityType<? extends PrimedTnt> type, Level world) {
         super(type, world);
@@ -34,8 +42,9 @@
     public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) {
         this(EntityType.TNT, world);
         this.setPos(x, y, z);
-        double d = world.random.nextDouble() * (float) (Math.PI * 2);
-        this.setDeltaMovement(-Math.sin(d) * 0.02, 0.2F, -Math.cos(d) * 0.02);
+        double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - Don't use level random in entity constructors
+
+        this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D);
         this.setFuse(80);
         this.xo = x;
         this.yo = y;
@@ -45,8 +54,8 @@
 
     @Override
     protected void defineSynchedData(SynchedEntityData.Builder builder) {
-        builder.define(DATA_FUSE_ID, 80);
-        builder.define(DATA_BLOCK_STATE_ID, Blocks.TNT.defaultBlockState());
+        builder.define(PrimedTnt.DATA_FUSE_ID, 80);
+        builder.define(PrimedTnt.DATA_BLOCK_STATE_ID, Blocks.TNT.defaultBlockState());
     }
 
     @Override
@@ -61,41 +70,80 @@
 
     @Override
     protected double getDefaultGravity() {
-        return 0.04;
+        return 0.04D;
     }
 
     @Override
     public void tick() {
+        if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot
         this.applyGravity();
         this.move(MoverType.SELF, this.getDeltaMovement());
-        this.setDeltaMovement(this.getDeltaMovement().scale(0.98));
+        // 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.98D));
         if (this.onGround()) {
-            this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7));
+            this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D));
         }
 
         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);
+                this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5D, this.getZ(), 0.0D, 0.0D, 0.0D);
             }
         }
+
+        // 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 = new net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket(this);
+
+                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() {
-        float f = 4.0F;
-        this.level().explode(this, this.getX(), this.getY(0.0625), this.getZ(), 4.0F, Level.ExplosionInteraction.TNT);
+        // CraftBukkit start
+        // float f = 4.0F;
+        ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent((org.bukkit.entity.Explosive)this.getBukkitEntity());
+
+        if (!event.isCancelled()) {
+            this.level().explode(this, this.getX(), this.getY(0.0625D), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.TNT);
+        }
+        // CraftBukkit end
     }
 
     @Override
     protected void addAdditionalSaveData(CompoundTag nbt) {
-        nbt.putShort("fuse", (short)this.getFuse());
+        nbt.putShort("fuse", (short) this.getFuse());
         nbt.put("block_state", NbtUtils.writeBlockState(this.getBlockState()));
     }
 
@@ -105,6 +153,7 @@
         if (nbt.contains("block_state", 10)) {
             this.setBlockState(NbtUtils.readBlockState(this.level().holderLookup(Registries.BLOCK), nbt.getCompound("block_state")));
         }
+
     }
 
     @Nullable
@@ -116,24 +165,32 @@
     @Override
     public void restoreFrom(Entity original) {
         super.restoreFrom(original);
-        if (original instanceof PrimedTnt primedTnt) {
-            this.owner = primedTnt.owner;
+        if (original instanceof PrimedTnt entitytntprimed) {
+            this.owner = entitytntprimed.owner;
         }
+
     }
 
     public void setFuse(int fuse) {
-        this.entityData.set(DATA_FUSE_ID, fuse);
+        this.entityData.set(PrimedTnt.DATA_FUSE_ID, fuse);
     }
 
     public int getFuse() {
-        return this.entityData.get(DATA_FUSE_ID);
+        return (Integer) this.entityData.get(PrimedTnt.DATA_FUSE_ID);
     }
 
     public void setBlockState(BlockState state) {
-        this.entityData.set(DATA_BLOCK_STATE_ID, state);
+        this.entityData.set(PrimedTnt.DATA_BLOCK_STATE_ID, state);
     }
 
     public BlockState getBlockState() {
-        return this.entityData.get(DATA_BLOCK_STATE_ID);
+        return (BlockState) this.entityData.get(PrimedTnt.DATA_BLOCK_STATE_ID);
     }
+
+    // Paper start - Option to prevent TNT from moving in water
+    @Override
+    public boolean isPushedByFluid() {
+        return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
+    }
+    // Paper end - Option to prevent TNT from moving in water
 }
