--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -178,7 +_,7 @@
     private static final int DEFAULT_CURRENT_IMPULSE_CONTEXT_RESET_GRACE_TIME = 0;
     private long timeEntitySatOnShoulder;
     final Inventory inventory;
-    protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer();
+    protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer(this); // CraftBukkit - add "this" to constructor
     public final InventoryMenu inventoryMenu;
     public AbstractContainerMenu containerMenu;
     protected FoodData foodData = new FoodData();
@@ -217,6 +_,18 @@
     public Entity currentExplosionCause;
     private boolean ignoreFallDamageFromCurrentImpulse = false;
     private int currentImpulseContextResetGraceTime = 0;
+    public boolean affectsSpawning = true; // Paper - Affects Spawning API
+    public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
+
+    // CraftBukkit start
+    public boolean fauxSleeping;
+    public int oldLevel = -1;
+
+    @Override
+    public org.bukkit.craftbukkit.entity.CraftHumanEntity getBukkitEntity() {
+        return (org.bukkit.craftbukkit.entity.CraftHumanEntity) super.getBukkitEntity();
+    }
+    // CraftBukkit end
 
     public Player(Level level, GameProfile gameProfile) {
         super(EntityType.PLAYER, level);
@@ -286,6 +_,13 @@
 
         if (this.isSleeping()) {
             this.sleepCounter++;
+            // Paper start - Add PlayerDeepSleepEvent
+            if (this.sleepCounter == SLEEP_DURATION) {
+                if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) {
+                    this.sleepCounter = Integer.MIN_VALUE;
+                }
+            }
+            // Paper end - Add PlayerDeepSleepEvent
             if (this.sleepCounter > 100) {
                 this.sleepCounter = 100;
             }
@@ -303,7 +_,7 @@
         this.updateIsUnderwater();
         super.tick();
         if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) {
-            this.closeContainer();
+            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason
             this.containerMenu = this.inventoryMenu;
         }
 
@@ -390,7 +_,7 @@
     }
 
     private void turtleHelmetTick() {
-        this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true));
+        this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit
     }
 
     private boolean isEquipped(Item item) {
@@ -537,6 +_,18 @@
         }
     }
 
+    // Paper start - Inventory close reason; unused code, but to keep signatures aligned
+    public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
+        this.closeContainer();
+        this.containerMenu = this.inventoryMenu;
+    }
+    // Paper end - Inventory close reason
+    // Paper start - special close for unloaded inventory
+    public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
+        this.containerMenu = this.inventoryMenu;
+    }
+    // Paper end - special close for unloaded inventory
+
     public void closeContainer() {
         this.containerMenu = this.inventoryMenu;
     }
@@ -548,8 +_,14 @@
     public void rideTick() {
         if (!this.level().isClientSide && this.wantsToStopRiding() && this.isPassenger()) {
             this.stopRiding();
-            this.setShiftKeyDown(false);
-        } else {
+            // CraftBukkit start - SPIGOT-7316: no longer passenger, dismount and return
+            if (!this.isPassenger()) {
+                this.setShiftKeyDown(false);
+                return;
+            }
+        }
+        {
+            // CraftBukkit end
             super.rideTick();
             this.oBob = this.bob;
             this.bob = 0.0F;
@@ -608,6 +_,7 @@
         this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft());
         this.playShoulderEntityAmbientSound(this.getShoulderEntityRight());
         if (!this.level().isClientSide && (this.fallDistance > 0.5 || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) {
+            if (!this.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) // Paper - Add option to make parrots stay
             this.removeEntitiesOnShoulder();
         }
     }
@@ -850,10 +_,10 @@
             if (this.isDeadOrDying()) {
                 return false;
             } else {
-                this.removeEntitiesOnShoulder();
+                // this.removeEntitiesOnShoulder(); // CraftBukkit - moved down
                 if (damageSource.scalesWithDifficulty()) {
                     if (level.getDifficulty() == Difficulty.PEACEFUL) {
-                        amount = 0.0F;
+                        return false; // CraftBukkit - f = 0.0f -> return false
                     }
 
                     if (level.getDifficulty() == Difficulty.EASY) {
@@ -865,7 +_,14 @@
                     }
                 }
 
-                return amount != 0.0F && super.hurtServer(level, damageSource, amount);
+                // return amount != 0.0F && super.hurtServer(level, damageSource, amount);
+                // CraftBukkit start - Don't filter out 0 damage
+                boolean damaged = super.hurtServer(level, damageSource, amount);
+                if (damaged) {
+                    this.removeEntitiesOnShoulder();
+                }
+                return damaged;
+                // CraftBukkit end
             }
         }
     }
