--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -1,5 +1,6 @@
 package net.minecraft.world.entity;
 
+import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; // Paper
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -29,6 +30,8 @@
 import net.minecraft.core.particles.ItemParticleOption;
 import net.minecraft.core.particles.ParticleTypes;
 import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.FloatTag;
+import net.minecraft.nbt.IntTag;
 import net.minecraft.nbt.ListTag;
 import net.minecraft.nbt.NbtOps;
 import net.minecraft.nbt.Tag;
@@ -74,11 +77,11 @@
 import net.minecraft.world.entity.ai.attributes.Attributes;
 import net.minecraft.world.entity.ai.attributes.DefaultAttributes;
 import net.minecraft.world.entity.ai.targeting.TargetingConditions;
+import net.minecraft.world.entity.animal.Animal;
 import net.minecraft.world.entity.animal.FlyingAnimal;
 import net.minecraft.world.entity.animal.Wolf;
 import net.minecraft.world.entity.boss.wither.WitherBoss;
 import net.minecraft.world.entity.item.ItemEntity;
-import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.entity.projectile.AbstractArrow;
 import net.minecraft.world.food.FoodProperties;
 import net.minecraft.world.item.ArmorItem;
@@ -114,17 +117,41 @@
 import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
 import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
 import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.EntityHitResult;
 import net.minecraft.world.phys.HitResult;
 import net.minecraft.world.phys.Vec3;
 import net.minecraft.world.scores.PlayerTeam;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import com.google.common.base.Function;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_19_R2.attribute.CraftAttributeMap;
+import org.bukkit.craftbukkit.v1_19_R2.event.CraftEventFactory;
+import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.event.entity.ArrowBodyCountChangeEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.EntityDamageEvent.DamageModifier;
+import org.bukkit.event.entity.EntityPotionEffectEvent;
+import org.bukkit.event.entity.EntityRegainHealthEvent;
+import org.bukkit.event.entity.EntityResurrectEvent;
+import org.bukkit.event.entity.EntityTeleportEvent;
+import org.bukkit.event.player.PlayerItemConsumeEvent;
+// CraftBukkit end
+
+import co.aikar.timings.MinecraftTimings; // Paper
+
 public abstract class LivingEntity extends Entity {
+
     private static final Logger LOGGER = LogUtils.getLogger();
     private static final UUID SPEED_MODIFIER_SPRINTING_UUID = UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D");
     private static final UUID SPEED_MODIFIER_SOUL_SPEED_UUID = UUID.fromString("87f46a96-686f-4796-b035-22e16ee9e038");
     private static final UUID SPEED_MODIFIER_POWDER_SNOW_UUID = UUID.fromString("1eaf83ff-7207-4596-b37a-d7a07b3ec4ce");
-    private static final AttributeModifier SPEED_MODIFIER_SPRINTING = new AttributeModifier(SPEED_MODIFIER_SPRINTING_UUID, "Sprinting speed boost", (double)0.3F, AttributeModifier.Operation.MULTIPLY_TOTAL);
+    private static final AttributeModifier SPEED_MODIFIER_SPRINTING = new AttributeModifier(LivingEntity.SPEED_MODIFIER_SPRINTING_UUID, "Sprinting speed boost", 0.30000001192092896D, AttributeModifier.Operation.MULTIPLY_TOTAL);
     public static final int HAND_SLOTS = 2;
     public static final int ARMOR_SLOTS = 4;
     public static final int EQUIPMENT_SLOT_OFFSET = 98;
@@ -156,10 +183,10 @@
     private final AttributeMap attributes;
     public CombatTracker combatTracker = new CombatTracker(this);
     public final Map<MobEffect, MobEffectInstance> activeEffects = Maps.newHashMap();
-    private final NonNullList<ItemStack> lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY);
-    private final NonNullList<ItemStack> lastArmorItemStacks = NonNullList.withSize(4, ItemStack.EMPTY);
+    private final NonNullList<ItemStack> lastHandItemStacks;
+    private final NonNullList<ItemStack> lastArmorItemStacks;
     public boolean swinging;
-    private boolean discardFriction = false;
+    private boolean discardFriction;
     public InteractionHand swingingArm;
     public int swingTime;
     public int removeArrowTime;
@@ -174,16 +201,16 @@
     public float animationSpeedOld;
     public float animationSpeed;
     public float animationPosition;
-    public int invulnerableDuration = 20;
+    public int invulnerableDuration;
     public final float timeOffs;
     public final float rotA;
     public float yBodyRot;
     public float yBodyRotO;
     public float yHeadRot;
     public float yHeadRotO;
-    public float flyingSpeed = 0.02F;
+    public float flyingSpeed;
     @Nullable
-    public Player lastHurtByPlayer;
+    public net.minecraft.world.entity.player.Player lastHurtByPlayer;
     public int lastHurtByPlayerTime;
     protected boolean dead;
     protected int noActionTime;
@@ -206,7 +233,7 @@
     protected double lerpXRot;
     protected double lyHeadRot;
     protected int lerpHeadSteps;
-    public boolean effectsDirty = true;
+    public boolean effectsDirty;
     @Nullable
     public LivingEntity lastHurtByMob;
     public int lastHurtByMobTimestamp;
@@ -215,11 +242,11 @@
     private float speed;
     private int noJumpDelay;
     private float absorptionAmount;
-    protected ItemStack useItem = ItemStack.EMPTY;
+    protected ItemStack useItem;
     protected int useItemRemaining;
     protected int fallFlyTicks;
     private BlockPos lastPos;
-    private Optional<BlockPos> lastClimbablePos = Optional.empty();
+    private Optional<BlockPos> lastClimbablePos;
     @Nullable
     private DamageSource lastDamageSource;
     private long lastDamageStamp;
@@ -228,20 +255,55 @@
     private float swimAmountO;
     protected Brain<?> brain;
     private boolean skipDropExperience;
+    // CraftBukkit start
+    public int expToDrop;
+    public boolean forceDrops;
+    public ArrayList<org.bukkit.inventory.ItemStack> drops = new ArrayList<org.bukkit.inventory.ItemStack>();
+    public final org.bukkit.craftbukkit.v1_19_R2.attribute.CraftAttributeMap craftAttributes;
+    public boolean collides = true;
+    public Set<UUID> collidableExemptions = new HashSet<>();
+    public boolean bukkitPickUpLoot;
+    public org.bukkit.craftbukkit.v1_19_R2.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.v1_19_R2.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
+    public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
+    public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper
+
+    @Override
+    public float getBukkitYaw() {
+        return this.getYHeadRot();
+    }
+    // CraftBukkit end
+    // Spigot start
+    public void inactiveTick()
+    {
+        super.inactiveTick();
+        ++this.noActionTime; // Above all the floats
+    }
+    // Spigot end
 
     protected LivingEntity(EntityType<? extends LivingEntity> type, Level world) {
         super(type, world);
+        this.lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY);
+        this.lastArmorItemStacks = NonNullList.withSize(4, ItemStack.EMPTY);
+        this.discardFriction = false;
+        this.invulnerableDuration = 20;
+        this.flyingSpeed = 0.02F;
+        this.effectsDirty = true;
+        this.useItem = ItemStack.EMPTY;
+        this.lastClimbablePos = Optional.empty();
         this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type));
-        this.setHealth(this.getMaxHealth());
+        this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
+        // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
+        this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue());
         this.blocksBuilding = true;
-        this.rotA = (float)((Math.random() + 1.0D) * (double)0.01F);
+        this.rotA = (float) ((Math.random() + 1.0D) * 0.009999999776482582D);
         this.reapplyPosition();
-        this.timeOffs = (float)Math.random() * 12398.0F;
-        this.setYRot((float)(Math.random() * (double)((float)Math.PI * 2F)));
+        this.timeOffs = (float) Math.random() * 12398.0F;
+        this.setYRot((float) (Math.random() * 6.2831854820251465D));
         this.yHeadRot = this.getYRot();
         this.maxUpStep = 0.6F;
-        NbtOps nbtOps = NbtOps.INSTANCE;
-        this.brain = this.makeBrain(new Dynamic<>(nbtOps, nbtOps.createMap(ImmutableMap.of(nbtOps.createString("memories"), nbtOps.emptyMap()))));
+        NbtOps dynamicopsnbt = NbtOps.INSTANCE;
+
+        this.brain = this.makeBrain(new Dynamic(dynamicopsnbt, (Tag) dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), (Tag) dynamicopsnbt.emptyMap()))));
     }
 
     public Brain<?> getBrain() {
@@ -267,13 +329,13 @@
 
     @Override
     protected void defineSynchedData() {
-        this.entityData.define(DATA_LIVING_ENTITY_FLAGS, (byte)0);
-        this.entityData.define(DATA_EFFECT_COLOR_ID, 0);
-        this.entityData.define(DATA_EFFECT_AMBIENCE_ID, false);
-        this.entityData.define(DATA_ARROW_COUNT_ID, 0);
-        this.entityData.define(DATA_STINGER_COUNT_ID, 0);
-        this.entityData.define(DATA_HEALTH_ID, 1.0F);
-        this.entityData.define(SLEEPING_POS_ID, Optional.empty());
+        this.entityData.define(LivingEntity.DATA_LIVING_ENTITY_FLAGS, (byte) 0);
+        this.entityData.define(LivingEntity.DATA_EFFECT_COLOR_ID, 0);
+        this.entityData.define(LivingEntity.DATA_EFFECT_AMBIENCE_ID, false);
+        this.entityData.define(LivingEntity.DATA_ARROW_COUNT_ID, 0);
+        this.entityData.define(LivingEntity.DATA_STINGER_COUNT_ID, 0);
+        this.entityData.define(LivingEntity.DATA_HEALTH_ID, 1.0F);
+        this.entityData.define(LivingEntity.SLEEPING_POS_ID, Optional.empty());
     }
 
     public static AttributeSupplier.Builder createLivingAttributes() {
@@ -292,11 +354,19 @@
         }
 
         if (!this.level.isClientSide && this.fallDistance > 3.0F && onGround) {
-            float f = (float)Mth.ceil(this.fallDistance - 3.0F);
+            float f = (float) Mth.ceil(this.fallDistance - 3.0F);
+
             if (!state.isAir()) {
-                double d = Math.min((double)(0.2F + f / 15.0F), 2.5D);
-                int i = (int)(150.0D * d);
-                ((ServerLevel)this.level).sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), this.getX(), this.getY(), this.getZ(), i, 0.0D, 0.0D, 0.0D, (double)0.15F);
+                double d1 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
+                int i = (int) (150.0D * d1);
+
+                // CraftBukkit start - visiblity api
+                if (this instanceof ServerPlayer) {
+                    ((ServerLevel) this.level).sendParticles((ServerPlayer) this, new BlockParticleOption(ParticleTypes.BLOCK, state), this.getX(), this.getY(), this.getZ(), i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, false);
+                } else {
+                    ((ServerLevel) this.level).sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), this.getX(), this.getY(), this.getZ(), i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D);
+                }
+                // CraftBukkit end
             }
         }
 
