--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
@@ -36,20 +_,21 @@
 import net.minecraft.world.phys.Vec3;
 
 public class FireworkRocketEntity extends Projectile implements ItemSupplier {
-    private static final EntityDataAccessor<ItemStack> DATA_ID_FIREWORKS_ITEM = SynchedEntityData.defineId(
+    public static final EntityDataAccessor<ItemStack> DATA_ID_FIREWORKS_ITEM = SynchedEntityData.defineId(
         FireworkRocketEntity.class, EntityDataSerializers.ITEM_STACK
     );
-    private static final EntityDataAccessor<OptionalInt> DATA_ATTACHED_TO_TARGET = SynchedEntityData.defineId(
+    public static final EntityDataAccessor<OptionalInt> DATA_ATTACHED_TO_TARGET = SynchedEntityData.defineId(
         FireworkRocketEntity.class, EntityDataSerializers.OPTIONAL_UNSIGNED_INT
     );
-    private static final EntityDataAccessor<Boolean> DATA_SHOT_AT_ANGLE = SynchedEntityData.defineId(FireworkRocketEntity.class, EntityDataSerializers.BOOLEAN);
+    public static final EntityDataAccessor<Boolean> DATA_SHOT_AT_ANGLE = SynchedEntityData.defineId(FireworkRocketEntity.class, EntityDataSerializers.BOOLEAN);
     private static final int DEFAULT_LIFE = 0;
     private static final int DEFAULT_LIFE_TIME = 0;
     private static final boolean DEFAULT_SHOT_AT_ANGLE = false;
-    private int life = 0;
-    private int lifetime = 0;
+    public int life = 0;
+    public int lifetime = 0;
     @Nullable
-    private LivingEntity attachedToEntity;
+    public LivingEntity attachedToEntity;
+    @Nullable public java.util.UUID spawningEntity; // Paper
 
     public FireworkRocketEntity(EntityType<? extends FireworkRocketEntity> entityType, Level level) {
         super(entityType, level);
@@ -108,6 +_,21 @@
         return super.shouldRender(x, y, z) && !this.isAttachedToEntity();
     }
 
+    // Paper start - EAR 2
+    @Override
+    public void inactiveTick() {
+        this.life++;
+        if (this.life > this.lifetime && this.level() instanceof ServerLevel serverLevel) {
+            // Paper start - Call FireworkExplodeEvent
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this)) {
+                this.explode(serverLevel);
+            }
+            // Paper end - Call FireworkExplodeEvent
+        }
+        super.inactiveTick();
+    }
+    // Paper end - EAR 2
+
     @Override
     public void tick() {
         super.tick();
@@ -165,7 +_,7 @@
         }
 
         if (!this.noPhysics && this.isAlive() && hitResultOnMoveVector.getType() != HitResult.Type.MISS) {
-            this.hitTargetOrDeflectSelf(hitResultOnMoveVector);
+            this.preHitTargetOrDeflectSelf(hitResultOnMoveVector); // CraftBukkit - projectile hit event
             this.hasImpulse = true;
         }
 
@@ -189,7 +_,11 @@
         }
 
         if (this.life > this.lifetime && this.level() instanceof ServerLevel serverLevel) {
-            this.explode(serverLevel);
+            // Paper start - Call FireworkExplodeEvent
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this)) {
+                this.explode(serverLevel);
+            }
+            // Paper end - Call FireworkExplodeEvent
         }
     }
 
@@ -197,14 +_,18 @@
         level.broadcastEntityEvent(this, (byte)17);
         this.gameEvent(GameEvent.EXPLODE, this.getOwner());
         this.dealExplosionDamage(level);
-        this.discard();
+        this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
     }
 
     @Override
     protected void onHitEntity(EntityHitResult result) {
         super.onHitEntity(result);
         if (this.level() instanceof ServerLevel serverLevel) {
-            this.explode(serverLevel);
+            // Paper start - Call FireworkExplodeEvent
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this)) {
+                this.explode(serverLevel);
+            }
+            // Paper end - Call FireworkExplodeEvent
         }
     }
 
@@ -213,7 +_,11 @@
         BlockPos blockPos = new BlockPos(result.getBlockPos());
         this.level().getBlockState(blockPos).entityInside(this.level(), blockPos, this, InsideBlockEffectApplier.NOOP);
         if (this.level() instanceof ServerLevel serverLevel && this.hasExplosion()) {
-            this.explode(serverLevel);
+            // Paper start - Call FireworkExplodeEvent
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this)) {
+                this.explode(serverLevel);
+            }
+            // Paper end - Call FireworkExplodeEvent
         }
 
         super.onHitBlock(result);
@@ -286,6 +_,7 @@
         RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
         compound.store("FireworksItem", ItemStack.CODEC, registryOps, this.getItem());
         compound.putBoolean("ShotAtAngle", this.entityData.get(DATA_SHOT_AT_ANGLE));
+        compound.storeNullable("SpawningEntity", net.minecraft.core.UUIDUtil.CODEC, this.spawningEntity); // Paper
     }
 
     @Override
@@ -296,6 +_,7 @@
         RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
         this.entityData.set(DATA_ID_FIREWORKS_ITEM, compound.read("FireworksItem", ItemStack.CODEC, registryOps).orElse(getDefaultItem()));
         this.entityData.set(DATA_SHOT_AT_ANGLE, compound.getBooleanOr("ShotAtAngle", false));
+        this.spawningEntity = compound.read("SpawningEntity", net.minecraft.core.UUIDUtil.CODEC).orElse(null); // Paper
     }
 
     private List<FireworkExplosion> getExplosions() {
@@ -314,7 +_,7 @@
         return false;
     }
 
-    private static ItemStack getDefaultItem() {
+    public static ItemStack getDefaultItem() {
         return new ItemStack(Items.FIREWORK_ROCKET);
     }
 