@@ -877,7 +_,7 @@
         BlocksAttacks blocksAttacks = itemBlockingWith != null ? itemBlockingWith.get(DataComponents.BLOCKS_ATTACKS) : null;
         float secondsToDisableBlocking = entity.getSecondsToDisableBlocking();
         if (secondsToDisableBlocking > 0.0F && blocksAttacks != null) {
-            blocksAttacks.disable(level, this, secondsToDisableBlocking, itemBlockingWith);
+            blocksAttacks.disable(level, this, secondsToDisableBlocking, itemBlockingWith, entity); // Paper - Add PlayerShieldDisableEvent
         }
     }
 
@@ -887,9 +_,29 @@
     }
 
     public boolean canHarmPlayer(Player other) {
-        Team team = this.getTeam();
-        Team team1 = other.getTeam();
-        return team == null || !team.isAlliedTo(team1) || team.isAllowFriendlyFire();
+        // CraftBukkit start - Change to check OTHER player's scoreboard team according to API
+        // To summarize this method's logic, it's "Can parameter hurt this"
+        org.bukkit.scoreboard.Team team;
+        if (other instanceof ServerPlayer) {
+            ServerPlayer thatPlayer = (ServerPlayer) other;
+            team = thatPlayer.getBukkitEntity().getScoreboard().getPlayerTeam(thatPlayer.getBukkitEntity());
+            if (team == null || team.allowFriendlyFire()) {
+                return true;
+            }
+        } else {
+            // This should never be called, but is implemented anyway
+            org.bukkit.OfflinePlayer thisPlayer = other.level().getCraftServer().getOfflinePlayer(other.getScoreboardName());
+            team = other.level().getCraftServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(thisPlayer);
+            if (team == null || team.allowFriendlyFire()) {
+                return true;
+            }
+        }
+
+        if (this instanceof ServerPlayer) {
+            return !team.hasPlayer(((ServerPlayer) this).getBukkitEntity());
+        }
+        return !team.hasPlayer(this.level().getCraftServer().getOfflinePlayer(this.getScoreboardName()));
+        // CraftBukkit end
     }
 
     @Override
@@ -903,7 +_,12 @@
     }
 
     @Override