@@ -329,34 +399,39 @@
         }
 
         if (this.isAlive()) {
-            boolean bl = this instanceof Player;
+            boolean flag = this instanceof net.minecraft.world.entity.player.Player;
+
             if (!this.level.isClientSide) {
                 if (this.isInWall()) {
                     this.hurt(DamageSource.IN_WALL, 1.0F);
-                } else if (bl && !this.level.getWorldBorder().isWithinBounds(this.getBoundingBox())) {
-                    double d = this.level.getWorldBorder().getDistanceToBorder(this) + this.level.getWorldBorder().getDamageSafeZone();
-                    if (d < 0.0D) {
-                        double e = this.level.getWorldBorder().getDamagePerBlock();
-                        if (e > 0.0D) {
-                            this.hurt(DamageSource.IN_WALL, (float)Math.max(1, Mth.floor(-d * e)));
+                } else if (flag && !this.level.getWorldBorder().isWithinBounds(this.getBoundingBox())) {
+                    double d0 = this.level.getWorldBorder().getDistanceToBorder(this) + this.level.getWorldBorder().getDamageSafeZone();
+
+                    if (d0 < 0.0D) {
+                        double d1 = this.level.getWorldBorder().getDamagePerBlock();
+
+                        if (d1 > 0.0D) {
+                            this.hurt(DamageSource.IN_WALL, (float) Math.max(1, Mth.floor(-d0 * d1)));
                         }
                     }
                 }
             }
 
             if (this.isEyeInFluid(FluidTags.WATER) && !this.level.getBlockState(new BlockPos(this.getX(), this.getEyeY(), this.getZ())).is(Blocks.BUBBLE_COLUMN)) {
-                boolean bl2 = !this.canBreatheUnderwater() && !MobEffectUtil.hasWaterBreathing(this) && (!bl || !((Player)this).getAbilities().invulnerable);
-                if (bl2) {
+                boolean flag1 = !this.canBreatheUnderwater() && !MobEffectUtil.hasWaterBreathing(this) && (!flag || !((net.minecraft.world.entity.player.Player) this).getAbilities().invulnerable);
+
+                if (flag1) {
                     this.setAirSupply(this.decreaseAirSupply(this.getAirSupply()));
                     if (this.getAirSupply() == -20) {
                         this.setAirSupply(0);
-                        Vec3 vec3 = this.getDeltaMovement();
+                        Vec3 vec3d = this.getDeltaMovement();
 
-                        for(int i = 0; i < 8; ++i) {
-                            double f = this.random.nextDouble() - this.random.nextDouble();
-                            double g = this.random.nextDouble() - this.random.nextDouble();
-                            double h = this.random.nextDouble() - this.random.nextDouble();
-                            this.level.addParticle(ParticleTypes.BUBBLE, this.getX() + f, this.getY() + g, this.getZ() + h, vec3.x, vec3.y, vec3.z);
+                        for (int i = 0; i < 8; ++i) {
+                            double d2 = this.random.nextDouble() - this.random.nextDouble();
+                            double d3 = this.random.nextDouble() - this.random.nextDouble();
+                            double d4 = this.random.nextDouble() - this.random.nextDouble();
+
+                            this.level.addParticle(ParticleTypes.BUBBLE, this.getX() + d2, this.getY() + d3, this.getZ() + d4, vec3d.x, vec3d.y, vec3d.z);
                         }
 
                         this.hurt(DamageSource.DROWN, 2.0F);
@@ -371,10 +446,11 @@
             }
 
             if (!this.level.isClientSide) {
-                BlockPos blockPos = this.blockPosition();
-                if (!Objects.equal(this.lastPos, blockPos)) {
-                    this.lastPos = blockPos;
-                    this.onChangedBlock(blockPos);
+                BlockPos blockposition = this.blockPosition();
+
+                if (!Objects.equal(this.lastPos, blockposition)) {
+                    this.lastPos = blockposition;
+                    this.onChangedBlock(blockposition);
                 }
             }
         }
@@ -407,9 +483,9 @@
 
         if (this.lastHurtByMob != null) {
             if (!this.lastHurtByMob.isAlive()) {
-                this.setLastHurtByMob((LivingEntity)null);
+                this.setLastHurtByMob((LivingEntity) null);
             } else if (this.tickCount - this.lastHurtByMobTimestamp > 100) {
-                this.setLastHurtByMob((LivingEntity)null);
+                this.setLastHurtByMob((LivingEntity) null);
             }
         }
 
@@ -427,9 +503,11 @@
     }
 
     protected void spawnSoulSpeedParticle() {
-        Vec3 vec3 = this.getDeltaMovement();
-        this.level.addParticle(ParticleTypes.SOUL, this.getX() + (this.random.nextDouble() - 0.5D) * (double)this.getBbWidth(), this.getY() + 0.1D, this.getZ() + (this.random.nextDouble() - 0.5D) * (double)this.getBbWidth(), vec3.x * -0.2D, 0.1D, vec3.z * -0.2D);
+        Vec3 vec3d = this.getDeltaMovement();
+
+        this.level.addParticle(ParticleTypes.SOUL, this.getX() + (this.random.nextDouble() - 0.5D) * (double) this.getBbWidth(), this.getY() + 0.1D, this.getZ() + (this.random.nextDouble() - 0.5D) * (double) this.getBbWidth(), vec3d.x * -0.2D, 0.1D, vec3d.z * -0.2D);
         float f = this.random.nextFloat() * 0.4F + this.random.nextFloat() > 0.9F ? 0.6F : 0.0F;
+
         this.playSound(SoundEvents.SOUL_ESCAPE, f, 0.6F + this.random.nextFloat() * 0.4F);
     }
 
@@ -447,10 +525,11 @@
     }
 
     protected void removeSoulSpeed() {
-        AttributeInstance attributeInstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
-        if (attributeInstance != null) {
-            if (attributeInstance.getModifier(SPEED_MODIFIER_SOUL_SPEED_UUID) != null) {
-                attributeInstance.removeModifier(SPEED_MODIFIER_SOUL_SPEED_UUID);
+        AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
+
+        if (attributemodifiable != null) {
+            if (attributemodifiable.getModifier(LivingEntity.SPEED_MODIFIER_SOUL_SPEED_UUID) != null) {
+                attributemodifiable.removeModifier(LivingEntity.SPEED_MODIFIER_SOUL_SPEED_UUID);
             }
 
         }
@@ -459,17 +538,20 @@
     protected void tryAddSoulSpeed() {
         if (!this.getBlockStateOnLegacy().isAir()) {
             int i = EnchantmentHelper.getEnchantmentLevel(Enchantments.SOUL_SPEED, this);
+
             if (i > 0 && this.onSoulSpeedBlock()) {
-                AttributeInstance attributeInstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
-                if (attributeInstance == null) {
+                AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
+
+                if (attributemodifiable == null) {
                     return;
                 }
 
-                attributeInstance.addTransientModifier(new AttributeModifier(SPEED_MODIFIER_SOUL_SPEED_UUID, "Soul speed boost", (double)(0.03F * (1.0F + (float)i * 0.35F)), AttributeModifier.Operation.ADDITION));
+                attributemodifiable.addTransientModifier(new AttributeModifier(LivingEntity.SPEED_MODIFIER_SOUL_SPEED_UUID, "Soul speed boost", (double) (0.03F * (1.0F + (float) i * 0.35F)), AttributeModifier.Operation.ADDITION));
                 if (this.getRandom().nextFloat() < 0.04F) {
-                    ItemStack itemStack = this.getItemBySlot(EquipmentSlot.FEET);
-                    itemStack.hurtAndBreak(1, this, (player) -> {
-                        player.broadcastBreakEvent(EquipmentSlot.FEET);
+                    ItemStack itemstack = this.getItemBySlot(EquipmentSlot.FEET);
+
+                    itemstack.hurtAndBreak(1, this, (entityliving) -> {
+                        entityliving.broadcastBreakEvent(EquipmentSlot.FEET);
                     });
                 }
             }
@@ -478,10 +560,11 @@
     }
 
     protected void removeFrost() {
-        AttributeInstance attributeInstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
-        if (attributeInstance != null) {
-            if (attributeInstance.getModifier(SPEED_MODIFIER_POWDER_SNOW_UUID) != null) {
-                attributeInstance.removeModifier(SPEED_MODIFIER_POWDER_SNOW_UUID);
+        AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
+
+        if (attributemodifiable != null) {
+            if (attributemodifiable.getModifier(LivingEntity.SPEED_MODIFIER_POWDER_SNOW_UUID) != null) {
+                attributemodifiable.removeModifier(LivingEntity.SPEED_MODIFIER_POWDER_SNOW_UUID);
             }
 
         }
@@ -490,14 +573,17 @@
     protected void tryAddFrost() {
         if (!this.getBlockStateOnLegacy().isAir()) {
             int i = this.getTicksFrozen();
+
             if (i > 0) {
-                AttributeInstance attributeInstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
-                if (attributeInstance == null) {
+                AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
+
+                if (attributemodifiable == null) {
                     return;
                 }
 
                 float f = -0.05F * this.getPercentFrozen();
-                attributeInstance.addTransientModifier(new AttributeModifier(SPEED_MODIFIER_POWDER_SNOW_UUID, "Powder snow slow", (double)f, AttributeModifier.Operation.ADDITION));
+
+                attributemodifiable.addTransientModifier(new AttributeModifier(LivingEntity.SPEED_MODIFIER_POWDER_SNOW_UUID, "Powder snow slow", (double) f, AttributeModifier.Operation.ADDITION));
             }
         }
 
@@ -505,6 +591,7 @@
 
     protected void onChangedBlock(BlockPos pos) {
         int i = EnchantmentHelper.getEnchantmentLevel(Enchantments.FROST_WALKER, this);
+
         if (i > 0) {
             FrostWalkerEnchantment.onEntityMoved(this, this.level, pos, i);
         }
@@ -536,7 +623,7 @@
     protected void tickDeath() {
         ++this.deathTime;
         if (this.deathTime >= 20 && !this.level.isClientSide() && !this.isRemoved()) {
-            this.level.broadcastEntityEvent(this, (byte)60);
+            this.level.broadcastEntityEvent(this, (byte) 60);
             this.remove(Entity.RemovalReason.KILLED);
         }
 
@@ -551,8 +638,9 @@
     }
 
     protected int decreaseAirSupply(int air) {
-        int i = EnchantmentHelper.getRespiration(this);
-        return i > 0 && this.random.nextInt(i + 1) > 0 ? air : air - 1;
+        int j = EnchantmentHelper.getRespiration(this);
+
+        return j > 0 && this.random.nextInt(j + 1) > 0 ? air : air - 1;
     }
 
     protected int increaseAirSupply(int air) {
@@ -580,7 +668,7 @@
         return this.lastHurtByMobTimestamp;
     }
 
-    public void setLastHurtByPlayer(@Nullable Player attacking) {
+    public void setLastHurtByPlayer(@Nullable net.minecraft.world.entity.player.Player attacking) {
         this.lastHurtByPlayer = attacking;
         this.lastHurtByPlayerTime = this.tickCount;
     }
@@ -601,7 +689,7 @@
 
     public void setLastHurtMob(Entity target) {
         if (target instanceof LivingEntity) {
-            this.lastHurtMob = (LivingEntity)target;
+            this.lastHurtMob = (LivingEntity) target;
         } else {
             this.lastHurtMob = null;
         }
@@ -618,7 +706,7 @@
     }
 
     public boolean shouldDiscardFriction() {
-        return this.discardFriction;
+        return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper
     }
 
     public void setDiscardFriction(boolean noDrag) {
@@ -630,13 +718,20 @@
     }
 
     public void onEquipItem(EquipmentSlot slot, ItemStack oldStack, ItemStack newStack) {
-        boolean bl = newStack.isEmpty() && oldStack.isEmpty();
-        if (!bl && !ItemStack.isSame(oldStack, newStack) && !this.firstTick) {
-            if (slot.getType() == EquipmentSlot.Type.ARMOR) {
-                this.playEquipSound(newStack);
+        // CraftBukkit start
+        this.onEquipItem(slot, oldStack, newStack, false);
+    }
+
+    public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) {
+        // CraftBukkit end
+        boolean flag = itemstack1.isEmpty() && itemstack.isEmpty();
+
+        if (!flag && !ItemStack.isSame(itemstack, itemstack1) && !this.firstTick) {
+            if (enumitemslot.getType() == EquipmentSlot.Type.ARMOR && !silent) { // CraftBukkit
+                this.playEquipSound(itemstack1);
             }
 
-            if (this.doesEmitEquipEvent(slot)) {
+            if (this.doesEmitEquipEvent(enumitemslot)) {
                 this.gameEvent(GameEvent.EQUIP);
             }
 
@@ -645,9 +740,10 @@
 
     protected void playEquipSound(ItemStack stack) {
         if (!stack.isEmpty() && !this.isSpectator()) {
-            SoundEvent soundEvent = stack.getEquipSound();
-            if (soundEvent != null) {
-                this.playSound(soundEvent, 1.0F, 1.0F);
+            SoundEvent soundeffect = stack.getEquipSound();
+
+            if (soundeffect != null) {
+                this.playSound(soundeffect, 1.0F, 1.0F);
             }
 
         }
@@ -655,53 +751,91 @@
 
     @Override
     public void addAdditionalSaveData(CompoundTag nbt) {
+        // Paper start
+        if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) {
+            nbt.putString("Paper.FrictionState", this.frictionState.toString());
+        }
+        // Paper end
         nbt.putFloat("Health", this.getHealth());
-        nbt.putShort("HurtTime", (short)this.hurtTime);
+        nbt.putShort("HurtTime", (short) this.hurtTime);
         nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp);
-        nbt.putShort("DeathTime", (short)this.deathTime);
+        nbt.putShort("DeathTime", (short) this.deathTime);
         nbt.putFloat("AbsorptionAmount", this.getAbsorptionAmount());
         nbt.put("Attributes", this.getAttributes().save());
         if (!this.activeEffects.isEmpty()) {
-            ListTag listTag = new ListTag();
+            ListTag nbttaglist = new ListTag();
+            Iterator iterator = this.activeEffects.values().iterator();
 
-            for(MobEffectInstance mobEffectInstance : this.activeEffects.values()) {
-                listTag.add(mobEffectInstance.save(new CompoundTag()));
+            while (iterator.hasNext()) {
+                MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
+
+                nbttaglist.add(mobeffect.save(new CompoundTag()));
             }
 
-            nbt.put("ActiveEffects", listTag);
+            nbt.put("ActiveEffects", nbttaglist);
         }
 
         nbt.putBoolean("FallFlying", this.isFallFlying());
-        this.getSleepingPos().ifPresent((pos) -> {
-            nbt.putInt("SleepingX", pos.getX());
-            nbt.putInt("SleepingY", pos.getY());
-            nbt.putInt("SleepingZ", pos.getZ());
+        this.getSleepingPos().ifPresent((blockposition) -> {
+            nbt.putInt("SleepingX", blockposition.getX());
+            nbt.putInt("SleepingY", blockposition.getY());
+            nbt.putInt("SleepingZ", blockposition.getZ());
         });
-        DataResult<Tag> dataResult = this.brain.serializeStart(NbtOps.INSTANCE);
-        dataResult.resultOrPartial(LOGGER::error).ifPresent((brain) -> {
-            nbt.put("Brain", brain);
+        DataResult<Tag> dataresult = this.brain.serializeStart(NbtOps.INSTANCE);
+        Logger logger = LivingEntity.LOGGER;
+
+        java.util.Objects.requireNonNull(logger);
+        dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
+            nbt.put("Brain", nbtbase);
         });
     }
 
     @Override
     public void readAdditionalSaveData(CompoundTag nbt) {
-        this.setAbsorptionAmount(nbt.getFloat("AbsorptionAmount"));
+        // Paper start - jvm keeps optimizing the setter
+        float absorptionAmount = nbt.getFloat("AbsorptionAmount");
+        if (Float.isNaN(absorptionAmount)) {
+            absorptionAmount = 0;
+        }
+        this.setAbsorptionAmount(absorptionAmount);
+
+        if (nbt.contains("Paper.FrictionState")) {
+            String fs = nbt.getString("Paper.FrictionState");
+            try {
+                frictionState = net.kyori.adventure.util.TriState.valueOf(fs);
+            } catch (Exception ignored) {
+                LOGGER.error("Unknown friction state " + fs + " for " + this);
+            }
+        }
+        // Paper end
         if (nbt.contains("Attributes", 9) && this.level != null && !this.level.isClientSide) {
             this.getAttributes().load(nbt.getList("Attributes", 10));
         }
 
         if (nbt.contains("ActiveEffects", 9)) {
-            ListTag listTag = nbt.getList("ActiveEffects", 10);
+            ListTag nbttaglist = nbt.getList("ActiveEffects", 10);
 
-            for(int i = 0; i < listTag.size(); ++i) {
-                CompoundTag compoundTag = listTag.getCompound(i);
-                MobEffectInstance mobEffectInstance = MobEffectInstance.load(compoundTag);
-                if (mobEffectInstance != null) {
-                    this.activeEffects.put(mobEffectInstance.getEffect(), mobEffectInstance);
+            for (int i = 0; i < nbttaglist.size(); ++i) {
+                CompoundTag nbttagcompound1 = nbttaglist.getCompound(i);
+                MobEffectInstance mobeffect = MobEffectInstance.load(nbttagcompound1);
+
+                if (mobeffect != null) {
+                    this.activeEffects.put(mobeffect.getEffect(), mobeffect);
                 }
             }
         }
 
+        // CraftBukkit start
+        if (nbt.contains("Bukkit.MaxHealth")) {
+            Tag nbtbase = nbt.get("Bukkit.MaxHealth");
+            if (nbtbase.getId() == 5) {
+                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((FloatTag) nbtbase).getAsDouble());
+            } else if (nbtbase.getId() == 3) {
+                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((IntTag) nbtbase).getAsDouble());
+            }
+        }
+        // CraftBukkit end
+
         if (nbt.contains("Health", 99)) {
             this.setHealth(nbt.getFloat("Health"));
         }
@@ -710,11 +844,13 @@
         this.deathTime = nbt.getShort("DeathTime");
         this.lastHurtByMobTimestamp = nbt.getInt("HurtByTimestamp");
         if (nbt.contains("Team", 8)) {
-            String string = nbt.getString("Team");
-            PlayerTeam playerTeam = this.level.getScoreboard().getPlayerTeam(string);
-            boolean bl = playerTeam != null && this.level.getScoreboard().addPlayerToTeam(this.getStringUUID(), playerTeam);
-            if (!bl) {
-                LOGGER.warn("Unable to add mob to team \"{}\" (that team probably doesn't exist)", (Object)string);
+            String s = nbt.getString("Team");
+            PlayerTeam scoreboardteam = this.level.getScoreboard().getPlayerTeam(s);
+            if (!level.paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper
+            boolean flag = scoreboardteam != null && this.level.getScoreboard().addPlayerToTeam(this.getStringUUID(), scoreboardteam);
+
+            if (!flag) {
+                LivingEntity.LOGGER.warn("Unable to add mob to team \"{}\" (that team probably doesn't exist)", s);
             }
         }
 
@@ -723,40 +859,83 @@
         }
 
         if (nbt.contains("SleepingX", 99) && nbt.contains("SleepingY", 99) && nbt.contains("SleepingZ", 99)) {
-            BlockPos blockPos = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ"));
-            this.setSleepingPos(blockPos);
-            this.entityData.set(DATA_POSE, Pose.SLEEPING);
+            BlockPos blockposition = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ"));
+
+            this.setSleepingPos(blockposition);
+            this.entityData.set(LivingEntity.DATA_POSE, Pose.SLEEPING);
             if (!this.firstTick) {
-                this.setPosToBed(blockPos);
+                this.setPosToBed(blockposition);
             }
         }
 
         if (nbt.contains("Brain", 10)) {
-            this.brain = this.makeBrain(new Dynamic<>(NbtOps.INSTANCE, nbt.get("Brain")));
+            this.brain = this.makeBrain(new Dynamic(NbtOps.INSTANCE, nbt.get("Brain")));
+        }
+
+    }
+
+    // CraftBukkit start
+    private boolean isTickingEffects = false;
+    private List<ProcessableEffect> effectsToProcess = Lists.newArrayList();
+
+    private static class ProcessableEffect {
+
+        private MobEffect type;
+        private MobEffectInstance effect;
+        private final EntityPotionEffectEvent.Cause cause;
+
+        private ProcessableEffect(MobEffectInstance effect, EntityPotionEffectEvent.Cause cause) {
+            this.effect = effect;
+            this.cause = cause;
         }
 
+        private ProcessableEffect(MobEffect type, EntityPotionEffectEvent.Cause cause) {
+            this.type = type;
+            this.cause = cause;
+        }
     }
+    // CraftBukkit end
 
     protected void tickEffects() {
-        Iterator<MobEffect> iterator = this.activeEffects.keySet().iterator();
+        Iterator iterator = this.activeEffects.keySet().iterator();
 
+        this.isTickingEffects = true; // CraftBukkit
         try {
-            while(iterator.hasNext()) {
-                MobEffect mobEffect = iterator.next();
-                MobEffectInstance mobEffectInstance = this.activeEffects.get(mobEffect);
-                if (!mobEffectInstance.tick(this, () -> {
-                    this.onEffectUpdated(mobEffectInstance, true, (Entity)null);
+            while (iterator.hasNext()) {
+                MobEffect mobeffectlist = (MobEffect) iterator.next();
+                MobEffectInstance mobeffect = (MobEffectInstance) this.activeEffects.get(mobeffectlist);
+
+                if (!mobeffect.tick(this, () -> {
+                    this.onEffectUpdated(mobeffect, true, (Entity) null);
                 })) {
                     if (!this.level.isClientSide) {
+                        // CraftBukkit start
+                        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect, null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.EXPIRATION);
+                        if (event.isCancelled()) {
+                            continue;
+                        }
+                        // CraftBukkit end
                         iterator.remove();
-                        this.onEffectRemoved(mobEffectInstance);
+                        this.onEffectRemoved(mobeffect);
                     }
-                } else if (mobEffectInstance.getDuration() % 600 == 0) {
-                    this.onEffectUpdated(mobEffectInstance, false, (Entity)null);
+                } else if (mobeffect.getDuration() % 600 == 0) {
+                    this.onEffectUpdated(mobeffect, false, (Entity) null);
                 }
             }
-        } catch (ConcurrentModificationException var11) {
+        } catch (ConcurrentModificationException concurrentmodificationexception) {
+            ;
+        }
+        // CraftBukkit start
+        this.isTickingEffects = false;
+        for (ProcessableEffect e : this.effectsToProcess) {
+            if (e.effect != null) {
+                this.addEffect(e.effect, e.cause);
+            } else {
+                this.removeEffect(e.type, e.cause);
+            }
         }
+        this.effectsToProcess.clear();
+        // CraftBukkit end
 
         if (this.effectsDirty) {
             if (!this.level.isClientSide) {
@@ -767,25 +946,28 @@
             this.effectsDirty = false;
         }
 
-        int i = this.entityData.get(DATA_EFFECT_COLOR_ID);
-        boolean bl = this.entityData.get(DATA_EFFECT_AMBIENCE_ID);
+        int i = (Integer) this.entityData.get(LivingEntity.DATA_EFFECT_COLOR_ID);
+        boolean flag = (Boolean) this.entityData.get(LivingEntity.DATA_EFFECT_AMBIENCE_ID);
+
         if (i > 0) {
-            boolean bl2;
+            boolean flag1;
+
             if (this.isInvisible()) {
-                bl2 = this.random.nextInt(15) == 0;
+                flag1 = this.random.nextInt(15) == 0;
             } else {
-                bl2 = this.random.nextBoolean();
+                flag1 = this.random.nextBoolean();
             }
 
-            if (bl) {
-                bl2 &= this.random.nextInt(5) == 0;
+            if (flag) {
+                flag1 &= this.random.nextInt(5) == 0;
             }
 
-            if (bl2 && i > 0) {
-                double d = (double)(i >> 16 & 255) / 255.0D;
-                double e = (double)(i >> 8 & 255) / 255.0D;
-                double f = (double)(i >> 0 & 255) / 255.0D;
-                this.level.addParticle(bl ? ParticleTypes.AMBIENT_ENTITY_EFFECT : ParticleTypes.ENTITY_EFFECT, this.getRandomX(0.5D), this.getRandomY(), this.getRandomZ(0.5D), d, e, f);
+            if (flag1 && i > 0) {
+                double d0 = (double) (i >> 16 & 255) / 255.0D;
+                double d1 = (double) (i >> 8 & 255) / 255.0D;
+                double d2 = (double) (i >> 0 & 255) / 255.0D;
+
+                this.level.addParticle(flag ? ParticleTypes.AMBIENT_ENTITY_EFFECT : ParticleTypes.ENTITY_EFFECT, this.getRandomX(0.5D), this.getRandomY(), this.getRandomZ(0.5D), d0, d1, d2);
             }
         }
 
@@ -797,49 +979,54 @@
             this.setInvisible(false);
         } else {
             Collection<MobEffectInstance> collection = this.activeEffects.values();
-            this.entityData.set(DATA_EFFECT_AMBIENCE_ID, areAllEffectsAmbient(collection));
-            this.entityData.set(DATA_EFFECT_COLOR_ID, PotionUtils.getColor(collection));
+
+            this.entityData.set(LivingEntity.DATA_EFFECT_AMBIENCE_ID, LivingEntity.areAllEffectsAmbient(collection));
+            this.entityData.set(LivingEntity.DATA_EFFECT_COLOR_ID, PotionUtils.getColor(collection));
             this.setInvisible(this.hasEffect(MobEffects.INVISIBILITY));
         }
 
     }
 
     private void updateGlowingStatus() {
-        boolean bl = this.isCurrentlyGlowing();
-        if (this.getSharedFlag(6) != bl) {
-            this.setSharedFlag(6, bl);
+        boolean flag = this.isCurrentlyGlowing();
+
+        if (this.getSharedFlag(6) != flag) {
+            this.setSharedFlag(6, flag);
         }
 
     }
 
     public double getVisibilityPercent(@Nullable Entity entity) {
-        double d = 1.0D;
+        double d0 = 1.0D;
+
         if (this.isDiscrete()) {
-            d *= 0.8D;
+            d0 *= 0.8D;
         }
 
         if (this.isInvisible()) {
             float f = this.getArmorCoverPercentage();
+
             if (f < 0.1F) {
                 f = 0.1F;
             }
 
-            d *= 0.7D * (double)f;
+            d0 *= 0.7D * (double) f;
         }
 
         if (entity != null) {
-            ItemStack itemStack = this.getItemBySlot(EquipmentSlot.HEAD);
-            EntityType<?> entityType = entity.getType();
-            if (entityType == EntityType.SKELETON && itemStack.is(Items.SKELETON_SKULL) || entityType == EntityType.ZOMBIE && itemStack.is(Items.ZOMBIE_HEAD) || entityType == EntityType.PIGLIN && itemStack.is(Items.PIGLIN_HEAD) || entityType == EntityType.PIGLIN_BRUTE && itemStack.is(Items.PIGLIN_HEAD) || entityType == EntityType.CREEPER && itemStack.is(Items.CREEPER_HEAD)) {
-                d *= 0.5D;
+            ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
+            EntityType<?> entitytypes = entity.getType();
+
+            if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL) || entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD) || entitytypes == EntityType.PIGLIN && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.PIGLIN_BRUTE && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) {
+                d0 *= 0.5D;
             }
         }
 
-        return d;
+        return d0;
     }
 
     public boolean canAttack(LivingEntity target) {
-        return target instanceof Player && this.level.getDifficulty() == Difficulty.PEACEFUL ? false : target.canBeSeenAsEnemy();
+        return target instanceof net.minecraft.world.entity.player.Player && this.level.getDifficulty() == Difficulty.PEACEFUL ? false : target.canBeSeenAsEnemy();
     }
 
     public boolean canAttack(LivingEntity entity, TargetingConditions predicate) {
@@ -855,33 +1042,53 @@
     }
 
     public static boolean areAllEffectsAmbient(Collection<MobEffectInstance> effects) {
-        for(MobEffectInstance mobEffectInstance : effects) {
-            if (mobEffectInstance.isVisible() && !mobEffectInstance.isAmbient()) {
-                return false;
+        Iterator iterator = effects.iterator();
+
+        MobEffectInstance mobeffect;
+
+        do {
+            if (!iterator.hasNext()) {
+                return true;
             }
-        }
 
-        return true;
+            mobeffect = (MobEffectInstance) iterator.next();
+        } while (!mobeffect.isVisible() || mobeffect.isAmbient());
+
+        return false;
     }
 
     protected void removeEffectParticles() {
-        this.entityData.set(DATA_EFFECT_AMBIENCE_ID, false);
-        this.entityData.set(DATA_EFFECT_COLOR_ID, 0);
+        this.entityData.set(LivingEntity.DATA_EFFECT_AMBIENCE_ID, false);
+        this.entityData.set(LivingEntity.DATA_EFFECT_COLOR_ID, 0);
     }
 
+    // CraftBukkit start
     public boolean removeAllEffects() {
+        return this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
+    }
+
+    public boolean removeAllEffects(EntityPotionEffectEvent.Cause cause) {
+        // CraftBukkit end
         if (this.level.isClientSide) {
             return false;
         } else {
             Iterator<MobEffectInstance> iterator = this.activeEffects.values().iterator();
 
-            boolean bl;
-            for(bl = false; iterator.hasNext(); bl = true) {
-                this.onEffectRemoved(iterator.next());
+            boolean flag;
+
+            for (flag = false; iterator.hasNext(); flag = true) {
+                // CraftBukkit start
+                MobEffectInstance effect = (MobEffectInstance) iterator.next();
+                EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
+                if (event.isCancelled()) {
+                    continue;
+                }
+                this.onEffectRemoved(effect);
+                // CraftBukkit end
                 iterator.remove();
             }
 
-            return bl;
+            return flag;
         }
     }
 
@@ -899,24 +1106,56 @@
 
     @Nullable
     public MobEffectInstance getEffect(MobEffect effect) {
-        return this.activeEffects.get(effect);
+        return (MobEffectInstance) this.activeEffects.get(effect);
     }
 
     public final boolean addEffect(MobEffectInstance effect) {
-        return this.addEffect(effect, (Entity)null);
+        return this.addEffect(effect, (Entity) null);
+    }
+
+    // CraftBukkit start
+    public boolean addEffect(MobEffectInstance mobeffect, EntityPotionEffectEvent.Cause cause) {
+        return this.addEffect(mobeffect, (Entity) null, cause);
     }
 
     public boolean addEffect(MobEffectInstance effect, @Nullable Entity source) {
-        if (!this.canBeAffected(effect)) {
+        return this.addEffect(effect, source, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
+    }
+
+    public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) {
+        org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot
+        if (this.isTickingEffects) {
+            this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause));
+            return true;
+        }
+        // CraftBukkit end
+
+        if (!this.canBeAffected(mobeffect)) {
             return false;
         } else {
-            MobEffectInstance mobEffectInstance = this.activeEffects.get(effect.getEffect());
-            if (mobEffectInstance == null) {
-                this.activeEffects.put(effect.getEffect(), effect);
-                this.onEffectAdded(effect, source);
+            MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.get(mobeffect.getEffect());
+
+            // CraftBukkit start
+            boolean override = false;
+            if (mobeffect1 != null) {
+                override = new MobEffectInstance(mobeffect1).update(mobeffect);
+            }
+
+            EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override);
+            if (event.isCancelled()) {
+                return false;
+            }
+            // CraftBukkit end
+
+            if (mobeffect1 == null) {
+                this.activeEffects.put(mobeffect.getEffect(), mobeffect);
+                this.onEffectAdded(mobeffect, entity);
                 return true;
-            } else if (mobEffectInstance.update(effect)) {
-                this.onEffectUpdated(mobEffectInstance, true, source);
+                // CraftBukkit start
+            } else if (event.isOverride()) {
+                mobeffect1.update(mobeffect);
+                this.onEffectUpdated(mobeffect1, true, entity);
+                // CraftBukkit end
                 return true;
             } else {
                 return false;
@@ -926,8 +1165,9 @@
 
     public boolean canBeAffected(MobEffectInstance effect) {
         if (this.getMobType() == MobType.UNDEAD) {
-            MobEffect mobEffect = effect.getEffect();
-            if (mobEffect == MobEffects.REGENERATION || mobEffect == MobEffects.POISON) {
+            MobEffect mobeffectlist = effect.getEffect();
+
+            if ((mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) && this.level.paperConfig().entities.mobEffects.undeadImmuneToCertainEffects) { // Paper
                 return false;
             }
         }
@@ -937,8 +1177,9 @@
 
     public void forceAddEffect(MobEffectInstance effect, @Nullable Entity source) {
         if (this.canBeAffected(effect)) {
-            MobEffectInstance mobEffectInstance = this.activeEffects.put(effect.getEffect(), effect);
-            if (mobEffectInstance == null) {
+            MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.put(effect.getEffect(), effect);
+
+            if (mobeffect1 == null) {
                 this.onEffectAdded(effect, source);
             } else {
                 this.onEffectUpdated(effect, true, source);
@@ -951,15 +1192,42 @@
         return this.getMobType() == MobType.UNDEAD;
     }
 
+    // CraftBukkit start
     @Nullable
     public MobEffectInstance removeEffectNoUpdate(@Nullable MobEffect type) {
-        return this.activeEffects.remove(type);
+        return this.c(type, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
+    }
+
+    @Nullable
+    public MobEffectInstance c(@Nullable MobEffect mobeffectlist, EntityPotionEffectEvent.Cause cause) {
+        if (this.isTickingEffects) {
+            this.effectsToProcess.add(new ProcessableEffect(mobeffectlist, cause));
+            return null;
+        }
+
+        MobEffectInstance effect = this.activeEffects.get(mobeffectlist);
+        if (effect == null) {
+            return null;
+        }
+
+        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause);
+        if (event.isCancelled()) {
+            return null;
+        }
+
+        return (MobEffectInstance) this.activeEffects.remove(mobeffectlist);
     }
 
     public boolean removeEffect(MobEffect type) {
-        MobEffectInstance mobEffectInstance = this.removeEffectNoUpdate(type);
-        if (mobEffectInstance != null) {
-            this.onEffectRemoved(mobEffectInstance);
+        return this.removeEffect(type, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
+    }
+
+    public boolean removeEffect(MobEffect mobeffectlist, EntityPotionEffectEvent.Cause cause) {
+        MobEffectInstance mobeffect = this.c(mobeffectlist, cause);
+        // CraftBukkit end
+
+        if (mobeffect != null) {
+            this.onEffectRemoved(mobeffect);
             return true;
         } else {
             return false;
@@ -977,9 +1245,10 @@
     protected void onEffectUpdated(MobEffectInstance effect, boolean reapplyEffect, @Nullable Entity source) {
         this.effectsDirty = true;
         if (reapplyEffect && !this.level.isClientSide) {
-            MobEffect mobEffect = effect.getEffect();
-            mobEffect.removeAttributeModifiers(this, this.getAttributes(), effect.getAmplifier());
-            mobEffect.addAttributeModifiers(this, this.getAttributes(), effect.getAmplifier());
+            MobEffect mobeffectlist = effect.getEffect();
+
+            mobeffectlist.removeAttributeModifiers(this, this.getAttributes(), effect.getAmplifier());
+            mobeffectlist.addAttributeModifiers(this, this.getAttributes(), effect.getAmplifier());
         }
 
     }
@@ -992,20 +1261,66 @@
 
     }
 
+    // CraftBukkit start - Delegate so we can handle providing a reason for health being regained
     public void heal(float amount) {
-        float f = this.getHealth();
-        if (f > 0.0F) {
-            this.setHealth(f + amount);
+        this.heal(amount, EntityRegainHealthEvent.RegainReason.CUSTOM);
+    }
+
+    public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) {
+        // Paper start - Forward
+        heal(f, regainReason, false);
+    }
+
+    public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) {
+        // Paper end
+        float f1 = this.getHealth();
+
+        if (f1 > 0.0F) {
+            EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason, isFastRegen); // Paper
+            // Suppress during worldgen
+            if (this.valid) {
+                this.level.getCraftServer().getPluginManager().callEvent(event);
+            }
+
+            if (!event.isCancelled()) {
+                this.setHealth((float) (this.getHealth() + event.getAmount()));
+            }
+            // CraftBukkit end
         }
 
     }
 
     public float getHealth() {
-        return this.entityData.get(DATA_HEALTH_ID);
+        // CraftBukkit start - Use unscaled health
+        if (this instanceof ServerPlayer) {
+            return (float) ((ServerPlayer) this).getBukkitEntity().getHealth();
+        }
+        // CraftBukkit end
+        return (Float) this.entityData.get(LivingEntity.DATA_HEALTH_ID);
     }
 
     public void setHealth(float health) {
-        this.entityData.set(DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
+        // Paper start
+        if (Float.isNaN(health)) { health = getMaxHealth(); if (this.valid) {
+            System.err.println("[NAN-HEALTH] " + getScoreboardName() + " had NaN health set");
+        } } // Paper end
+        // CraftBukkit start - Handle scaled health
+        if (this instanceof ServerPlayer) {
+            org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity();
+            // Squeeze
+            if (health < 0.0F) {
+                player.setRealHealth(0.0D);
+            } else if (health > player.getMaxHealth()) {
+                player.setRealHealth(player.getMaxHealth());
+            } else {
+                player.setRealHealth(health);
+            }
+
+            player.updateScaledHealth(false);
+            return;
+        }
+        // CraftBukkit end
+        this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
     }
 
     public boolean isDeadOrDying() {
@@ -1018,7 +1333,7 @@
             return false;
         } else if (this.level.isClientSide) {
             return false;
-        } else if (this.isDeadOrDying()) {
+        } else if (this.isRemoved() || this.dead || this.getHealth() <= 0.0F) { // CraftBukkit - Don't allow entities that got set to dead/killed elsewhere to get damaged and die
             return false;
         } else if (source.isFire() && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
             return false;
@@ -1028,64 +1343,91 @@
             }
 
             this.noActionTime = 0;
-            float f = amount;
-            boolean bl = false;
-            float g = 0.0F;
-            if (amount > 0.0F && this.isDamageSourceBlocked(source)) {
+            float f1 = amount;
+            boolean flag = amount > 0.0F && this.isDamageSourceBlocked(source); // Copied from below
+            float f2 = 0.0F;
+
+            // CraftBukkit - Moved into damageEntity0(DamageSource, float)
+            if (false && amount > 0.0F && this.isDamageSourceBlocked(source)) {
                 this.hurtCurrentlyUsedShield(amount);
-                g = amount;
+                f2 = amount;
                 amount = 0.0F;
                 if (!source.isProjectile()) {
                     Entity entity = source.getDirectEntity();
-                    if (entity instanceof LivingEntity) {
-                        LivingEntity livingEntity = (LivingEntity)entity;
-                        this.blockUsingShield(livingEntity);
+
+                    if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper
+                        LivingEntity entityliving = (LivingEntity) entity;
+
+                        this.blockUsingShield(entityliving);
                     }
                 }
 
-                bl = true;
+                flag = true;
             }
 
             this.animationSpeed = 1.5F;
-            boolean bl2 = true;
-            if ((float)this.invulnerableTime > 10.0F) {
+            boolean flag1 = true;
+
+            if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F) { // CraftBukkit - restore use of maxNoDamageTicks
                 if (amount <= this.lastHurt) {
                     return false;
                 }
 
-                this.actuallyHurt(source, amount - this.lastHurt);
+                // CraftBukkit start
+                if (!this.damageEntity0(source, amount - this.lastHurt)) {
+                    return false;
+                }
+                // CraftBukkit end
                 this.lastHurt = amount;
-                bl2 = false;
+                flag1 = false;
             } else {
+                // CraftBukkit start
+                if (!this.damageEntity0(source, amount)) {
+                    return false;
+                }
                 this.lastHurt = amount;
-                this.invulnerableTime = 20;
-                this.actuallyHurt(source, amount);
+                this.invulnerableTime = this.invulnerableDuration; // CraftBukkit - restore use of maxNoDamageTicks
+                // this.damageEntity0(damagesource, f);
+                // CraftBukkit end
                 this.hurtDuration = 10;
                 this.hurtTime = this.hurtDuration;
             }
 
-            if (source.isDamageHelmet() && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+            // CraftBukkit - Moved into damageEntity0(DamageSource, float)
+            if (false && source.isDamageHelmet() && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
                 this.hurtHelmet(source, amount);
                 amount *= 0.75F;
             }
 
+            // CraftBukkit start
+            if (this instanceof Animal) {
+                ((Animal) this).resetLove();
+                if (this instanceof TamableAnimal) {
+                    ((TamableAnimal) this).setOrderedToSit(false);
+                }
+            }
+            // CraftBukkit end
+
             this.hurtDir = 0.0F;
-            Entity entity2 = source.getEntity();
-            if (entity2 != null) {
-                if (entity2 instanceof LivingEntity && !source.isNoAggro()) {
-                    this.setLastHurtByMob((LivingEntity)entity2);
+            Entity entity1 = source.getEntity();
+
+            if (entity1 != null) {
+                if (entity1 instanceof LivingEntity && !source.isNoAggro()) {
+                    this.setLastHurtByMob((LivingEntity) entity1);
                 }
 
-                if (entity2 instanceof Player) {
+                if (entity1 instanceof net.minecraft.world.entity.player.Player) {
                     this.lastHurtByPlayerTime = 100;
-                    this.lastHurtByPlayer = (Player)entity2;
-                } else if (entity2 instanceof Wolf) {
-                    Wolf wolf = (Wolf)entity2;
-                    if (wolf.isTame()) {
+                    this.lastHurtByPlayer = (net.minecraft.world.entity.player.Player) entity1;
+                } else if (entity1 instanceof Wolf) {
+                    Wolf entitywolf = (Wolf) entity1;
+
+                    if (entitywolf.isTame()) {
                         this.lastHurtByPlayerTime = 100;
-                        LivingEntity livingEntity2 = wolf.getOwner();
-                        if (livingEntity2 != null && livingEntity2.getType() == EntityType.PLAYER) {
-                            this.lastHurtByPlayer = (Player)livingEntity2;
+                        LivingEntity entityliving1 = entitywolf.getOwner();
+
+                        if (entityliving1 != null && entityliving1.getType() == EntityType.PLAYER) {
+                            this.lastHurtByPlayer = (net.minecraft.world.entity.player.Player) entityliving1;
                         } else {
                             this.lastHurtByPlayer = null;
                         }
@@ -1093,78 +1435,85 @@
                 }
             }
 
-            if (bl2) {
-                if (bl) {
-                    this.level.broadcastEntityEvent(this, (byte)29);
-                } else if (source instanceof EntityDamageSource && ((EntityDamageSource)source).isThorns()) {
-                    this.level.broadcastEntityEvent(this, (byte)33);
+            boolean knockbackCancelled = level.paperConfig().environment.disableExplosionKnockback && source.isExplosion() && this instanceof net.minecraft.world.entity.player.Player; // Paper - Disable explosion knockback
+            if (flag1) {
+                if (flag) {
+                    this.level.broadcastEntityEvent(this, (byte) 29);
+                } else if (source instanceof EntityDamageSource && ((EntityDamageSource) source).isThorns()) {
+                    this.level.broadcastEntityEvent(this, (byte) 33);
                 } else {
-                    byte b;
+                    byte b0;
+
                     if (source == DamageSource.DROWN) {
-                        b = 36;
+                        b0 = 36;
                     } else if (source.isFire()) {
-                        b = 37;
+                        b0 = 37;
                     } else if (source == DamageSource.SWEET_BERRY_BUSH) {
-                        b = 44;
+                        b0 = 44;
                     } else if (source == DamageSource.FREEZE) {
-                        b = 57;
+                        b0 = 57;
                     } else {
-                        b = 2;
+                        b0 = 2;
                     }
 
-                    this.level.broadcastEntityEvent(this, b);
+                    if (!knockbackCancelled) // Paper - Disable explosion knockback
+                    this.level.broadcastEntityEvent(this, b0);
                 }
 
-                if (source != DamageSource.DROWN && (!bl || amount > 0.0F)) {
+                if (source != DamageSource.DROWN && (!flag || amount > 0.0F)) {
                     this.markHurt();
                 }
 
-                if (entity2 != null && !source.isExplosion()) {
-                    double i = entity2.getX() - this.getX();
+                if (entity1 != null && !source.isExplosion()) {
+                    final boolean far = entity1.distanceToSqr(this) > (200.0 * 200.0); // Paper
+                    double d0 = far ? (Math.random() - Math.random()) : entity1.getX() - this.getX(); // Paper
 
-                    double j;
-                    for(j = entity2.getZ() - this.getZ(); i * i + j * j < 1.0E-4D; j = (Math.random() - Math.random()) * 0.01D) {
-                        i = (Math.random() - Math.random()) * 0.01D;
+                    double d1;
+
+                    for (d1 = far ? Math.random() - Math.random() : entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { // Paper
+                        d0 = (Math.random() - Math.random()) * 0.01D;
                     }
 
-                    this.hurtDir = (float)(Mth.atan2(j, i) * (double)(180F / (float)Math.PI) - (double)this.getYRot());
-                    this.knockback((double)0.4F, i, j);
+                    this.hurtDir = (float) (Mth.atan2(d1, d0) * 57.2957763671875D - (double) this.getYRot());
+                    this.knockback(0.4000000059604645D, d0, d1, entity1);
                 } else {
-                    this.hurtDir = (float)((int)(Math.random() * 2.0D) * 180);
+                    this.hurtDir = (float) ((int) (Math.random() * 2.0D) * 180);
                 }
             }
 
+            if (knockbackCancelled) this.level.broadcastEntityEvent(this, (byte) 2); // Paper - Disable explosion knockback
             if (this.isDeadOrDying()) {
                 if (!this.checkTotemDeathProtection(source)) {
-                    SoundEvent soundEvent = this.getDeathSound();
-                    if (bl2 && soundEvent != null) {
-                        this.playSound(soundEvent, this.getSoundVolume(), this.getVoicePitch());
-                    }
+                    // Paper start - moved into CraftEventFactory event caller for cancellable death event
+                    this.silentDeath = !flag1; // mark entity as dying silently
+                    // Paper end
 
                     this.die(source);
+                    this.silentDeath = false; // Paper - cancellable death event - reset to default
                 }
-            } else if (bl2) {
+            } else if (flag1) {
                 this.playHurtSound(source);
             }
 
-            boolean bl3 = !bl || amount > 0.0F;
-            if (bl3) {
+            boolean flag2 = !flag || amount > 0.0F;
+
+            if (flag2) {
                 this.lastDamageSource = source;
                 this.lastDamageStamp = this.level.getGameTime();
             }
 
             if (this instanceof ServerPlayer) {
-                CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer)this, source, f, amount, bl);
-                if (g > 0.0F && g < 3.4028235E37F) {
-                    ((ServerPlayer)this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(g * 10.0F));
+                CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, source, f1, amount, flag);
+                if (f2 > 0.0F && f2 < 3.4028235E37F) {
+                    ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(f2 * 10.0F));
                 }
             }
 
-            if (entity2 instanceof ServerPlayer) {
-                CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer)entity2, this, source, f, amount, bl);
+            if (entity1 instanceof ServerPlayer) {
+                CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) entity1, this, source, f1, amount, flag);
             }
 
-            return bl3;
+            return flag2;
         }
     }
 
@@ -1173,40 +1522,60 @@
     }
 
     protected void blockedByShield(LivingEntity target) {
-        target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ());
+        target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this);
     }
 
     private boolean checkTotemDeathProtection(DamageSource source) {
         if (source.isBypassInvul()) {
             return false;
         } else {
-            ItemStack itemStack = null;
+            ItemStack itemstack = null;
+            InteractionHand[] aenumhand = InteractionHand.values();
+            int i = aenumhand.length;
 
-            for(InteractionHand interactionHand : InteractionHand.values()) {
-                ItemStack itemStack2 = this.getItemInHand(interactionHand);
-                if (itemStack2.is(Items.TOTEM_OF_UNDYING)) {
-                    itemStack = itemStack2.copy();
-                    itemStack2.shrink(1);
+            // CraftBukkit start
+            InteractionHand hand = null;
+            ItemStack itemstack1 = ItemStack.EMPTY;
+            for (int j = 0; j < i; ++j) {
+                InteractionHand enumhand = aenumhand[j];
+                itemstack1 = this.getItemInHand(enumhand);
+
+                if (itemstack1.is(Items.TOTEM_OF_UNDYING)) {
+                    hand = enumhand; // CraftBukkit
+                    itemstack = itemstack1.copy();
+                    // itemstack1.subtract(1); // CraftBukkit
                     break;
                 }
             }
 
-            if (itemStack != null) {
-                if (this instanceof ServerPlayer) {
-                    ServerPlayer serverPlayer = (ServerPlayer)this;
-                    serverPlayer.awardStat(Stats.ITEM_USED.get(Items.TOTEM_OF_UNDYING));
-                    CriteriaTriggers.USED_TOTEM.trigger(serverPlayer, itemStack);
+            org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.v1_19_R2.CraftEquipmentSlot.getHand(hand) : null;
+            EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
+            event.setCancelled(itemstack == null);
+            this.level.getCraftServer().getPluginManager().callEvent(event);
+
+            if (!event.isCancelled()) {
+                if (!itemstack1.isEmpty()) {
+                    itemstack1.shrink(1);
+                }
+                if (itemstack != null && this instanceof ServerPlayer) {
+                    // CraftBukkit end
+                    ServerPlayer entityplayer = (ServerPlayer) this;
+
+                    entityplayer.awardStat(Stats.ITEM_USED.get(Items.TOTEM_OF_UNDYING));
+                    CriteriaTriggers.USED_TOTEM.trigger(entityplayer, itemstack);
                 }
 
                 this.setHealth(1.0F);
-                this.removeAllEffects();
-                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 900, 1));
-                this.addEffect(new MobEffectInstance(MobEffects.ABSORPTION, 100, 1));
-                this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 800, 0));
-                this.level.broadcastEntityEvent(this, (byte)35);
+                // CraftBukkit start
+                this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
+                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 900, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
+                this.addEffect(new MobEffectInstance(MobEffects.ABSORPTION, 100, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
+                this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 800, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
+                // CraftBukkit end
+                this.level.broadcastEntityEvent(this, (byte) 35);
             }
 
-            return itemStack != null;
+            return !event.isCancelled();
         }
     }
 
@@ -1220,29 +1589,35 @@
     }
 
     protected void playHurtSound(DamageSource source) {
-        SoundEvent soundEvent = this.getHurtSound(source);
-        if (soundEvent != null) {
-            this.playSound(soundEvent, this.getSoundVolume(), this.getVoicePitch());
+        SoundEvent soundeffect = this.getHurtSound(source);
+
+        if (soundeffect != null) {
+            this.playSound(soundeffect, this.getSoundVolume(), this.getVoicePitch());
         }
 
     }
 
     public boolean isDamageSourceBlocked(DamageSource source) {
         Entity entity = source.getDirectEntity();
-        boolean bl = false;
-        if (entity instanceof AbstractArrow abstractArrow) {
-            if (abstractArrow.getPierceLevel() > 0) {
-                bl = true;
+        boolean flag = false;
+
+        if (entity instanceof AbstractArrow) {
+            AbstractArrow entityarrow = (AbstractArrow) entity;
+
+            if (entityarrow.getPierceLevel() > 0) {
+                flag = true;
             }
         }
 
-        if (!source.isBypassArmor() && this.isBlocking() && !bl) {
-            Vec3 vec3 = source.getSourcePosition();
-            if (vec3 != null) {
-                Vec3 vec32 = this.getViewVector(1.0F);
-                Vec3 vec33 = vec3.vectorTo(this.position()).normalize();
-                vec33 = new Vec3(vec33.x, 0.0D, vec33.z);
-                if (vec33.dot(vec32) < 0.0D) {
+        if (!source.isBypassArmor() && this.isBlocking() && !flag) {
+            Vec3 vec3d = source.getSourcePosition();
+
+            if (vec3d != null) {
+                Vec3 vec3d1 = this.getViewVector(1.0F);
+                Vec3 vec3d2 = vec3d.vectorTo(this.position()).normalize();
+
+                vec3d2 = new Vec3(vec3d2.x, 0.0D, vec3d2.z);
+                if (vec3d2.dot(vec3d1) < 0.0D) {
                     return true;
                 }
             }
@@ -1265,9 +1640,10 @@
     public void die(DamageSource damageSource) {
         if (!this.isRemoved() && !this.dead) {
             Entity entity = damageSource.getEntity();
-            LivingEntity livingEntity = this.getKillCredit();
-            if (this.deathScore >= 0 && livingEntity != null) {
-                livingEntity.awardKillScore(this, this.deathScore, damageSource);
+            LivingEntity entityliving = this.getKillCredit();
+            /* // Paper - move down to make death event cancellable - this is the awardKillScore below
+            if (this.deathScore >= 0 && entityliving != null) {
+                entityliving.awardKillScore(this, this.deathScore, damageSource);
             }
 
             if (this.isSleeping()) {
@@ -1275,106 +1651,205 @@
             }
 
             if (!this.level.isClientSide && this.hasCustomName()) {
-                LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString());
+                if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot
             }
+             */ // Paper - move down to make death event cancellable - this is the awardKillScore below
 
             this.dead = true;
-            this.getCombatTracker().recheckStatus();
+            // Paper - moved into if below
             if (this.level instanceof ServerLevel) {
-                if (entity == null || entity.wasKilled((ServerLevel)this.level, this)) {
+                // Paper - move below into if for onKill
+
+                // Paper start
+                org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource);
+                if (deathEvent == null || !deathEvent.isCancelled()) {
+                    // if (this.deathScore >= 0 && entityliving != null) { // Paper moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent
+                    //     entityliving.awardKillScore(this, this.deathScore, damageSource);
+                    // }
+                    // Paper start - clear equipment if event is not cancelled
+                    if (this instanceof Mob) {
+                        for (EquipmentSlot slot : this.clearedEquipmentSlots) {
+                            this.setItemSlot(slot, ItemStack.EMPTY);
+                        }
+                        this.clearedEquipmentSlots.clear();
+                    }
+                    // Paper end
+
+                    if (this.isSleeping()) {
+                        this.stopSleeping();
+                    }
+
+                    if (!this.level.isClientSide && this.hasCustomName()) {
+                        if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot
+                    }
+
+                    this.getCombatTracker().recheckStatus();
+                    if (entity != null) {
+                        entity.wasKilled((ServerLevel) this.level, this);
+                    }
                     this.gameEvent(GameEvent.ENTITY_DIE);
-                    this.dropAllDeathLoot(damageSource);
-                    this.createWitherRose(livingEntity);
+                } else {
+                    this.dead = false;
+                    this.setHealth((float) deathEvent.getReviveHealth());
                 }
-
-                this.level.broadcastEntityEvent(this, (byte)3);
+                // Paper end
+                this.createWitherRose(entityliving);
             }
 
+            if (this.dead) { // Paper
+            this.level.broadcastEntityEvent(this, (byte) 3);
             this.setPose(Pose.DYING);
+            } // Paper
         }
     }
 
     protected void createWitherRose(@Nullable LivingEntity adversary) {
         if (!this.level.isClientSide) {
-            boolean bl = false;
-            if (adversary instanceof WitherBoss) {
+            boolean flag = false;
+
+            if (this.dead && adversary instanceof WitherBoss) { // Paper
                 if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-                    BlockPos blockPos = this.blockPosition();
-                    BlockState blockState = Blocks.WITHER_ROSE.defaultBlockState();
-                    if (this.level.getBlockState(blockPos).isAir() && blockState.canSurvive(this.level, blockPos)) {
-                        this.level.setBlock(blockPos, blockState, 3);
-                        bl = true;
+                    BlockPos blockposition = this.blockPosition();
+                    BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
+
+                    if (this.level.getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level, blockposition)) {
+                        // CraftBukkit start - call EntityBlockFormEvent for Wither Rose
+                        flag = org.bukkit.craftbukkit.v1_19_R2.event.CraftEventFactory.handleBlockFormEvent(this.level, blockposition, iblockdata, 3, this);
+                        // CraftBukkit end
                     }
                 }
 
-                if (!bl) {
-                    ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE));
-                    this.level.addFreshEntity(itemEntity);
+                if (!flag) {
+                    ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE));
+
+                    // CraftBukkit start
+                    org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
+                    CraftEventFactory.callEvent(event);
+                    if (event.isCancelled()) {
+                        return;
+                    }
+                    // CraftBukkit end
+                    this.level.addFreshEntity(entityitem);
                 }
             }
 
         }
     }
 
-    protected void dropAllDeathLoot(DamageSource source) {
+    // Paper start
+    protected boolean clearEquipmentSlots = true;
+    protected Set<EquipmentSlot> clearedEquipmentSlots = new java.util.HashSet<>();
+    protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) {
+    // Paper end
         Entity entity = source.getEntity();
         int i;
-        if (entity instanceof Player) {
-            i = EnchantmentHelper.getMobLooting((LivingEntity)entity);
+
+        if (entity instanceof net.minecraft.world.entity.player.Player) {
+            i = EnchantmentHelper.getMobLooting((LivingEntity) entity);
         } else {
             i = 0;
         }
 
-        boolean bl = this.lastHurtByPlayerTime > 0;
+        boolean flag = this.lastHurtByPlayerTime > 0;
+
+        this.dropEquipment(); // CraftBukkit - from below
         if (this.shouldDropLoot() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
-            this.dropFromLootTable(source, bl);
-            this.dropCustomDeathLoot(source, i, bl);
+            this.dropFromLootTable(source, flag);
+            // Paper start
+            final boolean prev = this.clearEquipmentSlots;
+            this.clearEquipmentSlots = false;
+            this.clearedEquipmentSlots.clear();
+            // Paper end
+            this.dropCustomDeathLoot(source, i, flag);
+            this.clearEquipmentSlots = prev; // Paper
         }
+        // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment
+        org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> {
+            final LivingEntity entityliving = this.getKillCredit();
+            if (this.deathScore >= 0 && entityliving != null) {
+                entityliving.awardKillScore(this, this.deathScore, source);
+            }
+        }); // Paper end
+        this.postDeathDropItems(deathEvent); // Paper
+        this.drops = new ArrayList<>();
+        // CraftBukkit end
 
-        this.dropEquipment();
+        // this.dropInventory();// CraftBukkit - moved up
         this.dropExperience();
+        return deathEvent; // Paper
     }
 
-    protected void dropEquipment() {
+    protected void dropEquipment() {}
+    protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled
+
+    // CraftBukkit start
+    public int getExpReward() {
+        if (this.level instanceof ServerLevel && !this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) {
+            int i = this.getExperienceReward();
+            return i;
+        } else {
+            return 0;
+        }
     }
+    // CraftBukkit end
 
     protected void dropExperience() {
-        if (this.level instanceof ServerLevel && !this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) {
-            ExperienceOrb.award((ServerLevel)this.level, this.position(), this.getExperienceReward());
+        // CraftBukkit start - Update getExpReward() above if the removed if() changes!
+        if (true && !(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time
+            LivingEntity attacker = this.lastHurtByPlayer != null ? this.lastHurtByPlayer : this.lastHurtByMob; // Paper
+            ExperienceOrb.award((ServerLevel) this.level, this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper
+            this.expToDrop = 0;
         }
+        // CraftBukkit end
 
     }
 
-    protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) {
-    }
+    protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) {}
 
     public ResourceLocation getLootTable() {
         return this.getType().getDefaultLootTable();
     }
 
     protected void dropFromLootTable(DamageSource source, boolean causedByPlayer) {
-        ResourceLocation resourceLocation = this.getLootTable();
-        LootTable lootTable = this.level.getServer().getLootTables().get(resourceLocation);
-        LootContext.Builder builder = this.createLootContext(causedByPlayer, source);
-        lootTable.getRandomItems(builder.create(LootContextParamSets.ENTITY), this::spawnAtLocation);
+        ResourceLocation minecraftkey = this.getLootTable();
+        LootTable loottable = this.level.getServer().getLootTables().get(minecraftkey);
+        LootContext.Builder loottableinfo_builder = this.createLootContext(causedByPlayer, source);
+
+        loottable.getRandomItems(loottableinfo_builder.create(LootContextParamSets.ENTITY), this::spawnAtLocation);
     }
 
     protected LootContext.Builder createLootContext(boolean causedByPlayer, DamageSource source) {
-        LootContext.Builder builder = (new LootContext.Builder((ServerLevel)this.level)).withRandom(this.random).withParameter(LootContextParams.THIS_ENTITY, this).withParameter(LootContextParams.ORIGIN, this.position()).withParameter(LootContextParams.DAMAGE_SOURCE, source).withOptionalParameter(LootContextParams.KILLER_ENTITY, source.getEntity()).withOptionalParameter(LootContextParams.DIRECT_KILLER_ENTITY, source.getDirectEntity());
+        LootContext.Builder loottableinfo_builder = (new LootContext.Builder((ServerLevel) this.level)).withRandom(this.random).withParameter(LootContextParams.THIS_ENTITY, this).withParameter(LootContextParams.ORIGIN, this.position()).withParameter(LootContextParams.DAMAGE_SOURCE, source).withOptionalParameter(LootContextParams.KILLER_ENTITY, source.getEntity()).withOptionalParameter(LootContextParams.DIRECT_KILLER_ENTITY, source.getDirectEntity());
+
         if (causedByPlayer && this.lastHurtByPlayer != null) {
-            builder = builder.withParameter(LootContextParams.LAST_DAMAGE_PLAYER, this.lastHurtByPlayer).withLuck(this.lastHurtByPlayer.getLuck());
+            loottableinfo_builder = loottableinfo_builder.withParameter(LootContextParams.LAST_DAMAGE_PLAYER, this.lastHurtByPlayer).withLuck(this.lastHurtByPlayer.getLuck());
         }
 
-        return builder;
+        return loottableinfo_builder;
     }
 
     public void knockback(double strength, double x, double z) {
+        // Paper start - add knockbacking entity parameter
+        this.knockback(strength, x, z, null);
+    }
+    public void knockback(double strength, double x, double z, Entity knockingBackEntity) {
+        // Paper end - add knockbacking entity parameter
         strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
-        if (!(strength <= 0.0D)) {
+        if (strength > 0.0D) {
             this.hasImpulse = true;
-            Vec3 vec3 = this.getDeltaMovement();
-            Vec3 vec32 = (new Vec3(x, 0.0D, z)).normalize().scale(strength);
-            this.setDeltaMovement(vec3.x / 2.0D - vec32.x, this.onGround ? Math.min(0.4D, vec3.y / 2.0D + strength) : vec3.y, vec3.z / 2.0D - vec32.z);
+            Vec3 vec3d = this.getDeltaMovement();
+            Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength);
+
+            this.setDeltaMovement(vec3d.x / 2.0D - vec3d1.x, this.onGround ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z);
+            // Paper start - call EntityKnockbackByEntityEvent
+            Vec3 currentMovement = this.getDeltaMovement();
+            org.bukkit.util.Vector delta = new org.bukkit.util.Vector(currentMovement.x - vec3d.x, currentMovement.y - vec3d.y, currentMovement.z - vec3d.z);
+            // Restore old velocity to be able to access it in the event
+            this.setDeltaMovement(vec3d);
+            if (knockingBackEntity == null || new com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent((org.bukkit.entity.LivingEntity) getBukkitEntity(), knockingBackEntity.getBukkitEntity(), (float) strength, delta).callEvent()) {
+                this.setDeltaMovement(vec3d.x + delta.getX(), vec3d.y + delta.getY(), vec3d.z + delta.getZ());
+            }
+            // Paper end
         }
     }
 
@@ -1401,9 +1876,12 @@
     }
 
     protected Vec3 getMeleeAttackReferencePosition() {
-        Entity var2 = this.getVehicle();
-        if (var2 instanceof RiderShieldingMount riderShieldingMount) {
-            return this.position().add(0.0D, riderShieldingMount.getRiderShieldingHeight(), 0.0D);
+        Entity entity = this.getVehicle();
+
+        if (entity instanceof RiderShieldingMount) {
+            RiderShieldingMount ridershieldingmount = (RiderShieldingMount) entity;
+
+            return this.position().add(0.0D, ridershieldingmount.getRiderShieldingHeight(), 0.0D);
         } else {
             return this.position();
         }
@@ -1421,6 +1899,28 @@
         return stack.getEatingSound();
     }
 
+    // CraftBukkit start - Add delegate methods
+    public SoundEvent getHurtSound0(DamageSource damagesource) {
+        return this.getHurtSound(damagesource);
+    }
+
+    public SoundEvent getDeathSound0() {
+        return this.getDeathSound();
+    }
+
+    public SoundEvent getFallDamageSound0(int fallHeight) {
+        return this.getFallDamageSound(fallHeight);
+    }
+
+    public SoundEvent getDrinkingSound0(ItemStack itemstack) {
+        return this.getDrinkingSound(itemstack);
+    }
+
+    public SoundEvent getEatingSound0(ItemStack itemstack) {
+        return this.getEatingSound(itemstack);
+    }
+    // CraftBukkit end
+
     @Override
     public void setOnGround(boolean onGround) {
         super.setOnGround(onGround);
@@ -1438,13 +1938,14 @@
         if (this.isSpectator()) {
             return false;
         } else {
-            BlockPos blockPos = this.blockPosition();
-            BlockState blockState = this.getFeetBlockState();
-            if (blockState.is(BlockTags.CLIMBABLE)) {
-                this.lastClimbablePos = Optional.of(blockPos);
+            BlockPos blockposition = this.blockPosition();
+            BlockState iblockdata = this.getFeetBlockState();
+
+            if (iblockdata.is(BlockTags.CLIMBABLE)) {
+                this.lastClimbablePos = Optional.of(blockposition);
                 return true;
-            } else if (blockState.getBlock() instanceof TrapDoorBlock && this.trapdoorUsableAsLadder(blockPos, blockState)) {
-                this.lastClimbablePos = Optional.of(blockPos);
+            } else if (iblockdata.getBlock() instanceof TrapDoorBlock && this.trapdoorUsableAsLadder(blockposition, iblockdata)) {
+                this.lastClimbablePos = Optional.of(blockposition);
                 return true;
             } else {
                 return false;
@@ -1453,9 +1954,10 @@
     }
 
     private boolean trapdoorUsableAsLadder(BlockPos pos, BlockState state) {
-        if (state.getValue(TrapDoorBlock.OPEN)) {
-            BlockState blockState = this.level.getBlockState(pos.below());
-            if (blockState.is(Blocks.LADDER) && blockState.getValue(LadderBlock.FACING) == state.getValue(TrapDoorBlock.FACING)) {
+        if ((Boolean) state.getValue(TrapDoorBlock.OPEN)) {
+            BlockState iblockdata1 = this.level.getBlockState(pos.below());
+
+            if (iblockdata1.is(Blocks.LADDER) && iblockdata1.getValue(LadderBlock.FACING) == state.getValue(TrapDoorBlock.FACING)) {
                 return true;
             }
         }
@@ -1470,33 +1972,42 @@
 
     @Override
     public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
-        boolean bl = super.causeFallDamage(fallDistance, damageMultiplier, damageSource);
+        boolean flag = super.causeFallDamage(fallDistance, damageMultiplier, damageSource);
         int i = this.calculateFallDamage(fallDistance, damageMultiplier);
+
         if (i > 0) {
+            // CraftBukkit start
+            if (!this.hurt(damageSource, (float) i)) {
+                return true;
+            }
+            // CraftBukkit end
             this.playSound(this.getFallDamageSound(i), 1.0F, 1.0F);
             this.playBlockFallSound();
-            this.hurt(damageSource, (float)i);
+            // this.damageEntity(damagesource, (float) i); // CraftBukkit - moved up
             return true;
         } else {
-            return bl;
+            return flag;
         }
     }
 
     protected int calculateFallDamage(float fallDistance, float damageMultiplier) {
-        MobEffectInstance mobEffectInstance = this.getEffect(MobEffects.JUMP);
-        float f = mobEffectInstance == null ? 0.0F : (float)(mobEffectInstance.getAmplifier() + 1);
-        return Mth.ceil((fallDistance - 3.0F - f) * damageMultiplier);
+        MobEffectInstance mobeffect = this.getEffect(MobEffects.JUMP);
+        float f2 = mobeffect == null ? 0.0F : (float) (mobeffect.getAmplifier() + 1);
+
+        return Mth.ceil((fallDistance - 3.0F - f2) * damageMultiplier);
     }
 
     protected void playBlockFallSound() {
         if (!this.isSilent()) {
             int i = Mth.floor(this.getX());
-            int j = Mth.floor(this.getY() - (double)0.2F);
+            int j = Mth.floor(this.getY() - 0.20000000298023224D);
             int k = Mth.floor(this.getZ());
-            BlockState blockState = this.level.getBlockState(new BlockPos(i, j, k));
-            if (!blockState.isAir()) {
-                SoundType soundType = blockState.getSoundType();
-                this.playSound(soundType.getFallSound(), soundType.getVolume() * 0.5F, soundType.getPitch() * 0.75F);
+            BlockState iblockdata = this.level.getBlockState(new BlockPos(i, j, k));
+
+            if (!iblockdata.isAir()) {
+                SoundType soundeffecttype = iblockdata.getSoundType();
+
+                this.playSound(soundeffecttype.getFallSound(), soundeffecttype.getVolume() * 0.5F, soundeffecttype.getPitch() * 0.75F);
             }
 
         }
@@ -1513,19 +2024,16 @@
         return Mth.floor(this.getAttributeValue(Attributes.ARMOR));
     }
 
-    protected void hurtArmor(DamageSource source, float amount) {
-    }
+    protected void hurtArmor(DamageSource source, float amount) {}
 
-    protected void hurtHelmet(DamageSource source, float amount) {
-    }
+    protected void hurtHelmet(DamageSource source, float amount) {}
 
-    protected void hurtCurrentlyUsedShield(float amount) {
-    }
+    protected void hurtCurrentlyUsedShield(float amount) {}
 
     protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) {
         if (!source.isBypassArmor()) {
-            this.hurtArmor(source, amount);
-            amount = CombatRules.getDamageAfterAbsorb(amount, (float)this.getArmorValue(), (float)this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
+            // this.damageArmor(damagesource, f); // CraftBukkit - Moved into damageEntity0(DamageSource, float)
+            amount = CombatRules.getDamageAfterAbsorb(amount, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
         }
 
         return amount;
@@ -1535,18 +2043,23 @@
         if (source.isBypassMagic()) {
             return amount;
         } else {
-            if (this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && source != DamageSource.OUT_OF_WORLD) {
-                int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
+            int i;
+
+            // CraftBukkit - Moved to damageEntity0(DamageSource, float)
+            if (false && this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && source != DamageSource.OUT_OF_WORLD) {
+                i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
                 int j = 25 - i;
-                float f = amount * (float)j;
-                float g = amount;
-                amount = Math.max(f / 25.0F, 0.0F);
-                float h = g - amount;
-                if (h > 0.0F && h < 3.4028235E37F) {
+                float f1 = amount * (float) j;
+                float f2 = amount;
+
+                amount = Math.max(f1 / 25.0F, 0.0F);
+                float f3 = f2 - amount;
+
+                if (f3 > 0.0F && f3 < 3.4028235E37F) {
                     if (this instanceof ServerPlayer) {
-                        ((ServerPlayer)this).awardStat(Stats.DAMAGE_RESISTED, Math.round(h * 10.0F));
+                        ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F));
                     } else if (source.getEntity() instanceof ServerPlayer) {
-                        ((ServerPlayer)source.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(h * 10.0F));
+                        ((ServerPlayer) source.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F));
                     }
                 }
             }
@@ -1556,9 +2069,9 @@
             } else if (source.isBypassEnchantments()) {
                 return amount;
             } else {
-                int k = EnchantmentHelper.getDamageProtection(this.getArmorSlots(), source);
-                if (k > 0) {
-                    amount = CombatRules.getDamageAfterMagicAbsorb(amount, (float)k);
+                i = EnchantmentHelper.getDamageProtection(this.getArmorSlots(), source);
+                if (i > 0) {
+                    amount = CombatRules.getDamageAfterMagicAbsorb(amount, (float) i);
                 }
 
                 return amount;
@@ -1566,25 +2079,183 @@
         }
     }
 
-    protected void actuallyHurt(DamageSource source, float amount) {
-        if (!this.isInvulnerableTo(source)) {
-            amount = this.getDamageAfterArmorAbsorb(source, amount);
-            amount = this.getDamageAfterMagicAbsorb(source, amount);
-            float var8 = Math.max(amount - this.getAbsorptionAmount(), 0.0F);
-            this.setAbsorptionAmount(this.getAbsorptionAmount() - (amount - var8));
-            float g = amount - var8;
-            if (g > 0.0F && g < 3.4028235E37F && source.getEntity() instanceof ServerPlayer) {
-                ((ServerPlayer)source.getEntity()).awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(g * 10.0F));
+    // CraftBukkit start
+    protected boolean damageEntity0(final DamageSource damagesource, float f) { // void -> boolean, add final
+       if (!this.isInvulnerableTo(damagesource)) {
+            final boolean human = this instanceof net.minecraft.world.entity.player.Player;
+            float originalDamage = f;
+            Function<Double, Double> hardHat = new Function<Double, Double>() {
+                @Override
+                public Double apply(Double f) {
+                    if (damagesource.isDamageHelmet() && !LivingEntity.this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+                        return -(f - (f * 0.75F));
+
+                    }
+                    return -0.0;
+                }
+            };
+            float hardHatModifier = hardHat.apply((double) f).floatValue();
+            f += hardHatModifier;
+
+            Function<Double, Double> blocking = new Function<Double, Double>() {
+                @Override
+                public Double apply(Double f) {
+                    return -((LivingEntity.this.isDamageSourceBlocked(damagesource)) ? f : 0.0);
+                }
+            };
+            float blockingModifier = blocking.apply((double) f).floatValue();
+            f += blockingModifier;
+
+            Function<Double, Double> armor = new Function<Double, Double>() {
+                @Override
+                public Double apply(Double f) {
+                    return -(f - LivingEntity.this.getDamageAfterArmorAbsorb(damagesource, f.floatValue()));
+                }
+            };
+            float armorModifier = armor.apply((double) f).floatValue();
+            f += armorModifier;
+
+            Function<Double, Double> resistance = new Function<Double, Double>() {
+                @Override
+                public Double apply(Double f) {
+                    if (!damagesource.isBypassMagic() && LivingEntity.this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && damagesource != DamageSource.OUT_OF_WORLD) {
+                        int i = (LivingEntity.this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
+                        int j = 25 - i;
+                        float f1 = f.floatValue() * (float) j;
+                        return -(f - (f1 / 25.0F));
+                    }
+                    return -0.0;
+                }
+            };
+            float resistanceModifier = resistance.apply((double) f).floatValue();
+            f += resistanceModifier;
+
+            Function<Double, Double> magic = new Function<Double, Double>() {
+                @Override
+                public Double apply(Double f) {
+                    return -(f - LivingEntity.this.getDamageAfterMagicAbsorb(damagesource, f.floatValue()));
+                }
+            };
+            float magicModifier = magic.apply((double) f).floatValue();
+            f += magicModifier;
+
+            Function<Double, Double> absorption = new Function<Double, Double>() {
+                @Override
+                public Double apply(Double f) {
+                    return -(Math.max(f - Math.max(f - LivingEntity.this.getAbsorptionAmount(), 0.0F), 0.0F));
+                }
+            };
+            float absorptionModifier = absorption.apply((double) f).floatValue();
+
+            EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption);
+            if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) {
+                // Paper start - PlayerAttackEntityCooldownResetEvent
+                if (damagesource.getEntity() instanceof ServerPlayer) {
+                    ServerPlayer player = (ServerPlayer) damagesource.getEntity();
+                    if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) {
+                        player.resetAttackStrengthTicker();
+                    }
+                } else {
+                    ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker();
+                }
+                // Paper end
+            }
+            if (event.isCancelled()) {
+                return false;
             }
 
-            if (var8 != 0.0F) {
-                float h = this.getHealth();
-                this.setHealth(h - var8);
-                this.getCombatTracker().recordDamage(source, h, var8);
-                this.setAbsorptionAmount(this.getAbsorptionAmount() - var8);
+            f = (float) event.getFinalDamage();
+
+            // Resistance
+            if (event.getDamage(DamageModifier.RESISTANCE) < 0) {
+                float f3 = (float) -event.getDamage(DamageModifier.RESISTANCE);
+                if (f3 > 0.0F && f3 < 3.4028235E37F) {
+                    if (this instanceof ServerPlayer) {
+                        ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F));
+                    } else if (damagesource.getEntity() instanceof ServerPlayer) {
+                        ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F));
+                    }
+                }
+            }
+
+            // Apply damage to helmet
+            if (damagesource.isDamageHelmet() && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+                this.hurtHelmet(damagesource, f);
+            }
+
+            // Apply damage to armor
+            if (!damagesource.isBypassArmor()) {
+                float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT));
+                this.hurtArmor(damagesource, armorDamage);
+            }
+
+            // Apply blocking code // PAIL: steal from above
+            if (event.getDamage(DamageModifier.BLOCKING) < 0) {
+                this.level.broadcastEntityEvent(this, (byte) 29); // SPIGOT-4635 - shield damage sound
+                this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
+                Entity entity = damagesource.getDirectEntity();
+
+                if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper
+                    this.blockUsingShield((LivingEntity) entity);
+                }
+            }
+
+            absorptionModifier = (float) -event.getDamage(DamageModifier.ABSORPTION);
+            this.setAbsorptionAmount(Math.max(this.getAbsorptionAmount() - absorptionModifier, 0.0F));
+            float f2 = absorptionModifier;
+
+            if (f2 > 0.0F && f2 < 3.4028235E37F && this instanceof net.minecraft.world.entity.player.Player) {
+                ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F));
+            }
+            if (f2 > 0.0F && f2 < 3.4028235E37F && damagesource.getEntity() instanceof ServerPlayer) {
+                ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f2 * 10.0F));
+            }
+
+            if (f > 0 || !human) {
+                if (human) {
+                    // PAIL: Be sure to drag all this code from the EntityHuman subclass each update.
+                    ((net.minecraft.world.entity.player.Player) this).causeFoodExhaustion(damagesource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
+                    if (f < 3.4028235E37F) {
+                        ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F));
+                    }
+                }
+                // CraftBukkit end
+                float f3 = this.getHealth();
+
+                // Paper start - MC-121048
+                this.getCombatTracker().recordDamage(damagesource, f3, f);
+                this.setHealth(f3 - f);
+                // Paper end
+                // CraftBukkit start
+                if (!human) {
+                    this.setAbsorptionAmount(this.getAbsorptionAmount() - f);
+                }
                 this.gameEvent(GameEvent.ENTITY_DAMAGE);
+
+                return true;
+            } else {
+                // Duplicate triggers if blocking
+                if (event.getDamage(DamageModifier.BLOCKING) < 0) {
+                    if (this instanceof ServerPlayer) {
+                        CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, f, originalDamage, true);
+                        f2 = (float) -event.getDamage(DamageModifier.BLOCKING);
+                        if (f2 > 0.0F && f2 < 3.4028235E37F) {
+                            ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F));
+                        }
+                    }
+
+                    if (damagesource.getEntity() instanceof ServerPlayer) {
+                        CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, f, originalDamage, true);
+                    }
+
+                    return false;
+                } else {
+                    return originalDamage > 0;
+                }
+                // CraftBukkit end
             }
         }
+        return false; // CraftBukkit
     }
 
     public CombatTracker getCombatTracker() {
@@ -1593,41 +2264,41 @@
 
     @Nullable
     public LivingEntity getKillCredit() {
-        if (this.combatTracker.getKiller() != null) {
-            return this.combatTracker.getKiller();
-        } else if (this.lastHurtByPlayer != null) {
-            return this.lastHurtByPlayer;
-        } else {
-            return this.lastHurtByMob != null ? this.lastHurtByMob : null;
-        }
+        return (LivingEntity) (this.combatTracker.getKiller() != null ? this.combatTracker.getKiller() : (this.lastHurtByPlayer != null ? this.lastHurtByPlayer : (this.lastHurtByMob != null ? this.lastHurtByMob : null)));
     }
 
     public final float getMaxHealth() {
-        return (float)this.getAttributeValue(Attributes.MAX_HEALTH);
+        return (float) this.getAttributeValue(Attributes.MAX_HEALTH);
     }
 
     public final int getArrowCount() {
-        return this.entityData.get(DATA_ARROW_COUNT_ID);
+        return (Integer) this.entityData.get(LivingEntity.DATA_ARROW_COUNT_ID);
     }
 
     public final void setArrowCount(int stuckArrowCount) {
-        this.entityData.set(DATA_ARROW_COUNT_ID, stuckArrowCount);
+        // CraftBukkit start
+        this.setArrowCount(stuckArrowCount, false);
+    }
+
+    public final void setArrowCount(int i, boolean flag) {
+        ArrowBodyCountChangeEvent event = CraftEventFactory.callArrowBodyCountChangeEvent(this, this.getArrowCount(), i, flag);
+        if (event.isCancelled()) {
+            return;
+        }
+        this.entityData.set(LivingEntity.DATA_ARROW_COUNT_ID, event.getNewAmount());
     }
+    // CraftBukkit end
 
     public final int getStingerCount() {
-        return this.entityData.get(DATA_STINGER_COUNT_ID);
+        return (Integer) this.entityData.get(LivingEntity.DATA_STINGER_COUNT_ID);
     }
 
     public final void setStingerCount(int stingerCount) {
-        this.entityData.set(DATA_STINGER_COUNT_ID, stingerCount);
+        this.entityData.set(LivingEntity.DATA_STINGER_COUNT_ID, stingerCount);
     }
 
     private int getCurrentSwingDuration() {
-        if (MobEffectUtil.hasDigSpeed(this)) {
-            return 6 - (1 + MobEffectUtil.getDigSpeedAmplification(this));
-        } else {
-            return this.hasEffect(MobEffects.DIG_SLOWDOWN) ? 6 + (1 + this.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) * 2 : 6;
-        }
+        return MobEffectUtil.hasDigSpeed(this) ? 6 - (1 + MobEffectUtil.getDigSpeedAmplification(this)) : (this.hasEffect(MobEffects.DIG_SLOWDOWN) ? 6 + (1 + this.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) * 2 : 6);
     }
 
     public void swing(InteractionHand hand) {
@@ -1640,12 +2311,13 @@
             this.swinging = true;
             this.swingingArm = hand;
             if (this.level instanceof ServerLevel) {
-                ClientboundAnimatePacket clientboundAnimatePacket = new ClientboundAnimatePacket(this, hand == InteractionHand.MAIN_HAND ? 0 : 3);
-                ServerChunkCache serverChunkCache = ((ServerLevel)this.level).getChunkSource();
+                ClientboundAnimatePacket packetplayoutanimation = new ClientboundAnimatePacket(this, hand == InteractionHand.MAIN_HAND ? 0 : 3);
+                ServerChunkCache chunkproviderserver = ((ServerLevel) this.level).getChunkSource();
+
                 if (fromServerPlayer) {
-                    serverChunkCache.broadcastAndSend(this, clientboundAnimatePacket);
+                    chunkproviderserver.broadcastAndSend(this, packetplayoutanimation);
                 } else {
-                    serverChunkCache.broadcast(this, clientboundAnimatePacket);
+                    chunkproviderserver.broadcast(this, packetplayoutanimation);
                 }
             }
         }
@@ -1670,35 +2342,38 @@
                     this.playSound(SoundEvents.THORNS_HIT, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                 }
 
-                DamageSource damageSource;
+                DamageSource damagesource;
+
                 if (status == 37) {
-                    damageSource = DamageSource.ON_FIRE;
+                    damagesource = DamageSource.ON_FIRE;
                 } else if (status == 36) {
-                    damageSource = DamageSource.DROWN;
+                    damagesource = DamageSource.DROWN;
                 } else if (status == 44) {
-                    damageSource = DamageSource.SWEET_BERRY_BUSH;
+                    damagesource = DamageSource.SWEET_BERRY_BUSH;
                 } else if (status == 57) {
-                    damageSource = DamageSource.FREEZE;
+                    damagesource = DamageSource.FREEZE;
                 } else {
-                    damageSource = DamageSource.GENERIC;
+                    damagesource = DamageSource.GENERIC;
                 }
 
-                SoundEvent soundEvent = this.getHurtSound(damageSource);
-                if (soundEvent != null) {
-                    this.playSound(soundEvent, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
+                SoundEvent soundeffect = this.getHurtSound(damagesource);
+
+                if (soundeffect != null) {
+                    this.playSound(soundeffect, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                 }
 
                 this.hurt(DamageSource.GENERIC, 0.0F);
-                this.lastDamageSource = damageSource;
+                this.lastDamageSource = damagesource;
                 this.lastDamageStamp = this.level.getGameTime();
                 break;
             case 3:
-                SoundEvent soundEvent2 = this.getDeathSound();
-                if (soundEvent2 != null) {
-                    this.playSound(soundEvent2, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
+                SoundEvent soundeffect1 = this.getDeathSound();
+
+                if (soundeffect1 != null) {
+                    this.playSound(soundeffect1, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                 }
 
-                if (!(this instanceof Player)) {
+                if (!(this instanceof net.minecraft.world.entity.player.Player)) {
                     this.setHealth(0.0F);
                     this.die(DamageSource.GENERIC);
                 }
@@ -1753,19 +2428,21 @@
                 this.playSound(SoundEvents.SHIELD_BREAK, 0.8F, 0.8F + this.level.random.nextFloat() * 0.4F);
                 break;
             case 46:
-                int i = 128;
+                boolean flag = true;
 
-                for(int j = 0; j < 128; ++j) {
-                    double d = (double)j / 127.0D;
+                for (int i = 0; i < 128; ++i) {
+                    double d0 = (double) i / 127.0D;
                     float f = (this.random.nextFloat() - 0.5F) * 0.2F;
-                    float g = (this.random.nextFloat() - 0.5F) * 0.2F;
-                    float h = (this.random.nextFloat() - 0.5F) * 0.2F;
-                    double e = Mth.lerp(d, this.xo, this.getX()) + (this.random.nextDouble() - 0.5D) * (double)this.getBbWidth() * 2.0D;
-                    double k = Mth.lerp(d, this.yo, this.getY()) + this.random.nextDouble() * (double)this.getBbHeight();
-                    double l = Mth.lerp(d, this.zo, this.getZ()) + (this.random.nextDouble() - 0.5D) * (double)this.getBbWidth() * 2.0D;
-                    this.level.addParticle(ParticleTypes.PORTAL, e, k, l, (double)f, (double)g, (double)h);
+                    float f1 = (this.random.nextFloat() - 0.5F) * 0.2F;
+                    float f2 = (this.random.nextFloat() - 0.5F) * 0.2F;
+                    double d1 = Mth.lerp(d0, this.xo, this.getX()) + (this.random.nextDouble() - 0.5D) * (double) this.getBbWidth() * 2.0D;
+                    double d2 = Mth.lerp(d0, this.yo, this.getY()) + this.random.nextDouble() * (double) this.getBbHeight();
+                    double d3 = Mth.lerp(d0, this.zo, this.getZ()) + (this.random.nextDouble() - 0.5D) * (double) this.getBbWidth() * 2.0D;
+
+                    this.level.addParticle(ParticleTypes.PORTAL, d1, d2, d3, (double) f, (double) f1, (double) f2);
                 }
-                break;
+
+                return;
             case 47:
                 this.breakItem(this.getItemBySlot(EquipmentSlot.MAINHAND));
                 break;
@@ -1797,19 +2474,21 @@
     }
 
     private void makePoofParticles() {
-        for(int i = 0; i < 20; ++i) {
-            double d = this.random.nextGaussian() * 0.02D;
-            double e = this.random.nextGaussian() * 0.02D;
-            double f = this.random.nextGaussian() * 0.02D;
-            this.level.addParticle(ParticleTypes.POOF, this.getRandomX(1.0D), this.getRandomY(), this.getRandomZ(1.0D), d, e, f);
+        for (int i = 0; i < 20; ++i) {
+            double d0 = this.random.nextGaussian() * 0.02D;
+            double d1 = this.random.nextGaussian() * 0.02D;
+            double d2 = this.random.nextGaussian() * 0.02D;
+
+            this.level.addParticle(ParticleTypes.POOF, this.getRandomX(1.0D), this.getRandomY(), this.getRandomZ(1.0D), d0, d1, d2);
         }
 
     }
 
     private void swapHandItems() {
-        ItemStack itemStack = this.getItemBySlot(EquipmentSlot.OFFHAND);
+        ItemStack itemstack = this.getItemBySlot(EquipmentSlot.OFFHAND);
+
         this.setItemSlot(EquipmentSlot.OFFHAND, this.getItemBySlot(EquipmentSlot.MAINHAND));
-        this.setItemSlot(EquipmentSlot.MAINHAND, itemStack);
+        this.setItemSlot(EquipmentSlot.MAINHAND, itemstack);
     }
 
     @Override
@@ -1819,6 +2498,7 @@
 
     protected void updateSwingTime() {
         int i = this.getCurrentSwingDuration();
+
         if (this.swinging) {
             ++this.swingTime;
             if (this.swingTime >= i) {
@@ -1829,7 +2509,7 @@
             this.swingTime = 0;
         }
 
-        this.attackAnim = (float)this.swingTime / (float)i;
+        this.attackAnim = (float) this.swingTime / (float) i;
     }
 
     @Nullable
@@ -1838,7 +2518,7 @@
     }
 
     public double getAttributeValue(Holder<Attribute> attribute) {
-        return this.getAttributeValue(attribute.value());
+        return this.getAttributeValue((Attribute) attribute.value());
     }
 
     public double getAttributeValue(Attribute attribute) {
@@ -1846,7 +2526,7 @@
     }
 
     public double getAttributeBaseValue(Holder<Attribute> attribute) {
-        return this.getAttributeBaseValue(attribute.value());
+        return this.getAttributeBaseValue((Attribute) attribute.value());
     }
 
     public double getAttributeBaseValue(Attribute attribute) {
@@ -1870,8 +2550,8 @@
     }
 
     public boolean isHolding(Item item) {
-        return this.isHolding((stack) -> {
-            return stack.is(item);
+        return this.isHolding((itemstack) -> {
+            return itemstack.is(item);
         });
     }
 
@@ -1911,13 +2591,20 @@
 
     public abstract ItemStack getItemBySlot(EquipmentSlot slot);
 
+    // CraftBukkit start
+    public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
+        this.setItemSlot(enumitemslot, itemstack);
+    }
+    // CraftBukkit end
+
     @Override
     public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack);
 
     protected void verifyEquippedItem(ItemStack stack) {
-        CompoundTag compoundTag = stack.getTag();
-        if (compoundTag != null) {
-            stack.getItem().verifyTagAfterLoad(compoundTag);
+        CompoundTag nbttagcompound = stack.getTag();
+
+        if (nbttagcompound != null) {
+            stack.getItem().verifyTagAfterLoad(nbttagcompound);
         }
 
     }
@@ -1927,27 +2614,28 @@
         int i = 0;
         int j = 0;
 
-        for(ItemStack itemStack : iterable) {
-            if (!itemStack.isEmpty()) {
+        for (Iterator iterator = iterable.iterator(); iterator.hasNext(); ++i) {
+            ItemStack itemstack = (ItemStack) iterator.next();
+
+            if (!itemstack.isEmpty()) {
                 ++j;
             }
-
-            ++i;
         }
 
-        return i > 0 ? (float)j / (float)i : 0.0F;
+        return i > 0 ? (float) j / (float) i : 0.0F;
     }
 
     @Override
     public void setSprinting(boolean sprinting) {
         super.setSprinting(sprinting);
-        AttributeInstance attributeInstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
-        if (attributeInstance.getModifier(SPEED_MODIFIER_SPRINTING_UUID) != null) {
-            attributeInstance.removeModifier(SPEED_MODIFIER_SPRINTING);
+        AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
+
+        if (attributemodifiable.getModifier(LivingEntity.SPEED_MODIFIER_SPRINTING_UUID) != null) {
+            attributemodifiable.removeModifier(LivingEntity.SPEED_MODIFIER_SPRINTING);
         }
 
         if (sprinting) {
-            attributeInstance.addTransientModifier(SPEED_MODIFIER_SPRINTING);
+            attributemodifiable.addTransientModifier(LivingEntity.SPEED_MODIFIER_SPRINTING);
         }
 
     }
@@ -1973,17 +2661,19 @@
     }
 
     private void dismountVehicle(Entity vehicle) {
-        Vec3 vec3;
+        Vec3 vec3d;
+
         if (this.isRemoved()) {
-            vec3 = this.position();
+            vec3d = this.position();
         } else if (!vehicle.isRemoved() && !this.level.getBlockState(vehicle.blockPosition()).is(BlockTags.PORTALS)) {
-            vec3 = vehicle.getDismountLocationForPassenger(this);
+            vec3d = vehicle.getDismountLocationForPassenger(this);
         } else {
-            double d = Math.max(this.getY(), vehicle.getY());
-            vec3 = new Vec3(this.getX(), d, this.getZ());
+            double d0 = Math.max(this.getY(), vehicle.getY());
+
+            vec3d = new Vec3(this.getX(), d0, this.getZ());
         }
 
-        this.dismountTo(vec3.x, vec3.y, vec3.z);
+        this.dismountTo(vec3d.x, vec3d.y, vec3d.z);
     }
 
     @Override
@@ -1996,27 +2686,42 @@
     }
 
     public double getJumpBoostPower() {
-        return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F * (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D;
+        return this.hasEffect(MobEffects.JUMP) ? (double) (0.1F * (float) (this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D;
     }
 
+    protected long lastJumpTime = 0L; // Paper
     protected void jumpFromGround() {
-        double d = (double)this.getJumpPower() + this.getJumpBoostPower();
-        Vec3 vec3 = this.getDeltaMovement();
-        this.setDeltaMovement(vec3.x, d, vec3.z);
+        double d0 = (double) this.getJumpPower() + this.getJumpBoostPower();
+        Vec3 vec3d = this.getDeltaMovement();
+        // Paper start
+        long time = System.nanoTime();
+        boolean canCrit = true;
+        if (this instanceof net.minecraft.world.entity.player.Player) {
+            canCrit = false;
+            if (time - this.lastJumpTime > (long)(0.250e9)) {
+                this.lastJumpTime = time;
+                canCrit = true;
+            }
+        }
+        // Paper end
+
+        this.setDeltaMovement(vec3d.x, d0, vec3d.z);
         if (this.isSprinting()) {
-            float f = this.getYRot() * ((float)Math.PI / 180F);
-            this.setDeltaMovement(this.getDeltaMovement().add((double)(-Mth.sin(f) * 0.2F), 0.0D, (double)(Mth.cos(f) * 0.2F)));
+            float f = this.getYRot() * 0.017453292F;
+
+            if (canCrit) // Paper
+            this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F)));
         }
 
         this.hasImpulse = true;
     }
 
     protected void goDownInWater() {
-        this.setDeltaMovement(this.getDeltaMovement().add(0.0D, (double)-0.04F, 0.0D));
+        this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.03999999910593033D, 0.0D));
     }
 
     protected void jumpInLiquid(TagKey<Fluid> fluid) {
-        this.setDeltaMovement(this.getDeltaMovement().add(0.0D, (double)0.04F, 0.0D));
+        this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.03999999910593033D, 0.0D));
     }
 
     protected float getWaterSlowDown() {
@@ -2029,132 +2734,149 @@
 
     public void travel(Vec3 movementInput) {
         if (this.isEffectiveAi() || this.isControlledByLocalInstance()) {
-            double d = 0.08D;
-            boolean bl = this.getDeltaMovement().y <= 0.0D;
-            if (bl && this.hasEffect(MobEffects.SLOW_FALLING)) {
-                d = 0.01D;
+            double d0 = 0.08D;
+            boolean flag = this.getDeltaMovement().y <= 0.0D;
+
+            if (flag && this.hasEffect(MobEffects.SLOW_FALLING)) {
+                d0 = 0.01D;
                 this.resetFallDistance();
             }
 
-            FluidState fluidState = this.level.getFluidState(this.blockPosition());
-            if (this.isInWater() && this.isAffectedByFluids() && !this.canStandOnFluid(fluidState)) {
-                double e = this.getY();
-                float f = this.isSprinting() ? 0.9F : this.getWaterSlowDown();
-                float g = 0.02F;
-                float h = (float)EnchantmentHelper.getDepthStrider(this);
-                if (h > 3.0F) {
-                    h = 3.0F;
+            FluidState fluid = this.level.getFluidState(this.blockPosition());
+            double d1;
+            float f;
+
+            if (this.isInWater() && this.isAffectedByFluids() && !this.canStandOnFluid(fluid)) {
+                d1 = this.getY();
+                f = this.isSprinting() ? 0.9F : this.getWaterSlowDown();
+                float f1 = 0.02F;
+                float f2 = (float) EnchantmentHelper.getDepthStrider(this);
+
+                if (f2 > 3.0F) {
+                    f2 = 3.0F;
                 }
 
                 if (!this.onGround) {
-                    h *= 0.5F;
+                    f2 *= 0.5F;
                 }
 
-                if (h > 0.0F) {
-                    f += (0.54600006F - f) * h / 3.0F;
-                    g += (this.getSpeed() - g) * h / 3.0F;
+                if (f2 > 0.0F) {
+                    f += (0.54600006F - f) * f2 / 3.0F;
+                    f1 += (this.getSpeed() - f1) * f2 / 3.0F;
                 }
 
                 if (this.hasEffect(MobEffects.DOLPHINS_GRACE)) {
                     f = 0.96F;
                 }
 
-                this.moveRelative(g, movementInput);
+                this.moveRelative(f1, movementInput);
                 this.move(MoverType.SELF, this.getDeltaMovement());
-                Vec3 vec3 = this.getDeltaMovement();
+                Vec3 vec3d1 = this.getDeltaMovement();
+
                 if (this.horizontalCollision && this.onClimbable()) {
-                    vec3 = new Vec3(vec3.x, 0.2D, vec3.z);
+                    vec3d1 = new Vec3(vec3d1.x, 0.2D, vec3d1.z);
                 }
 
-                this.setDeltaMovement(vec3.multiply((double)f, (double)0.8F, (double)f));
-                Vec3 vec32 = this.getFluidFallingAdjustedMovement(d, bl, this.getDeltaMovement());
-                this.setDeltaMovement(vec32);
-                if (this.horizontalCollision && this.isFree(vec32.x, vec32.y + (double)0.6F - this.getY() + e, vec32.z)) {
-                    this.setDeltaMovement(vec32.x, (double)0.3F, vec32.z);
+                this.setDeltaMovement(vec3d1.multiply((double) f, 0.800000011920929D, (double) f));
+                Vec3 vec3d2 = this.getFluidFallingAdjustedMovement(d0, flag, this.getDeltaMovement());
+
+                this.setDeltaMovement(vec3d2);
+                if (this.horizontalCollision && this.isFree(vec3d2.x, vec3d2.y + 0.6000000238418579D - this.getY() + d1, vec3d2.z)) {
+                    this.setDeltaMovement(vec3d2.x, 0.30000001192092896D, vec3d2.z);
                 }
-            } else if (this.isInLava() && this.isAffectedByFluids() && !this.canStandOnFluid(fluidState)) {
-                double i = this.getY();
+            } else if (this.isInLava() && this.isAffectedByFluids() && !this.canStandOnFluid(fluid)) {
+                d1 = this.getY();
                 this.moveRelative(0.02F, movementInput);
                 this.move(MoverType.SELF, this.getDeltaMovement());
+                Vec3 vec3d3;
+
                 if (this.getFluidHeight(FluidTags.LAVA) <= this.getFluidJumpThreshold()) {
-                    this.setDeltaMovement(this.getDeltaMovement().multiply(0.5D, (double)0.8F, 0.5D));
-                    Vec3 vec33 = this.getFluidFallingAdjustedMovement(d, bl, this.getDeltaMovement());
-                    this.setDeltaMovement(vec33);
+                    this.setDeltaMovement(this.getDeltaMovement().multiply(0.5D, 0.800000011920929D, 0.5D));
+                    vec3d3 = this.getFluidFallingAdjustedMovement(d0, flag, this.getDeltaMovement());
+                    this.setDeltaMovement(vec3d3);
                 } else {
                     this.setDeltaMovement(this.getDeltaMovement().scale(0.5D));
                 }
 
                 if (!this.isNoGravity()) {
-                    this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -d / 4.0D, 0.0D));
+                    this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -d0 / 4.0D, 0.0D));
                 }
 
-                Vec3 vec34 = this.getDeltaMovement();
-                if (this.horizontalCollision && this.isFree(vec34.x, vec34.y + (double)0.6F - this.getY() + i, vec34.z)) {
-                    this.setDeltaMovement(vec34.x, (double)0.3F, vec34.z);
+                vec3d3 = this.getDeltaMovement();
+                if (this.horizontalCollision && this.isFree(vec3d3.x, vec3d3.y + 0.6000000238418579D - this.getY() + d1, vec3d3.z)) {
+                    this.setDeltaMovement(vec3d3.x, 0.30000001192092896D, vec3d3.z);
                 }
             } else if (this.isFallFlying()) {
                 this.checkSlowFallDistance();
-                Vec3 vec35 = this.getDeltaMovement();
-                Vec3 vec36 = this.getLookAngle();
-                float j = this.getXRot() * ((float)Math.PI / 180F);
-                double k = Math.sqrt(vec36.x * vec36.x + vec36.z * vec36.z);
-                double l = vec35.horizontalDistance();
-                double m = vec36.length();
-                double n = Math.cos((double)j);
-                n = n * n * Math.min(1.0D, m / 0.4D);
-                vec35 = this.getDeltaMovement().add(0.0D, d * (-1.0D + n * 0.75D), 0.0D);
-                if (vec35.y < 0.0D && k > 0.0D) {
-                    double o = vec35.y * -0.1D * n;
-                    vec35 = vec35.add(vec36.x * o / k, o, vec36.z * o / k);
+                Vec3 vec3d4 = this.getDeltaMovement();
+                Vec3 vec3d5 = this.getLookAngle();
+
+                f = this.getXRot() * 0.017453292F;
+                double d2 = Math.sqrt(vec3d5.x * vec3d5.x + vec3d5.z * vec3d5.z);
+                double d3 = vec3d4.horizontalDistance();
+                double d4 = vec3d5.length();
+                double d5 = Math.cos((double) f);
+
+                d5 = d5 * d5 * Math.min(1.0D, d4 / 0.4D);
+                vec3d4 = this.getDeltaMovement().add(0.0D, d0 * (-1.0D + d5 * 0.75D), 0.0D);
+                double d6;
+
+                if (vec3d4.y < 0.0D && d2 > 0.0D) {
+                    d6 = vec3d4.y * -0.1D * d5;
+                    vec3d4 = vec3d4.add(vec3d5.x * d6 / d2, d6, vec3d5.z * d6 / d2);
                 }
 
-                if (j < 0.0F && k > 0.0D) {
-                    double p = l * (double)(-Mth.sin(j)) * 0.04D;
-                    vec35 = vec35.add(-vec36.x * p / k, p * 3.2D, -vec36.z * p / k);
+                if (f < 0.0F && d2 > 0.0D) {
+                    d6 = d3 * (double) (-Mth.sin(f)) * 0.04D;
+                    vec3d4 = vec3d4.add(-vec3d5.x * d6 / d2, d6 * 3.2D, -vec3d5.z * d6 / d2);
                 }
 
-                if (k > 0.0D) {
-                    vec35 = vec35.add((vec36.x / k * l - vec35.x) * 0.1D, 0.0D, (vec36.z / k * l - vec35.z) * 0.1D);
+                if (d2 > 0.0D) {
+                    vec3d4 = vec3d4.add((vec3d5.x / d2 * d3 - vec3d4.x) * 0.1D, 0.0D, (vec3d5.z / d2 * d3 - vec3d4.z) * 0.1D);
                 }
 
-                this.setDeltaMovement(vec35.multiply((double)0.99F, (double)0.98F, (double)0.99F));
+                this.setDeltaMovement(vec3d4.multiply(0.9900000095367432D, 0.9800000190734863D, 0.9900000095367432D));
                 this.move(MoverType.SELF, this.getDeltaMovement());
                 if (this.horizontalCollision && !this.level.isClientSide) {
-                    double q = this.getDeltaMovement().horizontalDistance();
-                    double r = l - q;
-                    float s = (float)(r * 10.0D - 3.0D);
-                    if (s > 0.0F) {
-                        this.playSound(this.getFallDamageSound((int)s), 1.0F, 1.0F);
-                        this.hurt(DamageSource.FLY_INTO_WALL, s);
+                    d6 = this.getDeltaMovement().horizontalDistance();
+                    double d7 = d3 - d6;
+                    float f3 = (float) (d7 * 10.0D - 3.0D);
+
+                    if (f3 > 0.0F) {
+                        this.playSound(this.getFallDamageSound((int) f3), 1.0F, 1.0F);
+                        this.hurt(DamageSource.FLY_INTO_WALL, f3);
                     }
                 }
 
                 if (this.onGround && !this.level.isClientSide) {
+                    if (getSharedFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) // CraftBukkit
                     this.setSharedFlag(7, false);
                 }
             } else {
-                BlockPos blockPos = this.getBlockPosBelowThatAffectsMyMovement();
-                float t = this.level.getBlockState(blockPos).getBlock().getFriction();
-                float u = this.onGround ? t * 0.91F : 0.91F;
-                Vec3 vec37 = this.handleRelativeFrictionAndCalculateMovement(movementInput, t);
-                double v = vec37.y;
+                BlockPos blockposition = this.getBlockPosBelowThatAffectsMyMovement();
+                float f4 = this.level.getBlockState(blockposition).getBlock().getFriction();
+
+                f = this.onGround ? f4 * 0.91F : 0.91F;
+                Vec3 vec3d6 = this.handleRelativeFrictionAndCalculateMovement(movementInput, f4);
+                double d8 = vec3d6.y;
+
                 if (this.hasEffect(MobEffects.LEVITATION)) {
-                    v += (0.05D * (double)(this.getEffect(MobEffects.LEVITATION).getAmplifier() + 1) - vec37.y) * 0.2D;
+                    d8 += (0.05D * (double) (this.getEffect(MobEffects.LEVITATION).getAmplifier() + 1) - vec3d6.y) * 0.2D;
                     this.resetFallDistance();
-                } else if (this.level.isClientSide && !this.level.hasChunkAt(blockPos)) {
-                    if (this.getY() > (double)this.level.getMinBuildHeight()) {
-                        v = -0.1D;
+                } else if (this.level.isClientSide && !this.level.hasChunkAt(blockposition)) {
+                    if (this.getY() > (double) this.level.getMinBuildHeight()) {
+                        d8 = -0.1D;
                     } else {
-                        v = 0.0D;
+                        d8 = 0.0D;
                     }
                 } else if (!this.isNoGravity()) {
-                    v -= d;
+                    d8 -= d0;
                 }
 
                 if (this.shouldDiscardFriction()) {
-                    this.setDeltaMovement(vec37.x, v, vec37.z);
+                    this.setDeltaMovement(vec3d6.x, d8, vec3d6.z);
                 } else {
-                    this.setDeltaMovement(vec37.x * (double)u, v * (double)0.98F, vec37.z * (double)u);
+                    this.setDeltaMovement(vec3d6.x * (double) f, d8 * 0.9800000190734863D, vec3d6.z * (double) f);
                 }
             }
         }
@@ -2164,15 +2886,16 @@
 
     public void calculateEntityAnimation(LivingEntity entity, boolean flutter) {
         entity.animationSpeedOld = entity.animationSpeed;
-        double d = entity.getX() - entity.xo;
-        double e = flutter ? entity.getY() - entity.yo : 0.0D;
-        double f = entity.getZ() - entity.zo;
-        float g = (float)Math.sqrt(d * d + e * e + f * f) * 4.0F;
-        if (g > 1.0F) {
-            g = 1.0F;
+        double d0 = entity.getX() - entity.xo;
+        double d1 = flutter ? entity.getY() - entity.yo : 0.0D;
+        double d2 = entity.getZ() - entity.zo;
+        float f = (float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 4.0F;
+
+        if (f > 1.0F) {
+            f = 1.0F;
         }
 
-        entity.animationSpeed += (g - entity.animationSpeed) * 0.4F;
+        entity.animationSpeed += (f - entity.animationSpeed) * 0.4F;
         entity.animationPosition += entity.animationSpeed;
     }
 
@@ -2180,24 +2903,26 @@
         this.moveRelative(this.getFrictionInfluencedSpeed(slipperiness), movementInput);
         this.setDeltaMovement(this.handleOnClimbable(this.getDeltaMovement()));
         this.move(MoverType.SELF, this.getDeltaMovement());
-        Vec3 vec3 = this.getDeltaMovement();
+        Vec3 vec3d1 = this.getDeltaMovement();
+
         if ((this.horizontalCollision || this.jumping) && (this.onClimbable() || this.getFeetBlockState().is(Blocks.POWDER_SNOW) && PowderSnowBlock.canEntityWalkOnPowderSnow(this))) {
-            vec3 = new Vec3(vec3.x, 0.2D, vec3.z);
+            vec3d1 = new Vec3(vec3d1.x, 0.2D, vec3d1.z);
         }
 
-        return vec3;
+        return vec3d1;
     }
 
     public Vec3 getFluidFallingAdjustedMovement(double gravity, boolean falling, Vec3 motion) {
         if (!this.isNoGravity() && !this.isSprinting()) {
-            double d;
+            double d1;
+
             if (falling && Math.abs(motion.y - 0.005D) >= 0.003D && Math.abs(motion.y - gravity / 16.0D) < 0.003D) {
-                d = -0.003D;
+                d1 = -0.003D;
             } else {
-                d = motion.y - gravity / 16.0D;
+                d1 = motion.y - gravity / 16.0D;
             }
 
-            return new Vec3(motion.x, d, motion.z);
+            return new Vec3(motion.x, d1, motion.z);
         } else {
             return motion;
         }
@@ -2207,14 +2932,15 @@
         if (this.onClimbable()) {
             this.resetFallDistance();
             float f = 0.15F;
-            double d = Mth.clamp(motion.x, (double)-0.15F, (double)0.15F);
-            double e = Mth.clamp(motion.z, (double)-0.15F, (double)0.15F);
-            double g = Math.max(motion.y, (double)-0.15F);
-            if (g < 0.0D && !this.getFeetBlockState().is(Blocks.SCAFFOLDING) && this.isSuppressingSlidingDownLadder() && this instanceof Player) {
-                g = 0.0D;
+            double d0 = Mth.clamp(motion.x, -0.15000000596046448D, 0.15000000596046448D);
+            double d1 = Mth.clamp(motion.z, -0.15000000596046448D, 0.15000000596046448D);
+            double d2 = Math.max(motion.y, -0.15000000596046448D);
+
+            if (d2 < 0.0D && !this.getFeetBlockState().is(Blocks.SCAFFOLDING) && this.isSuppressingSlidingDownLadder() && this instanceof net.minecraft.world.entity.player.Player) {
+                d2 = 0.0D;
             }
 
-            motion = new Vec3(d, g, e);
+            motion = new Vec3(d0, d2, d1);
         }
 
         return motion;
@@ -2244,6 +2970,7 @@
         this.updateSwimAmount();
         if (!this.level.isClientSide) {
             int i = this.getArrowCount();
+
             if (i > 0) {
                 if (this.removeArrowTime <= 0) {
                     this.removeArrowTime = 20 * (30 - i);
@@ -2256,6 +2983,7 @@
             }
 
             int j = this.getStingerCount();
+
             if (j > 0) {
                 if (this.removeStingerTime <= 0) {
                     this.removeStingerTime = 20 * (30 - j);
@@ -2281,73 +3009,54 @@
             this.aiStep();
         }
 
-        double d = this.getX() - this.xo;
-        double e = this.getZ() - this.zo;
-        float f = (float)(d * d + e * e);
-        float g = this.yBodyRot;
-        float h = 0.0F;
+        double d0 = this.getX() - this.xo;
+        double d1 = this.getZ() - this.zo;
+        float f = (float) (d0 * d0 + d1 * d1);
+        float f1 = this.yBodyRot;
+        float f2 = 0.0F;
+
         this.oRun = this.run;
-        float k = 0.0F;
+        float f3 = 0.0F;
+
         if (f > 0.0025000002F) {
-            k = 1.0F;
-            h = (float)Math.sqrt((double)f) * 3.0F;
-            float l = (float)Mth.atan2(e, d) * (180F / (float)Math.PI) - 90.0F;
-            float m = Mth.abs(Mth.wrapDegrees(this.getYRot()) - l);
-            if (95.0F < m && m < 265.0F) {
-                g = l - 180.0F;
+            f3 = 1.0F;
+            f2 = (float) Math.sqrt((double) f) * 3.0F;
+            float f4 = (float) Mth.atan2(d1, d0) * 57.295776F - 90.0F;
+            float f5 = Mth.abs(Mth.wrapDegrees(this.getYRot()) - f4);
+
+            if (95.0F < f5 && f5 < 265.0F) {
+                f1 = f4 - 180.0F;
             } else {
-                g = l;
+                f1 = f4;
             }
         }
 
         if (this.attackAnim > 0.0F) {
-            g = this.getYRot();
+            f1 = this.getYRot();
         }
 
         if (!this.onGround) {
-            k = 0.0F;
+            f3 = 0.0F;
         }
 
-        this.run += (k - this.run) * 0.3F;
+        this.run += (f3 - this.run) * 0.3F;
         this.level.getProfiler().push("headTurn");
-        h = this.tickHeadTurn(g, h);
+        f2 = this.tickHeadTurn(f1, f2);
         this.level.getProfiler().pop();
         this.level.getProfiler().push("rangeChecks");
 
-        while(this.getYRot() - this.yRotO < -180.0F) {
-            this.yRotO -= 360.0F;
-        }
-
-        while(this.getYRot() - this.yRotO >= 180.0F) {
-            this.yRotO += 360.0F;
-        }
-
-        while(this.yBodyRot - this.yBodyRotO < -180.0F) {
-            this.yBodyRotO -= 360.0F;
-        }
-
-        while(this.yBodyRot - this.yBodyRotO >= 180.0F) {
-            this.yBodyRotO += 360.0F;
-        }
-
-        while(this.getXRot() - this.xRotO < -180.0F) {
-            this.xRotO -= 360.0F;
-        }
+        // Paper start - stop large pitch and yaw changes from crashing the server
+        this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F;
 
-        while(this.getXRot() - this.xRotO >= 180.0F) {
-            this.xRotO += 360.0F;
-        }
+        this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F;
 
-        while(this.yHeadRot - this.yHeadRotO < -180.0F) {
-            this.yHeadRotO -= 360.0F;
-        }
+        this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F;
 
-        while(this.yHeadRot - this.yHeadRotO >= 180.0F) {
-            this.yHeadRotO += 360.0F;
-        }
+        this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F;
+        // Paper end
 
         this.level.getProfiler().pop();
-        this.animStep += h;
+        this.animStep += f2;
         if (this.isFallFlying()) {
             ++this.fallFlyTicks;
         } else {
@@ -2357,11 +3066,11 @@
         if (this.isSleeping()) {
             this.setXRot(0.0F);
         }
-
     }
 
     public void detectEquipmentUpdates() {
         Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges();
+
         if (map != null) {
             this.handleHandSwap(map);
             if (!map.isEmpty()) {
@@ -2374,33 +3083,45 @@
     @Nullable
     private Map<EquipmentSlot, ItemStack> collectEquipmentChanges() {
         Map<EquipmentSlot, ItemStack> map = null;
+        EquipmentSlot[] aenumitemslot = EquipmentSlot.values();
+        int i = aenumitemslot.length;
 
-        for(EquipmentSlot equipmentSlot : EquipmentSlot.values()) {
-            ItemStack itemStack;
-            switch (equipmentSlot.getType()) {
+        for (int j = 0; j < i; ++j) {
+            EquipmentSlot enumitemslot = aenumitemslot[j];
+            ItemStack itemstack;
+
+            switch (enumitemslot.getType()) {
                 case HAND:
-                    itemStack = this.getLastHandItem(equipmentSlot);
+                    itemstack = this.getLastHandItem(enumitemslot);
                     break;
                 case ARMOR:
-                    itemStack = this.getLastArmorItem(equipmentSlot);
+                    itemstack = this.getLastArmorItem(enumitemslot);
                     break;
                 default:
                     continue;
             }
 
-            ItemStack itemStack4 = this.getItemBySlot(equipmentSlot);
-            if (this.equipmentHasChanged(itemStack, itemStack4)) {
+            ItemStack itemstack1 = this.getItemBySlot(enumitemslot);
+
+            if (this.equipmentHasChanged(itemstack, itemstack1)) {
+                // Paper start - PlayerArmorChangeEvent
+                if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.ARMOR) {
+                    final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemstack);
+                    final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemstack1);
+                    new PlayerArmorChangeEvent((Player) this.getBukkitEntity(), PlayerArmorChangeEvent.SlotType.valueOf(enumitemslot.name()), oldItem, newItem).callEvent();
+                }
+                // Paper end
                 if (map == null) {
                     map = Maps.newEnumMap(EquipmentSlot.class);
                 }
 
-                map.put(equipmentSlot, itemStack4);
-                if (!itemStack.isEmpty()) {
-                    this.getAttributes().removeAttributeModifiers(itemStack.getAttributeModifiers(equipmentSlot));
+                map.put(enumitemslot, itemstack1);
+                if (!itemstack.isEmpty()) {
+                    this.getAttributes().removeAttributeModifiers(itemstack.getAttributeModifiers(enumitemslot));
                 }
 
-                if (!itemStack4.isEmpty()) {
-                    this.getAttributes().addTransientAttributeModifiers(itemStack4.getAttributeModifiers(equipmentSlot));
+                if (!itemstack1.isEmpty()) {
+                    this.getAttributes().addTransientAttributeModifiers(itemstack1.getAttributeModifiers(enumitemslot));
                 }
             }
         }
@@ -2413,37 +3134,135 @@
     }
 
     private void handleHandSwap(Map<EquipmentSlot, ItemStack> equipmentChanges) {
-        ItemStack itemStack = equipmentChanges.get(EquipmentSlot.MAINHAND);
-        ItemStack itemStack2 = equipmentChanges.get(EquipmentSlot.OFFHAND);
-        if (itemStack != null && itemStack2 != null && ItemStack.matches(itemStack, this.getLastHandItem(EquipmentSlot.OFFHAND)) && ItemStack.matches(itemStack2, this.getLastHandItem(EquipmentSlot.MAINHAND))) {
-            ((ServerLevel)this.level).getChunkSource().broadcast(this, new ClientboundEntityEventPacket(this, (byte)55));
+        ItemStack itemstack = (ItemStack) equipmentChanges.get(EquipmentSlot.MAINHAND);
+        ItemStack itemstack1 = (ItemStack) equipmentChanges.get(EquipmentSlot.OFFHAND);
+
+        if (itemstack != null && itemstack1 != null && ItemStack.matches(itemstack, this.getLastHandItem(EquipmentSlot.OFFHAND)) && ItemStack.matches(itemstack1, this.getLastHandItem(EquipmentSlot.MAINHAND))) {
+            ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundEntityEventPacket(this, (byte) 55));
             equipmentChanges.remove(EquipmentSlot.MAINHAND);
             equipmentChanges.remove(EquipmentSlot.OFFHAND);
-            this.setLastHandItem(EquipmentSlot.MAINHAND, itemStack.copy());
-            this.setLastHandItem(EquipmentSlot.OFFHAND, itemStack2.copy());
+            this.setLastHandItem(EquipmentSlot.MAINHAND, itemstack.copy());
+            this.setLastHandItem(EquipmentSlot.OFFHAND, itemstack1.copy());
         }
 
     }
 
     private void handleEquipmentChanges(Map<EquipmentSlot, ItemStack> equipmentChanges) {
         List<Pair<EquipmentSlot, ItemStack>> list = Lists.newArrayListWithCapacity(equipmentChanges.size());
-        equipmentChanges.forEach((slot, stack) -> {
-            ItemStack itemStack = stack.copy();
-            list.add(Pair.of(slot, itemStack));
-            switch (slot.getType()) {
+
+        equipmentChanges.forEach((enumitemslot, itemstack) -> {
+            ItemStack itemstack1 = itemstack.copy();
+
+            // Paper start - prevent oversized data
+            ItemStack toSend = sanitizeItemStack(itemstack1, true);
+            list.add(Pair.of(enumitemslot, stripMeta(toSend, toSend == itemstack1))); // Paper - hide unnecessary item meta
+            // Paper end
+            switch (enumitemslot.getType()) {
                 case HAND:
-                    this.setLastHandItem(slot, itemStack);
+                    this.setLastHandItem(enumitemslot, itemstack1);
                     break;
                 case ARMOR:
-                    this.setLastArmorItem(slot, itemStack);
+                    this.setLastArmorItem(enumitemslot, itemstack1);
             }
 
         });
-        ((ServerLevel)this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list));
+        ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list));
     }
 
+    // Paper start - hide unnecessary item meta
+    public ItemStack stripMeta(final ItemStack itemStack, final boolean copyItemStack) {
+        if (itemStack.isEmpty() || (!itemStack.hasTag() && itemStack.getCount() < 2)) {
+            return itemStack;
+        }
+
+        final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack;
+        if (level.paperConfig().anticheat.obfuscation.items.hideDurability) {
+            // Only show damage values for elytra's, since they show a different texture when broken.
+            if (!copy.is(Items.ELYTRA) || copy.getDamageValue() < copy.getMaxDamage() - 1) {
+                copy.setDamageValue(0);
+            }
+        }
+
+        final CompoundTag tag = copy.getTag();
+        if (level.paperConfig().anticheat.obfuscation.items.hideItemmeta) {
+            // Some resource packs show different textures when there is more than one item. Since this shouldn't provide a big advantage,
+            // we'll tell the client if there's one or (more than) two items.
+            copy.setCount(copy.getCount() > 1 ? 2 : 1);
+            // We can't just strip out display, leather helmets still use the display.color tag.
+            if (tag != null) {
+                if (tag.get("display") instanceof CompoundTag displayTag) {
+                    displayTag.remove("Lore");
+                    displayTag.remove("Name");
+                }
+
+                if (tag.get("Enchantments") instanceof ListTag enchantmentsTag && !enchantmentsTag.isEmpty()) {
+                    // The client still renders items with the enchantment glow if the enchantments tag contains at least one (empty) child.
+                    ListTag enchantments = new ListTag();
+                    CompoundTag fakeEnchantment = new CompoundTag();
+                    // Soul speed boots generate client side particles.
+                    if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SOUL_SPEED, itemStack) > 0) {
+                        fakeEnchantment.putString("id", org.bukkit.enchantments.Enchantment.SOUL_SPEED.getKey().asString());
+                        fakeEnchantment.putInt("lvl", 1);
+                    }
+                    enchantments.add(fakeEnchantment);
+                    tag.put("Enchantments", enchantments);
+                }
+                tag.remove("AttributeModifiers");
+
+                // Books
+                tag.remove("author");
+                tag.remove("filtered_title");
+                tag.remove("pages");
+                tag.remove("filtered_pages");
+                tag.remove("title");
+                tag.remove("generation");
+            }
+        }
+
+        if (level.paperConfig().anticheat.obfuscation.items.hideItemmetaWithVisualEffects && tag != null) {
+            // Lodestone compasses
+            tag.remove("LodestonePos");
+            if (tag.contains("LodestoneDimension")) {
+                // The client shows the glint if either the position or the dimension is present, so we just wipe
+                // the position and fake the dimension
+                tag.putString("LodestoneDimension", "paper:paper");
+            }
+        }
+
+        return copy;
+    }
+    // Paper end
+
+    // Paper start - prevent oversized data
+    public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) {
+        if (itemStack.isEmpty() || !itemStack.hasTag()) {
+            return itemStack;
+        }
+
+        final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack;
+        final CompoundTag tag = copy.getTag();
+        if (copy.is(Items.BUNDLE) && tag.get("Items") instanceof ListTag oldItems && !oldItems.isEmpty()) {
+            // Bundles change their texture based on their fullness.
+            org.bukkit.inventory.meta.BundleMeta bundleMeta = (org.bukkit.inventory.meta.BundleMeta) copy.asBukkitMirror().getItemMeta();
+            int sizeUsed = 0;
+            for (org.bukkit.inventory.ItemStack item : bundleMeta.getItems()) {
+                int scale = 64 / item.getMaxStackSize();
+                sizeUsed += scale * item.getAmount();
+            }
+            // Now we add a single fake item that uses the same amount of slots as all other items.
+            ListTag items = new ListTag();
+            items.add(new ItemStack(Items.PAPER, sizeUsed).save(new CompoundTag()));
+            tag.put("Items", items);
+        }
+        if (tag.get("BlockEntityTag") instanceof CompoundTag blockEntityTag) {
+            blockEntityTag.remove("Items");
+        }
+        return copy;
+    }
+    // Paper end
+
     private ItemStack getLastArmorItem(EquipmentSlot slot) {
-        return this.lastArmorItemStacks.get(slot.getIndex());
+        return (ItemStack) this.lastArmorItemStacks.get(slot.getIndex());
     }
 
     private void setLastArmorItem(EquipmentSlot slot, ItemStack armor) {
@@ -2451,7 +3270,7 @@
     }
 
     private ItemStack getLastHandItem(EquipmentSlot slot) {
-        return this.lastHandItemStacks.get(slot.getIndex());
+        return (ItemStack) this.lastHandItemStacks.get(slot.getIndex());
     }
 
     private void setLastHandItem(EquipmentSlot slot, ItemStack stack) {
@@ -2459,24 +3278,26 @@
     }
 
     protected float tickHeadTurn(float bodyRotation, float headRotation) {
-        float f = Mth.wrapDegrees(bodyRotation - this.yBodyRot);
-        this.yBodyRot += f * 0.3F;
-        float g = Mth.wrapDegrees(this.getYRot() - this.yBodyRot);
-        boolean bl = g < -90.0F || g >= 90.0F;
-        if (g < -75.0F) {
-            g = -75.0F;
+        float f2 = Mth.wrapDegrees(bodyRotation - this.yBodyRot);
+
+        this.yBodyRot += f2 * 0.3F;
+        float f3 = Mth.wrapDegrees(this.getYRot() - this.yBodyRot);
+        boolean flag = f3 < -90.0F || f3 >= 90.0F;
+
+        if (f3 < -75.0F) {
+            f3 = -75.0F;
         }
 
-        if (g >= 75.0F) {
-            g = 75.0F;
+        if (f3 >= 75.0F) {
+            f3 = 75.0F;
         }
 
-        this.yBodyRot = this.getYRot() - g;
-        if (g * g > 2500.0F) {
-            this.yBodyRot += g * 0.2F;
+        this.yBodyRot = this.getYRot() - f3;
+        if (f3 * f3 > 2500.0F) {
+            this.yBodyRot += f3 * 0.2F;
         }
 
-        if (bl) {
+        if (flag) {
             headRotation *= -1.0F;
         }
 
@@ -2494,41 +3315,43 @@
         }
 
         if (this.lerpSteps > 0) {
-            double d = this.getX() + (this.lerpX - this.getX()) / (double)this.lerpSteps;
-            double e = this.getY() + (this.lerpY - this.getY()) / (double)this.lerpSteps;
-            double f = this.getZ() + (this.lerpZ - this.getZ()) / (double)this.lerpSteps;
-            double g = Mth.wrapDegrees(this.lerpYRot - (double)this.getYRot());
-            this.setYRot(this.getYRot() + (float)g / (float)this.lerpSteps);
-            this.setXRot(this.getXRot() + (float)(this.lerpXRot - (double)this.getXRot()) / (float)this.lerpSteps);
+            double d0 = this.getX() + (this.lerpX - this.getX()) / (double) this.lerpSteps;
+            double d1 = this.getY() + (this.lerpY - this.getY()) / (double) this.lerpSteps;
+            double d2 = this.getZ() + (this.lerpZ - this.getZ()) / (double) this.lerpSteps;
+            double d3 = Mth.wrapDegrees(this.lerpYRot - (double) this.getYRot());
+
+            this.setYRot(this.getYRot() + (float) d3 / (float) this.lerpSteps);
+            this.setXRot(this.getXRot() + (float) (this.lerpXRot - (double) this.getXRot()) / (float) this.lerpSteps);
             --this.lerpSteps;
-            this.setPos(d, e, f);
+            this.setPos(d0, d1, d2);
             this.setRot(this.getYRot(), this.getXRot());
         } else if (!this.isEffectiveAi()) {
             this.setDeltaMovement(this.getDeltaMovement().scale(0.98D));
         }
 
         if (this.lerpHeadSteps > 0) {
-            this.yHeadRot += (float)Mth.wrapDegrees(this.lyHeadRot - (double)this.yHeadRot) / (float)this.lerpHeadSteps;
+            this.yHeadRot += (float) Mth.wrapDegrees(this.lyHeadRot - (double) this.yHeadRot) / (float) this.lerpHeadSteps;
             --this.lerpHeadSteps;
         }
 
-        Vec3 vec3 = this.getDeltaMovement();
-        double h = vec3.x;
-        double i = vec3.y;
-        double j = vec3.z;
-        if (Math.abs(vec3.x) < 0.003D) {
-            h = 0.0D;
+        Vec3 vec3d = this.getDeltaMovement();
+        double d4 = vec3d.x;
+        double d5 = vec3d.y;
+        double d6 = vec3d.z;
+
+        if (Math.abs(vec3d.x) < 0.003D) {
+            d4 = 0.0D;
         }
 
-        if (Math.abs(vec3.y) < 0.003D) {
-            i = 0.0D;
+        if (Math.abs(vec3d.y) < 0.003D) {
+            d5 = 0.0D;
         }
 
-        if (Math.abs(vec3.z) < 0.003D) {
-            j = 0.0D;
+        if (Math.abs(vec3d.z) < 0.003D) {
+            d6 = 0.0D;
         }
 
-        this.setDeltaMovement(h, i, j);
+        this.setDeltaMovement(d4, d5, d6);
         this.level.getProfiler().push("ai");
         if (this.isImmobile()) {
             this.jumping = false;
@@ -2543,26 +3366,26 @@
         this.level.getProfiler().pop();
         this.level.getProfiler().push("jump");
         if (this.jumping && this.isAffectedByFluids()) {
-            double k;
+            double d7;
+
             if (this.isInLava()) {
-                k = this.getFluidHeight(FluidTags.LAVA);
+                d7 = this.getFluidHeight(FluidTags.LAVA);
             } else {
-                k = this.getFluidHeight(FluidTags.WATER);
+                d7 = this.getFluidHeight(FluidTags.WATER);
             }
 
-            boolean bl = this.isInWater() && k > 0.0D;
-            double m = this.getFluidJumpThreshold();
-            if (!bl || this.onGround && !(k > m)) {
-                if (!this.isInLava() || this.onGround && !(k > m)) {
-                    if ((this.onGround || bl && k <= m) && this.noJumpDelay == 0) {
-                        this.jumpFromGround();
-                        this.noJumpDelay = 10;
-                    }
-                } else {
-                    this.jumpInLiquid(FluidTags.LAVA);
-                }
-            } else {
+            boolean flag = this.isInWater() && d7 > 0.0D;
+            double d8 = this.getFluidJumpThreshold();
+
+            if (flag && (!this.onGround || d7 > d8)) {
                 this.jumpInLiquid(FluidTags.WATER);
+            } else if (this.isInLava() && (!this.onGround || d7 > d8)) {
+                this.jumpInLiquid(FluidTags.LAVA);
+            } else if ((this.onGround || flag && d7 <= d8) && this.noJumpDelay == 0) {
+                if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper
+                this.jumpFromGround();
+                this.noJumpDelay = 10;
+                } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop
             }
         } else {
             this.noJumpDelay = 0;
@@ -2573,36 +3396,55 @@
         this.xxa *= 0.98F;
         this.zza *= 0.98F;
         this.updateFallFlying();
-        AABB aABB = this.getBoundingBox();
-        this.travel(new Vec3((double)this.xxa, (double)this.yya, (double)this.zza));
+        AABB axisalignedbb = this.getBoundingBox();
+
+        // SpigotTimings.timerEntityAIMove.startTiming(); // Spigot // Paper
+        this.travel(new Vec3((double) this.xxa, (double) this.yya, (double) this.zza));
+        // SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot // Paper
         this.level.getProfiler().pop();
         this.level.getProfiler().push("freezing");
-        boolean bl2 = this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES);
-        if (!this.level.isClientSide && !this.isDeadOrDying()) {
-            int n = this.getTicksFrozen();
+        boolean flag1 = this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES);
+        int i;
+
+        if (!this.level.isClientSide && !this.isDeadOrDying() && !freezeLocked) { // Paper - Freeze Tick Lock API
+            i = this.getTicksFrozen();
             if (this.isInPowderSnow && this.canFreeze()) {
-                this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), n + 1));
+                this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), i + 1));
             } else {
-                this.setTicksFrozen(Math.max(0, n - 2));
+                this.setTicksFrozen(Math.max(0, i - 2));
             }
         }
 
         this.removeFrost();
         this.tryAddFrost();
         if (!this.level.isClientSide && this.tickCount % 40 == 0 && this.isFullyFrozen() && this.canFreeze()) {
-            int o = bl2 ? 5 : 1;
-            this.hurt(DamageSource.FREEZE, (float)o);
+            i = flag1 ? 5 : 1;
+            this.hurt(DamageSource.FREEZE, (float) i);
         }
 
         this.level.getProfiler().pop();
         this.level.getProfiler().push("push");
         if (this.autoSpinAttackTicks > 0) {
             --this.autoSpinAttackTicks;
-            this.checkAutoSpinAttack(aABB, this.getBoundingBox());
+            this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox());
         }
 
         this.pushEntities();
         this.level.getProfiler().pop();
+        // Paper start
+        if (((ServerLevel) this.level).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
+            if (this.xo != getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
+                Location from = new Location(this.level.getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO);
+                Location to = new Location (this.level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+                io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone());
+                if (!event.callEvent()) {
+                    absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch());
+                } else if (!to.equals(event.getTo())) {
+                    absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch());
+                }
+            }
+        }
+        // Paper end
         if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
             this.hurt(DamageSource.DROWN, 1.0F);
         }
@@ -2614,50 +3456,71 @@
     }
 
     private void updateFallFlying() {
-        boolean bl = this.getSharedFlag(7);
-        if (bl && !this.onGround && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) {
-            ItemStack itemStack = this.getItemBySlot(EquipmentSlot.CHEST);
-            if (itemStack.is(Items.ELYTRA) && ElytraItem.isFlyEnabled(itemStack)) {
-                bl = true;
+        boolean flag = this.getSharedFlag(7);
+
+        if (flag && !this.onGround && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) {
+            ItemStack itemstack = this.getItemBySlot(EquipmentSlot.CHEST);
+
+            if (itemstack.is(Items.ELYTRA) && ElytraItem.isFlyEnabled(itemstack)) {
+                flag = true;
                 int i = this.fallFlyTicks + 1;
+
                 if (!this.level.isClientSide && i % 10 == 0) {
                     int j = i / 10;
+
                     if (j % 2 == 0) {
-                        itemStack.hurtAndBreak(1, this, (player) -> {
-                            player.broadcastBreakEvent(EquipmentSlot.CHEST);
+                        itemstack.hurtAndBreak(1, this, (entityliving) -> {
+                            entityliving.broadcastBreakEvent(EquipmentSlot.CHEST);
                         });
                     }
 
                     this.gameEvent(GameEvent.ELYTRA_GLIDE);
                 }
             } else {
-                bl = false;
+                flag = false;
             }
         } else {
-            bl = false;
+            flag = false;
         }
 
         if (!this.level.isClientSide) {
-            this.setSharedFlag(7, bl);
+            if (flag != this.getSharedFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, flag).isCancelled()) // CraftBukkit
+            this.setSharedFlag(7, flag);
         }
 
     }
 
-    protected void serverAiStep() {
-    }
+    protected void serverAiStep() {}
 
     protected void pushEntities() {
         if (this.level.isClientSide()) {
-            this.level.getEntities(EntityTypeTest.forClass(Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush);
+            this.level.getEntities(EntityTypeTest.forClass(net.minecraft.world.entity.player.Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush);
         } else {
-            List<Entity> list = this.level.getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this));
+            // Paper start - don't run getEntities if we're not going to use its result
+            if (!this.isPushable()) {
+                return;
+            }
+            net.minecraft.world.scores.Team team = this.getTeam();
+            if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) {
+                return;
+            }
+
+            int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
+            if (i <= 0 && level.paperConfig().collisions.maxEntityCollisions <= 0) {
+                return;
+            }
+            // Paper end - don't run getEntities if we're not going to use its result
+            List<Entity> list = this.level.getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, level.paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule
+
             if (!list.isEmpty()) {
-                int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
+                // Paper - moved up
+                int j;
+
                 if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) {
-                    int j = 0;
+                    j = 0;
 
-                    for(int k = 0; k < list.size(); ++k) {
-                        if (!list.get(k).isPassenger()) {
+                    for (int k = 0; k < list.size(); ++k) {
+                        if (!((Entity) list.get(k)).isPassenger()) {
                             ++j;
                         }
                     }
@@ -2667,8 +3530,12 @@
                     }
                 }
 
-                for(int l = 0; l < list.size(); ++l) {
-                    Entity entity = list.get(l);
+            this.numCollisions = Math.max(0, this.numCollisions - this.level.paperConfig().collisions.maxEntityCollisions); // Paper
+            for (j = 0; j < list.size() && this.numCollisions < this.level.paperConfig().collisions.maxEntityCollisions; ++j) { // Paper
+                    Entity entity = (Entity) list.get(j);
+                entity.numCollisions++; // Paper
+                this.numCollisions++; // Paper
+
                     this.doPush(entity);
                 }
             }
@@ -2677,13 +3544,15 @@
     }
 
     protected void checkAutoSpinAttack(AABB a, AABB b) {
-        AABB aABB = a.minmax(b);
-        List<Entity> list = this.level.getEntities(this, aABB);
+        AABB axisalignedbb2 = a.minmax(b);
+        List<Entity> list = this.level.getEntities(this, axisalignedbb2);
+
         if (!list.isEmpty()) {
-            for(int i = 0; i < list.size(); ++i) {
-                Entity entity = list.get(i);
+            for (int i = 0; i < list.size(); ++i) {
+                Entity entity = (Entity) list.get(i);
+
                 if (entity instanceof LivingEntity) {
-                    this.doAutoAttackOnTouch((LivingEntity)entity);
+                    this.doAutoAttackOnTouch((LivingEntity) entity);
                     this.autoSpinAttackTicks = 0;
                     this.setDeltaMovement(this.getDeltaMovement().scale(-0.2D));
                     break;
@@ -2703,18 +3572,24 @@
         entity.push(this);
     }
 
-    protected void doAutoAttackOnTouch(LivingEntity target) {
-    }
+    protected void doAutoAttackOnTouch(LivingEntity target) {}
 
     public boolean isAutoSpinAttack() {
-        return (this.entityData.get(DATA_LIVING_ENTITY_FLAGS) & 4) != 0;
+        return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 4) != 0;
     }
 
     @Override
     public void stopRiding() {
+        // Paper start
+        stopRiding(false);
+    }
+    @Override
+    public void stopRiding(boolean suppressCancellation) {
+        // Paper end
         Entity entity = this.getVehicle();
-        super.stopRiding();
-        if (entity != null && entity != this.getVehicle() && !this.level.isClientSide) {
+
+        super.stopRiding(suppressCancellation); // Paper - suppress
+        if (entity != null && entity != this.getVehicle() && !this.level.isClientSide && entity.valid) { // Paper - don't process on world gen
             this.dismountVehicle(entity);
         }
 
@@ -2733,14 +3608,14 @@
         this.lerpX = x;
         this.lerpY = y;
         this.lerpZ = z;
-        this.lerpYRot = (double)yaw;
-        this.lerpXRot = (double)pitch;
+        this.lerpYRot = (double) yaw;
+        this.lerpXRot = (double) pitch;
         this.lerpSteps = interpolationSteps;
     }
 
     @Override
     public void lerpHeadTo(float yaw, int interpolationSteps) {
-        this.lyHeadRot = (double)yaw;
+        this.lyHeadRot = (double) yaw;
         this.lerpHeadSteps = interpolationSteps;
     }
 
@@ -2749,16 +3624,17 @@
     }
 
     public void onItemPickup(ItemEntity item) {
-        Player player = item.getThrower() != null ? this.level.getPlayerByUUID(item.getThrower()) : null;
-        if (player instanceof ServerPlayer) {
-            CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer)player, item.getItem(), this);
+        net.minecraft.world.entity.player.Player entityhuman = item.getThrower() != null ? this.level.getGlobalPlayerByUUID(item.getThrower()) : null; // Paper - check all players
+
+        if (entityhuman instanceof ServerPlayer) {
+            CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entityhuman, item.getItem(), this);
         }
 
     }
 
     public void take(Entity item, int count) {
         if (!item.isRemoved() && !this.level.isClientSide && (item instanceof ItemEntity || item instanceof AbstractArrow || item instanceof ExperienceOrb)) {
-            ((ServerLevel)this.level).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count));
+            ((ServerLevel) this.level).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count));
         }
 
     }
@@ -2767,13 +3643,11 @@
         if (entity.level != this.level) {
             return false;
         } else {
-            Vec3 vec3 = new Vec3(this.getX(), this.getEyeY(), this.getZ());
-            Vec3 vec32 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ());
-            if (vec32.distanceTo(vec3) > 128.0D) {
-                return false;
-            } else {
-                return this.level.clip(new ClipContext(vec3, vec32, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS;
-            }
+            Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ());
+            Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ());
+
+            // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists
+            return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - use distanceToSqr
         }
     }
 
@@ -2783,12 +3657,13 @@
     }
 
     public float getAttackAnim(float tickDelta) {
-        float f = this.attackAnim - this.oAttackAnim;
-        if (f < 0.0F) {
-            ++f;
+        float f1 = this.attackAnim - this.oAttackAnim;
+
+        if (f1 < 0.0F) {
+            ++f1;
         }
 
-        return this.oAttackAnim + f * tickDelta;
+        return this.oAttackAnim + f1 * tickDelta;
     }
 
     public boolean isEffectiveAi() {
@@ -2797,13 +3672,27 @@
 
     @Override
     public boolean isPickable() {
-        return !this.isRemoved();
+        return !this.isRemoved() && this.collides; // CraftBukkit
     }
 
+    // Paper start
     @Override
     public boolean isPushable() {
-        return this.isAlive() && !this.isSpectator() && !this.onClimbable();
+        return this.isCollidable(level.paperConfig().collisions.fixClimbingBypassingCrammingRule);
+    }
+
+    @Override
+    public boolean isCollidable(boolean ignoreClimbing) {
+        return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit
+        // Paper end
+    }
+
+    // CraftBukkit start - collidable API
+    @Override
+    public boolean canCollideWithBukkit(Entity entity) {
+        return this.isPushable() && this.collides != this.collidableExemptions.contains(entity.getUUID());
     }
+    // CraftBukkit end
 
     @Override
     public float getYHeadRot() {
@@ -2822,7 +3711,7 @@
 
     @Override
     protected Vec3 getRelativePortalPosition(Direction.Axis portalAxis, BlockUtil.FoundRectangle portalRect) {
-        return resetForwardDirectionOfRelativePortalPosition(super.getRelativePortalPosition(portalAxis, portalRect));
+        return LivingEntity.resetForwardDirectionOfRelativePortalPosition(super.getRelativePortalPosition(portalAxis, portalRect));
     }
 
     public static Vec3 resetForwardDirectionOfRelativePortalPosition(Vec3 pos) {
@@ -2834,18 +3723,16 @@
     }
 
     public void setAbsorptionAmount(float amount) {
-        if (amount < 0.0F) {
+        if (amount < 0.0F || Float.isNaN(amount)) { // Paper
             amount = 0.0F;
         }
 
         this.absorptionAmount = amount;
     }
 
-    public void onEnterCombat() {
-    }
+    public void onEnterCombat() {}
 
-    public void onLeaveCombat() {
-    }
+    public void onLeaveCombat() {}
 
     protected void updateEffectVisibility() {
         this.effectsDirty = true;
@@ -2854,13 +3741,18 @@
     public abstract HumanoidArm getMainArm();
 
     public boolean isUsingItem() {
-        return (this.entityData.get(DATA_LIVING_ENTITY_FLAGS) & 1) > 0;
+        return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 1) > 0;
     }
 
     public InteractionHand getUsedItemHand() {
-        return (this.entityData.get(DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
+        return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
     }
 
+    // Paper start - lag compensate eating
+    protected long eatStartTime;
+    protected int totalEatTimeTicks;
+    // Paper end
+
     private void updatingUsingItem() {
         if (this.isUsingItem()) {
             if (ItemStack.isSame(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
@@ -2878,8 +3770,12 @@
         if (this.shouldTriggerItemUseEffects()) {
             this.triggerItemUseEffects(stack, 5);
         }
-
-        if (--this.useItemRemaining == 0 && !this.level.isClientSide && !stack.useOnRelease()) {
+        // Paper start - lag compensate eating
+        // we add 1 to the expected time to avoid lag compensating when we should not
+        boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000));
+        if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level.isClientSide && !this.useItem.useOnRelease()) {
+            this.useItemRemaining = 0;
+            // Paper end
             this.completeUsingItem();
         }
 
@@ -2887,10 +3783,11 @@
 
     private boolean shouldTriggerItemUseEffects() {
         int i = this.getUseItemRemainingTicks();
-        FoodProperties foodProperties = this.useItem.getItem().getFoodProperties();
-        boolean bl = foodProperties != null && foodProperties.isFastFood();
-        bl |= i <= this.useItem.getUseDuration() - 7;
-        return bl && i % 4 == 0;
+        FoodProperties foodinfo = this.useItem.getItem().getFoodProperties();
+        boolean flag = foodinfo != null && foodinfo.isFastFood();
+
+        flag |= i <= this.useItem.getUseDuration() - 7;
+        return flag && i % 4 == 0;
     }
 
     private void updateSwimAmount() {
@@ -2904,21 +3801,32 @@
     }
 
     protected void setLivingEntityFlag(int mask, boolean value) {
-        int i = this.entityData.get(DATA_LIVING_ENTITY_FLAGS);
+        byte b0 = (Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS);
+        int j;
+
         if (value) {
-            i |= mask;
+            j = b0 | mask;
         } else {
-            i &= ~mask;
+            j = b0 & ~mask;
         }
 
-        this.entityData.set(DATA_LIVING_ENTITY_FLAGS, (byte)i);
+        this.entityData.set(LivingEntity.DATA_LIVING_ENTITY_FLAGS, (byte) j);
     }
 
     public void startUsingItem(InteractionHand hand) {
-        ItemStack itemStack = this.getItemInHand(hand);
-        if (!itemStack.isEmpty() && !this.isUsingItem()) {
-            this.useItem = itemStack;
-            this.useItemRemaining = itemStack.getUseDuration();
+        // Paper start - forwarder to method with forceUpdate parameter
+        this.startUsingItem(hand, false);
+    }
+    public void startUsingItem(InteractionHand hand, boolean forceUpdate) {
+        // Paper end
+        ItemStack itemstack = this.getItemInHand(hand);
+
+        if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper use override flag
+            this.useItem = itemstack;
+            // Paper start - lag compensate eating
+            this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration();
+            this.eatStartTime = System.nanoTime();
+            // Paper end
             if (!this.level.isClientSide) {
                 this.setLivingEntityFlag(1, true);
                 this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
@@ -2931,11 +3839,11 @@
     @Override
     public void onSyncedDataUpdated(EntityDataAccessor<?> data) {
         super.onSyncedDataUpdated(data);
-        if (SLEEPING_POS_ID.equals(data)) {
+        if (LivingEntity.SLEEPING_POS_ID.equals(data)) {
             if (this.level.isClientSide) {
                 this.getSleepingPos().ifPresent(this::setPosToBed);
             }
-        } else if (DATA_LIVING_ENTITY_FLAGS.equals(data) && this.level.isClientSide) {
+        } else if (LivingEntity.DATA_LIVING_ENTITY_FLAGS.equals(data) && this.level.isClientSide) {
             if (this.isUsingItem() && this.useItem.isEmpty()) {
                 this.useItem = this.getItemInHand(this.getUsedItemHand());
                 if (!this.useItem.isEmpty()) {
@@ -2943,7 +3851,10 @@
                 }
             } else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
                 this.useItem = ItemStack.EMPTY;
-                this.useItemRemaining = 0;
+                // Paper start - lag compensate eating
+                this.useItemRemaining = this.totalEatTimeTicks = 0;
+                this.eatStartTime = -1L;
+                // Paper end
             }
         }
 
@@ -2965,41 +3876,79 @@
 
             if (stack.getUseAnimation() == UseAnim.EAT) {
                 this.spawnItemParticles(stack, particleCount);
-                this.playSound(this.getEatingSound(stack), 0.5F + 0.5F * (float)this.random.nextInt(2), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
+                this.playSound(this.getEatingSound(stack), 0.5F + 0.5F * (float) this.random.nextInt(2), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
             }
 
         }
     }
 
     private void spawnItemParticles(ItemStack stack, int count) {
-        for(int i = 0; i < count; ++i) {
-            Vec3 vec3 = new Vec3(((double)this.random.nextFloat() - 0.5D) * 0.1D, Math.random() * 0.1D + 0.1D, 0.0D);
-            vec3 = vec3.xRot(-this.getXRot() * ((float)Math.PI / 180F));
-            vec3 = vec3.yRot(-this.getYRot() * ((float)Math.PI / 180F));
-            double d = (double)(-this.random.nextFloat()) * 0.6D - 0.3D;
-            Vec3 vec32 = new Vec3(((double)this.random.nextFloat() - 0.5D) * 0.3D, d, 0.6D);
-            vec32 = vec32.xRot(-this.getXRot() * ((float)Math.PI / 180F));
-            vec32 = vec32.yRot(-this.getYRot() * ((float)Math.PI / 180F));
-            vec32 = vec32.add(this.getX(), this.getEyeY(), this.getZ());
-            this.level.addParticle(new ItemParticleOption(ParticleTypes.ITEM, stack), vec32.x, vec32.y, vec32.z, vec3.x, vec3.y + 0.05D, vec3.z);
+        for (int j = 0; j < count; ++j) {
+            Vec3 vec3d = new Vec3(((double) this.random.nextFloat() - 0.5D) * 0.1D, Math.random() * 0.1D + 0.1D, 0.0D);
+
+            vec3d = vec3d.xRot(-this.getXRot() * 0.017453292F);
+            vec3d = vec3d.yRot(-this.getYRot() * 0.017453292F);
+            double d0 = (double) (-this.random.nextFloat()) * 0.6D - 0.3D;
+            Vec3 vec3d1 = new Vec3(((double) this.random.nextFloat() - 0.5D) * 0.3D, d0, 0.6D);
+
+            vec3d1 = vec3d1.xRot(-this.getXRot() * 0.017453292F);
+            vec3d1 = vec3d1.yRot(-this.getYRot() * 0.017453292F);
+            vec3d1 = vec3d1.add(this.getX(), this.getEyeY(), this.getZ());
+            this.level.addParticle(new ItemParticleOption(ParticleTypes.ITEM, stack), vec3d1.x, vec3d1.y, vec3d1.z, vec3d.x, vec3d.y + 0.05D, vec3d.z);
         }
 
     }
 
     protected void completeUsingItem() {
         if (!this.level.isClientSide || this.isUsingItem()) {
-            InteractionHand interactionHand = this.getUsedItemHand();
-            if (!this.useItem.equals(this.getItemInHand(interactionHand))) {
+            InteractionHand enumhand = this.getUsedItemHand();
+
+            if (!this.useItem.equals(this.getItemInHand(enumhand))) {
                 this.releaseUsingItem();
             } else {
                 if (!this.useItem.isEmpty() && this.isUsingItem()) {
+                this.startUsingItem(this.getUsedItemHand(), true); // Paper
                     this.triggerItemUseEffects(this.useItem, 16);
-                    ItemStack itemStack = this.useItem.finishUsingItem(this.level, this);
-                    if (itemStack != this.useItem) {
-                        this.setItemInHand(interactionHand, itemStack);
+                    // CraftBukkit start - fire PlayerItemConsumeEvent
+                    ItemStack itemstack;
+                    PlayerItemConsumeEvent event = null; // Paper
+                    if (this instanceof ServerPlayer) {
+                        org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem);
+                        org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.v1_19_R2.CraftEquipmentSlot.getHand(enumhand);
+                        event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); // Paper
+                        level.getCraftServer().getPluginManager().callEvent(event);
+
+                        if (event.isCancelled()) {
+                        this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use
+                            // Update client
+                            ((ServerPlayer) this).getBukkitEntity().updateInventory();
+                            ((ServerPlayer) this).getBukkitEntity().updateScaledHealth();
+                            return;
+                        }
+
+                        itemstack = (craftItem.equals(event.getItem())) ? this.useItem.finishUsingItem(this.level, this) : CraftItemStack.asNMSCopy(event.getItem()).finishUsingItem(level, this);
+                    } else {
+                        itemstack = this.useItem.finishUsingItem(this.level, this);
+                    }
+
+                // Paper start - save the default replacement item and change it if necessary
+                final ItemStack defaultReplacement = itemstack;
+                if (event != null && event.getReplacement() != null) {
+                    itemstack = CraftItemStack.asNMSCopy(event.getReplacement());
+                }
+                // Paper end
+                    // CraftBukkit end
+
+                    if (itemstack != this.useItem) {
+                        this.setItemInHand(enumhand, itemstack);
                     }
 
                     this.stopUsingItem();
+                // Paper start
+                if (this instanceof ServerPlayer) {
+                    ((ServerPlayer) this).getBukkitEntity().updateInventory();
+                }
+                // Paper end
                 }
 
             }
@@ -3020,6 +3969,7 @@
 
     public void releaseUsingItem() {
         if (!this.useItem.isEmpty()) {
+            if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper
             this.useItem.releaseUsing(this.level, this, this.getUseItemRemainingTicks());
             if (this.useItem.useOnRelease()) {
                 this.updatingUsingItem();
@@ -3031,30 +3981,92 @@
 
     public void stopUsingItem() {
         if (!this.level.isClientSide) {
-            boolean bl = this.isUsingItem();
+            boolean flag = this.isUsingItem();
+
             this.setLivingEntityFlag(1, false);
-            if (bl) {
+            if (flag) {
                 this.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
             }
         }
 
         this.useItem = ItemStack.EMPTY;
-        this.useItemRemaining = 0;
+        // Paper start - lag compensate eating
+        this.useItemRemaining = this.totalEatTimeTicks = 0;
+        this.eatStartTime = -1L;
+        // Paper end
     }
 
     public boolean isBlocking() {
         if (this.isUsingItem() && !this.useItem.isEmpty()) {
             Item item = this.useItem.getItem();
-            if (item.getUseAnimation(this.useItem) != UseAnim.BLOCK) {
-                return false;
-            } else {
-                return item.getUseDuration(this.useItem) - this.useItemRemaining >= 5;
-            }
+
+            return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= getShieldBlockingDelay(); // Paper - shieldBlockingDelay
         } else {
             return false;
         }
     }
 
+    // Paper start
+    public HitResult getRayTrace(int maxDistance) {
+        return getRayTrace(maxDistance, ClipContext.Fluid.NONE);
+    }
+
+    public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) {
+        if (maxDistance < 1 || maxDistance > 120) {
+            throw new IllegalArgumentException("maxDistance must be between 1-120");
+        }
+
+        Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ());
+        org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance);
+        Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ());
+        ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this);
+
+        return level.clip(raytrace);
+    }
+
+    public EntityHitResult getTargetEntity(int maxDistance) {
+        if (maxDistance < 1 || maxDistance > 120) {
+            throw new IllegalArgumentException("maxDistance must be between 1-120");
+        }
+
+        Vec3 start = this.getEyePosition(1.0F);
+        Vec3 direction = this.getLookAngle();
+        Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance);
+
+        List<Entity> entityList = level.getEntities(this, getBoundingBox().expandTowards(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR.and(Entity::isPickable));
+
+        double distance = 0.0D;
+        EntityHitResult result = null;
+
+        for (Entity entity : entityList) {
+            final double inflationAmount = (double) entity.getPickRadius();
+            AABB aabb = entity.getBoundingBox().inflate(inflationAmount, inflationAmount, inflationAmount);
+            Optional<Vec3> rayTraceResult = aabb.clip(start, end);
+
+            if (rayTraceResult.isPresent()) {
+                Vec3 rayTrace = rayTraceResult.get();
+                double distanceTo = start.distanceToSqr(rayTrace);
+                if (distanceTo < distance || distance == 0.0D) {
+                    result = new EntityHitResult(entity, rayTrace);
+                    distance = distanceTo;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public int shieldBlockingDelay = level.paperConfig().misc.shieldBlockingDelay;
+
+    public int getShieldBlockingDelay() {
+        return shieldBlockingDelay;
+    }
+
+    public void setShieldBlockingDelay(int shieldBlockingDelay) {
+        this.shieldBlockingDelay = shieldBlockingDelay;
+    }
+    // Paper end
+
     public boolean isSuppressingSlidingDownLadder() {
         return this.isShiftKeyDown();
     }
@@ -3073,48 +4085,81 @@
     }
 
     public boolean randomTeleport(double x, double y, double z, boolean particleEffects) {
-        double d = this.getX();
-        double e = this.getY();
-        double f = this.getZ();
-        double g = y;
-        boolean bl = false;
-        BlockPos blockPos = new BlockPos(x, y, z);
-        Level level = this.level;
-        if (level.hasChunkAt(blockPos)) {
-            boolean bl2 = false;
+        // CraftBukkit start
+        return this.randomTeleport(x, y, z, particleEffects, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN).orElse(false);
+    }
 
-            while(!bl2 && blockPos.getY() > level.getMinBuildHeight()) {
-                BlockPos blockPos2 = blockPos.below();
-                BlockState blockState = level.getBlockState(blockPos2);
-                if (blockState.getMaterial().blocksMotion()) {
-                    bl2 = true;
+    public Optional<Boolean> randomTeleport(double d0, double d1, double d2, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
+        // CraftBukkit end
+        double d3 = this.getX();
+        double d4 = this.getY();
+        double d5 = this.getZ();
+        double d6 = d1;
+        boolean flag1 = false;
+        BlockPos blockposition = new BlockPos(d0, d1, d2);
+        Level world = this.level;
+
+        if (world.hasChunkAt(blockposition)) {
+            boolean flag2 = false;
+
+            while (!flag2 && blockposition.getY() > world.getMinBuildHeight()) {
+                BlockPos blockposition1 = blockposition.below();
+                BlockState iblockdata = world.getBlockState(blockposition1);
+
+                if (iblockdata.getMaterial().blocksMotion()) {
+                    flag2 = true;
                 } else {
-                    --g;
-                    blockPos = blockPos2;
+                    --d6;
+                    blockposition = blockposition1;
                 }
             }
 
-            if (bl2) {
-                this.teleportTo(x, g, z);
-                if (level.noCollision(this) && !level.containsAnyLiquid(this.getBoundingBox())) {
-                    bl = true;
+            if (flag2) {
+                // CraftBukkit start - Teleport event
+                // this.teleportTo(d0, d6, d2);
+
+                // first set position, to check if the place to teleport is valid
+                this.setPos(d0, d6, d2);
+                if (world.noCollision((Entity) this) && !world.containsAnyLiquid(this.getBoundingBox())) {
+                    flag1 = true;
+                }
+                // now revert and call event if the teleport place is valid
+                this.setPos(d3, d4, d5);
+
+                if (flag1) {
+                    if (!(this instanceof ServerPlayer)) {
+                        EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level.getWorld(), d3, d4, d5), new Location(this.level.getWorld(), d0, d6, d2));
+                        this.level.getCraftServer().getPluginManager().callEvent(teleport);
+                        if (!teleport.isCancelled()) {
+                            Location to = teleport.getTo();
+                            this.teleportTo(to.getX(), to.getY(), to.getZ());
+                        } else {
+                            return Optional.empty();
+                        }
+                    } else {
+                        // player teleport event is called in the underlining code
+                        if (((ServerPlayer) this).connection.teleport(d0, d6, d2, this.getYRot(), this.getXRot(), java.util.Collections.emptySet(), false, cause)) {
+                            return Optional.empty();
+                        }
+                    }
                 }
+                // CraftBukkit end
             }
         }
 
-        if (!bl) {
-            this.teleportTo(d, e, f);
-            return false;
+        if (!flag1) {
+            // this.enderTeleportTo(d3, d4, d5); // CraftBukkit - already set the location back
+            return Optional.of(false); // CraftBukkit
         } else {
-            if (particleEffects) {
-                level.broadcastEntityEvent(this, (byte)46);
+            if (flag) {
+                world.broadcastEntityEvent(this, (byte) 46);
             }
 
             if (this instanceof PathfinderMob) {
-                ((PathfinderMob)this).getNavigation().stop();
+                ((PathfinderMob) this).getNavigation().stop();
             }
 
-            return true;
+            return Optional.of(true); // CraftBukkit
         }
     }
 
@@ -3126,8 +4171,7 @@
         return true;
     }
 
-    public void setRecordPlayingNearby(BlockPos songPosition, boolean playing) {
-    }
+    public void setRecordPlayingNearby(BlockPos songPosition, boolean playing) {}
 
     public boolean canTakeItem(ItemStack stack) {
         return false;
@@ -3135,7 +4179,7 @@
 
     @Override
     public EntityDimensions getDimensions(Pose pose) {
-        return pose == Pose.SLEEPING ? SLEEPING_DIMENSIONS : super.getDimensions(pose).scale(this.getScale());
+        return pose == Pose.SLEEPING ? LivingEntity.SLEEPING_DIMENSIONS : super.getDimensions(pose).scale(this.getScale());
     }
 
     public ImmutableList<Pose> getDismountPoses() {
@@ -3143,20 +4187,21 @@
     }
 
     public AABB getLocalBoundsForPose(Pose pose) {
-        EntityDimensions entityDimensions = this.getDimensions(pose);
-        return new AABB((double)(-entityDimensions.width / 2.0F), 0.0D, (double)(-entityDimensions.width / 2.0F), (double)(entityDimensions.width / 2.0F), (double)entityDimensions.height, (double)(entityDimensions.width / 2.0F));
+        EntityDimensions entitysize = this.getDimensions(pose);
+
+        return new AABB((double) (-entitysize.width / 2.0F), 0.0D, (double) (-entitysize.width / 2.0F), (double) (entitysize.width / 2.0F), (double) entitysize.height, (double) (entitysize.width / 2.0F));
     }
 
     public Optional<BlockPos> getSleepingPos() {
-        return this.entityData.get(SLEEPING_POS_ID);
+        return (Optional) this.entityData.get(LivingEntity.SLEEPING_POS_ID);
     }
 
     public void setSleepingPos(BlockPos pos) {
-        this.entityData.set(SLEEPING_POS_ID, Optional.of(pos));
+        this.entityData.set(LivingEntity.SLEEPING_POS_ID, Optional.of(pos));
     }
 
     public void clearSleepingPos() {
-        this.entityData.set(SLEEPING_POS_ID, Optional.empty());
+        this.entityData.set(LivingEntity.SLEEPING_POS_ID, Optional.empty());
     }
 
     public boolean isSleeping() {
@@ -3168,9 +4213,10 @@
             this.stopRiding();
         }
 
-        BlockState blockState = this.level.getBlockState(pos);
-        if (blockState.getBlock() instanceof BedBlock) {
-            this.level.setBlock(pos, blockState.setValue(BedBlock.OCCUPIED, Boolean.valueOf(true)), 3);
+        BlockState iblockdata = this.level.getBlockState(pos);
+
+        if (iblockdata.getBlock() instanceof BedBlock) {
+            this.level.setBlock(pos, (BlockState) iblockdata.setValue(BedBlock.OCCUPIED, true), 3);
         }
 
         this.setPose(Pose.SLEEPING);
@@ -3181,43 +4227,53 @@
     }
 
     private void setPosToBed(BlockPos pos) {
-        this.setPos((double)pos.getX() + 0.5D, (double)pos.getY() + 0.6875D, (double)pos.getZ() + 0.5D);
+        this.setPos((double) pos.getX() + 0.5D, (double) pos.getY() + 0.6875D, (double) pos.getZ() + 0.5D);
     }
 
     private boolean checkBedExists() {
-        return this.getSleepingPos().map((pos) -> {
-            return this.level.getBlockState(pos).getBlock() instanceof BedBlock;
+        return (Boolean) this.getSleepingPos().map((blockposition) -> {
+            return this.level.getBlockState(blockposition).getBlock() instanceof BedBlock;
         }).orElse(false);
     }
 
     public void stopSleeping() {
-        this.getSleepingPos().filter(this.level::hasChunkAt).ifPresent((pos) -> {
-            BlockState blockState = this.level.getBlockState(pos);
-            if (blockState.getBlock() instanceof BedBlock) {
-                Direction direction = blockState.getValue(BedBlock.FACING);
-                this.level.setBlock(pos, blockState.setValue(BedBlock.OCCUPIED, Boolean.valueOf(false)), 3);
-                Vec3 vec3 = BedBlock.findStandUpPosition(this.getType(), this.level, pos, direction, this.getYRot()).orElseGet(() -> {
-                    BlockPos blockPos2 = pos.above();
-                    return new Vec3((double)blockPos2.getX() + 0.5D, (double)blockPos2.getY() + 0.1D, (double)blockPos2.getZ() + 0.5D);
+        Optional<BlockPos> optional = this.getSleepingPos(); // CraftBukkit - decompile error
+        Level world = this.level;
+
+        java.util.Objects.requireNonNull(this.level);
+        optional.filter(world::hasChunkAt).ifPresent((blockposition) -> {
+            BlockState iblockdata = this.level.getBlockState(blockposition);
+
+            if (iblockdata.getBlock() instanceof BedBlock) {
+                Direction enumdirection = (Direction) iblockdata.getValue(BedBlock.FACING);
+
+                this.level.setBlock(blockposition, (BlockState) iblockdata.setValue(BedBlock.OCCUPIED, false), 3);
+                Vec3 vec3d = (Vec3) BedBlock.findStandUpPosition(this.getType(), this.level, blockposition, enumdirection, this.getYRot()).orElseGet(() -> {
+                    BlockPos blockposition1 = blockposition.above();
+
+                    return new Vec3((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.1D, (double) blockposition1.getZ() + 0.5D);
                 });
-                Vec3 vec32 = Vec3.atBottomCenterOf(pos).subtract(vec3).normalize();
-                float f = (float)Mth.wrapDegrees(Mth.atan2(vec32.z, vec32.x) * (double)(180F / (float)Math.PI) - 90.0D);
-                this.setPos(vec3.x, vec3.y, vec3.z);
+                Vec3 vec3d1 = Vec3.atBottomCenterOf(blockposition).subtract(vec3d).normalize();
+                float f = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D);
+
+                this.setPos(vec3d.x, vec3d.y, vec3d.z);
                 this.setYRot(f);
                 this.setXRot(0.0F);
             }
 
         });
-        Vec3 vec3 = this.position();
+        Vec3 vec3d = this.position();
+
         this.setPose(Pose.STANDING);
-        this.setPos(vec3.x, vec3.y, vec3.z);
+        this.setPos(vec3d.x, vec3d.y, vec3d.z);
         this.clearSleepingPos();
     }
 
     @Nullable
     public Direction getBedOrientation() {
-        BlockPos blockPos = this.getSleepingPos().orElse((BlockPos)null);
-        return blockPos != null ? BedBlock.getBedOrientation(this.level, blockPos) : null;
+        BlockPos blockposition = (BlockPos) this.getSleepingPos().orElse(null); // CraftBukkit - decompile error
+
+        return blockposition != null ? BedBlock.getBedOrientation(this.level, blockposition) : null;
     }
 
     @Override
@@ -3240,9 +4296,9 @@
 
     public ItemStack eat(Level world, ItemStack stack) {
         if (stack.isEdible()) {
-            world.playSound((Player)null, this.getX(), this.getY(), this.getZ(), this.getEatingSound(stack), SoundSource.NEUTRAL, 1.0F, 1.0F + (world.random.nextFloat() - world.random.nextFloat()) * 0.4F);
+            world.playSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), this.getEatingSound(stack), SoundSource.NEUTRAL, 1.0F, 1.0F + (world.random.nextFloat() - world.random.nextFloat()) * 0.4F);
             this.addEatEffect(stack, world, this);
-            if (!(this instanceof Player) || !((Player)this).getAbilities().instabuild) {
+            if (!(this instanceof net.minecraft.world.entity.player.Player) || !((net.minecraft.world.entity.player.Player) this).getAbilities().instabuild) {
                 stack.shrink(1);
             }
 
@@ -3254,10 +4310,16 @@
 
     private void addEatEffect(ItemStack stack, Level world, LivingEntity targetEntity) {
         Item item = stack.getItem();
+
         if (item.isEdible()) {
-            for(Pair<MobEffectInstance, Float> pair : item.getFoodProperties().getEffects()) {
-                if (!world.isClientSide && pair.getFirst() != null && world.random.nextFloat() < pair.getSecond()) {
-                    targetEntity.addEffect(new MobEffectInstance(pair.getFirst()));
+            List<Pair<MobEffectInstance, Float>> list = item.getFoodProperties().getEffects();
+            Iterator iterator = list.iterator();
+
+            while (iterator.hasNext()) {
+                Pair<MobEffectInstance, Float> pair = (Pair) iterator.next();
+
+                if (!world.isClientSide && pair.getFirst() != null && world.random.nextFloat() < (Float) pair.getSecond()) {
+                    targetEntity.addEffect(new MobEffectInstance((MobEffectInstance) pair.getFirst()), EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit
                 }
             }
         }
@@ -3284,7 +4346,7 @@
     }
 
     public void broadcastBreakEvent(EquipmentSlot slot) {
-        this.level.broadcastEntityEvent(this, entityEventForEquipmentBreak(slot));
+        this.level.broadcastEntityEvent(this, LivingEntity.entityEventForEquipmentBreak(slot));
     }
 
     public void broadcastBreakEvent(InteractionHand hand) {
@@ -3295,6 +4357,7 @@
     public AABB getBoundingBoxForCulling() {
         if (this.getItemBySlot(EquipmentSlot.HEAD).is(Items.DRAGON_HEAD)) {
             float f = 0.5F;
+
             return this.getBoundingBox().inflate(0.5D, 0.5D, 0.5D);
         } else {
             return super.getBoundingBoxForCulling();
@@ -3303,46 +4366,26 @@
 
     public static EquipmentSlot getEquipmentSlotForItem(ItemStack stack) {
         Item item = stack.getItem();
-        if (!stack.is(Items.CARVED_PUMPKIN) && (!(item instanceof BlockItem) || !(((BlockItem)item).getBlock() instanceof AbstractSkullBlock))) {
-            if (item instanceof ArmorItem) {
-                return ((ArmorItem)item).getSlot();
-            } else if (stack.is(Items.ELYTRA)) {
-                return EquipmentSlot.CHEST;
-            } else {
-                return stack.is(Items.SHIELD) ? EquipmentSlot.OFFHAND : EquipmentSlot.MAINHAND;
-            }
-        } else {
-            return EquipmentSlot.HEAD;
-        }
+
+        return !stack.is(Items.CARVED_PUMPKIN) && (!(item instanceof BlockItem) || !(((BlockItem) item).getBlock() instanceof AbstractSkullBlock)) ? (item instanceof ArmorItem ? ((ArmorItem) item).getSlot() : (stack.is(Items.ELYTRA) ? EquipmentSlot.CHEST : (stack.is(Items.SHIELD) ? EquipmentSlot.OFFHAND : EquipmentSlot.MAINHAND))) : EquipmentSlot.HEAD;
     }
 
     private static SlotAccess createEquipmentSlotAccess(LivingEntity entity, EquipmentSlot slot) {
-        return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND ? SlotAccess.forEquipmentSlot(entity, slot, (stack) -> {
-            return stack.isEmpty() || Mob.getEquipmentSlotForItem(stack) == slot;
+        return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND ? SlotAccess.forEquipmentSlot(entity, slot, (itemstack) -> {
+            return itemstack.isEmpty() || Mob.getEquipmentSlotForItem(itemstack) == slot;
         }) : SlotAccess.forEquipmentSlot(entity, slot);
     }
 
     @Nullable
     private static EquipmentSlot getEquipmentSlot(int slotId) {
-        if (slotId == 100 + EquipmentSlot.HEAD.getIndex()) {
-            return EquipmentSlot.HEAD;
-        } else if (slotId == 100 + EquipmentSlot.CHEST.getIndex()) {
-            return EquipmentSlot.CHEST;
-        } else if (slotId == 100 + EquipmentSlot.LEGS.getIndex()) {
-            return EquipmentSlot.LEGS;
-        } else if (slotId == 100 + EquipmentSlot.FEET.getIndex()) {
-            return EquipmentSlot.FEET;
-        } else if (slotId == 98) {
-            return EquipmentSlot.MAINHAND;
-        } else {
-            return slotId == 99 ? EquipmentSlot.OFFHAND : null;
-        }
+        return slotId == 100 + EquipmentSlot.HEAD.getIndex() ? EquipmentSlot.HEAD : (slotId == 100 + EquipmentSlot.CHEST.getIndex() ? EquipmentSlot.CHEST : (slotId == 100 + EquipmentSlot.LEGS.getIndex() ? EquipmentSlot.LEGS : (slotId == 100 + EquipmentSlot.FEET.getIndex() ? EquipmentSlot.FEET : (slotId == 98 ? EquipmentSlot.MAINHAND : (slotId == 99 ? EquipmentSlot.OFFHAND : null)))));
     }
 
     @Override
     public SlotAccess getSlot(int mappedIndex) {
-        EquipmentSlot equipmentSlot = getEquipmentSlot(mappedIndex);
-        return equipmentSlot != null ? createEquipmentSlotAccess(this, equipmentSlot) : super.getSlot(mappedIndex);
+        EquipmentSlot enumitemslot = LivingEntity.getEquipmentSlot(mappedIndex);
+
+        return enumitemslot != null ? LivingEntity.createEquipmentSlotAccess(this, enumitemslot) : super.getSlot(mappedIndex);
     }
 
     @Override
@@ -3350,8 +4393,9 @@
         if (this.isSpectator()) {
             return false;
         } else {
-            boolean bl = !this.getItemBySlot(EquipmentSlot.HEAD).is(ItemTags.FREEZE_IMMUNE_WEARABLES) && !this.getItemBySlot(EquipmentSlot.CHEST).is(ItemTags.FREEZE_IMMUNE_WEARABLES) && !this.getItemBySlot(EquipmentSlot.LEGS).is(ItemTags.FREEZE_IMMUNE_WEARABLES) && !this.getItemBySlot(EquipmentSlot.FEET).is(ItemTags.FREEZE_IMMUNE_WEARABLES);
-            return bl && super.canFreeze();
+            boolean flag = !this.getItemBySlot(EquipmentSlot.HEAD).is(ItemTags.FREEZE_IMMUNE_WEARABLES) && !this.getItemBySlot(EquipmentSlot.CHEST).is(ItemTags.FREEZE_IMMUNE_WEARABLES) && !this.getItemBySlot(EquipmentSlot.LEGS).is(ItemTags.FREEZE_IMMUNE_WEARABLES) && !this.getItemBySlot(EquipmentSlot.FEET).is(ItemTags.FREEZE_IMMUNE_WEARABLES);
+
+            return flag && super.canFreeze();
         }
     }
 
@@ -3367,19 +4411,20 @@
 
     @Override
     public void recreateFromPacket(ClientboundAddEntityPacket packet) {
-        double d = packet.getX();
-        double e = packet.getY();
-        double f = packet.getZ();
-        float g = packet.getYRot();
-        float h = packet.getXRot();
-        this.syncPacketPositionCodec(d, e, f);
+        double d0 = packet.getX();
+        double d1 = packet.getY();
+        double d2 = packet.getZ();
+        float f = packet.getYRot();
+        float f1 = packet.getXRot();
+
+        this.syncPacketPositionCodec(d0, d1, d2);
         this.yBodyRot = packet.getYHeadRot();
         this.yHeadRot = packet.getYHeadRot();
         this.yBodyRotO = this.yBodyRot;
         this.yHeadRotO = this.yHeadRot;
         this.setId(packet.getId());
         this.setUUID(packet.getUUID());
-        this.absMoveTo(d, e, f, g, h);
+        this.absMoveTo(d0, d1, d2, f, f1);
         this.setDeltaMovement(packet.getXa(), packet.getYa(), packet.getZa());
     }
 
@@ -3388,5 +4433,6 @@
     }
 
     public static record Fallsounds(SoundEvent small, SoundEvent big) {
+
     }
 }
