--- a/net/minecraft/world/entity/projectile/Projectile.java
+++ b/net/minecraft/world/entity/projectile/Projectile.java
@@ -39,12 +_,13 @@
     private static final boolean DEFAULT_LEFT_OWNER = false;
     private static final boolean DEFAULT_HAS_BEEN_SHOT = false;
     @Nullable
-    protected EntityReference<Entity> owner;
-    private boolean leftOwner = false;
+    public EntityReference<Entity> owner;
+    public boolean leftOwner = false;
     private boolean leftOwnerChecked;
-    private boolean hasBeenShot = false;
+    public boolean hasBeenShot = false;
     @Nullable
     private Entity lastDeflectedBy;
+    protected boolean hitCancelled = false; // CraftBukkit
 
     Projectile(EntityType<? extends Projectile> entityType, Level level) {
         super(entityType, level);
@@ -52,12 +_,25 @@
 
     protected void setOwner(@Nullable EntityReference<Entity> owner) {
         this.owner = owner;
+        this.refreshProjectileSource(false); // Paper
     }
 
     public void setOwner(@Nullable Entity owner) {
         this.setOwner(EntityReference.of(owner));
     }
 
+    // Paper start - Refresh ProjectileSource for projectiles
+    public void refreshProjectileSource(boolean fillCache) {
+        if (fillCache) {
+            this.getOwner();
+        }
+        Entity owner = this.getOwner();
+        if (owner != null && this.projectileSource == null && owner.getBukkitEntity() instanceof org.bukkit.projectiles.ProjectileSource projSource) {
+            this.projectileSource = projSource;
+        }
+    }
+    // Paper end - Refresh ProjectileSource for projectiles
+
     @Nullable
     @Override
     public Entity getOwner() {
@@ -85,6 +_,7 @@
     @Override
     protected void readAdditionalSaveData(ValueInput input) {
         this.setOwner(EntityReference.read(input, "Owner"));
+        if (this instanceof ThrownEnderpearl && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && this.level().paperConfig().misc.legacyEnderPearlBehavior) { this.owner = null; } // Paper - Reset pearls when they stop being ticked; Don't store shooter name for pearls to block enderpearl travel exploit
         this.leftOwner = input.getBooleanOr("LeftOwner", false);
         this.hasBeenShot = input.getBooleanOr("HasBeenShot", false);
     }
@@ -157,7 +_,14 @@
         float f2 = Mth.cos(y * (float) (Math.PI / 180.0)) * Mth.cos(x * (float) (Math.PI / 180.0));
         this.shoot(f, f1, f2, velocity, inaccuracy);
         Vec3 knownMovement = shooter.getKnownMovement();
+        // Paper start - allow disabling relative velocity
+        if (Double.isNaN(knownMovement.x) || Double.isNaN(knownMovement.y) || Double.isNaN(knownMovement.z)) {
+            knownMovement = new Vec3(0, 0, 0);
+        }
+        if (!shooter.level().paperConfig().misc.disableRelativeProjectileVelocity) {
         this.setDeltaMovement(this.getDeltaMovement().add(knownMovement.x, shooter.onGround() ? 0.0 : knownMovement.y, knownMovement.z));
+        }
+        // Paper end - allow disabling relative velocity
     }
 
     @Override
@@ -177,7 +_,12 @@
     public static <T extends Projectile> T spawnProjectileFromRotation(
         Projectile.ProjectileFactory<T> factory, ServerLevel level, ItemStack spawnedFrom, LivingEntity owner, float z, float velocity, float inaccuracy
     ) {
-        return spawnProjectile(
+        // Paper start - PlayerLaunchProjectileEvent
+        return spawnProjectileFromRotationDelayed(factory, level, spawnedFrom, owner, z, velocity, inaccuracy).spawn();
+    }
+    public static <T extends Projectile> Delayed<T> spawnProjectileFromRotationDelayed(Projectile.ProjectileFactory<T> factory, ServerLevel level, ItemStack spawnedFrom, LivingEntity owner, float z, float velocity, float inaccuracy) {
+        return spawnProjectileDelayed(
+        // Paper end - PlayerLaunchProjectileEvent
             factory.create(level, owner, spawnedFrom),
             level,
             spawnedFrom,
@@ -202,7 +_,13 @@
     public static <T extends Projectile> T spawnProjectileUsingShoot(
         T projectile, ServerLevel level, ItemStack spawnedFrom, double x, double y, double z, float velocity, float inaccuracy
     ) {
-        return spawnProjectile(projectile, level, spawnedFrom, projectile1 -> projectile.shoot(x, y, z, velocity, inaccuracy));
+        // Paper start - fixes and addition to spawn reason API
+        return spawnProjectileUsingShootDelayed(projectile, level, spawnedFrom, x, y, z, velocity, inaccuracy).spawn();
+    }
+
+    public static <T extends Projectile> Delayed<T> spawnProjectileUsingShootDelayed(T projectile, ServerLevel level, ItemStack spawnedFrom, double x, double y, double z, float velocity, float inaccuracy) {
+        return spawnProjectileDelayed(projectile, level, spawnedFrom, projectile1 -> projectile.shoot(x, y, z, velocity, inaccuracy));
+        // Paper end - fixes and addition to spawn reason API
     }
 
     public static <T extends Projectile> T spawnProjectile(T projectile, ServerLevel level, ItemStack spawnedFrom) {
@@ -210,11 +_,46 @@
     }
 
     public static <T extends Projectile> T spawnProjectile(T projectile, ServerLevel level, ItemStack stack, Consumer<T> adapter) {
+        // Paper start - delayed projectile spawning
+        return spawnProjectileDelayed(projectile, level, stack, adapter).spawn();
+    }
+
+    public static <T extends Projectile> Delayed<T> spawnProjectileDelayed(T projectile, ServerLevel level, ItemStack stack, Consumer<T> adapter) {
+        // Paper end - delayed projectile spawning
         adapter.accept(projectile);
-        level.addFreshEntity(projectile);
-        projectile.applyOnProjectileSpawned(level, stack);
-        return projectile;
-    }
+        return new Delayed<>(projectile, level, stack); // Paper - delayed projectile spawning
+    }
+
+    // Paper start - delayed projectile spawning
+    public record Delayed<T extends Projectile>(
+        T projectile,
+        ServerLevel world,
+        ItemStack projectileStack
+    ) {
+        // Taken from net.minecraft.world.entity.projectile.Projectile.spawnProjectile(T, net.minecraft.server.level.ServerLevel, net.minecraft.world.item.ItemStack, java.util.function.Consumer<T>)
+        public boolean attemptSpawn() {
+            if (!this.world.addFreshEntity(this.projectile)) return false;
+            this.projectile.applyOnProjectileSpawned(this.world, this.projectileStack);
+            return true;
+        }
+
+        public T spawn() {
+            this.attemptSpawn();
+            return this.projectile();
+        }
+
+        public boolean attemptSpawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+            if (!this.world.addFreshEntity(this.projectile, reason)) return false;
+            this.projectile.applyOnProjectileSpawned(this.world, this.projectileStack);
+            return true;
+        }
+
+        public T spawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+            this.attemptSpawn(reason);
+            return this.projectile();
+        }
+    }
+    // Paper end - delayed projectile spawning
 
     public void applyOnProjectileSpawned(ServerLevel level, ItemStack spawnedFrom) {
         EnchantmentHelper.onProjectileSpawned(level, spawnedFrom, this, item -> {});
@@ -226,6 +_,17 @@
         }
     }
 