-    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
+    // CraftBukkit start
+    protected boolean actuallyHurt(ServerLevel level, DamageSource damageSource, float amount, org.bukkit.event.entity.EntityDamageEvent event) { // void -> boolean
+        if (true) {
+            return super.actuallyHurt(level, damageSource, amount, event);
+        }
+        // CraftBukkit end
         if (!this.isInvulnerableTo(level, damageSource)) {
             amount = this.getDamageAfterArmorAbsorb(damageSource, amount);
             amount = this.getDamageAfterMagicAbsorb(damageSource, amount);
@@ -915,7 +_,7 @@
             }
 
             if (var8 != 0.0F) {
-                this.causeFoodExhaustion(damageSource.getFoodExhaustion());
+                this.causeFoodExhaustion(damageSource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
                 this.getCombatTracker().recordDamage(damageSource, var8);
                 this.setHealth(this.getHealth() - var8);
                 if (var8 < 3.4028235E37F) {
@@ -925,6 +_,7 @@
                 this.gameEvent(GameEvent.ENTITY_DAMAGE);
             }
         }
+        return false; // CraftBukkit
     }
 
     public boolean isTextFilteringEnabled() {
@@ -1009,13 +_,19 @@
 
     @Override
     public void removeVehicle() {
-        super.removeVehicle();
+        // Paper start - Force entity dismount during teleportation
+        this.removeVehicle(false);
+    }
+    @Override
+    public void removeVehicle(boolean suppressCancellation) {
+        super.removeVehicle(suppressCancellation);
+        // Paper end - Force entity dismount during teleportation
         this.boardingCooldown = 0;
     }
 
     @Override
     protected boolean isImmobile() {
-        return super.isImmobile() || this.isSleeping();
+        return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move...
     }
 
     @Override
@@ -1094,8 +_,17 @@
     }
 
     public void attack(Entity target) {
-        if (target.isAttackable()) {
-            if (!target.skipAttackInteraction(this)) {
+        // Paper start - PlayerAttackEntityEvent
+        boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic
+        io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent(
+            (org.bukkit.entity.Player) this.getBukkitEntity(),
+            target.getBukkitEntity(),
+            willAttack
+        );
+
+        if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable.
+            {
+        // Paper end - PlayerAttackEntityEvent
                 float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE);
                 ItemStack weaponItem = this.getWeaponItem();
                 DamageSource damageSource = Optional.ofNullable(weaponItem.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this));
@@ -1103,18 +_,25 @@
                 float attackStrengthScale = this.getAttackStrengthScale(0.5F);
                 f *= 0.2F + attackStrengthScale * attackStrengthScale * 0.8F;
                 f1 *= attackStrengthScale;
-                this.resetAttackStrengthTicker();
+                // this.resetAttackStrengthTicker(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt
                 if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE)
-                    && target instanceof Projectile projectile
-                    && projectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) {
-                    this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource());
-                } else {
+                    && target instanceof Projectile projectile) {
+                        // CraftBukkit start
+                        if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(target, damageSource, f1, false)) {
+                            return;
+                        }
+                        if (projectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) {
+                            this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource());
+                            return;
+                        }
+                }
+                {
+                    // CraftBukkit end
                     if (f > 0.0F || f1 > 0.0F) {
                         boolean flag = attackStrengthScale > 0.9F;
                         boolean flag1;
                         if (this.isSprinting() && flag) {
-                            this.level()
-                                .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F);
+                            this.sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
                             flag1 = true;
                         } else {
                             flag1 = false;
@@ -1130,7 +_,9 @@
                             && !this.isPassenger()
                             && target instanceof LivingEntity
                             && !this.isSprinting();
+                        flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits
                         if (flag2) {
+                            damageSource = damageSource.critical(); // Paper - critical damage API
                             f *= 1.5F;
                         }
 
@@ -1157,17 +_,23 @@
                                 if (target instanceof LivingEntity livingEntity1) {
                                     livingEntity1.knockback(
                                         f4 * 0.5F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0))
+                                        , this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK // Paper - knockback events
                                     );
                                 } else {
                                     target.push(
                                         -Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)) * f4 * 0.5F,
                                         0.1,
                                         Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) * f4 * 0.5F
+                                        , this // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
                                     );
                                 }
 
                                 this.setDeltaMovement(this.getDeltaMovement().multiply(0.6, 1.0, 0.6));
