--- a/net/minecraft/world/entity/projectile/Projectile.java
+++ b/net/minecraft/world/entity/projectile/Projectile.java
@@ -1,6 +1,7 @@
 package net.minecraft.world.entity.projectile;
 
 import com.google.common.base.MoreObjects;
+import java.util.Iterator;
 import java.util.UUID;
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
@@ -22,8 +23,12 @@
 import net.minecraft.world.phys.EntityHitResult;
 import net.minecraft.world.phys.HitResult;
 import net.minecraft.world.phys.Vec3;
+// CraftBukkit start
+import org.bukkit.projectiles.ProjectileSource;
+// CraftBukkit end
 
 public abstract class Projectile extends Entity implements TraceableEntity {
+
     @Nullable
     public UUID ownerUUID;
     @Nullable
@@ -31,6 +36,10 @@
     public boolean leftOwner;
     public boolean hasBeenShot;
 
+    // CraftBukkit start
+    protected boolean hitCancelled = false;
+    // CraftBukkit end
+
     Projectile(EntityType<? extends Projectile> type, Level world) {
         super(type, world);
     }
@@ -40,16 +49,47 @@
             this.ownerUUID = entity.getUUID();
             this.cachedOwner = entity;
         }
-
+        // Paper start - Fix null owners not being handled
+        else {
+            this.ownerUUID = null;
+            this.cachedOwner = null;
+            this.projectileSource = null;
+        }
+        // Paper end
+        this.refreshProjectileSource(false); // Paper
     }
+    // Paper start
+    public void refreshProjectileSource(boolean fillCache) {
+        if (fillCache) {
+            this.getOwner();
+        }
+        if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof ProjectileSource projSource) {
+            this.projectileSource = projSource;
+        }
+    }
+    // Paper end
 
     @Nullable
     @Override
     public Entity getOwner() {
         if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) {
+            this.refreshProjectileSource(false); // Paper
             return this.cachedOwner;
         } else if (this.ownerUUID != null && this.level() instanceof ServerLevel) {
-            this.cachedOwner = ((ServerLevel)this.level()).getEntity(this.ownerUUID);
+            this.cachedOwner = ((ServerLevel) this.level()).getEntity(this.ownerUUID);
+            // Paper start - check all worlds
+            if (this.cachedOwner == null) {
+                for (final ServerLevel level : this.level().getServer().getAllLevels()) {
+                    if (level == this.level()) continue;
+                    final Entity entity = level.getEntity(this.ownerUUID);
+                    if (entity != null) {
+                        this.cachedOwner = entity;
+                        break;
+                    }
+                }
+            }
+            // Paper end
+            this.refreshProjectileSource(false); // Paper
             return this.cachedOwner;
         } else {
             return null;
@@ -57,7 +97,7 @@
     }
 
     public Entity getEffectSource() {
-        return MoreObjects.firstNonNull(this.getOwner(), this);
+        return (Entity) MoreObjects.firstNonNull(this.getOwner(), this);
     }
 
     @Override
@@ -82,6 +122,7 @@
         if (nbt.hasUUID("Owner")) {
             this.ownerUUID = nbt.getUUID("Owner");
             this.cachedOwner = null;
+            if (this instanceof ThrownEnderpearl && this.level() != null && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit) { this.ownerUUID = null; } // Paper - Don't store shooter name for pearls to block enderpearl travel exploit
         }
 
         this.leftOwner = nbt.getBoolean("LeftOwner");