+    // CraftBukkit start - call projectile hit event
+    public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) {
+        org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, hitResult);
+        this.hitCancelled = event != null && event.isCancelled();
+        if (hitResult.getType() == HitResult.Type.BLOCK || !this.hitCancelled) {
+            return this.hitTargetOrDeflectSelf(hitResult);
+        }
+        return ProjectileDeflection.NONE;
+    }
+    // CraftBukkit end
+
     protected ProjectileDeflection hitTargetOrDeflectSelf(HitResult hitResult) {
         if (hitResult.getType() == HitResult.Type.ENTITY) {
             EntityHitResult entityHitResult = (EntityHitResult)hitResult;
@@ -293,15 +_,35 @@
     }
 
     protected void onHitBlock(BlockHitResult result) {
+        // CraftBukkit start - cancellable hit event
+        if (this.hitCancelled) {
+            return;
+        }
+        // CraftBukkit end
         BlockState blockState = this.level().getBlockState(result.getBlockPos());
         blockState.onProjectileHit(this.level(), blockState, result, this);
     }
 
+    // Paper start
+    public boolean canHitEntityPublic(final Entity target) {
+        return this.canHitEntity(target);
+    }
+    // Paper end
+
     protected boolean canHitEntity(Entity target) {
         if (!target.canBeHitByProjectile()) {
             return false;
         } else {
             Entity owner = this.getOwner();
+            // Paper start - Cancel hit for vanished entities
+            if (owner instanceof net.minecraft.server.level.ServerPlayer) {
+                org.bukkit.entity.Entity collided = target.getBukkitEntity();
+                org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) owner.getBukkitEntity();
+                if (!shooter.canSee(collided)) {
+                    return false;
+                }
+            }
+            // Paper end - Cancel hit for vanished entities
             return owner == null || this.leftOwner || !owner.isPassengerOfSameVehicle(target);
         }
     }
@@ -314,13 +_,7 @@
     }
 
     protected static float lerpRotation(float currentRotation, float targetRotation) {
-        while (targetRotation - currentRotation < -180.0F) {
-            currentRotation -= 360.0F;
-        }
-
-        while (targetRotation - currentRotation >= 180.0F) {
-            currentRotation += 360.0F;
-        }
+        currentRotation += Math.round((targetRotation - currentRotation) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server
 
         return Mth.lerp(0.2F, currentRotation, targetRotation);
     }