+                                // Paper start - Configurable sprint interruption on attack
+                                if (!this.level().paperConfig().misc.disableSprintInterruptionOnAttack) {
                                 this.setSprinting(false);
+                                }
+                                // Paper end - Configurable sprint interruption on attack
                             }
 
                             if (flag3) {
@@ -1181,42 +_,59 @@
                                         && !(livingEntity2 instanceof ArmorStand armorStand && armorStand.isMarker())
                                         && this.distanceToSqr(livingEntity2) < 9.0) {
                                         float f6 = this.getEnchantedDamage(livingEntity2, f5, damageSource) * attackStrengthScale;
-                                        if (this.level() instanceof ServerLevel serverLevel && livingEntity2.hurtServer(serverLevel, damageSource, f6)) {
+                                        // Paper start - Only apply knockback if the event is not cancelled
+                                        livingEntity2.lastDamageCancelled = false;
+                                        if (this.level() instanceof ServerLevel serverLevel && livingEntity2.hurtServer(serverLevel, damageSource.knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.ENTITY_SWEEP_ATTACK), f6) && !livingEntity2.lastDamageCancelled) {
+                                        // Paper end - Only apply knockback if the event is not cancelled
                                             livingEntity2.knockback(
                                                 0.4F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0))
+                                                    , this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK // Paper - knockback events
                                             );
                                             EnchantmentHelper.doPostAttackEffects(serverLevel, livingEntity2, damageSource);
                                         }
                                     }
                                 }
 
-                                this.level()
-                                    .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F);
+                                sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
                                 this.sweepAttack();
                             }
 
                             if (target instanceof ServerPlayer && target.hurtMarked) {
+                                // CraftBukkit start - Add Velocity Event
+                                boolean cancelled = false;
+                                org.bukkit.entity.Player player = (org.bukkit.entity.Player) target.getBukkitEntity();
+                                org.bukkit.util.Vector velocity = org.bukkit.craftbukkit.util.CraftVector.toBukkit(deltaMovement);
+
+                                org.bukkit.event.player.PlayerVelocityEvent event = new org.bukkit.event.player.PlayerVelocityEvent(player, velocity.clone());
+                                this.level().getCraftServer().getPluginManager().callEvent(event);
+
+                                if (event.isCancelled()) {
+                                    cancelled = true;
+                                } else if (!velocity.equals(event.getVelocity())) {
+                                    player.setVelocity(event.getVelocity());
+                                }
+
+                                if (!cancelled) {
                                 ((ServerPlayer)target).connection.send(new ClientboundSetEntityMotionPacket(target));
                                 target.hurtMarked = false;
                                 target.setDeltaMovement(deltaMovement);
+                                }
+                                // CraftBukkit end
                             }
 
                             if (flag2) {
-                                this.level()
-                                    .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F);
+                                sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
                                 this.crit(target);
                             }
 
                             if (!flag2 && !flag3) {
                                 if (flag) {
-                                    this.level()
-                                        .playSound(
-                                            null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F
+                                    sendSoundEffect(
+                                            this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F // Paper - send while respecting visibility
                                         );
                                 } else {
-                                    this.level()
-                                        .playSound(
-                                            null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F
+                                    sendSoundEffect(
+                                            this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F // Paper - send while respecting visibility
                                         );
                                 }
                             }
@@ -1264,10 +_,10 @@
                                 }
                             }
 
-                            this.causeFoodExhaustion(0.1F);
+                            this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value
                         } else {
-                            this.level()
-                                .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F);
+                            sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
+                            // CraftBukkit end
                         }
                     }
                 }
@@ -1302,8 +_,8 @@
     }
 
     @Override
