--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -16,12 +16,16 @@
 import net.minecraft.world.entity.Pose;
 import net.minecraft.world.level.Explosion;
 import net.minecraft.world.level.Level;
+import org.bukkit.event.entity.ExplosionPrimeEvent; // CraftBukkit
 
 public class PrimedTnt extends Entity {
+
     private static final EntityDataAccessor<Integer> DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT);
     private static final int DEFAULT_FUSE_TIME = 80;
     @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);
@@ -31,8 +35,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() * (double)((float)Math.PI * 2F);
-        this.setDeltaMovement(-Math.sin(d) * 0.02D, (double)0.2F, -Math.cos(d) * 0.02D);
+        double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - don't use world random in entity constructor
+
+        this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D);
         this.setFuse(80);
         this.xo = x;
         this.yo = y;
@@ -42,7 +47,7 @@
 
     @Override
     protected void defineSynchedData() {
-        this.entityData.define(DATA_FUSE_ID, 80);
+        this.entityData.define(PrimedTnt.DATA_FUSE_ID, 80);
     }
 
     @Override
@@ -57,23 +62,34 @@
 
     @Override
     public void tick() {
+        if (level.spigotConfig.maxTntTicksPerTick > 0 && ++level.spigotConfig.currentPrimedTnt > level.spigotConfig.maxTntTicksPerTick) { return; } // Spigot
         if (!this.isNoGravity()) {
             this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.04D, 0.0D));
         }
 
         this.move(MoverType.SELF, this.getDeltaMovement());
+        // Paper start - Configurable TNT entity height nerf
+        if (this.level.paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) {
+            this.discard();
+            return;
+        }
+        // Paper end
         this.setDeltaMovement(this.getDeltaMovement().scale(0.98D));
         if (this.onGround) {
             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();
+            // CraftBukkit end
         } else {
             this.updateInWaterStateAndDoFluidPushing();
             if (this.level.isClientSide) {
@@ -81,21 +97,58 @@
             }
         }
 
+        // Paper start - Optional 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
     }
 
     private void explode() {
-        float f = 4.0F;
-        this.level.explode(this, this.getX(), this.getY(0.0625D), this.getZ(), 4.0F, Explosion.BlockInteraction.BREAK);
+        // CraftBukkit start
+        // float f = 4.0F;
+
+        ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
+        this.level.getCraftServer().getPluginManager().callEvent(event);
+
+        if (!event.isCancelled()) {
+            this.level.explode(this, this.getX(), this.getY(0.0625D), this.getZ(), event.getRadius(), event.getFire(), Explosion.BlockInteraction.BREAK);
+        }
+        // CraftBukkit end
     }
 
     @Override
     protected void addAdditionalSaveData(CompoundTag nbt) {
-        nbt.putShort("Fuse", (short)this.getFuse());
+        nbt.putShort("Fuse", (short) this.getFuse());
     }
 
     @Override
     protected void readAdditionalSaveData(CompoundTag nbt) {
         this.setFuse(nbt.getShort("Fuse"));
+        // Paper start - Try and load origin location from the old NBT tags for backwards compatibility
+        if (nbt.contains("SourceLoc_x")) {
+            int srcX = nbt.getInt("SourceLoc_x");
+            int srcY = nbt.getInt("SourceLoc_y");
+            int srcZ = nbt.getInt("SourceLoc_z");
+            this.setOrigin(new org.bukkit.Location(level.getWorld(), srcX, srcY, srcZ));
+        }
+        // Paper end
     }
 
     @Nullable
@@ -109,15 +162,22 @@
     }
 
     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);
     }
 
     @Override
     public Packet<?> getAddEntityPacket() {
         return new ClientboundAddEntityPacket(this);
     }
+
+    // Paper start - Optional prevent TNT from moving in water
+    @Override
+    public boolean isPushedByFluid() {
+        return !level.paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
+    }
+    // Paper end
 }