@@ -104,11 +145,16 @@
 
     private boolean checkLeftOwner() {
         Entity entity = this.getOwner();
+
         if (entity != null) {
-            for(Entity entity2 : this.level().getEntities(this, this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0D), (entityx) -> {
-                return !entityx.isSpectator() && entityx.isPickable();
-            })) {
-                if (entity2.getRootVehicle() == entity.getRootVehicle()) {
+            Iterator iterator = this.level().getEntities((Entity) this, this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0D), (entity1) -> {
+                return !entity1.isSpectator() && entity1.isPickable();
+            }).iterator();
+
+            while (iterator.hasNext()) {
+                Entity entity1 = (Entity) iterator.next();
+
+                if (entity1.getRootVehicle() == entity.getRootVehicle()) {
                     return false;
                 }
             }
@@ -118,53 +164,76 @@
     }
 
     public void shoot(double x, double y, double z, float speed, float divergence) {
-        Vec3 vec3 = (new Vec3(x, y, z)).normalize().add(this.random.triangle(0.0D, 0.0172275D * (double)divergence), this.random.triangle(0.0D, 0.0172275D * (double)divergence), this.random.triangle(0.0D, 0.0172275D * (double)divergence)).scale((double)speed);
-        this.setDeltaMovement(vec3);
-        double d = vec3.horizontalDistance();
-        this.setYRot((float)(Mth.atan2(vec3.x, vec3.z) * (double)(180F / (float)Math.PI)));
-        this.setXRot((float)(Mth.atan2(vec3.y, d) * (double)(180F / (float)Math.PI)));
+        Vec3 vec3d = (new Vec3(x, y, z)).normalize().add(this.random.triangle(0.0D, 0.0172275D * (double) divergence), this.random.triangle(0.0D, 0.0172275D * (double) divergence), this.random.triangle(0.0D, 0.0172275D * (double) divergence)).scale((double) speed);
+
+        this.setDeltaMovement(vec3d);
+        double d3 = vec3d.horizontalDistance();
+
+        this.setYRot((float) (Mth.atan2(vec3d.x, vec3d.z) * 57.2957763671875D));
+        this.setXRot((float) (Mth.atan2(vec3d.y, d3) * 57.2957763671875D));
         this.yRotO = this.getYRot();
         this.xRotO = this.getXRot();
     }
 
     public void shootFromRotation(Entity shooter, float pitch, float yaw, float roll, float speed, float divergence) {
-        float f = -Mth.sin(yaw * ((float)Math.PI / 180F)) * Mth.cos(pitch * ((float)Math.PI / 180F));
-        float g = -Mth.sin((pitch + roll) * ((float)Math.PI / 180F));
-        float h = Mth.cos(yaw * ((float)Math.PI / 180F)) * Mth.cos(pitch * ((float)Math.PI / 180F));
-        this.shoot((double)f, (double)g, (double)h, speed, divergence);
-        Vec3 vec3 = shooter.getDeltaMovement();
-        this.setDeltaMovement(this.getDeltaMovement().add(vec3.x, shooter.onGround() ? 0.0D : vec3.y, vec3.z));
+        float f5 = -Mth.sin(yaw * 0.017453292F) * Mth.cos(pitch * 0.017453292F);
+        float f6 = -Mth.sin((pitch + roll) * 0.017453292F);
+        float f7 = Mth.cos(yaw * 0.017453292F) * Mth.cos(pitch * 0.017453292F);
+
+        this.shoot((double) f5, (double) f6, (double) f7, speed, divergence);
+        Vec3 vec3d = shooter.getDeltaMovement();
+
+        if (!shooter.level().paperConfig().misc.disableRelativeProjectileVelocity) this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, shooter.onGround() ? 0.0D : vec3d.y, vec3d.z)); // Paper - allow disabling relative velocity
     }
 