-    public void remove(Entity.RemovalReason reason) {
-        super.remove(reason);
+    public void remove(Entity.RemovalReason reason, @Nullable org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) { // CraftBukkit - add Bukkit remove cause
+        super.remove(reason, eventCause); // CraftBukkit - add Bukkit remove cause
         this.inventoryMenu.removed(this);
         if (this.containerMenu != null && this.hasContainerOpen()) {
             this.doCloseContainer();
@@ -1367,6 +_,12 @@
     }
 
     public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos bedPos) {
+        // CraftBukkit start
+        return this.startSleepInBed(bedPos, false);
+    }
+
+    public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos bedPos, boolean force) {
+        // CraftBukkit end
         this.startSleeping(bedPos);
         this.sleepCounter = 0;
         return Either.right(Unit.INSTANCE);
@@ -1478,7 +_,7 @@
 
     @Override
     public boolean causeFallDamage(double fallDistance, float damageMultiplier, DamageSource damageSource) {
-        if (this.abilities.mayfly) {
+        if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage
             return false;
         } else {
             if (fallDistance >= 2.0) {
@@ -1519,7 +_,15 @@
     }
 
     public void startFallFlying() {
-        this.setSharedFlag(7, true);
+        // CraftBukkit start
+        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, true).isCancelled()) {
+            this.setSharedFlag(7, true);
+        } else {
+            // SPIGOT-5542: must toggle like below
+            this.setSharedFlag(7, true);
+            this.setSharedFlag(7, false);
+        }
+        // CraftBukkit end
     }
 
     @Override
@@ -1625,15 +_,35 @@
     public int getXpNeededForNextLevel() {
         if (this.experienceLevel >= 30) {
             return 112 + (this.experienceLevel - 30) * 9;
-        } else {
+        } else { // Paper - diff on change; calculateTotalExperiencePoints
             return this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2;
         }
     }
 
+    // Paper start - send while respecting visibility
+    private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) {
+        fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity itself
+        if (fromEntity instanceof ServerPlayer serverPlayer) {
+            serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong()));
+        }
+    }
+    // Paper end - send while respecting visibility
+
     public void causeFoodExhaustion(float exhaustion) {
+        // CraftBukkit start
+        this.causeFoodExhaustion(exhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.UNKNOWN);
+    }
+
+    public void causeFoodExhaustion(float exhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason reason) {
+        // CraftBukkit end
         if (!this.abilities.invulnerable) {
             if (!this.level().isClientSide) {
-                this.foodData.addExhaustion(exhaustion);
+                // CraftBukkit start
+                org.bukkit.event.entity.EntityExhaustionEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerExhaustionEvent(this, reason, exhaustion);
+                if (!event.isCancelled()) {
+                    this.foodData.addExhaustion(event.getExhaustion());
+                }
+                // CraftBukkit end
             }
         }
     }
@@ -1727,31 +_,60 @@
 
     public void removeEntitiesOnShoulder() {
         if (this.timeEntitySatOnShoulder + 20L < this.level().getGameTime()) {
-            this.respawnEntityOnShoulder(this.getShoulderEntityLeft());
+            // CraftBukkit start
+            if (this.respawnEntityOnShoulder(this.getShoulderEntityLeft())) {
+                this.setShoulderEntityLeft(new CompoundTag());
+            }
+            if (this.respawnEntityOnShoulder(this.getShoulderEntityRight())) {
+                this.setShoulderEntityRight(new CompoundTag());
+            }
+            // CraftBukkit end
+        }
+    }
+
+    // Paper start - release entity api
+    public Entity releaseLeftShoulderEntity() {
+        Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityLeft());
+        if (entity != null) {
             this.setShoulderEntityLeft(new CompoundTag());
-            this.respawnEntityOnShoulder(this.getShoulderEntityRight());
+        }
+        return entity;
+    }
+
+    public Entity releaseRightShoulderEntity() {
+        Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityRight());
+        if (entity != null) {
             this.setShoulderEntityRight(new CompoundTag());
         }
+        return entity;
     }
+    // Paper end - release entity api
 
-    private void respawnEntityOnShoulder(CompoundTag entityCompound) {
+    private boolean respawnEntityOnShoulder(CompoundTag entityCompound) { // CraftBukkit void->boolean
+    // Paper start - release entity api - return entity - overload
+        return this.respawnEntityOnShoulder0(entityCompound) != null;
+    }
+    @Nullable
+    private Entity respawnEntityOnShoulder0(CompoundTag entityCompound) { // CraftBukkit void->boolean
+    // Paper end - release entity api - return entity - overload
         if (this.level() instanceof ServerLevel serverLevel && !entityCompound.isEmpty()) {
             try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(this.problemPath(), LOGGER)) {
-                EntityType.create(
+                return EntityType.create( // Paper - release entity api
                         TagValueInput.create(scopedCollector.forChild(() -> ".shoulder"), serverLevel.registryAccess(), entityCompound),
                         serverLevel,
                         EntitySpawnReason.LOAD
                     )
-                    .ifPresent(entity -> {
+                    .map(entity -> { // Paper - release entity api
                         if (entity instanceof TamableAnimal tamableAnimal) {
                             tamableAnimal.setOwner(this);
                         }
 
                         entity.setPos(this.getX(), this.getY() + 0.7F, this.getZ());
-                        serverLevel.addWithUUID(entity);
-                    });
+                        return serverLevel.addWithUUID(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY) ? entity : null; // Paper - spawn reason
+                    }).orElse(null); // Paper - release entity api - return entity
             }
         }
+        return null; // Paper - return null
     }
 
     @Nullable
@@ -1945,17 +_,32 @@
         return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING);
     }
 