+    // CraftBukkit start - call projectile hit event
+    public void preOnHit(HitResult movingobjectposition) {
+        org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition);
+        this.hitCancelled = event != null && event.isCancelled();
+        if (movingobjectposition.getType() == HitResult.Type.BLOCK || !this.hitCancelled) {
+            this.onHit(movingobjectposition);
+        }
+    }
+    // CraftBukkit end
+
     protected void onHit(HitResult hitResult) {
-        HitResult.Type type = hitResult.getType();
-        if (type == HitResult.Type.ENTITY) {
-            this.onHitEntity((EntityHitResult)hitResult);
-            this.level().gameEvent(GameEvent.PROJECTILE_LAND, hitResult.getLocation(), GameEvent.Context.of(this, (BlockState)null));
-        } else if (type == HitResult.Type.BLOCK) {
-            BlockHitResult blockHitResult = (BlockHitResult)hitResult;
-            this.onHitBlock(blockHitResult);
-            BlockPos blockPos = blockHitResult.getBlockPos();
-            this.level().gameEvent(GameEvent.PROJECTILE_LAND, blockPos, GameEvent.Context.of(this, this.level().getBlockState(blockPos)));
+        HitResult.Type movingobjectposition_enummovingobjecttype = hitResult.getType();
+
+        if (movingobjectposition_enummovingobjecttype == HitResult.Type.ENTITY) {
+            this.onHitEntity((EntityHitResult) hitResult);
+            this.level().gameEvent(GameEvent.PROJECTILE_LAND, hitResult.getLocation(), GameEvent.Context.of(this, (BlockState) null));
+        } else if (movingobjectposition_enummovingobjecttype == HitResult.Type.BLOCK) {
+            BlockHitResult movingobjectpositionblock = (BlockHitResult) hitResult;
+
+            this.onHitBlock(movingobjectpositionblock);
+            BlockPos blockposition = movingobjectpositionblock.getBlockPos();
+
+            this.level().gameEvent(GameEvent.PROJECTILE_LAND, blockposition, GameEvent.Context.of(this, this.level().getBlockState(blockposition)));
         }
 
     }
 
-    protected void onHitEntity(EntityHitResult entityHitResult) {
-    }
+    protected void onHitEntity(EntityHitResult entityHitResult) {}
 
     protected void onHitBlock(BlockHitResult blockHitResult) {
-        BlockState blockState = this.level().getBlockState(blockHitResult.getBlockPos());
-        blockState.onProjectileHit(this.level(), blockState, blockHitResult, this);
+        // CraftBukkit start - cancellable hit event
+        if (this.hitCancelled) {
+            return;
+        }
+        // CraftBukkit end
+        BlockState iblockdata = this.level().getBlockState(blockHitResult.getBlockPos());
+
+        iblockdata.onProjectileHit(this.level(), iblockdata, blockHitResult, this);
     }
 
     @Override
     public void lerpMotion(double x, double y, double z) {
         this.setDeltaMovement(x, y, z);
         if (this.xRotO == 0.0F && this.yRotO == 0.0F) {
-            double d = Math.sqrt(x * x + z * z);
-            this.setXRot((float)(Mth.atan2(y, d) * (double)(180F / (float)Math.PI)));
-            this.setYRot((float)(Mth.atan2(x, z) * (double)(180F / (float)Math.PI)));
+            double d3 = Math.sqrt(x * x + z * z);
+
+            this.setXRot((float) (Mth.atan2(y, d3) * 57.2957763671875D));
+            this.setYRot((float) (Mth.atan2(x, z) * 57.2957763671875D));
             this.xRotO = this.getXRot();
             this.yRotO = this.getYRot();
             this.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
@@ -176,26 +245,31 @@
         if (!entity.canBeHitByProjectile()) {
             return false;
         } else {
-            Entity entity2 = this.getOwner();
-            return entity2 == null || this.leftOwner || !entity2.isPassengerOfSameVehicle(entity);
+            Entity entity1 = this.getOwner();
+
+            // Paper start - Cancel hit for vanished players
+            if (entity1 instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer) {
+                org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity();
+                org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity();
+                if (!shooter.canSee(collided)) {
+                    return false;
+                }
+            }
+            // Paper end
+            return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity);
         }
     }
 
     protected void updateRotation() {
-        Vec3 vec3 = this.getDeltaMovement();
-        double d = vec3.horizontalDistance();
-        this.setXRot(lerpRotation(this.xRotO, (float)(Mth.atan2(vec3.y, d) * (double)(180F / (float)Math.PI))));
-        this.setYRot(lerpRotation(this.yRotO, (float)(Mth.atan2(vec3.x, vec3.z) * (double)(180F / (float)Math.PI))));
+        Vec3 vec3d = this.getDeltaMovement();
+        double d0 = vec3d.horizontalDistance();
+
+        this.setXRot(Projectile.lerpRotation(this.xRotO, (float) (Mth.atan2(vec3d.y, d0) * 57.2957763671875D)));
+        this.setYRot(Projectile.lerpRotation(this.yRotO, (float) (Mth.atan2(vec3d.x, vec3d.z) * 57.2957763671875D)));
     }
 
     protected static float lerpRotation(float prevRot, float newRot) {
-        while(newRot - prevRot < -180.0F) {
-            prevRot -= 360.0F;
-        }
-
-        while(newRot - prevRot >= 180.0F) {
-            prevRot += 360.0F;
-        }
+        prevRot += Math.round((newRot - prevRot) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server
 
         return Mth.lerp(0.2F, prevRot, newRot);
     }
@@ -203,6 +277,7 @@
     @Override
     public Packet<ClientGamePacketListener> getAddEntityPacket() {
         Entity entity = this.getOwner();
+
         return new ClientboundAddEntityPacket(this, entity == null ? 0 : entity.getId());
     }
 
@@ -210,6 +285,7 @@
     public void recreateFromPacket(ClientboundAddEntityPacket packet) {
         super.recreateFromPacket(packet);
         Entity entity = this.level().getEntity(packet.getData());
+
         if (entity != null) {
             this.setOwner(entity);
         }
@@ -219,10 +295,7 @@
     @Override
     public boolean mayInteract(Level world, BlockPos pos) {
         Entity entity = this.getOwner();
-        if (entity instanceof Player) {
-            return entity.mayInteract(world, pos);
-        } else {
-            return entity == null || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
-        }
+
+        return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
     }
 }