+    // Paper start - PlayerReadyArrowEvent
+    // We pass a result mutable boolean in to allow the caller of this method to know if the event was cancelled.
+    protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack, final org.apache.commons.lang3.mutable.MutableBoolean cancelled) {
+        if (!(this instanceof final ServerPlayer serverPlayer)) return true;
+        final boolean notCancelled = new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent(
+            serverPlayer.getBukkitEntity(),
+            org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow),
+            org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)
+        ).callEvent();
+        if (!notCancelled) cancelled.setValue(true);
+        return notCancelled;
+    }
+    // Paper end - PlayerReadyArrowEvent
+
     @Override
     public ItemStack getProjectile(ItemStack shootable) {
         if (!(shootable.getItem() instanceof ProjectileWeaponItem)) {
             return ItemStack.EMPTY;
         } else {
-            Predicate<ItemStack> supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getSupportedHeldProjectiles();
+            final org.apache.commons.lang3.mutable.MutableBoolean anyEventCancelled = new org.apache.commons.lang3.mutable.MutableBoolean(); // Paper - PlayerReadyArrowEvent
+            Predicate<ItemStack> supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getSupportedHeldProjectiles().and(item -> this.tryReadyArrow(shootable, item, anyEventCancelled)); // Paper - PlayerReadyArrowEvent
             ItemStack heldProjectile = ProjectileWeaponItem.getHeldProjectile(this, supportedHeldProjectiles);
             if (!heldProjectile.isEmpty()) {
                 return heldProjectile;
             } else {
-                supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getAllSupportedProjectiles();
+                supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getAllSupportedProjectiles().and(item -> this.tryReadyArrow(shootable, item, anyEventCancelled)); // Paper - PlayerReadyArrowEvent
 
                 for (int i = 0; i < this.inventory.getContainerSize(); i++) {
                     ItemStack item = this.inventory.getItem(i);
@@ -1964,6 +_,7 @@
                     }
                 }
 
+                if (anyEventCancelled.booleanValue() && !this.abilities.instabuild && this instanceof final ServerPlayer player) this.resyncUsingItem(player); // Paper - resync if no item matched the Predicate
                 return this.hasInfiniteMaterials() ? new ItemStack(Items.ARROW) : ItemStack.EMPTY;
             }
         }
@@ -2046,12 +_,20 @@
     }
 
     public boolean hasClientLoaded() {
-        return this.clientLoaded || this.clientLoadedTimeoutTimer <= 0;
+        return this.clientLoaded; // Paper - Add PlayerLoadedWorldEvent
     }
 
     public void tickClientLoadTimeout() {
         if (!this.clientLoaded) {
             this.clientLoadedTimeoutTimer--;
+            // Paper start - Add PlayerLoadedWorldEvent
+            if (this.clientLoadedTimeoutTimer <= 0) {
+                this.clientLoaded = true;
+
+                final io.papermc.paper.event.player.PlayerClientLoadedWorldEvent event = new io.papermc.paper.event.player.PlayerClientLoadedWorldEvent((org.bukkit.craftbukkit.entity.CraftPlayer) getBukkitEntity(), true);
+                event.callEvent();
+            }
+            // Paper end - Add PlayerLoadedWorldEvent
         }
     }
 
