--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1,18 +1,21 @@
 package net.minecraft.world.entity;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.UnmodifiableIterator;
 import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.doubles.DoubleList;
+import it.unimi.dsi.fastutil.doubles.DoubleListIterator;
 import it.unimi.dsi.fastutil.floats.FloatArraySet;
 import it.unimi.dsi.fastutil.floats.FloatArrays;
-import it.unimi.dsi.fastutil.floats.FloatSet;
 import it.unimi.dsi.fastutil.objects.Object2DoubleArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -45,12 +48,15 @@
 import net.minecraft.nbt.FloatTag;
 import net.minecraft.nbt.ListTag;
 import net.minecraft.nbt.StringTag;
+import net.minecraft.network.chat.ClickEvent;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.chat.HoverEvent;
 import net.minecraft.network.chat.MutableComponent;
 import net.minecraft.network.protocol.Packet;
 import net.minecraft.network.protocol.game.ClientGamePacketListener;
 import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
+import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
+import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
 import net.minecraft.network.protocol.game.VecDeltaCodec;
 import net.minecraft.network.syncher.EntityDataAccessor;
 import net.minecraft.network.syncher.EntityDataSerializers;
@@ -126,8 +132,157 @@
 import net.minecraft.world.scores.ScoreHolder;
 import net.minecraft.world.scores.Team;
 import org.slf4j.Logger;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Server;
+import org.bukkit.block.BlockFace;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Hanging;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Vehicle;
+import org.bukkit.event.entity.EntityCombustByEntityEvent;
+import org.bukkit.event.hanging.HangingBreakByEntityEvent;
+import org.bukkit.event.vehicle.VehicleBlockCollisionEvent;
+import org.bukkit.event.vehicle.VehicleEnterEvent;
+import org.bukkit.event.vehicle.VehicleExitEvent;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.entity.CraftEntity;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.craftbukkit.event.CraftPortalEvent;
+import org.bukkit.craftbukkit.util.CraftLocation;
+import org.bukkit.entity.Pose;
+import org.bukkit.event.entity.EntityAirChangeEvent;
+import org.bukkit.event.entity.EntityCombustEvent;
+import org.bukkit.event.entity.EntityDismountEvent;
+import org.bukkit.event.entity.EntityDropItemEvent;
+import org.bukkit.event.entity.EntityMountEvent;
+import org.bukkit.event.entity.EntityPortalEvent;
+import org.bukkit.event.entity.EntityPoseChangeEvent;
+import org.bukkit.event.entity.EntityRemoveEvent;
+import org.bukkit.event.entity.EntityTeleportEvent;
+import org.bukkit.event.entity.EntityUnleashEvent;
+import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.plugin.PluginManager;
+// CraftBukkit end
+
+public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, CommandSource, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker
+
+    // CraftBukkit start
+    private static final int CURRENT_LEVEL = 2;
+    public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation
+    static boolean isLevelAtLeast(CompoundTag tag, int level) {
+        return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level;
+    }
+
+    // Paper start - Share random for entities to make them more random
+    public static RandomSource SHARED_RANDOM = new RandomRandomSource();
+    private static final class RandomRandomSource extends java.util.Random implements net.minecraft.world.level.levelgen.BitRandomSource {
+        private boolean locked = false;
+
+        @Override
+        public synchronized void setSeed(long seed) {
+            if (locked) {
+                LOGGER.error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable());
+            } else {
+                super.setSeed(seed);
+                locked = true;
+            }
+        }
+
+        @Override
+        public RandomSource fork() {
+            return new net.minecraft.world.level.levelgen.LegacyRandomSource(this.nextLong());
+        }
+
+        @Override
+        public net.minecraft.world.level.levelgen.PositionalRandomFactory forkPositional() {
+            return new net.minecraft.world.level.levelgen.LegacyRandomSource.LegacyPositionalRandomFactory(this.nextLong());
+        }
+
+        // these below are added to fix reobf issues that I don't wanna deal with right now
+        @Override
+        public int next(int bits) {
+            return super.next(bits);
+        }
+
+        @Override
+        public int nextInt(int origin, int bound) {
+            return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(origin, bound);
+        }
+
+        @Override
+        public long nextLong() {
+            return net.minecraft.world.level.levelgen.BitRandomSource.super.nextLong();
+        }
+
+        @Override
+        public int nextInt() {
+            return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt();
+        }
+
+        @Override
+        public int nextInt(int bound) {
+            return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(bound);
+        }
+
+        @Override
+        public boolean nextBoolean() {
+            return net.minecraft.world.level.levelgen.BitRandomSource.super.nextBoolean();
+        }
+
+        @Override
+        public float nextFloat() {
+            return net.minecraft.world.level.levelgen.BitRandomSource.super.nextFloat();
+        }
+
+        @Override
+        public double nextDouble() {
+            return net.minecraft.world.level.levelgen.BitRandomSource.super.nextDouble();
+        }
+
+        @Override
+        public double nextGaussian() {
+            return super.nextGaussian();
+        }
+    }
+    // Paper end - Share random for entities to make them more random
+    public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
+
+    public boolean collisionLoadChunks = false; // Paper
+    private CraftEntity bukkitEntity;
+
+    public CraftEntity getBukkitEntity() {
+        if (this.bukkitEntity == null) {
+            // Paper start - Folia schedulers
+            synchronized (this) {
+                if (this.bukkitEntity == null) {
+                    return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this);
+                }
+            }
+            // Paper end - Folia schedulers
+        }
+        return this.bukkitEntity;
+    }
+
+    // Paper start
+    public CraftEntity getBukkitEntityRaw() {
+        return this.bukkitEntity;
+    }
+    // Paper end
+
+    @Override
+    public CommandSender getBukkitSender(CommandSourceStack wrapper) {
+        return this.getBukkitEntity();
+    }
+
+    // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+    public int getDefaultMaxAirSupply() {
+        return Entity.TOTAL_AIR_SUPPLY;
+    }
+    // CraftBukkit end
 
-public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, CommandSource, ScoreHolder {
     private static final Logger LOGGER = LogUtils.getLogger();
     public static final String ID_TAG = "id";
     public static final String PASSENGERS_TAG = "Passengers";
@@ -137,21 +292,21 @@
     public static final int TOTAL_AIR_SUPPLY = 300;
     public static final int MAX_ENTITY_TAG_COUNT = 1024;
     public static final float DELTA_AFFECTED_BY_BLOCKS_BELOW_0_2 = 0.2F;
-    public static final double DELTA_AFFECTED_BY_BLOCKS_BELOW_0_5 = 0.500001;
-    public static final double DELTA_AFFECTED_BY_BLOCKS_BELOW_1_0 = 0.999999;
+    public static final double DELTA_AFFECTED_BY_BLOCKS_BELOW_0_5 = 0.500001D;
+    public static final double DELTA_AFFECTED_BY_BLOCKS_BELOW_1_0 = 0.999999D;
     public static final int BASE_TICKS_REQUIRED_TO_FREEZE = 140;
     public static final int FREEZE_HURT_FREQUENCY = 40;
     public static final int BASE_SAFE_FALL_DISTANCE = 3;
-    private static final AABB INITIAL_AABB = new AABB(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-    private static final double WATER_FLOW_SCALE = 0.014;
-    private static final double LAVA_FAST_FLOW_SCALE = 0.007;
-    private static final double LAVA_SLOW_FLOW_SCALE = 0.0023333333333333335;
+    private static final AABB INITIAL_AABB = new AABB(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D);
+    private static final double WATER_FLOW_SCALE = 0.014D;
+    private static final double LAVA_FAST_FLOW_SCALE = 0.007D;
+    private static final double LAVA_SLOW_FLOW_SCALE = 0.0023333333333333335D;
     public static final String UUID_TAG = "UUID";
-    private static double viewScale = 1.0;
+    private static double viewScale = 1.0D;
     private final EntityType<?> type;
-    private int id = ENTITY_COUNTER.incrementAndGet();
+    private int id;
     public boolean blocksBuilding;
-    public ImmutableList<Entity> passengers = ImmutableList.of();
+    public ImmutableList<Entity> passengers;
     protected int boardingCooldown;
     @Nullable
     private Entity vehicle;
@@ -162,19 +317,19 @@
     private Vec3 position;
     private BlockPos blockPosition;
     private ChunkPos chunkPosition;
-    private Vec3 deltaMovement = Vec3.ZERO;
+    private Vec3 deltaMovement;
     private float yRot;
     private float xRot;
     public float yRotO;
     public float xRotO;
-    private AABB bb = INITIAL_AABB;
+    private AABB bb;
     public boolean onGround;
     public boolean horizontalCollision;
     public boolean verticalCollision;
     public boolean verticalCollisionBelow;
     public boolean minorHorizontalCollision;
     public boolean hurtMarked;
-    protected Vec3 stuckSpeedMultiplier = Vec3.ZERO;
+    protected Vec3 stuckSpeedMultiplier;
     @Nullable
     private Entity.RemovalReason removalReason;
     public static final float DEFAULT_BB_WIDTH = 0.6F;
@@ -184,20 +339,20 @@
     public float moveDist;
     public float flyDist;
     public float fallDistance;
-    private float nextStep = 1.0F;
+    private float nextStep;
     public double xOld;
     public double yOld;
     public double zOld;
     public boolean noPhysics;
-    public final RandomSource random = RandomSource.create();
+    public final RandomSource random;
     public int tickCount;
-    private int remainingFireTicks = -this.getFireImmuneTicks();
+    private int remainingFireTicks;
     public boolean wasTouchingWater;
-    protected Object2DoubleMap<TagKey<Fluid>> fluidHeight = new Object2DoubleArrayMap<>(2);
+    protected Object2DoubleMap<TagKey<Fluid>> fluidHeight;
     protected boolean wasEyeInWater;
-    private final Set<TagKey<Fluid>> fluidOnEyes = new HashSet<>();
+    private final Set<TagKey<Fluid>> fluidOnEyes;
     public int invulnerableTime;
-    protected boolean firstTick = true;
+    protected boolean firstTick;
     protected final SynchedEntityData entityData;
     protected static final EntityDataAccessor<Byte> DATA_SHARED_FLAGS_ID = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BYTE);
     protected static final int FLAG_ONFIRE = 0;
@@ -208,72 +363,254 @@
     protected static final int FLAG_GLOWING = 6;
     protected static final int FLAG_FALL_FLYING = 7;
     private static final EntityDataAccessor<Integer> DATA_AIR_SUPPLY_ID = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT);
-    private static final EntityDataAccessor<Optional<Component>> DATA_CUSTOM_NAME = SynchedEntityData.defineId(
-        Entity.class, EntityDataSerializers.OPTIONAL_COMPONENT
-    );
+    private static final EntityDataAccessor<Optional<Component>> DATA_CUSTOM_NAME = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.OPTIONAL_COMPONENT);
     private static final EntityDataAccessor<Boolean> DATA_CUSTOM_NAME_VISIBLE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
     private static final EntityDataAccessor<Boolean> DATA_SILENT = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
     private static final EntityDataAccessor<Boolean> DATA_NO_GRAVITY = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
-    protected static final EntityDataAccessor<Pose> DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE);
+    protected static final EntityDataAccessor<net.minecraft.world.entity.Pose> DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE);
     private static final EntityDataAccessor<Integer> DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT);
-    private EntityInLevelCallback levelCallback = EntityInLevelCallback.NULL;
-    private final VecDeltaCodec packetPositionCodec = new VecDeltaCodec();
+    private EntityInLevelCallback levelCallback;
+    private final VecDeltaCodec packetPositionCodec;
     public boolean noCulling;
     public boolean hasImpulse;
     @Nullable
     public PortalProcessor portalProcess;
     public int portalCooldown;
     private boolean invulnerable;
-    protected UUID uuid = Mth.createInsecureUUID(this.random);
-    protected String stringUUID = this.uuid.toString();
+    protected UUID uuid;
+    protected String stringUUID;
     private boolean hasGlowingTag;
-    private final Set<String> tags = Sets.newHashSet();
-    private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0};
+    private final Set<String> tags;
+    private final double[] pistonDeltas;
     private long pistonDeltasGameTime;
     private EntityDimensions dimensions;
     private float eyeHeight;
     public boolean isInPowderSnow;
     public boolean wasInPowderSnow;
     public boolean wasOnFire;
-    public Optional<BlockPos> mainSupportingBlockPos = Optional.empty();
-    private boolean onGroundNoBlocks = false;
+    public Optional<BlockPos> mainSupportingBlockPos;
+    private boolean onGroundNoBlocks;
     private float crystalSoundIntensity;
     private int lastCrystalSoundPlayTick;
     public boolean hasVisualFire;
     @Nullable
-    private BlockState inBlockState = null;
+    private BlockState inBlockState;
+    // CraftBukkit start
+    public boolean forceDrops;
+    public boolean persist = true;
+    public boolean visibleByDefault = true;
+    public boolean valid;
+    public boolean inWorld = false;
+    public boolean generation;
+    public int maxAirTicks = this.getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+    @Nullable // Paper - Refresh ProjectileSource for projectiles
+    public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only
+    public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled
+    public boolean persistentInvisibility = false;
+    public BlockPos lastLavaContact;
+    // Marks an entity, that it was removed by a plugin via Entity#remove
+    // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed
+    public boolean pluginRemoved = false;
+    // Spigot start
+    public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this);
+    public final boolean defaultActivationState;
+    public long activatedTick = Integer.MIN_VALUE;
+    public void inactiveTick() { }
+    // Spigot end
+    protected int numCollisions = 0; // Paper - Cap entity collisions
+    public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals
+    public long activatedImmunityTick = Integer.MIN_VALUE; // Paper - EAR
+    public boolean isTemporarilyActive; // Paper - EAR
+    public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one
+    // Paper start - Entity origin API
+    @javax.annotation.Nullable
+    private org.bukkit.util.Vector origin;
+    @javax.annotation.Nullable
+    private UUID originWorld;
+    public boolean freezeLocked = false; // Paper - Freeze Tick Lock API
+    public boolean fixedPose = false; // Paper - Expand Pose API
+
+    public void setOrigin(@javax.annotation.Nonnull Location location) {
+        this.origin = location.toVector();
+        this.originWorld = location.getWorld().getUID();
+    }
+
+    @javax.annotation.Nullable
+    public org.bukkit.util.Vector getOriginVector() {
+        return this.origin != null ? this.origin.clone() : null;
+    }
+
+    @javax.annotation.Nullable
+    public UUID getOriginWorld() {
+        return this.originWorld;
+    }
+    // Paper end - Entity origin API
+    public float getBukkitYaw() {
+        return this.yRot;
+    }
+
+    public boolean isChunkLoaded() {
+        return this.level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4);
+    }
+    // CraftBukkit end
+    // Paper start
+    public final AABB getBoundingBoxAt(double x, double y, double z) {
+        return this.dimensions.makeBoundingBox(x, y, z);
+    }
+    // Paper end
+    // Paper start - rewrite chunk system
+    private final boolean isHardColliding = this.moonrise$isHardCollidingUncached();
+    private net.minecraft.server.level.FullChunkStatus chunkStatus;
+    private int sectionX = Integer.MIN_VALUE;
+    private int sectionY = Integer.MIN_VALUE;
+    private int sectionZ = Integer.MIN_VALUE;
+    private boolean updatingSectionStatus;
+
+    @Override
+    public final boolean moonrise$isHardColliding() {
+        return this.isHardColliding;
+    }
+
+    @Override
+    public final net.minecraft.server.level.FullChunkStatus moonrise$getChunkStatus() {
+        return this.chunkStatus;
+    }
+
+    @Override
+    public final void moonrise$setChunkStatus(final net.minecraft.server.level.FullChunkStatus status) {
+        this.chunkStatus = status;
+    }
+
+    @Override
+    public final int moonrise$getSectionX() {
+        return this.sectionX;
+    }
+
+    @Override
+    public final void moonrise$setSectionX(final int x) {
+        this.sectionX = x;
+    }
+
+    @Override
+    public final int moonrise$getSectionY() {
+        return this.sectionY;
+    }
+
+    @Override
+    public final void moonrise$setSectionY(final int y) {
+        this.sectionY = y;
+    }
+
+    @Override
+    public final int moonrise$getSectionZ() {
+        return this.sectionZ;
+    }
+
+    @Override
+    public final void moonrise$setSectionZ(final int z) {
+        this.sectionZ = z;
+    }
+
+    @Override
+    public final boolean moonrise$isUpdatingSectionStatus() {
+        return this.updatingSectionStatus;
+    }
+
+    @Override
+    public final void moonrise$setUpdatingSectionStatus(final boolean to) {
+        this.updatingSectionStatus = to;
+    }
+
+    @Override
+    public final boolean moonrise$hasAnyPlayerPassengers() {
+        if (this.passengers.isEmpty()) {
+            return false;
+        }
+        return this.getIndirectPassengersStream().anyMatch((entity) -> entity instanceof Player);
+    }
+    // Paper end - rewrite chunk system
+    // Paper start - optimise entity tracker
+    private net.minecraft.server.level.ChunkMap.TrackedEntity trackedEntity;
+
+    @Override
+    public final net.minecraft.server.level.ChunkMap.TrackedEntity moonrise$getTrackedEntity() {
+        return this.trackedEntity;
+    }
+
+    @Override
+    public final void moonrise$setTrackedEntity(final net.minecraft.server.level.ChunkMap.TrackedEntity trackedEntity) {
+        this.trackedEntity = trackedEntity;
+    }
+
+    private static void collectIndirectPassengers(final List<Entity> into, final List<Entity> from) {
+        for (final Entity passenger : from) {
+            into.add(passenger);
+            collectIndirectPassengers(into, ((Entity)(Object)passenger).passengers);
+        }
+    }
+    // Paper end - optimise entity tracker
 
     public Entity(EntityType<?> type, Level world) {
+        this.id = Entity.ENTITY_COUNTER.incrementAndGet();
+        this.passengers = ImmutableList.of();
+        this.deltaMovement = Vec3.ZERO;
+        this.bb = Entity.INITIAL_AABB;
+        this.stuckSpeedMultiplier = Vec3.ZERO;
+        this.nextStep = 1.0F;
+        this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
+        this.remainingFireTicks = -this.getFireImmuneTicks();
+        this.fluidHeight = new Object2DoubleArrayMap(2);
+        this.fluidOnEyes = new HashSet();
+        this.firstTick = true;
+        this.levelCallback = EntityInLevelCallback.NULL;
+        this.packetPositionCodec = new VecDeltaCodec();
+        this.uuid = Mth.createInsecureUUID(this.random);
+        this.stringUUID = this.uuid.toString();
+        this.tags = Sets.newHashSet();
+        this.pistonDeltas = new double[]{0.0D, 0.0D, 0.0D};
+        this.mainSupportingBlockPos = Optional.empty();
+        this.onGroundNoBlocks = false;
+        this.inBlockState = null;
         this.type = type;
         this.level = world;
         this.dimensions = type.getDimensions();
         this.position = Vec3.ZERO;
         this.blockPosition = BlockPos.ZERO;
         this.chunkPosition = ChunkPos.ZERO;
-        SynchedEntityData.Builder builder = new SynchedEntityData.Builder(this);
-        builder.define(DATA_SHARED_FLAGS_ID, (byte)0);
-        builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
-        builder.define(DATA_CUSTOM_NAME_VISIBLE, false);
-        builder.define(DATA_CUSTOM_NAME, Optional.empty());
-        builder.define(DATA_SILENT, false);
-        builder.define(DATA_NO_GRAVITY, false);
-        builder.define(DATA_POSE, Pose.STANDING);
-        builder.define(DATA_TICKS_FROZEN, 0);
-        this.defineSynchedData(builder);
-        this.entityData = builder.build();
-        this.setPos(0.0, 0.0, 0.0);
+        // Spigot start
+        if (world != null) {
+            this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig);
+        } else {
+            this.defaultActivationState = false;
+        }
+        // Spigot end
+        SynchedEntityData.Builder datawatcher_a = new SynchedEntityData.Builder(this);
+
+        datawatcher_a.define(Entity.DATA_SHARED_FLAGS_ID, (byte) 0);
+        datawatcher_a.define(Entity.DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
+        datawatcher_a.define(Entity.DATA_CUSTOM_NAME_VISIBLE, false);
+        datawatcher_a.define(Entity.DATA_CUSTOM_NAME, Optional.empty());
+        datawatcher_a.define(Entity.DATA_SILENT, false);
+        datawatcher_a.define(Entity.DATA_NO_GRAVITY, false);
+        datawatcher_a.define(Entity.DATA_POSE, net.minecraft.world.entity.Pose.STANDING);
+        datawatcher_a.define(Entity.DATA_TICKS_FROZEN, 0);
+        this.defineSynchedData(datawatcher_a);
+        this.entityData = datawatcher_a.build();
+        this.setPos(0.0D, 0.0D, 0.0D);
         this.eyeHeight = this.dimensions.eyeHeight();
     }
 
     public boolean isColliding(BlockPos pos, BlockState state) {
-        VoxelShape voxelShape = state.getCollisionShape(this.level(), pos, CollisionContext.of(this));
-        VoxelShape voxelShape2 = voxelShape.move((double)pos.getX(), (double)pos.getY(), (double)pos.getZ());
-        return Shapes.joinIsNotEmpty(voxelShape2, Shapes.create(this.getBoundingBox()), BooleanOp.AND);
+        VoxelShape voxelshape = state.getCollisionShape(this.level(), pos, CollisionContext.of(this));
+        VoxelShape voxelshape1 = voxelshape.move((double) pos.getX(), (double) pos.getY(), (double) pos.getZ());
+
+        return Shapes.joinIsNotEmpty(voxelshape1, Shapes.create(this.getBoundingBox()), BooleanOp.AND);
     }
 
     public int getTeamColor() {
-        Team team = this.getTeam();
-        return team != null && team.getColor().getColor() != null ? team.getColor().getColor() : 16777215;
+        PlayerTeam scoreboardteam = this.getTeam();
+
+        return scoreboardteam != null && scoreboardteam.getColor().getColor() != null ? scoreboardteam.getColor().getColor() : 16777215;
     }
 
     public boolean isSpectator() {
@@ -288,6 +625,7 @@
         if (this.isPassenger()) {
             this.stopRiding();
         }
+
     }
 
     public void syncPacketPositionCodec(double x, double y, double z) {
@@ -316,7 +654,7 @@
     }
 
     public boolean addTag(String tag) {
-        return this.tags.size() < 1024 && this.tags.add(tag);
+        return this.tags.size() >= 1024 ? false : this.tags.add(tag);
     }
 
     public boolean removeTag(String tag) {
@@ -324,12 +662,18 @@
     }
 
     public void kill() {
-        this.remove(Entity.RemovalReason.KILLED);
+        this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
         this.gameEvent(GameEvent.ENTITY_DIE);
     }
 
     public final void discard() {
-        this.remove(Entity.RemovalReason.DISCARDED);
+        // CraftBukkit start - add Bukkit remove cause
+        this.discard(null);
+    }
+
+    public final void discard(EntityRemoveEvent.Cause cause) {
+        this.remove(Entity.RemovalReason.DISCARDED, cause);
+        // CraftBukkit end
     }
 
     protected abstract void defineSynchedData(SynchedEntityData.Builder builder);
@@ -338,32 +682,88 @@
         return this.entityData;
     }
 
-    @Override
+    // CraftBukkit start
+    public void refreshEntityData(ServerPlayer to) {
+        List<SynchedEntityData.DataValue<?>> list = this.entityData.packAll(); // Paper - Update EVERYTHING not just not default
+
+        if (list != null && to.getBukkitEntity().canSee(this.getBukkitEntity())) { // Paper
+            to.connection.send(new ClientboundSetEntityDataPacket(this.getId(), list));
+        }
+    }
+    // CraftBukkit end
+    // Paper start
+    // This method should only be used if the data of an entity could have become desynced
+    // due to interactions on the client.
+    public void resendPossiblyDesyncedEntityData(net.minecraft.server.level.ServerPlayer player) {
+        if (player.getBukkitEntity().canSee(this.getBukkitEntity())) {
+            ServerLevel world = (net.minecraft.server.level.ServerLevel)this.level();
+            net.minecraft.server.level.ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.getId());
+            if (tracker == null) {
+                return;
+            }
+            final net.minecraft.server.level.ServerEntity serverEntity = tracker.serverEntity;
+            final List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> list = new java.util.ArrayList<>();
+            serverEntity.sendPairingData(player, list::add);
+            player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(list));
+        }
+    }
+
+    // This method allows you to specifically resend certain data accessor keys to the client
+    public void resendPossiblyDesyncedDataValues(List<EntityDataAccessor<?>> keys, ServerPlayer to) {
+        if (!to.getBukkitEntity().canSee(this.getBukkitEntity())) {
+            return;
+        }
+
+        final List<SynchedEntityData.DataValue<?>> values = new java.util.ArrayList<>(keys.size());
+        for (final EntityDataAccessor<?> key : keys) {
+            final SynchedEntityData.DataItem<?> synchedValue = this.entityData.getItem(key);
+            values.add(synchedValue.value());
+        }
+
+        to.connection.send(new ClientboundSetEntityDataPacket(this.id, values));
+    }
+    // Paper end
+
     public boolean equals(Object object) {
-        return object instanceof Entity && ((Entity)object).id == this.id;
+        return object instanceof Entity ? ((Entity) object).id == this.id : false;
     }
 
-    @Override
     public int hashCode() {
         return this.id;
     }
 
     public void remove(Entity.RemovalReason reason) {
-        this.setRemoved(reason);
+        // CraftBukkit start - add Bukkit remove cause
+        this.setRemoved(reason, null);
     }
 
-    public void onClientRemoval() {
+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
+        this.setRemoved(entity_removalreason, cause);
+        // CraftBukkit end
     }
 
-    public void setPose(Pose pose) {
-        this.entityData.set(DATA_POSE, pose);
+    public void onClientRemoval() {}
+
+    public void setPose(net.minecraft.world.entity.Pose pose) {
+        if (this.fixedPose) return; // Paper - Expand Pose API
+        // CraftBukkit start
+        if (pose == this.getPose()) {
+            return;
+        }
+        // Paper start - Don't fire sync event during generation
+        if (!this.generation) {
+            this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()]));
+        }
+        // Paper end - Don't fire sync event during generation
+        // CraftBukkit end
+        this.entityData.set(Entity.DATA_POSE, pose);
     }
 
-    public Pose getPose() {
-        return this.entityData.get(DATA_POSE);
+    public net.minecraft.world.entity.Pose getPose() {
+        return (net.minecraft.world.entity.Pose) this.entityData.get(Entity.DATA_POSE);
     }
 
-    public boolean hasPose(Pose pose) {
+    public boolean hasPose(net.minecraft.world.entity.Pose pose) {
         return this.getPose() == pose;
     }
 
@@ -372,13 +772,41 @@
     }
 
     public boolean closerThan(Entity entity, double horizontalRadius, double verticalRadius) {
-        double d = entity.getX() - this.getX();
-        double e = entity.getY() - this.getY();
-        double f = entity.getZ() - this.getZ();
-        return Mth.lengthSquared(d, f) < Mth.square(horizontalRadius) && Mth.square(e) < Mth.square(verticalRadius);
+        double d2 = entity.getX() - this.getX();
+        double d3 = entity.getY() - this.getY();
+        double d4 = entity.getZ() - this.getZ();
+
+        return Mth.lengthSquared(d2, d4) < Mth.square(horizontalRadius) && Mth.square(d3) < Mth.square(verticalRadius);
     }
 
     public void setRot(float yaw, float pitch) {
+        // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0
+        if (Float.isNaN(yaw)) {
+            yaw = 0;
+        }
+
+        if (yaw == Float.POSITIVE_INFINITY || yaw == Float.NEGATIVE_INFINITY) {
+            if (this instanceof ServerPlayer) {
+                this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid yaw");
+                ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)");
+            }
+            yaw = 0;
+        }
+
+        // pitch was sometimes set to NaN, so we need to set it back to 0
+        if (Float.isNaN(pitch)) {
+            pitch = 0;
+        }
+
+        if (pitch == Float.POSITIVE_INFINITY || pitch == Float.NEGATIVE_INFINITY) {
+            if (this instanceof ServerPlayer) {
+                this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid pitch");
+                ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)");
+            }
+            pitch = 0;
+        }
+        // CraftBukkit end
+
         this.setYRot(yaw % 360.0F);
         this.setXRot(pitch % 360.0F);
     }
@@ -388,8 +816,8 @@
     }
 
     public void setPos(double x, double y, double z) {
-        this.setPosRaw(x, y, z);
-        this.setBoundingBox(this.makeBoundingBox());
+        this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update
+        // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw
     }
 
     protected AABB makeBoundingBox() {
@@ -401,38 +829,50 @@
     }
 
     public void turn(double cursorDeltaX, double cursorDeltaY) {
-        float f = (float)cursorDeltaY * 0.15F;
-        float g = (float)cursorDeltaX * 0.15F;
+        float f = (float) cursorDeltaY * 0.15F;
+        float f1 = (float) cursorDeltaX * 0.15F;
+
         this.setXRot(this.getXRot() + f);
-        this.setYRot(this.getYRot() + g);
+        this.setYRot(this.getYRot() + f1);
         this.setXRot(Mth.clamp(this.getXRot(), -90.0F, 90.0F));
         this.xRotO += f;
-        this.yRotO += g;
+        this.yRotO += f1;
         this.xRotO = Mth.clamp(this.xRotO, -90.0F, 90.0F);
         if (this.vehicle != null) {
             this.vehicle.onPassengerTurned(this);
         }
+
     }
 
     public void tick() {
         this.baseTick();
     }
 
+    // CraftBukkit start
+    public void postTick() {
+        // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle
+        if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities
+            this.handlePortal();
+        }
+    }
+    // CraftBukkit end
+
     public void baseTick() {
         this.level().getProfiler().push("entityBaseTick");
+        if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups
         this.inBlockState = null;
         if (this.isPassenger() && this.getVehicle().isRemoved()) {
             this.stopRiding();
         }
 
         if (this.boardingCooldown > 0) {
-            this.boardingCooldown--;
+            --this.boardingCooldown;
         }
 
         this.walkDistO = this.walkDist;
         this.xRotO = this.getXRot();
         this.yRotO = this.getYRot();
-        this.handlePortal();
+        if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick
         if (this.canSpawnSprintParticle()) {
             this.spawnSprintParticle();
         }
@@ -458,15 +898,19 @@
                 this.setRemainingFireTicks(this.remainingFireTicks - 1);
             }
 
-            if (this.getTicksFrozen() > 0) {
+            if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API
                 this.setTicksFrozen(0);
-                this.level().levelEvent(null, 1009, this.blockPosition, 1);
+                this.level().levelEvent((Player) null, 1009, this.blockPosition, 1);
             }
         }
 
         if (this.isInLava()) {
             this.lavaHurt();
             this.fallDistance *= 0.5F;
+            // CraftBukkit start
+        } else {
+            this.lastLavaContact = null;
+            // CraftBukkit end
         }
 
         this.checkBelowWorld();
@@ -476,7 +920,7 @@
 
         this.firstTick = false;
         if (!this.level().isClientSide && this instanceof Leashable) {
-            Leashable.tickLeash((Entity)((Leashable)this));
+            Leashable.tickLeash((Entity & Leashable) this); // CraftBukkit - decompile error
         }
 
         this.level().getProfiler().pop();
@@ -487,9 +931,14 @@
     }
 
     public void checkBelowWorld() {
-        if (this.getY() < (double)(this.level().getMinBuildHeight() - 64)) {
+        // Paper start - Configurable nether ceiling damage
+        if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER
+            && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
+            && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
+            // Paper end - Configurable nether ceiling damage
             this.onBelowWorld();
         }
+
     }
 
     public void setPortalCooldown() {
@@ -510,27 +959,62 @@
 
     protected void processPortalCooldown() {
         if (this.isOnPortalCooldown()) {
-            this.portalCooldown--;
+            --this.portalCooldown;
         }
+
     }
 
     public void lavaHurt() {
         if (!this.fireImmune()) {
-            this.igniteForSeconds(15.0F);
-            if (this.hurt(this.damageSources().lava(), 4.0F)) {
+            // CraftBukkit start - Fallen in lava TODO: this event spams!
+            if (this instanceof net.minecraft.world.entity.LivingEntity && this.remainingFireTicks <= 0) {
+                // not on fire yet
+                org.bukkit.block.Block damager = (this.lastLavaContact == null) ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact);
+                org.bukkit.entity.Entity damagee = this.getBukkitEntity();
+                EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15);
+                this.level.getCraftServer().getPluginManager().callEvent(combustEvent);
+
+                if (!combustEvent.isCancelled()) {
+                    this.igniteForSeconds(combustEvent.getDuration(), false);
+                }
+            } else {
+                // This will be called every single tick the entity is in lava, so don't throw an event
+                this.igniteForSeconds(15.0F, false);
+            }
+
+            if (this.hurt(this.damageSources().lava().directBlock(this.level, this.lastLavaContact), 4.0F)) {
                 this.playSound(SoundEvents.GENERIC_BURN, 0.4F, 2.0F + this.random.nextFloat() * 0.4F);
             }
+            // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls
+
         }
     }
 
     public final void igniteForSeconds(float seconds) {
-        this.igniteForTicks(Mth.floor(seconds * 20.0F));
+        // CraftBukkit start
+        this.igniteForSeconds(seconds, true);
+    }
+
+    public final void igniteForSeconds(float f, boolean callEvent) {
+        if (callEvent) {
+            EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), f);
+            this.level.getCraftServer().getPluginManager().callEvent(event);
+
+            if (event.isCancelled()) {
+                return;
+            }
+
+            f = event.getDuration();
+        }
+        // CraftBukkit end
+        this.igniteForTicks(Mth.floor(f * 20.0F));
     }
 
     public void igniteForTicks(int ticks) {
         if (this.remainingFireTicks < ticks) {
             this.setRemainingFireTicks(ticks);
         }
+
     }
 
     public void setRemainingFireTicks(int fireTicks) {
@@ -546,7 +1030,7 @@
     }
 
     protected void onBelowWorld() {
-        this.discard();
+        this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause
     }
 
     public boolean isFree(double offsetX, double offsetY, double offsetZ) {
@@ -559,7 +1043,7 @@
 
     public void setOnGround(boolean onGround) {
         this.onGround = onGround;
-        this.checkSupportingBlock(onGround, null);
+        this.checkSupportingBlock(onGround, (Vec3) null);
     }
 
     public void setOnGroundWithMovement(boolean onGround, Vec3 movement) {
@@ -568,19 +1052,23 @@
     }
 
     public boolean isSupportedBy(BlockPos pos) {
-        return this.mainSupportingBlockPos.isPresent() && this.mainSupportingBlockPos.get().equals(pos);
+        return this.mainSupportingBlockPos.isPresent() && ((BlockPos) this.mainSupportingBlockPos.get()).equals(pos);
     }
 
     protected void checkSupportingBlock(boolean onGround, @Nullable Vec3 movement) {
         if (onGround) {
-            AABB aABB = this.getBoundingBox();
-            AABB aABB2 = new AABB(aABB.minX, aABB.minY - 1.0E-6, aABB.minZ, aABB.maxX, aABB.minY, aABB.maxZ);
-            Optional<BlockPos> optional = this.level.findSupportingBlock(this, aABB2);
-            if (optional.isPresent() || this.onGroundNoBlocks) {
-                this.mainSupportingBlockPos = optional;
-            } else if (movement != null) {
-                AABB aABB3 = aABB2.move(-movement.x, 0.0, -movement.z);
-                optional = this.level.findSupportingBlock(this, aABB3);
+            AABB axisalignedbb = this.getBoundingBox();
+            AABB axisalignedbb1 = new AABB(axisalignedbb.minX, axisalignedbb.minY - 1.0E-6D, axisalignedbb.minZ, axisalignedbb.maxX, axisalignedbb.minY, axisalignedbb.maxZ);
+            Optional<BlockPos> optional = this.level.findSupportingBlock(this, axisalignedbb1);
+
+            if (!optional.isPresent() && !this.onGroundNoBlocks) {
+                if (movement != null) {
+                    AABB axisalignedbb2 = axisalignedbb1.move(-movement.x, 0.0D, -movement.z);
+
+                    optional = this.level.findSupportingBlock(this, axisalignedbb2);
+                    this.mainSupportingBlockPos = optional;
+                }
+            } else {
                 this.mainSupportingBlockPos = optional;
             }
 
@@ -591,18 +1079,57 @@
                 this.mainSupportingBlockPos = Optional.empty();
             }
         }
+
     }
 
     public boolean onGround() {
         return this.onGround;
     }
 
+    // Paper start - detailed watchdog information
+    public final Object posLock = new Object(); // Paper - log detailed entity tick information
+
+    private Vec3 moveVector;
+    private double moveStartX;
+    private double moveStartY;
+    private double moveStartZ;
+
+    public final Vec3 getMoveVector() {
+        return this.moveVector;
+    }
+
+    public final double getMoveStartX() {
+        return this.moveStartX;
+    }
+
+    public final double getMoveStartY() {
+        return this.moveStartY;
+    }
+
+    public final double getMoveStartZ() {
+        return this.moveStartZ;
+    }
+    // Paper end - detailed watchdog information
+
     public void move(MoverType movementType, Vec3 movement) {
+        final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity
+        // Paper start - detailed watchdog information
+        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main");
+        synchronized (this.posLock) {
+            this.moveStartX = this.getX();
+            this.moveStartY = this.getY();
+            this.moveStartZ = this.getZ();
+            this.moveVector = movement;
+        }
+        try {
+        // Paper end - detailed watchdog information
         if (this.noPhysics) {
             this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
         } else {
             this.wasOnFire = this.isOnFire();
             if (movementType == MoverType.PISTON) {
+                this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper
+                this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20);   // Paper
                 movement = this.limitPistonMovement(movement);
                 if (movement.equals(Vec3.ZERO)) {
                     return;
@@ -610,108 +1137,145 @@
             }
 
             this.level().getProfiler().push("move");
-            if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7) {
+            if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) {
                 movement = movement.multiply(this.stuckSpeedMultiplier);
                 this.stuckSpeedMultiplier = Vec3.ZERO;
                 this.setDeltaMovement(Vec3.ZERO);
             }
+            // Paper start - ignore movement changes while inactive.
+            if (isTemporarilyActive && !(this instanceof ItemEntity) && movement == getDeltaMovement() && movementType == MoverType.SELF) {
+                setDeltaMovement(Vec3.ZERO);
+                this.level.getProfiler().pop();
+                return;
+            }
+            // Paper end
 
             movement = this.maybeBackOffFromEdge(movement, movementType);
-            Vec3 vec3 = this.collide(movement);
-            double d = vec3.lengthSqr();
-            if (d > 1.0E-7) {
-                if (this.fallDistance != 0.0F && d >= 1.0) {
-                    BlockHitResult blockHitResult = this.level()
-                        .clip(
-                            new ClipContext(this.position(), this.position().add(vec3), ClipContext.Block.FALLDAMAGE_RESETTING, ClipContext.Fluid.WATER, this)
-                        );
-                    if (blockHitResult.getType() != HitResult.Type.MISS) {
+            Vec3 vec3d1 = this.collide(movement);
+            double d0 = vec3d1.lengthSqr();
+
+            if (d0 > 1.0E-7D) {
+                if (this.fallDistance != 0.0F && d0 >= 1.0D) {
+                    BlockHitResult movingobjectpositionblock = this.level().clip(new ClipContext(this.position(), this.position().add(vec3d1), ClipContext.Block.FALLDAMAGE_RESETTING, ClipContext.Fluid.WATER, this));
+
+                    if (movingobjectpositionblock.getType() != HitResult.Type.MISS) {
                         this.resetFallDistance();
                     }
                 }
 
-                this.setPos(this.getX() + vec3.x, this.getY() + vec3.y, this.getZ() + vec3.z);
+                this.setPos(this.getX() + vec3d1.x, this.getY() + vec3d1.y, this.getZ() + vec3d1.z);
             }
 
             this.level().getProfiler().pop();
             this.level().getProfiler().push("rest");
-            boolean bl = !Mth.equal(movement.x, vec3.x);
-            boolean bl2 = !Mth.equal(movement.z, vec3.z);
-            this.horizontalCollision = bl || bl2;
-            this.verticalCollision = movement.y != vec3.y;
-            this.verticalCollisionBelow = this.verticalCollision && movement.y < 0.0;
+            boolean flag = !Mth.equal(movement.x, vec3d1.x);
+            boolean flag1 = !Mth.equal(movement.z, vec3d1.z);
+
+            this.horizontalCollision = flag || flag1;
+            this.verticalCollision = movement.y != vec3d1.y;
+            this.verticalCollisionBelow = this.verticalCollision && movement.y < 0.0D;
             if (this.horizontalCollision) {
-                this.minorHorizontalCollision = this.isHorizontalCollisionMinor(vec3);
+                this.minorHorizontalCollision = this.isHorizontalCollisionMinor(vec3d1);
             } else {
                 this.minorHorizontalCollision = false;
             }
 
-            this.setOnGroundWithMovement(this.verticalCollisionBelow, vec3);
-            BlockPos blockPos = this.getOnPosLegacy();
-            BlockState blockState = this.level().getBlockState(blockPos);
-            this.checkFallDamage(vec3.y, this.onGround(), blockState, blockPos);
+            this.setOnGroundWithMovement(this.verticalCollisionBelow, vec3d1);
+            BlockPos blockposition = this.getOnPosLegacy();
+            BlockState iblockdata = this.level().getBlockState(blockposition);
+
+            this.checkFallDamage(vec3d1.y, this.onGround(), iblockdata, blockposition);
             if (this.isRemoved()) {
                 this.level().getProfiler().pop();
             } else {
                 if (this.horizontalCollision) {
-                    Vec3 vec32 = this.getDeltaMovement();
-                    this.setDeltaMovement(bl ? 0.0 : vec32.x, vec32.y, bl2 ? 0.0 : vec32.z);
+                    Vec3 vec3d2 = this.getDeltaMovement();
+
+                    this.setDeltaMovement(flag ? 0.0D : vec3d2.x, vec3d2.y, flag1 ? 0.0D : vec3d2.z);
                 }
 
-                Block block = blockState.getBlock();
-                if (movement.y != vec3.y) {
+                Block block = iblockdata.getBlock();
+
+                if (movement.y != vec3d1.y) {
                     block.updateEntityAfterFallOn(this.level(), this);
                 }
 
+                // CraftBukkit start
+                if (this.horizontalCollision && this.getBukkitEntity() instanceof Vehicle) {
+                    Vehicle vehicle = (Vehicle) this.getBukkitEntity();
+                    org.bukkit.block.Block bl = this.level.getWorld().getBlockAt(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()));
+
+                    if (movement.x > vec3d1.x) {
+                        bl = bl.getRelative(BlockFace.EAST);
+                    } else if (movement.x < vec3d1.x) {
+                        bl = bl.getRelative(BlockFace.WEST);
+                    } else if (movement.z > vec3d1.z) {
+                        bl = bl.getRelative(BlockFace.SOUTH);
+                    } else if (movement.z < vec3d1.z) {
+                        bl = bl.getRelative(BlockFace.NORTH);
+                    }
+
+                    if (!bl.getType().isAir()) {
+                        VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity
+                        this.level.getCraftServer().getPluginManager().callEvent(event);
+                    }
+                }
+                // CraftBukkit end
+
                 if (this.onGround()) {
-                    block.stepOn(this.level(), blockPos, blockState, this);
+                    block.stepOn(this.level(), blockposition, iblockdata, this);
                 }
 
-                Entity.MovementEmission movementEmission = this.getMovementEmission();
-                if (movementEmission.emitsAnything() && !this.isPassenger()) {
-                    double e = vec3.x;
-                    double f = vec3.y;
-                    double g = vec3.z;
-                    this.flyDist = this.flyDist + (float)(vec3.length() * 0.6);
-                    BlockPos blockPos2 = this.getOnPos();
-                    BlockState blockState2 = this.level().getBlockState(blockPos2);
-                    boolean bl3 = this.isStateClimbable(blockState2);
-                    if (!bl3) {
-                        f = 0.0;
+                Entity.MovementEmission entity_movementemission = this.getMovementEmission();
+
+                if (entity_movementemission.emitsAnything() && !this.isPassenger()) {
+                    double d1 = vec3d1.x;
+                    double d2 = vec3d1.y;
+                    double d3 = vec3d1.z;
+
+                    this.flyDist += (float) (vec3d1.length() * 0.6D);
+                    BlockPos blockposition1 = this.getOnPos();
+                    BlockState iblockdata1 = this.level().getBlockState(blockposition1);
+                    boolean flag2 = this.isStateClimbable(iblockdata1);
+
+                    if (!flag2) {
+                        d2 = 0.0D;
                     }
 
-                    this.walkDist = this.walkDist + (float)vec3.horizontalDistance() * 0.6F;
-                    this.moveDist = this.moveDist + (float)Math.sqrt(e * e + f * f + g * g) * 0.6F;
-                    if (this.moveDist > this.nextStep && !blockState2.isAir()) {
-                        boolean bl4 = blockPos2.equals(blockPos);
-                        boolean bl5 = this.vibrationAndSoundEffectsFromBlock(blockPos, blockState, movementEmission.emitsSounds(), bl4, movement);
-                        if (!bl4) {
-                            bl5 |= this.vibrationAndSoundEffectsFromBlock(blockPos2, blockState2, false, movementEmission.emitsEvents(), movement);
+                    this.walkDist += (float) vec3d1.horizontalDistance() * 0.6F;
+                    this.moveDist += (float) Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3) * 0.6F;
+                    if (this.moveDist > this.nextStep && !iblockdata1.isAir()) {
+                        boolean flag3 = blockposition1.equals(blockposition);
+                        boolean flag4 = this.vibrationAndSoundEffectsFromBlock(blockposition, iblockdata, entity_movementemission.emitsSounds(), flag3, movement);
+
+                        if (!flag3) {
+                            flag4 |= this.vibrationAndSoundEffectsFromBlock(blockposition1, iblockdata1, false, entity_movementemission.emitsEvents(), movement);
                         }
 
-                        if (bl5) {
+                        if (flag4) {
                             this.nextStep = this.nextStep();
                         } else if (this.isInWater()) {
                             this.nextStep = this.nextStep();
-                            if (movementEmission.emitsSounds()) {
+                            if (entity_movementemission.emitsSounds()) {
                                 this.waterSwimSound();
                             }
 
-                            if (movementEmission.emitsEvents()) {
+                            if (entity_movementemission.emitsEvents()) {
                                 this.gameEvent(GameEvent.SWIM);
                             }
                         }
-                    } else if (blockState2.isAir()) {
+                    } else if (iblockdata1.isAir()) {
                         this.processFlappingMovement();
                     }
                 }
 
                 this.tryCheckInsideBlocks();
-                float h = this.getBlockSpeedFactor();
-                this.setDeltaMovement(this.getDeltaMovement().multiply((double)h, 1.0, (double)h));
-                if (this.level()
-                    .getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6))
-                    .noneMatch(state -> state.is(BlockTags.FIRE) || state.is(Blocks.LAVA))) {
+                float f = this.getBlockSpeedFactor();
+
+                this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f));
+                if (this.level().getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata2) -> {
+                    return iblockdata2.is(BlockTags.FIRE) || iblockdata2.is(Blocks.LAVA);
+                })) {
                     if (this.remainingFireTicks <= 0) {
                         this.setRemainingFireTicks(-this.getFireImmuneTicks());
                     }
@@ -728,6 +1292,13 @@
                 this.level().getProfiler().pop();
             }
         }
+        // Paper start - detailed watchdog information
+        } finally {
+            synchronized (this.posLock) { // Paper
+                this.moveVector = null;
+            } // Paper
+        }
+        // Paper end - detailed watchdog information
     }
 
     private boolean isStateClimbable(BlockState state) {
@@ -738,14 +1309,15 @@
         if (state.isAir()) {
             return false;
         } else {
-            boolean bl = this.isStateClimbable(state);
-            if ((this.onGround() || bl || this.isCrouching() && movement.y == 0.0 || this.isOnRails()) && !this.isSwimming()) {
+            boolean flag2 = this.isStateClimbable(state);
+
+            if ((this.onGround() || flag2 || this.isCrouching() && movement.y == 0.0D || this.isOnRails()) && !this.isSwimming()) {
                 if (playSound) {
                     this.walkingStepSound(pos, state);
                 }
 
                 if (emitEvent) {
-                    this.level().gameEvent(GameEvent.STEP, this.position(), GameEvent.Context.of(this, state));
+                    this.level().gameEvent((Holder) GameEvent.STEP, this.position(), GameEvent.Context.of(this, state));
                 }
 
                 return true;
@@ -762,11 +1334,12 @@
     protected void tryCheckInsideBlocks() {
         try {
             this.checkInsideBlocks();
-        } catch (Throwable var4) {
-            CrashReport crashReport = CrashReport.forThrowable(var4, "Checking entity block collision");
-            CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being checked for collision");
-            this.fillCrashReportCategory(crashReportCategory);
-            throw new ReportedException(crashReport);
+        } catch (Throwable throwable) {
+            CrashReport crashreport = CrashReport.forThrowable(throwable, "Checking entity block collision");
+            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being checked for collision");
+
+            this.fillCrashReportCategory(crashreportsystemdetails);
+            throw new ReportedException(crashreport);
         }
     }
 
@@ -789,8 +1362,10 @@
                 this.gameEvent(GameEvent.FLAP);
             }
         }
+
     }
 
+    /** @deprecated */
     @Deprecated
     public BlockPos getOnPosLegacy() {
         return this.getOnPos(0.2F);
@@ -805,40 +1380,37 @@
     }
 
     protected BlockPos getOnPos(float offset) {
-        if (this.mainSupportingBlockPos.isPresent()) {
-            BlockPos blockPos = this.mainSupportingBlockPos.get();
-            if (!(offset > 1.0E-5F)) {
-                return blockPos;
+        if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads
+            BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get();
+
+            if (offset <= 1.0E-5F) {
+                return blockposition;
             } else {
-                BlockState blockState = this.level().getBlockState(blockPos);
-                return (!((double)offset <= 0.5) || !blockState.is(BlockTags.FENCES))
-                        && !blockState.is(BlockTags.WALLS)
-                        && !(blockState.getBlock() instanceof FenceGateBlock)
-                    ? blockPos.atY(Mth.floor(this.position.y - (double)offset))
-                    : blockPos;
+                BlockState iblockdata = this.level().getBlockState(blockposition);
+
+                return ((double) offset > 0.5D || !iblockdata.is(BlockTags.FENCES)) && !iblockdata.is(BlockTags.WALLS) && !(iblockdata.getBlock() instanceof FenceGateBlock) ? blockposition.atY(Mth.floor(this.position.y - (double) offset)) : blockposition;
             }
         } else {
             int i = Mth.floor(this.position.x);
-            int j = Mth.floor(this.position.y - (double)offset);
+            int j = Mth.floor(this.position.y - (double) offset);
             int k = Mth.floor(this.position.z);
+
             return new BlockPos(i, j, k);
         }
     }
 
     protected float getBlockJumpFactor() {
         float f = this.level().getBlockState(this.blockPosition()).getBlock().getJumpFactor();
-        float g = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getJumpFactor();
-        return (double)f == 1.0 ? g : f;
+        float f1 = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getJumpFactor();
+
+        return (double) f == 1.0D ? f1 : f;
     }
 
     protected float getBlockSpeedFactor() {
-        BlockState blockState = this.level().getBlockState(this.blockPosition());
-        float f = blockState.getBlock().getSpeedFactor();
-        if (!blockState.is(Blocks.WATER) && !blockState.is(Blocks.BUBBLE_COLUMN)) {
-            return (double)f == 1.0 ? this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getSpeedFactor() : f;
-        } else {
-            return f;
-        }
+        BlockState iblockdata = this.level().getBlockState(this.blockPosition());
+        float f = iblockdata.getBlock().getSpeedFactor();
+
+        return !iblockdata.is(Blocks.WATER) && !iblockdata.is(Blocks.BUBBLE_COLUMN) ? ((double) f == 1.0D ? this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getSpeedFactor() : f) : f;
     }
 
     protected Vec3 maybeBackOffFromEdge(Vec3 movement, MoverType type) {
@@ -846,24 +1418,27 @@
     }
 
     protected Vec3 limitPistonMovement(Vec3 movement) {
-        if (movement.lengthSqr() <= 1.0E-7) {
+        if (movement.lengthSqr() <= 1.0E-7D) {
             return movement;
         } else {
-            long l = this.level().getGameTime();
-            if (l != this.pistonDeltasGameTime) {
-                Arrays.fill(this.pistonDeltas, 0.0);
-                this.pistonDeltasGameTime = l;
+            long i = this.level().getGameTime();
+
+            if (i != this.pistonDeltasGameTime) {
+                Arrays.fill(this.pistonDeltas, 0.0D);
+                this.pistonDeltasGameTime = i;
             }
 
-            if (movement.x != 0.0) {
-                double d = this.applyPistonMovementRestriction(Direction.Axis.X, movement.x);
-                return Math.abs(d) <= 1.0E-5F ? Vec3.ZERO : new Vec3(d, 0.0, 0.0);
-            } else if (movement.y != 0.0) {
-                double e = this.applyPistonMovementRestriction(Direction.Axis.Y, movement.y);
-                return Math.abs(e) <= 1.0E-5F ? Vec3.ZERO : new Vec3(0.0, e, 0.0);
-            } else if (movement.z != 0.0) {
-                double f = this.applyPistonMovementRestriction(Direction.Axis.Z, movement.z);
-                return Math.abs(f) <= 1.0E-5F ? Vec3.ZERO : new Vec3(0.0, 0.0, f);
+            double d0;
+
+            if (movement.x != 0.0D) {
+                d0 = this.applyPistonMovementRestriction(Direction.Axis.X, movement.x);
+                return Math.abs(d0) <= 9.999999747378752E-6D ? Vec3.ZERO : new Vec3(d0, 0.0D, 0.0D);
+            } else if (movement.y != 0.0D) {
+                d0 = this.applyPistonMovementRestriction(Direction.Axis.Y, movement.y);
+                return Math.abs(d0) <= 9.999999747378752E-6D ? Vec3.ZERO : new Vec3(0.0D, d0, 0.0D);
+            } else if (movement.z != 0.0D) {
+                d0 = this.applyPistonMovementRestriction(Direction.Axis.Z, movement.z);
+                return Math.abs(d0) <= 9.999999747378752E-6D ? Vec3.ZERO : new Vec3(0.0D, 0.0D, d0);
             } else {
                 return Vec3.ZERO;
             }
@@ -872,79 +1447,139 @@
 
     private double applyPistonMovementRestriction(Direction.Axis axis, double offsetFactor) {
         int i = axis.ordinal();
-        double d = Mth.clamp(offsetFactor + this.pistonDeltas[i], -0.51, 0.51);
-        offsetFactor = d - this.pistonDeltas[i];
-        this.pistonDeltas[i] = d;
+        double d1 = Mth.clamp(offsetFactor + this.pistonDeltas[i], -0.51D, 0.51D);
+
+        offsetFactor = d1 - this.pistonDeltas[i];
+        this.pistonDeltas[i] = d1;
         return offsetFactor;
     }
 
     private Vec3 collide(Vec3 movement) {
-        AABB aABB = this.getBoundingBox();
-        List<VoxelShape> list = this.level().getEntityCollisions(this, aABB.expandTowards(movement));
-        Vec3 vec3 = movement.lengthSqr() == 0.0 ? movement : collideBoundingBox(this, movement, aABB, this.level(), list);
-        boolean bl = movement.x != vec3.x;
-        boolean bl2 = movement.y != vec3.y;
-        boolean bl3 = movement.z != vec3.z;
-        boolean bl4 = bl2 && movement.y < 0.0;
-        if (this.maxUpStep() > 0.0F && (bl4 || this.onGround()) && (bl || bl3)) {
-            AABB aABB2 = bl4 ? aABB.move(0.0, vec3.y, 0.0) : aABB;
-            AABB aABB3 = aABB2.expandTowards(movement.x, (double)this.maxUpStep(), movement.z);
-            if (!bl4) {
-                aABB3 = aABB3.expandTowards(0.0, -1.0E-5F, 0.0);
-            }
+        // Paper start - optimise collisions
+        final boolean xZero = movement.x == 0.0;
+        final boolean yZero = movement.y == 0.0;
+        final boolean zZero = movement.z == 0.0;
+        if (xZero & yZero & zZero) {
+            return movement;
+        }
 
-            List<VoxelShape> list2 = collectColliders(this, this.level, list, aABB3);
-            float f = (float)vec3.y;
-            float[] fs = collectCandidateStepUpHeights(aABB2, list2, this.maxUpStep(), f);
+        final Level world = this.level;
+        final AABB currBoundingBox = this.getBoundingBox();
 
-            for (float g : fs) {
-                Vec3 vec32 = collideWithShapes(new Vec3(movement.x, (double)g, movement.z), aABB2, list2);
-                if (vec32.horizontalDistanceSqr() > vec3.horizontalDistanceSqr()) {
-                    double d = aABB.minY - aABB2.minY;
-                    return vec32.add(0.0, -d, 0.0);
+        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(currBoundingBox)) {
+            return movement;
+        }
+
+        final List<AABB> potentialCollisionsBB = new ArrayList<>();
+        final List<VoxelShape> potentialCollisionsVoxel = new ArrayList<>();
+        final double stepHeight = (double)this.maxUpStep();
+        final AABB collisionBox;
+        final boolean onGround = this.onGround;
+
+        if (xZero & zZero) {
+            if (movement.y > 0.0) {
+                collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currBoundingBox, movement.y);
+            } else {
+                collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currBoundingBox, movement.y);
+            }
+        } else {
+            // note: xZero == false or zZero == false
+            if (stepHeight > 0.0 && (onGround || (movement.y < 0.0))) {
+                // don't bother getting the collisions if we don't need them.
+                if (movement.y <= 0.0) {
+                    collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight);
+                } else {
+                    collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z);
                 }
+            } else {
+                collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z);
             }
         }
 
-        return vec3;
+        ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisions(
+            world, (Entity)(Object)this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB,
+            ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER,
+            null, null
+        );
+
+        if (potentialCollisionsVoxel.isEmpty() && potentialCollisionsBB.isEmpty()) {
+            return movement;
+        }
+
+        final Vec3 limitedMoveVector = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB);
+
+        if (stepHeight > 0.0
+            && (onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0))
+            && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) {
+            Vec3 vec3d2 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB);
+            final Vec3 vec3d3 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisionsVoxel, potentialCollisionsBB);
+
+            if (vec3d3.y < stepHeight) {
+                final Vec3 vec3d4 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisionsVoxel, potentialCollisionsBB).add(vec3d3);
+
+                if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
+                    vec3d2 = vec3d4;
+                }
+            }
+
+            if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) {
+                return vec3d2.add(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisionsVoxel, potentialCollisionsBB));
+            }
+
+            return limitedMoveVector;
+        } else {
+            return limitedMoveVector;
+        }
+        // Paper end - optimise collisions
     }
 
     private static float[] collectCandidateStepUpHeights(AABB collisionBox, List<VoxelShape> collisions, float f, float stepHeight) {
-        FloatSet floatSet = new FloatArraySet(4);
+        FloatArraySet floatarrayset = new FloatArraySet(4);
+        Iterator iterator = collisions.iterator();
 
-        for (VoxelShape voxelShape : collisions) {
-            for (double d : voxelShape.getCoords(Direction.Axis.Y)) {
-                float g = (float)(d - collisionBox.minY);
-                if (!(g < 0.0F) && g != stepHeight) {
-                    if (g > f) {
+        while (iterator.hasNext()) {
+            VoxelShape voxelshape = (VoxelShape) iterator.next();
+            DoubleList doublelist = voxelshape.getCoords(Direction.Axis.Y);
+            DoubleListIterator doublelistiterator = doublelist.iterator();
+
+            while (doublelistiterator.hasNext()) {
+                double d0 = (Double) doublelistiterator.next();
+                float f2 = (float) (d0 - collisionBox.minY);
+
+                if (f2 >= 0.0F && f2 != stepHeight) {
+                    if (f2 > f) {
                         break;
                     }
 
-                    floatSet.add(g);
+                    floatarrayset.add(f2);
                 }
             }
         }
 
-        float[] fs = floatSet.toFloatArray();
-        FloatArrays.unstableSort(fs);
-        return fs;
+        float[] afloat = floatarrayset.toFloatArray();
+
+        FloatArrays.unstableSort(afloat);
+        return afloat;
     }
 
     public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List<VoxelShape> collisions) {
-        List<VoxelShape> list = collectColliders(entity, world, collisions, entityBoundingBox.expandTowards(movement));
-        return collideWithShapes(movement, entityBoundingBox, list);
+        List<VoxelShape> list1 = Entity.collectColliders(entity, world, collisions, entityBoundingBox.expandTowards(movement));
+
+        return Entity.collideWithShapes(movement, entityBoundingBox, list1);
     }
 
     private static List<VoxelShape> collectColliders(@Nullable Entity entity, Level world, List<VoxelShape> regularCollisions, AABB movingEntityBoundingBox) {
         Builder<VoxelShape> builder = ImmutableList.builderWithExpectedSize(regularCollisions.size() + 1);
+
         if (!regularCollisions.isEmpty()) {
             builder.addAll(regularCollisions);
         }
 
-        WorldBorder worldBorder = world.getWorldBorder();
-        boolean bl = entity != null && worldBorder.isInsideCloseToBorder(entity, movingEntityBoundingBox);
-        if (bl) {
-            builder.add(worldBorder.getCollisionShape());
+        WorldBorder worldborder = world.getWorldBorder();
+        boolean flag = entity != null && worldborder.isInsideCloseToBorder(entity, movingEntityBoundingBox);
+
+        if (flag) {
+            builder.add(worldborder.getCollisionShape());
         }
 
         builder.addAll(world.getBlockCollisions(entity, movingEntityBoundingBox));
@@ -955,41 +1590,43 @@
         if (collisions.isEmpty()) {
             return movement;
         } else {
-            double d = movement.x;
-            double e = movement.y;
-            double f = movement.z;
-            if (e != 0.0) {
-                e = Shapes.collide(Direction.Axis.Y, entityBoundingBox, collisions, e);
-                if (e != 0.0) {
-                    entityBoundingBox = entityBoundingBox.move(0.0, e, 0.0);
+            double d0 = movement.x;
+            double d1 = movement.y;
+            double d2 = movement.z;
+
+            if (d1 != 0.0D) {
+                d1 = Shapes.collide(Direction.Axis.Y, entityBoundingBox, collisions, d1);
+                if (d1 != 0.0D) {
+                    entityBoundingBox = entityBoundingBox.move(0.0D, d1, 0.0D);
                 }
             }
 
-            boolean bl = Math.abs(d) < Math.abs(f);
-            if (bl && f != 0.0) {
-                f = Shapes.collide(Direction.Axis.Z, entityBoundingBox, collisions, f);
-                if (f != 0.0) {
-                    entityBoundingBox = entityBoundingBox.move(0.0, 0.0, f);
+            boolean flag = Math.abs(d0) < Math.abs(d2);
+
+            if (flag && d2 != 0.0D) {
+                d2 = Shapes.collide(Direction.Axis.Z, entityBoundingBox, collisions, d2);
+                if (d2 != 0.0D) {
+                    entityBoundingBox = entityBoundingBox.move(0.0D, 0.0D, d2);
                 }
             }
 
-            if (d != 0.0) {
-                d = Shapes.collide(Direction.Axis.X, entityBoundingBox, collisions, d);
-                if (!bl && d != 0.0) {
-                    entityBoundingBox = entityBoundingBox.move(d, 0.0, 0.0);
+            if (d0 != 0.0D) {
+                d0 = Shapes.collide(Direction.Axis.X, entityBoundingBox, collisions, d0);
+                if (!flag && d0 != 0.0D) {
+                    entityBoundingBox = entityBoundingBox.move(d0, 0.0D, 0.0D);
                 }
             }
 
-            if (!bl && f != 0.0) {
-                f = Shapes.collide(Direction.Axis.Z, entityBoundingBox, collisions, f);
+            if (!flag && d2 != 0.0D) {
+                d2 = Shapes.collide(Direction.Axis.Z, entityBoundingBox, collisions, d2);
             }
 
-            return new Vec3(d, e, f);
+            return new Vec3(d0, d1, d2);
         }
     }
 
     protected float nextStep() {
-        return (float)((int)this.moveDist + 1);
+        return (float) ((int) this.moveDist + 1);
     }
 
     protected SoundEvent getSwimSound() {
@@ -1004,46 +1641,63 @@
         return SoundEvents.GENERIC_SPLASH;
     }
 
+    // CraftBukkit start - Add delegate methods
+    public SoundEvent getSwimSound0() {
+        return this.getSwimSound();
+    }
+
+    public SoundEvent getSwimSplashSound0() {
+        return this.getSwimSplashSound();
+    }
+
+    public SoundEvent getSwimHighSpeedSplashSound0() {
+        return this.getSwimHighSpeedSplashSound();
+    }
+    // CraftBukkit end
+
     protected void checkInsideBlocks() {
-        AABB aABB = this.getBoundingBox();
-        BlockPos blockPos = BlockPos.containing(aABB.minX + 1.0E-7, aABB.minY + 1.0E-7, aABB.minZ + 1.0E-7);
-        BlockPos blockPos2 = BlockPos.containing(aABB.maxX - 1.0E-7, aABB.maxY - 1.0E-7, aABB.maxZ - 1.0E-7);
-        if (this.level().hasChunksAt(blockPos, blockPos2)) {
-            BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+        AABB axisalignedbb = this.getBoundingBox();
+        BlockPos blockposition = BlockPos.containing(axisalignedbb.minX + 1.0E-7D, axisalignedbb.minY + 1.0E-7D, axisalignedbb.minZ + 1.0E-7D);
+        BlockPos blockposition1 = BlockPos.containing(axisalignedbb.maxX - 1.0E-7D, axisalignedbb.maxY - 1.0E-7D, axisalignedbb.maxZ - 1.0E-7D);
 
-            for (int i = blockPos.getX(); i <= blockPos2.getX(); i++) {
-                for (int j = blockPos.getY(); j <= blockPos2.getY(); j++) {
-                    for (int k = blockPos.getZ(); k <= blockPos2.getZ(); k++) {
+        if (this.level().hasChunksAt(blockposition, blockposition1)) {
+            BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+
+            for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) {
+                for (int j = blockposition.getY(); j <= blockposition1.getY(); ++j) {
+                    for (int k = blockposition.getZ(); k <= blockposition1.getZ(); ++k) {
                         if (!this.isAlive()) {
                             return;
                         }
 
-                        mutableBlockPos.set(i, j, k);
-                        BlockState blockState = this.level().getBlockState(mutableBlockPos);
+                        blockposition_mutableblockposition.set(i, j, k);
+                        BlockState iblockdata = this.level().getBlockState(blockposition_mutableblockposition);
 
                         try {
-                            blockState.entityInside(this.level(), mutableBlockPos, this);
-                            this.onInsideBlock(blockState);
-                        } catch (Throwable var12) {
-                            CrashReport crashReport = CrashReport.forThrowable(var12, "Colliding entity with block");
-                            CrashReportCategory crashReportCategory = crashReport.addCategory("Block being collided with");
-                            CrashReportCategory.populateBlockDetails(crashReportCategory, this.level(), mutableBlockPos, blockState);
-                            throw new ReportedException(crashReport);
+                            iblockdata.entityInside(this.level(), blockposition_mutableblockposition, this);
+                            this.onInsideBlock(iblockdata);
+                        } catch (Throwable throwable) {
+                            CrashReport crashreport = CrashReport.forThrowable(throwable, "Colliding entity with block");
+                            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being collided with");
+
+                            CrashReportCategory.populateBlockDetails(crashreportsystemdetails, this.level(), blockposition_mutableblockposition, iblockdata);
+                            throw new ReportedException(crashreport);
                         }
                     }
                 }
             }
         }
-    }
 
-    protected void onInsideBlock(BlockState state) {
     }
 
+    protected void onInsideBlock(BlockState state) {}
+
     public BlockPos adjustSpawnLocation(ServerLevel world, BlockPos basePos) {
-        BlockPos blockPos = world.getSharedSpawnPos();
-        Vec3 vec3 = blockPos.getCenter();
-        int i = world.getChunkAt(blockPos).getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos.getX(), blockPos.getZ()) + 1;
-        return BlockPos.containing(vec3.x, (double)i, vec3.z);
+        BlockPos blockposition1 = world.getSharedSpawnPos();
+        Vec3 vec3d = blockposition1.getCenter();
+        int i = world.getChunkAt(blockposition1).getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockposition1.getX(), blockposition1.getZ()) + 1;
+
+        return BlockPos.containing(vec3d.x, (double) i, vec3d.z);
     }
 
     public void gameEvent(Holder<GameEvent> event, @Nullable Entity entity) {
@@ -1059,36 +1713,42 @@
         if (this.shouldPlayAmethystStepSound(state)) {
             this.playAmethystStepSound();
         }
+
     }
 
     protected void waterSwimSound() {
-        Entity entity = Objects.requireNonNullElse(this.getControllingPassenger(), this);
+        Entity entity = (Entity) Objects.requireNonNullElse(this.getControllingPassenger(), this);
         float f = entity == this ? 0.35F : 0.4F;
-        Vec3 vec3 = entity.getDeltaMovement();
-        float g = Math.min(1.0F, (float)Math.sqrt(vec3.x * vec3.x * 0.2F + vec3.y * vec3.y + vec3.z * vec3.z * 0.2F) * f);
-        this.playSwimSound(g);
+        Vec3 vec3d = entity.getDeltaMovement();
+        float f1 = Math.min(1.0F, (float) Math.sqrt(vec3d.x * vec3d.x * 0.20000000298023224D + vec3d.y * vec3d.y + vec3d.z * vec3d.z * 0.20000000298023224D) * f);
+
+        this.playSwimSound(f1);
     }
 
     protected BlockPos getPrimaryStepSoundBlockPos(BlockPos pos) {
-        BlockPos blockPos = pos.above();
-        BlockState blockState = this.level().getBlockState(blockPos);
-        return !blockState.is(BlockTags.INSIDE_STEP_SOUND_BLOCKS) && !blockState.is(BlockTags.COMBINATION_STEP_SOUND_BLOCKS) ? pos : blockPos;
+        BlockPos blockposition1 = pos.above();
+        BlockState iblockdata = this.level().getBlockState(blockposition1);
+
+        return !iblockdata.is(BlockTags.INSIDE_STEP_SOUND_BLOCKS) && !iblockdata.is(BlockTags.COMBINATION_STEP_SOUND_BLOCKS) ? pos : blockposition1;
     }
 
     protected void playCombinationStepSounds(BlockState primaryState, BlockState secondaryState) {
-        SoundType soundType = primaryState.getSoundType();
-        this.playSound(soundType.getStepSound(), soundType.getVolume() * 0.15F, soundType.getPitch());
+        SoundType soundeffecttype = primaryState.getSoundType();
+
+        this.playSound(soundeffecttype.getStepSound(), soundeffecttype.getVolume() * 0.15F, soundeffecttype.getPitch());
         this.playMuffledStepSound(secondaryState);
     }
 
     protected void playMuffledStepSound(BlockState state) {
-        SoundType soundType = state.getSoundType();
-        this.playSound(soundType.getStepSound(), soundType.getVolume() * 0.05F, soundType.getPitch() * 0.8F);
+        SoundType soundeffecttype = state.getSoundType();
+
+        this.playSound(soundeffecttype.getStepSound(), soundeffecttype.getVolume() * 0.05F, soundeffecttype.getPitch() * 0.8F);
     }
 
     protected void playStepSound(BlockPos pos, BlockState state) {
-        SoundType soundType = state.getSoundType();
-        this.playSound(soundType.getStepSound(), soundType.getVolume() * 0.15F, soundType.getPitch());
+        SoundType soundeffecttype = state.getSoundType();
+
+        this.playSound(soundeffecttype.getStepSound(), soundeffecttype.getVolume() * 0.15F, soundeffecttype.getPitch());
     }
 
     private boolean shouldPlayAmethystStepSound(BlockState state) {
@@ -1096,11 +1756,12 @@
     }
 
     private void playAmethystStepSound() {
-        this.crystalSoundIntensity = this.crystalSoundIntensity * (float)Math.pow(0.997, (double)(this.tickCount - this.lastCrystalSoundPlayTick));
+        this.crystalSoundIntensity *= (float) Math.pow(0.997D, (double) (this.tickCount - this.lastCrystalSoundPlayTick));
         this.crystalSoundIntensity = Math.min(1.0F, this.crystalSoundIntensity + 0.07F);
         float f = 0.5F + this.crystalSoundIntensity * this.random.nextFloat() * 1.2F;
-        float g = 0.1F + this.crystalSoundIntensity * 1.2F;
-        this.playSound(SoundEvents.AMETHYST_BLOCK_CHIME, g, f);
+        float f1 = 0.1F + this.crystalSoundIntensity * 1.2F;
+
+        this.playSound(SoundEvents.AMETHYST_BLOCK_CHIME, f1, f);
         this.lastCrystalSoundPlayTick = this.tickCount;
     }
 
@@ -1108,8 +1769,7 @@
         this.playSound(this.getSwimSound(), volume, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
     }
 
-    protected void onFlap() {
-    }
+    protected void onFlap() {}
 
     protected boolean isFlapping() {
         return false;
@@ -1117,45 +1777,49 @@
 
     public void playSound(SoundEvent sound, float volume, float pitch) {
         if (!this.isSilent()) {
-            this.level().playSound(null, this.getX(), this.getY(), this.getZ(), sound, this.getSoundSource(), volume, pitch);
+            this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), sound, this.getSoundSource(), volume, pitch);
         }
+
     }
 
     public void playSound(SoundEvent event) {
         if (!this.isSilent()) {
             this.playSound(event, 1.0F, 1.0F);
         }
+
     }
 
     public boolean isSilent() {
-        return this.entityData.get(DATA_SILENT);
+        return (Boolean) this.entityData.get(Entity.DATA_SILENT);
     }
 
     public void setSilent(boolean silent) {
-        this.entityData.set(DATA_SILENT, silent);
+        this.entityData.set(Entity.DATA_SILENT, silent);
     }
 
     public boolean isNoGravity() {
-        return this.entityData.get(DATA_NO_GRAVITY);
+        return (Boolean) this.entityData.get(Entity.DATA_NO_GRAVITY);
     }
 
     public void setNoGravity(boolean noGravity) {
-        this.entityData.set(DATA_NO_GRAVITY, noGravity);
+        this.entityData.set(Entity.DATA_NO_GRAVITY, noGravity);
     }
 
     protected double getDefaultGravity() {
-        return 0.0;
+        return 0.0D;
     }
 
     public final double getGravity() {
-        return this.isNoGravity() ? 0.0 : this.getDefaultGravity();
+        return this.isNoGravity() ? 0.0D : this.getDefaultGravity();
     }
 
     protected void applyGravity() {
-        double d = this.getGravity();
-        if (d != 0.0) {
-            this.setDeltaMovement(this.getDeltaMovement().add(0.0, -d, 0.0));
+        double d0 = this.getGravity();
+
+        if (d0 != 0.0D) {
+            this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -d0, 0.0D));
         }
+
     }
 
     protected Entity.MovementEmission getMovementEmission() {
@@ -1170,18 +1834,16 @@
         if (onGround) {
             if (this.fallDistance > 0.0F) {
                 state.getBlock().fallOn(this.level(), state, landedPosition, this, this.fallDistance);
-                this.level()
-                    .gameEvent(
-                        GameEvent.HIT_GROUND,
-                        this.position,
-                        GameEvent.Context.of(this, this.mainSupportingBlockPos.<BlockState>map(blockPos -> this.level().getBlockState(blockPos)).orElse(state))
-                    );
+                this.level().gameEvent((Holder) GameEvent.HIT_GROUND, this.position, GameEvent.Context.of(this, (BlockState) this.mainSupportingBlockPos.map((blockposition1) -> {
+                    return this.level().getBlockState(blockposition1);
+                }).orElse(state)));
             }
 
             this.resetFallDistance();
-        } else if (heightDifference < 0.0) {
-            this.fallDistance -= (float)heightDifference;
+        } else if (heightDifference < 0.0D) {
+            this.fallDistance -= (float) heightDifference;
         }
+
     }
 
     public boolean fireImmune() {
@@ -1193,7 +1855,11 @@
             return false;
         } else {
             if (this.isVehicle()) {
-                for (Entity entity : this.getPassengers()) {
+                Iterator iterator = this.getPassengers().iterator();
+
+                while (iterator.hasNext()) {
+                    Entity entity = (Entity) iterator.next();
+
                     entity.causeFallDamage(fallDistance, damageMultiplier, damageSource);
                 }
             }
@@ -1207,9 +1873,9 @@
     }
 
     public boolean isInRain() {
-        BlockPos blockPos = this.blockPosition();
-        return this.level().isRainingAt(blockPos)
-            || this.level().isRainingAt(BlockPos.containing((double)blockPos.getX(), this.getBoundingBox().maxY, (double)blockPos.getZ()));
+        BlockPos blockposition = this.blockPosition();
+
+        return this.level().isRainingAt(blockposition) || this.level().isRainingAt(BlockPos.containing((double) blockposition.getX(), this.getBoundingBox().maxY, (double) blockposition.getZ()));
     }
 
     public boolean isInBubbleColumn() {
@@ -1240,27 +1906,31 @@
         if (this.isSwimming()) {
             this.setSwimming(this.isSprinting() && this.isInWater() && !this.isPassenger());
         } else {
-            this.setSwimming(
-                this.isSprinting() && this.isUnderWater() && !this.isPassenger() && this.level().getFluidState(this.blockPosition).is(FluidTags.WATER)
-            );
+            this.setSwimming(this.isSprinting() && this.isUnderWater() && !this.isPassenger() && this.level().getFluidState(this.blockPosition).is(FluidTags.WATER));
         }
+
     }
 
     protected boolean updateInWaterStateAndDoFluidPushing() {
         this.fluidHeight.clear();
         this.updateInWaterStateAndDoWaterCurrentPushing();
-        double d = this.level().dimensionType().ultraWarm() ? 0.007 : 0.0023333333333333335;
-        boolean bl = this.updateFluidHeightAndDoFluidPushing(FluidTags.LAVA, d);
-        return this.isInWater() || bl;
+        double d0 = this.level().dimensionType().ultraWarm() ? 0.007D : 0.0023333333333333335D;
+        boolean flag = this.updateFluidHeightAndDoFluidPushing(FluidTags.LAVA, d0);
+
+        return this.isInWater() || flag;
     }
 
     void updateInWaterStateAndDoWaterCurrentPushing() {
-        if (this.getVehicle() instanceof Boat boat && !boat.isUnderWater()) {
-            this.wasTouchingWater = false;
-            return;
+        Entity entity = this.getVehicle();
+
+        if (entity instanceof Boat entityboat) {
+            if (!entityboat.isUnderWater()) {
+                this.wasTouchingWater = false;
+                return;
+            }
         }
 
-        if (this.updateFluidHeightAndDoFluidPushing(FluidTags.WATER, 0.014)) {
+        if (this.updateFluidHeightAndDoFluidPushing(FluidTags.WATER, 0.014D)) {
             if (!this.wasTouchingWater && !this.firstTick) {
                 this.doWaterSplashEffect();
             }
@@ -1271,55 +1941,69 @@
         } else {
             this.wasTouchingWater = false;
         }
+
     }
 
     private void updateFluidOnEyes() {
         this.wasEyeInWater = this.isEyeInFluid(FluidTags.WATER);
         this.fluidOnEyes.clear();
-        double d = this.getEyeY();
-        if (this.getVehicle() instanceof Boat boat && !boat.isUnderWater() && boat.getBoundingBox().maxY >= d && boat.getBoundingBox().minY <= d) {
-            return;
+        double d0 = this.getEyeY();
+        Entity entity = this.getVehicle();
+
+        if (entity instanceof Boat entityboat) {
+            if (!entityboat.isUnderWater() && entityboat.getBoundingBox().maxY >= d0 && entityboat.getBoundingBox().minY <= d0) {
+                return;
+            }
         }
 
-        BlockPos blockPos = BlockPos.containing(this.getX(), d, this.getZ());
-        FluidState fluidState = this.level().getFluidState(blockPos);
-        double e = (double)((float)blockPos.getY() + fluidState.getHeight(this.level(), blockPos));
-        if (e > d) {
-            fluidState.getTags().forEach(this.fluidOnEyes::add);
+        BlockPos blockposition = BlockPos.containing(this.getX(), d0, this.getZ());
+        FluidState fluid = this.level().getFluidState(blockposition);
+        double d1 = (double) ((float) blockposition.getY() + fluid.getHeight(this.level(), blockposition));
+
+        if (d1 > d0) {
+            Stream stream = fluid.getTags();
+            Set set = this.fluidOnEyes;
+
+            Objects.requireNonNull(this.fluidOnEyes);
+            stream.forEach(set::add);
         }
+
     }
 
     protected void doWaterSplashEffect() {
-        Entity entity = Objects.requireNonNullElse(this.getControllingPassenger(), this);
+        Entity entity = (Entity) Objects.requireNonNullElse(this.getControllingPassenger(), this);
         float f = entity == this ? 0.2F : 0.9F;
-        Vec3 vec3 = entity.getDeltaMovement();
-        float g = Math.min(1.0F, (float)Math.sqrt(vec3.x * vec3.x * 0.2F + vec3.y * vec3.y + vec3.z * vec3.z * 0.2F) * f);
-        if (g < 0.25F) {
-            this.playSound(this.getSwimSplashSound(), g, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
+        Vec3 vec3d = entity.getDeltaMovement();
+        float f1 = Math.min(1.0F, (float) Math.sqrt(vec3d.x * vec3d.x * 0.20000000298023224D + vec3d.y * vec3d.y + vec3d.z * vec3d.z * 0.20000000298023224D) * f);
+
+        if (f1 < 0.25F) {
+            this.playSound(this.getSwimSplashSound(), f1, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
         } else {
-            this.playSound(this.getSwimHighSpeedSplashSound(), g, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
+            this.playSound(this.getSwimHighSpeedSplashSound(), f1, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
         }
 
-        float h = (float)Mth.floor(this.getY());
+        float f2 = (float) Mth.floor(this.getY());
 
-        for (int i = 0; (float)i < 1.0F + this.dimensions.width() * 20.0F; i++) {
-            double d = (this.random.nextDouble() * 2.0 - 1.0) * (double)this.dimensions.width();
-            double e = (this.random.nextDouble() * 2.0 - 1.0) * (double)this.dimensions.width();
-            this.level()
-                .addParticle(
-                    ParticleTypes.BUBBLE, this.getX() + d, (double)(h + 1.0F), this.getZ() + e, vec3.x, vec3.y - this.random.nextDouble() * 0.2F, vec3.z
-                );
+        double d0;
+        double d1;
+        int i;
+
+        for (i = 0; (float) i < 1.0F + this.dimensions.width() * 20.0F; ++i) {
+            d0 = (this.random.nextDouble() * 2.0D - 1.0D) * (double) this.dimensions.width();
+            d1 = (this.random.nextDouble() * 2.0D - 1.0D) * (double) this.dimensions.width();
+            this.level().addParticle(ParticleTypes.BUBBLE, this.getX() + d0, (double) (f2 + 1.0F), this.getZ() + d1, vec3d.x, vec3d.y - this.random.nextDouble() * 0.20000000298023224D, vec3d.z);
         }
 
-        for (int j = 0; (float)j < 1.0F + this.dimensions.width() * 20.0F; j++) {
-            double k = (this.random.nextDouble() * 2.0 - 1.0) * (double)this.dimensions.width();
-            double l = (this.random.nextDouble() * 2.0 - 1.0) * (double)this.dimensions.width();
-            this.level().addParticle(ParticleTypes.SPLASH, this.getX() + k, (double)(h + 1.0F), this.getZ() + l, vec3.x, vec3.y, vec3.z);
+        for (i = 0; (float) i < 1.0F + this.dimensions.width() * 20.0F; ++i) {
+            d0 = (this.random.nextDouble() * 2.0D - 1.0D) * (double) this.dimensions.width();
+            d1 = (this.random.nextDouble() * 2.0D - 1.0D) * (double) this.dimensions.width();
+            this.level().addParticle(ParticleTypes.SPLASH, this.getX() + d0, (double) (f2 + 1.0F), this.getZ() + d1, vec3d.x, vec3d.y, vec3d.z);
         }
 
         this.gameEvent(GameEvent.SPLASH);
     }
 
+    /** @deprecated */
     @Deprecated
     protected BlockState getBlockStateOnLegacy() {
         return this.level().getBlockState(this.getOnPosLegacy());
@@ -1334,23 +2018,26 @@
     }
 
     protected void spawnSprintParticle() {
-        BlockPos blockPos = this.getOnPosLegacy();
-        BlockState blockState = this.level().getBlockState(blockPos);
-        if (blockState.getRenderShape() != RenderShape.INVISIBLE) {
-            Vec3 vec3 = this.getDeltaMovement();
-            BlockPos blockPos2 = this.blockPosition();
-            double d = this.getX() + (this.random.nextDouble() - 0.5) * (double)this.dimensions.width();
-            double e = this.getZ() + (this.random.nextDouble() - 0.5) * (double)this.dimensions.width();
-            if (blockPos2.getX() != blockPos.getX()) {
-                d = Mth.clamp(d, (double)blockPos.getX(), (double)blockPos.getX() + 1.0);
+        BlockPos blockposition = this.getOnPosLegacy();
+        BlockState iblockdata = this.level().getBlockState(blockposition);
+
+        if (iblockdata.getRenderShape() != RenderShape.INVISIBLE) {
+            Vec3 vec3d = this.getDeltaMovement();
+            BlockPos blockposition1 = this.blockPosition();
+            double d0 = this.getX() + (this.random.nextDouble() - 0.5D) * (double) this.dimensions.width();
+            double d1 = this.getZ() + (this.random.nextDouble() - 0.5D) * (double) this.dimensions.width();
+
+            if (blockposition1.getX() != blockposition.getX()) {
+                d0 = Mth.clamp(d0, (double) blockposition.getX(), (double) blockposition.getX() + 1.0D);
             }
 
-            if (blockPos2.getZ() != blockPos.getZ()) {
-                e = Mth.clamp(e, (double)blockPos.getZ(), (double)blockPos.getZ() + 1.0);
+            if (blockposition1.getZ() != blockposition.getZ()) {
+                d1 = Mth.clamp(d1, (double) blockposition.getZ(), (double) blockposition.getZ() + 1.0D);
             }
 
-            this.level().addParticle(new BlockParticleOption(ParticleTypes.BLOCK, blockState), d, this.getY() + 0.1, e, vec3.x * -4.0, 1.5, vec3.z * -4.0);
+            this.level().addParticle(new BlockParticleOption(ParticleTypes.BLOCK, iblockdata), d0, this.getY() + 0.1D, d1, vec3d.x * -4.0D, 1.5D, vec3d.z * -4.0D);
         }
+
     }
 
     public boolean isEyeInFluid(TagKey<Fluid> fluidTag) {
@@ -1358,31 +2045,33 @@
     }
 
     public boolean isInLava() {
-        return !this.firstTick && this.fluidHeight.getDouble(FluidTags.LAVA) > 0.0;
+        return !this.firstTick && this.fluidHeight.getDouble(FluidTags.LAVA) > 0.0D;
     }
 
     public void moveRelative(float speed, Vec3 movementInput) {
-        Vec3 vec3 = getInputVector(movementInput, speed, this.getYRot());
-        this.setDeltaMovement(this.getDeltaMovement().add(vec3));
+        Vec3 vec3d1 = Entity.getInputVector(movementInput, speed, this.getYRot());
+
+        this.setDeltaMovement(this.getDeltaMovement().add(vec3d1));
     }
 
     private static Vec3 getInputVector(Vec3 movementInput, float speed, float yaw) {
-        double d = movementInput.lengthSqr();
-        if (d < 1.0E-7) {
+        double d0 = movementInput.lengthSqr();
+
+        if (d0 < 1.0E-7D) {
             return Vec3.ZERO;
         } else {
-            Vec3 vec3 = (d > 1.0 ? movementInput.normalize() : movementInput).scale((double)speed);
-            float f = Mth.sin(yaw * (float) (Math.PI / 180.0));
-            float g = Mth.cos(yaw * (float) (Math.PI / 180.0));
-            return new Vec3(vec3.x * (double)g - vec3.z * (double)f, vec3.y, vec3.z * (double)g + vec3.x * (double)f);
+            Vec3 vec3d1 = (d0 > 1.0D ? movementInput.normalize() : movementInput).scale((double) speed);
+            float f2 = Mth.sin(yaw * 0.017453292F);
+            float f3 = Mth.cos(yaw * 0.017453292F);
+
+            return new Vec3(vec3d1.x * (double) f3 - vec3d1.z * (double) f2, vec3d1.y, vec3d1.z * (double) f3 + vec3d1.x * (double) f2);
         }
     }
 
+    /** @deprecated */
     @Deprecated
     public float getLightLevelDependentMagicValue() {
-        return this.level().hasChunkAt(this.getBlockX(), this.getBlockZ())
-            ? this.level().getLightLevelDependentMagicValue(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()))
-            : 0.0F;
+        return this.level().hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level().getLightLevelDependentMagicValue(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ())) : 0.0F;
     }
 
     public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
@@ -1395,15 +2084,18 @@
         this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F);
         this.yRotO = this.getYRot();
         this.xRotO = this.getXRot();
+        this.setYHeadRot(yaw); // Paper - Update head rotation
     }
 
     public void absMoveTo(double x, double y, double z) {
-        double d = Mth.clamp(x, -3.0E7, 3.0E7);
-        double e = Mth.clamp(z, -3.0E7, 3.0E7);
-        this.xo = d;
+        double d3 = Mth.clamp(x, -3.0E7D, 3.0E7D);
+        double d4 = Mth.clamp(z, -3.0E7D, 3.0E7D);
+
+        this.xo = d3;
         this.yo = y;
-        this.zo = e;
-        this.setPos(d, y, e);
+        this.zo = d4;
+        this.setPos(d3, y, d4);
+        if (this.valid) this.level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit
     }
 
     public void moveTo(Vec3 pos) {
@@ -1423,39 +2115,50 @@
     }
 
     public void moveTo(double x, double y, double z, float yaw, float pitch) {
+        // Paper start - Fix Entity Teleportation and cancel velocity if teleported
+        if (!preserveMotion) {
+            this.deltaMovement = Vec3.ZERO;
+        } else {
+            this.preserveMotion = false;
+        }
+        // Paper end - Fix Entity Teleportation and cancel velocity if teleported
         this.setPosRaw(x, y, z);
         this.setYRot(yaw);
         this.setXRot(pitch);
         this.setOldPosAndRot();
         this.reapplyPosition();
+        this.setYHeadRot(yaw); // Paper - Update head rotation
     }
 
     public final void setOldPosAndRot() {
-        double d = this.getX();
-        double e = this.getY();
-        double f = this.getZ();
-        this.xo = d;
-        this.yo = e;
-        this.zo = f;
-        this.xOld = d;
-        this.yOld = e;
-        this.zOld = f;
+        double d0 = this.getX();
+        double d1 = this.getY();
+        double d2 = this.getZ();
+
+        this.xo = d0;
+        this.yo = d1;
+        this.zo = d2;
+        this.xOld = d0;
+        this.yOld = d1;
+        this.zOld = d2;
         this.yRotO = this.getYRot();
         this.xRotO = this.getXRot();
     }
 
     public float distanceTo(Entity entity) {
-        float f = (float)(this.getX() - entity.getX());
-        float g = (float)(this.getY() - entity.getY());
-        float h = (float)(this.getZ() - entity.getZ());
-        return Mth.sqrt(f * f + g * g + h * h);
+        float f = (float) (this.getX() - entity.getX());
+        float f1 = (float) (this.getY() - entity.getY());
+        float f2 = (float) (this.getZ() - entity.getZ());
+
+        return Mth.sqrt(f * f + f1 * f1 + f2 * f2);
     }
 
     public double distanceToSqr(double x, double y, double z) {
-        double d = this.getX() - x;
-        double e = this.getY() - y;
-        double f = this.getZ() - z;
-        return d * d + e * e + f * f;
+        double d3 = this.getX() - x;
+        double d4 = this.getY() - y;
+        double d5 = this.getZ() - z;
+
+        return d3 * d3 + d4 * d4 + d5 * d5;
     }
 
     public double distanceToSqr(Entity entity) {
@@ -1463,42 +2166,46 @@
     }
 
     public double distanceToSqr(Vec3 vector) {
-        double d = this.getX() - vector.x;
-        double e = this.getY() - vector.y;
-        double f = this.getZ() - vector.z;
-        return d * d + e * e + f * f;
-    }
+        double d0 = this.getX() - vector.x;
+        double d1 = this.getY() - vector.y;
+        double d2 = this.getZ() - vector.z;
 
-    public void playerTouch(Player player) {
+        return d0 * d0 + d1 * d1 + d2 * d2;
     }
 
+    public void playerTouch(Player player) {}
+
     public void push(Entity entity) {
         if (!this.isPassengerOfSameVehicle(entity)) {
             if (!entity.noPhysics && !this.noPhysics) {
-                double d = entity.getX() - this.getX();
-                double e = entity.getZ() - this.getZ();
-                double f = Mth.absMax(d, e);
-                if (f >= 0.01F) {
-                    f = Math.sqrt(f);
-                    d /= f;
-                    e /= f;
-                    double g = 1.0 / f;
-                    if (g > 1.0) {
-                        g = 1.0;
+                if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant
+                double d0 = entity.getX() - this.getX();
+                double d1 = entity.getZ() - this.getZ();
+                double d2 = Mth.absMax(d0, d1);
+
+                if (d2 >= 0.009999999776482582D) {
+                    d2 = Math.sqrt(d2);
+                    d0 /= d2;
+                    d1 /= d2;
+                    double d3 = 1.0D / d2;
+
+                    if (d3 > 1.0D) {
+                        d3 = 1.0D;
                     }
 
-                    d *= g;
-                    e *= g;
-                    d *= 0.05F;
-                    e *= 0.05F;
+                    d0 *= d3;
+                    d1 *= d3;
+                    d0 *= 0.05000000074505806D;
+                    d1 *= 0.05000000074505806D;
                     if (!this.isVehicle() && this.isPushable()) {
-                        this.push(-d, 0.0, -e);
+                        this.push(-d0, 0.0D, -d1);
                     }
 
                     if (!entity.isVehicle() && entity.isPushable()) {
-                        entity.push(d, 0.0, e);
+                        entity.push(d0, 0.0D, d1);
                     }
                 }
+
             }
         }
     }
@@ -1508,7 +2215,21 @@
     }
 
     public void push(double deltaX, double deltaY, double deltaZ) {
-        this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ));
+        // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+        this.push(deltaX, deltaY, deltaZ, null);
+    }
+
+    public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) {
+        org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ);
+        if (pushingEntity != null) {
+            io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent event = new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(this.getBukkitEntity(), io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.PUSH, pushingEntity.getBukkitEntity(), delta);
+            if (!event.callEvent()) {
+                return;
+            }
+            delta = event.getKnockback();
+        }
+        this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ()));
+        // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
         this.hasImpulse = true;
     }
 
@@ -1542,13 +2263,14 @@
     }
 
     public final Vec3 calculateViewVector(float pitch, float yaw) {
-        float f = pitch * (float) (Math.PI / 180.0);
-        float g = -yaw * (float) (Math.PI / 180.0);
-        float h = Mth.cos(g);
-        float i = Mth.sin(g);
-        float j = Mth.cos(f);
-        float k = Mth.sin(f);
-        return new Vec3((double)(i * j), (double)(-k), (double)(h * j));
+        float f2 = pitch * 0.017453292F;
+        float f3 = -yaw * 0.017453292F;
+        float f4 = Mth.cos(f3);
+        float f5 = Mth.sin(f3);
+        float f6 = Mth.cos(f2);
+        float f7 = Mth.sin(f2);
+
+        return new Vec3((double) (f5 * f6), (double) (-f7), (double) (f4 * f6));
     }
 
     public final Vec3 getUpVector(float tickDelta) {
@@ -1564,10 +2286,11 @@
     }
 
     public final Vec3 getEyePosition(float tickDelta) {
-        double d = Mth.lerp((double)tickDelta, this.xo, this.getX());
-        double e = Mth.lerp((double)tickDelta, this.yo, this.getY()) + (double)this.getEyeHeight();
-        double f = Mth.lerp((double)tickDelta, this.zo, this.getZ());
-        return new Vec3(d, e, f);
+        double d0 = Mth.lerp((double) tickDelta, this.xo, this.getX());
+        double d1 = Mth.lerp((double) tickDelta, this.yo, this.getY()) + (double) this.getEyeHeight();
+        double d2 = Mth.lerp((double) tickDelta, this.zo, this.getZ());
+
+        return new Vec3(d0, d1, d2);
     }
 
     public Vec3 getLightProbePosition(float tickDelta) {
@@ -1575,17 +2298,19 @@
     }
 
     public final Vec3 getPosition(float delta) {
-        double d = Mth.lerp((double)delta, this.xo, this.getX());
-        double e = Mth.lerp((double)delta, this.yo, this.getY());
-        double f = Mth.lerp((double)delta, this.zo, this.getZ());
-        return new Vec3(d, e, f);
+        double d0 = Mth.lerp((double) delta, this.xo, this.getX());
+        double d1 = Mth.lerp((double) delta, this.yo, this.getY());
+        double d2 = Mth.lerp((double) delta, this.zo, this.getZ());
+
+        return new Vec3(d0, d1, d2);
     }
 
     public HitResult pick(double maxDistance, float tickDelta, boolean includeFluids) {
-        Vec3 vec3 = this.getEyePosition(tickDelta);
-        Vec3 vec32 = this.getViewVector(tickDelta);
-        Vec3 vec33 = vec3.add(vec32.x * maxDistance, vec32.y * maxDistance, vec32.z * maxDistance);
-        return this.level().clip(new ClipContext(vec3, vec33, ClipContext.Block.OUTLINE, includeFluids ? ClipContext.Fluid.ANY : ClipContext.Fluid.NONE, this));
+        Vec3 vec3d = this.getEyePosition(tickDelta);
+        Vec3 vec3d1 = this.getViewVector(tickDelta);
+        Vec3 vec3d2 = vec3d.add(vec3d1.x * maxDistance, vec3d1.y * maxDistance, vec3d1.z * maxDistance);
+
+        return this.level().clip(new ClipContext(vec3d, vec3d2, ClipContext.Block.OUTLINE, includeFluids ? ClipContext.Fluid.ANY : ClipContext.Fluid.NONE, this));
     }
 
     public boolean canBeHitByProjectile() {
@@ -1597,152 +2322,265 @@
     }
 
     public boolean isPushable() {
+        // Paper start - Climbing should not bypass cramming gamerule
+        return isCollidable(false);
+    }
+
+    public boolean isCollidable(boolean ignoreClimbing) {
+        // Paper end - Climbing should not bypass cramming gamerule
         return false;
     }
 
+    // CraftBukkit start - collidable API
+    public boolean canCollideWithBukkit(Entity entity) {
+        return this.isPushable();
+    }
+    // CraftBukkit end
+
     public void awardKillScore(Entity entityKilled, int score, DamageSource damageSource) {
         if (entityKilled instanceof ServerPlayer) {
-            CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer)entityKilled, this, damageSource);
+            CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) entityKilled, this, damageSource);
         }
+
     }
 
     public boolean shouldRender(double cameraX, double cameraY, double cameraZ) {
-        double d = this.getX() - cameraX;
-        double e = this.getY() - cameraY;
-        double f = this.getZ() - cameraZ;
-        double g = d * d + e * e + f * f;
-        return this.shouldRenderAtSqrDistance(g);
+        double d3 = this.getX() - cameraX;
+        double d4 = this.getY() - cameraY;
+        double d5 = this.getZ() - cameraZ;
+        double d6 = d3 * d3 + d4 * d4 + d5 * d5;
+
+        return this.shouldRenderAtSqrDistance(d6);
     }
 
     public boolean shouldRenderAtSqrDistance(double distance) {
-        double d = this.getBoundingBox().getSize();
-        if (Double.isNaN(d)) {
-            d = 1.0;
+        double d1 = this.getBoundingBox().getSize();
+
+        if (Double.isNaN(d1)) {
+            d1 = 1.0D;
         }
 
-        d *= 64.0 * viewScale;
-        return distance < d * d;
+        d1 *= 64.0D * Entity.viewScale;
+        return distance < d1 * d1;
     }
 
     public boolean saveAsPassenger(CompoundTag nbt) {
+        // CraftBukkit start - allow excluding certain data when saving
+        return this.saveAsPassenger(nbt, true);
+    }
+
+    public boolean saveAsPassenger(CompoundTag nbttagcompound, boolean includeAll) {
+        // CraftBukkit end
         if (this.removalReason != null && !this.removalReason.shouldSave()) {
             return false;
         } else {
-            String string = this.getEncodeId();
-            if (string == null) {
+            String s = this.getEncodeId();
+
+            if (!this.persist || s == null) { // CraftBukkit - persist flag
                 return false;
             } else {
-                nbt.putString("id", string);
-                this.saveWithoutId(nbt);
+                nbttagcompound.putString("id", s);
+                this.saveWithoutId(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
                 return true;
             }
         }
     }
 
+    // Paper start - Entity serialization api
+    public boolean serializeEntity(CompoundTag compound) {
+        List<Entity> pass = new java.util.ArrayList<>(this.getPassengers());
+        this.passengers = ImmutableList.of();
+        boolean result = save(compound);
+        this.passengers = ImmutableList.copyOf(pass);
+        return result;
+    }
+    // Paper end - Entity serialization api
     public boolean save(CompoundTag nbt) {
-        return !this.isPassenger() && this.saveAsPassenger(nbt);
+        return this.isPassenger() ? false : this.saveAsPassenger(nbt);
     }
 
     public CompoundTag saveWithoutId(CompoundTag nbt) {
+        // CraftBukkit start - allow excluding certain data when saving
+        return this.saveWithoutId(nbt, true);
+    }
+
+    public CompoundTag saveWithoutId(CompoundTag nbttagcompound, boolean includeAll) {
+        // CraftBukkit end
         try {
-            if (this.vehicle != null) {
-                nbt.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
-            } else {
-                nbt.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
+            // CraftBukkit start - selectively save position
+            if (includeAll) {
+                if (this.vehicle != null) {
+                    nbttagcompound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
+                } else {
+                    nbttagcompound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
+                }
             }
+            // CraftBukkit end
 
-            Vec3 vec3 = this.getDeltaMovement();
-            nbt.put("Motion", this.newDoubleList(vec3.x, vec3.y, vec3.z));
-            nbt.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
-            nbt.putFloat("FallDistance", this.fallDistance);
-            nbt.putShort("Fire", (short)this.remainingFireTicks);
-            nbt.putShort("Air", (short)this.getAirSupply());
-            nbt.putBoolean("OnGround", this.onGround());
-            nbt.putBoolean("Invulnerable", this.invulnerable);
-            nbt.putInt("PortalCooldown", this.portalCooldown);
-            nbt.putUUID("UUID", this.getUUID());
-            Component component = this.getCustomName();
-            if (component != null) {
-                nbt.putString("CustomName", Component.Serializer.toJson(component, this.registryAccess()));
+            Vec3 vec3d = this.getDeltaMovement();
+
+            nbttagcompound.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.z));
+
+            // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero
+            // TODO: make sure this is the best way to address this.
+            if (Float.isNaN(this.yRot)) {
+                this.yRot = 0;
+            }
+
+            if (Float.isNaN(this.xRot)) {
+                this.xRot = 0;
+            }
+            // CraftBukkit end
+
+            nbttagcompound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
+            nbttagcompound.putFloat("FallDistance", this.fallDistance);
+            nbttagcompound.putShort("Fire", (short) this.remainingFireTicks);
+            nbttagcompound.putShort("Air", (short) this.getAirSupply());
+            nbttagcompound.putBoolean("OnGround", this.onGround());
+            nbttagcompound.putBoolean("Invulnerable", this.invulnerable);
+            nbttagcompound.putInt("PortalCooldown", this.portalCooldown);
+            // CraftBukkit start - selectively save uuid and world
+            if (includeAll) {
+                nbttagcompound.putUUID("UUID", this.getUUID());
+                // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast
+                nbttagcompound.putLong("WorldUUIDLeast", ((ServerLevel) this.level).getWorld().getUID().getLeastSignificantBits());
+                nbttagcompound.putLong("WorldUUIDMost", ((ServerLevel) this.level).getWorld().getUID().getMostSignificantBits());
+            }
+            nbttagcompound.putInt("Bukkit.updateLevel", Entity.CURRENT_LEVEL);
+            if (!this.persist) {
+                nbttagcompound.putBoolean("Bukkit.persist", this.persist);
+            }
+            if (!this.visibleByDefault) {
+                nbttagcompound.putBoolean("Bukkit.visibleByDefault", this.visibleByDefault);
+            }
+            if (this.persistentInvisibility) {
+                nbttagcompound.putBoolean("Bukkit.invisible", this.persistentInvisibility);
+            }
+            // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+            if (this.maxAirTicks != this.getDefaultMaxAirSupply()) {
+                nbttagcompound.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply());
+            }
+            nbttagcompound.putInt("Spigot.ticksLived", this.tickCount);
+            // CraftBukkit end
+            Component ichatbasecomponent = this.getCustomName();
+
+            if (ichatbasecomponent != null) {
+                nbttagcompound.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent, this.registryAccess()));
             }
 
             if (this.isCustomNameVisible()) {
-                nbt.putBoolean("CustomNameVisible", this.isCustomNameVisible());
+                nbttagcompound.putBoolean("CustomNameVisible", this.isCustomNameVisible());
             }
 
             if (this.isSilent()) {
-                nbt.putBoolean("Silent", this.isSilent());
+                nbttagcompound.putBoolean("Silent", this.isSilent());
             }
 
             if (this.isNoGravity()) {
-                nbt.putBoolean("NoGravity", this.isNoGravity());
+                nbttagcompound.putBoolean("NoGravity", this.isNoGravity());
             }
 
             if (this.hasGlowingTag) {
-                nbt.putBoolean("Glowing", true);
+                nbttagcompound.putBoolean("Glowing", true);
             }
 
             int i = this.getTicksFrozen();
+
             if (i > 0) {
-                nbt.putInt("TicksFrozen", this.getTicksFrozen());
+                nbttagcompound.putInt("TicksFrozen", this.getTicksFrozen());
             }
 
             if (this.hasVisualFire) {
-                nbt.putBoolean("HasVisualFire", this.hasVisualFire);
+                nbttagcompound.putBoolean("HasVisualFire", this.hasVisualFire);
             }
 
+            ListTag nbttaglist;
+            Iterator iterator;
+
             if (!this.tags.isEmpty()) {
-                ListTag listTag = new ListTag();
+                nbttaglist = new ListTag();
+                iterator = this.tags.iterator();
 
-                for (String string : this.tags) {
-                    listTag.add(StringTag.valueOf(string));
+                while (iterator.hasNext()) {
+                    String s = (String) iterator.next();
+
+                    nbttaglist.add(StringTag.valueOf(s));
                 }
 
-                nbt.put("Tags", listTag);
+                nbttagcompound.put("Tags", nbttaglist);
             }
 
-            this.addAdditionalSaveData(nbt);
+            this.addAdditionalSaveData(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
             if (this.isVehicle()) {
-                ListTag listTag2 = new ListTag();
+                nbttaglist = new ListTag();
+                iterator = this.getPassengers().iterator();
 
-                for (Entity entity : this.getPassengers()) {
-                    CompoundTag compoundTag = new CompoundTag();
-                    if (entity.saveAsPassenger(compoundTag)) {
-                        listTag2.add(compoundTag);
+                while (iterator.hasNext()) {
+                    Entity entity = (Entity) iterator.next();
+                    CompoundTag nbttagcompound1 = new CompoundTag();
+
+                    if (entity.saveAsPassenger(nbttagcompound1, includeAll)) { // CraftBukkit - pass on includeAll
+                        nbttaglist.add(nbttagcompound1);
                     }
                 }
 
-                if (!listTag2.isEmpty()) {
-                    nbt.put("Passengers", listTag2);
+                if (!nbttaglist.isEmpty()) {
+                    nbttagcompound.put("Passengers", nbttaglist);
                 }
             }
 
-            return nbt;
-        } catch (Throwable var9) {
-            CrashReport crashReport = CrashReport.forThrowable(var9, "Saving entity NBT");
-            CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being saved");
-            this.fillCrashReportCategory(crashReportCategory);
-            throw new ReportedException(crashReport);
+            // CraftBukkit start - stores eventually existing bukkit values
+            if (this.bukkitEntity != null) {
+                this.bukkitEntity.storeBukkitValues(nbttagcompound);
+            }
+            // CraftBukkit end
+            // Paper start
+            if (this.origin != null) {
+                UUID originWorld = this.originWorld != null ? this.originWorld : this.level != null ? this.level.getWorld().getUID() : null;
+                if (originWorld != null) {
+                    nbttagcompound.putUUID("Paper.OriginWorld", originWorld);
+                }
+                nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ()));
+            }
+            if (spawnReason != null) {
+                nbttagcompound.putString("Paper.SpawnReason", spawnReason.name());
+            }
+            // Save entity's from mob spawner status
+            if (spawnedViaMobSpawner) {
+                nbttagcompound.putBoolean("Paper.FromMobSpawner", true);
+            }
+            if (fromNetherPortal) {
+                nbttagcompound.putBoolean("Paper.FromNetherPortal", true);
+            }
+            if (freezeLocked) {
+                nbttagcompound.putBoolean("Paper.FreezeLock", true);
+            }
+            // Paper end
+            return nbttagcompound;
+        } catch (Throwable throwable) {
+            CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT");
+            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being saved");
+
+            this.fillCrashReportCategory(crashreportsystemdetails);
+            throw new ReportedException(crashreport);
         }
     }
 
     public void load(CompoundTag nbt) {
         try {
-            ListTag listTag = nbt.getList("Pos", 6);
-            ListTag listTag2 = nbt.getList("Motion", 6);
-            ListTag listTag3 = nbt.getList("Rotation", 5);
-            double d = listTag2.getDouble(0);
-            double e = listTag2.getDouble(1);
-            double f = listTag2.getDouble(2);
-            this.setDeltaMovement(Math.abs(d) > 10.0 ? 0.0 : d, Math.abs(e) > 10.0 ? 0.0 : e, Math.abs(f) > 10.0 ? 0.0 : f);
-            double g = 3.0000512E7;
-            this.setPosRaw(
-                Mth.clamp(listTag.getDouble(0), -3.0000512E7, 3.0000512E7),
-                Mth.clamp(listTag.getDouble(1), -2.0E7, 2.0E7),
-                Mth.clamp(listTag.getDouble(2), -3.0000512E7, 3.0000512E7)
-            );
-            this.setYRot(listTag3.getFloat(0));
-            this.setXRot(listTag3.getFloat(1));
+            ListTag nbttaglist = nbt.getList("Pos", 6);
+            ListTag nbttaglist1 = nbt.getList("Motion", 6);
+            ListTag nbttaglist2 = nbt.getList("Rotation", 5);
+            double d0 = nbttaglist1.getDouble(0);
+            double d1 = nbttaglist1.getDouble(1);
+            double d2 = nbttaglist1.getDouble(2);
+
+            this.setDeltaMovement(Math.abs(d0) > 10.0D ? 0.0D : d0, Math.abs(d1) > 10.0D ? 0.0D : d1, Math.abs(d2) > 10.0D ? 0.0D : d2);
+            double d3 = 3.0000512E7D;
+
+            this.setPosRaw(Mth.clamp(nbttaglist.getDouble(0), -3.0000512E7D, 3.0000512E7D), Mth.clamp(nbttaglist.getDouble(1), -2.0E7D, 2.0E7D), Mth.clamp(nbttaglist.getDouble(2), -3.0000512E7D, 3.0000512E7D));
+            this.setYRot(nbttaglist2.getFloat(0));
+            this.setXRot(nbttaglist2.getFloat(1));
             this.setOldPosAndRot();
             this.setYHeadRot(this.getYRot());
             this.setYBodyRot(this.getYRot());
@@ -1760,49 +2598,118 @@
                 this.stringUUID = this.uuid.toString();
             }
 
-            if (!Double.isFinite(this.getX()) || !Double.isFinite(this.getY()) || !Double.isFinite(this.getZ())) {
-                throw new IllegalStateException("Entity has invalid position");
-            } else if (Double.isFinite((double)this.getYRot()) && Double.isFinite((double)this.getXRot())) {
-                this.reapplyPosition();
-                this.setRot(this.getYRot(), this.getXRot());
-                if (nbt.contains("CustomName", 8)) {
-                    String string = nbt.getString("CustomName");
+            if (Double.isFinite(this.getX()) && Double.isFinite(this.getY()) && Double.isFinite(this.getZ())) {
+                if (Double.isFinite((double) this.getYRot()) && Double.isFinite((double) this.getXRot())) {
+                    this.reapplyPosition();
+                    this.setRot(this.getYRot(), this.getXRot());
+                    if (nbt.contains("CustomName", 8)) {
+                        String s = nbt.getString("CustomName");
 
-                    try {
-                        this.setCustomName(Component.Serializer.fromJson(string, this.registryAccess()));
-                    } catch (Exception var16) {
-                        LOGGER.warn("Failed to parse entity custom name {}", string, var16);
+                        try {
+                            this.setCustomName(Component.Serializer.fromJson(s, this.registryAccess()));
+                        } catch (Exception exception) {
+                            Entity.LOGGER.warn("Failed to parse entity custom name {}", s, exception);
+                        }
                     }
-                }
 
-                this.setCustomNameVisible(nbt.getBoolean("CustomNameVisible"));
-                this.setSilent(nbt.getBoolean("Silent"));
-                this.setNoGravity(nbt.getBoolean("NoGravity"));
-                this.setGlowingTag(nbt.getBoolean("Glowing"));
-                this.setTicksFrozen(nbt.getInt("TicksFrozen"));
-                this.hasVisualFire = nbt.getBoolean("HasVisualFire");
-                if (nbt.contains("Tags", 9)) {
-                    this.tags.clear();
-                    ListTag listTag4 = nbt.getList("Tags", 8);
-                    int i = Math.min(listTag4.size(), 1024);
+                    this.setCustomNameVisible(nbt.getBoolean("CustomNameVisible"));
+                    this.setSilent(nbt.getBoolean("Silent"));
+                    this.setNoGravity(nbt.getBoolean("NoGravity"));
+                    this.setGlowingTag(nbt.getBoolean("Glowing"));
+                    this.setTicksFrozen(nbt.getInt("TicksFrozen"));
+                    this.hasVisualFire = nbt.getBoolean("HasVisualFire");
+                    if (nbt.contains("Tags", 9)) {
+                        this.tags.clear();
+                        ListTag nbttaglist3 = nbt.getList("Tags", 8);
+                        int i = Math.min(nbttaglist3.size(), 1024);
 
-                    for (int j = 0; j < i; j++) {
-                        this.tags.add(listTag4.getString(j));
+                        for (int j = 0; j < i; ++j) {
+                            this.tags.add(nbttaglist3.getString(j));
+                        }
                     }
-                }
 
-                this.readAdditionalSaveData(nbt);
-                if (this.repositionEntityAfterLoad()) {
-                    this.reapplyPosition();
+                    this.readAdditionalSaveData(nbt);
+                    if (this.repositionEntityAfterLoad()) {
+                        this.reapplyPosition();
+                    }
+
+                } else {
+                    throw new IllegalStateException("Entity has invalid rotation");
                 }
             } else {
-                throw new IllegalStateException("Entity has invalid rotation");
+                throw new IllegalStateException("Entity has invalid position");
             }
-        } catch (Throwable var17) {
-            CrashReport crashReport = CrashReport.forThrowable(var17, "Loading entity NBT");
-            CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
-            this.fillCrashReportCategory(crashReportCategory);
-            throw new ReportedException(crashReport);
+
+            // CraftBukkit start
+            // Spigot start
+            if (this instanceof net.minecraft.world.entity.LivingEntity) {
+                this.tickCount = nbt.getInt("Spigot.ticksLived");
+            }
+            // Spigot end
+            this.persist = !nbt.contains("Bukkit.persist") || nbt.getBoolean("Bukkit.persist");
+            this.visibleByDefault = !nbt.contains("Bukkit.visibleByDefault") || nbt.getBoolean("Bukkit.visibleByDefault");
+            // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+            if (nbt.contains("Bukkit.MaxAirSupply")) {
+                this.maxAirTicks = nbt.getInt("Bukkit.MaxAirSupply");
+            }
+            // CraftBukkit end
+
+            // CraftBukkit start
+            // Paper - move world parsing/loading to PlayerList#placeNewPlayer
+            this.getBukkitEntity().readBukkitValues(nbt);
+            if (nbt.contains("Bukkit.invisible")) {
+                boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible");
+                this.setInvisible(bukkitInvisible);
+                this.persistentInvisibility = bukkitInvisible;
+            }
+            // CraftBukkit end
+
+            // Paper start
+            ListTag originTag = nbt.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE);
+            if (!originTag.isEmpty()) {
+                UUID originWorld = null;
+                if (nbt.contains("Paper.OriginWorld")) {
+                    originWorld = nbt.getUUID("Paper.OriginWorld");
+                } else if (this.level != null) {
+                    originWorld = this.level.getWorld().getUID();
+                }
+                this.originWorld = originWorld;
+                origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2));
+            }
+
+            spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
+            fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal");
+            if (nbt.contains("Paper.SpawnReason")) {
+                String spawnReasonName = nbt.getString("Paper.SpawnReason");
+                try {
+                    spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName);
+                } catch (Exception ignored) {
+                    LOGGER.error("Unknown SpawnReason " + spawnReasonName + " for " + this);
+                }
+            }
+            if (spawnReason == null) {
+                if (spawnedViaMobSpawner) {
+                    spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER;
+                } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) {
+                    if (!nbt.getBoolean("PersistenceRequired")) {
+                        spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL;
+                    }
+                }
+            }
+            if (spawnReason == null) {
+                spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT;
+            }
+            if (nbt.contains("Paper.FreezeLock")) {
+                freezeLocked = nbt.getBoolean("Paper.FreezeLock");
+            }
+            // Paper end
+
+        } catch (Throwable throwable) {
+            CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT");
+            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded");
+
+            this.fillCrashReportCategory(crashreportsystemdetails);
+            throw new ReportedException(crashreport);
         }
     }
 
@@ -1812,33 +2719,48 @@
 
     @Nullable
     public final String getEncodeId() {
-        EntityType<?> entityType = this.getType();
-        ResourceLocation resourceLocation = EntityType.getKey(entityType);
-        return entityType.canSerialize() && resourceLocation != null ? resourceLocation.toString() : null;
+        EntityType<?> entitytypes = this.getType();
+        ResourceLocation minecraftkey = EntityType.getKey(entitytypes);
+
+        return entitytypes.canSerialize() && minecraftkey != null ? minecraftkey.toString() : null;
+    }
+
+    // CraftBukkit start - allow excluding certain data when saving
+    protected void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
+        this.addAdditionalSaveData(nbttagcompound);
     }
+    // CraftBukkit end
 
     protected abstract void readAdditionalSaveData(CompoundTag nbt);
 
     protected abstract void addAdditionalSaveData(CompoundTag nbt);
 
     protected ListTag newDoubleList(double... values) {
-        ListTag listTag = new ListTag();
+        ListTag nbttaglist = new ListTag();
+        double[] adouble1 = values;
+        int i = values.length;
 
-        for (double d : values) {
-            listTag.add(DoubleTag.valueOf(d));
+        for (int j = 0; j < i; ++j) {
+            double d0 = adouble1[j];
+
+            nbttaglist.add(DoubleTag.valueOf(d0));
         }
 
-        return listTag;
+        return nbttaglist;
     }
 
     protected ListTag newFloatList(float... values) {
-        ListTag listTag = new ListTag();
+        ListTag nbttaglist = new ListTag();
+        float[] afloat1 = values;
+        int i = values.length;
 
-        for (float f : values) {
-            listTag.add(FloatTag.valueOf(f));
+        for (int j = 0; j < i; ++j) {
+            float f = afloat1[j];
+
+            nbttaglist.add(FloatTag.valueOf(f));
         }
 
-        return listTag;
+        return nbttaglist;
     }
 
     @Nullable
@@ -1848,7 +2770,7 @@
 
     @Nullable
     public ItemEntity spawnAtLocation(ItemLike item, int yOffset) {
-        return this.spawnAtLocation(new ItemStack(item), (float)yOffset);
+        return this.spawnAtLocation(new ItemStack(item), (float) yOffset);
     }
 
     @Nullable
@@ -1858,15 +2780,64 @@
 
     @Nullable
     public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) {
+        // Paper start - Restore vanilla drops behavior
+        return this.spawnAtLocation(stack, yOffset, null);
+    }
+    public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer<ItemStack> dropConsumer) {
+        public DefaultDrop(final ItemStack stack, final java.util.function.Consumer<ItemStack> dropConsumer) {
+            this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer);
+        }
+
+        public void runConsumer(final org.bukkit.World fallbackWorld, final Location fallbackLoc) {
+            if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) {
+                fallbackWorld.dropItem(fallbackLoc, this.stack);
+            } else {
+                this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack));
+            }
+        }
+    }
+    @Nullable
+    public ItemEntity spawnAtLocation(ItemStack stack, float yOffset, @Nullable java.util.function.Consumer<? super ItemEntity> delayedAddConsumer) {
+        // Paper end - Restore vanilla drops behavior
         if (stack.isEmpty()) {
             return null;
         } else if (this.level().isClientSide) {
             return null;
         } else {
-            ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY() + (double)yOffset, this.getZ(), stack);
-            itemEntity.setDefaultPickUpDelay();
-            this.level().addFreshEntity(itemEntity);
-            return itemEntity;
+            // CraftBukkit start - Capture drops for death event
+            if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) {
+                // Paper start - Restore vanilla drops behavior
+                ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> {
+                    ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer
+                    itemEntity.setDefaultPickUpDelay();
+                    this.level.addFreshEntity(itemEntity);
+                    if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity);
+                }));
+                // Paper end - Restore vanilla drops behavior
+                return null;
+            }
+            // CraftBukkit end
+            ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original
+            stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe
+
+            entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer)
+            // Paper start - Call EntityDropItemEvent
+            return this.spawnAtLocation(entityitem);
+        }
+    }
+    @Nullable
+    public ItemEntity spawnAtLocation(ItemEntity entityitem) {
+        {
+            // Paper end - Call EntityDropItemEvent
+            // CraftBukkit start
+            EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
+            Bukkit.getPluginManager().callEvent(event);
+            if (event.isCancelled()) {
+                return null;
+            }
+            // CraftBukkit end
+            this.level().addFreshEntity(entityitem);
+            return entityitem;
         }
     }
 
@@ -1875,45 +2846,113 @@
     }
 
     public boolean isInWall() {
+        // Paper start - optimise collisions
         if (this.noPhysics) {
             return false;
-        } else {
-            float f = this.dimensions.width() * 0.8F;
-            AABB aABB = AABB.ofSize(this.getEyePosition(), (double)f, 1.0E-6, (double)f);
-            return BlockPos.betweenClosedStream(aABB)
-                .anyMatch(
-                    pos -> {
-                        BlockState blockState = this.level().getBlockState(pos);
-                        return !blockState.isAir()
-                            && blockState.isSuffocating(this.level(), pos)
-                            && Shapes.joinIsNotEmpty(
-                                blockState.getCollisionShape(this.level(), pos).move((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()),
-                                Shapes.create(aABB),
-                                BooleanOp.AND
-                            );
+        }
+
+        final float reducedWith = this.dimensions.width() * 0.8F;
+        final AABB box = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith);
+
+        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) {
+            return false;
+        }
+
+        final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos();
+
+        final int minX = Mth.floor(box.minX);
+        final int minY = Mth.floor(box.minY);
+        final int minZ = Mth.floor(box.minZ);
+        final int maxX = Mth.floor(box.maxX);
+        final int maxY = Mth.floor(box.maxY);
+        final int maxZ = Mth.floor(box.maxZ);
+
+        final net.minecraft.world.level.chunk.ChunkSource chunkProvider = this.level.getChunkSource();
+
+        long lastChunkKey = ChunkPos.INVALID_CHUNK_POS;
+        net.minecraft.world.level.chunk.LevelChunk lastChunk = null;
+        for (int fz = minZ; fz <= maxZ; ++fz) {
+            tempPos.setZ(fz);
+            for (int fx = minX; fx <= maxX; ++fx) {
+                final int newChunkX = fx >> 4;
+                final int newChunkZ = fz >> 4;
+                final net.minecraft.world.level.chunk.LevelChunk chunk = lastChunkKey == (lastChunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ?
+                    lastChunk : (lastChunk = (net.minecraft.world.level.chunk.LevelChunk)chunkProvider.getChunk(newChunkX, newChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true));
+                tempPos.setX(fx);
+                for (int fy = minY; fy <= maxY; ++fy) {
+                    tempPos.setY(fy);
+
+                    final BlockState state = chunk.getBlockState(tempPos);
+
+                    if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) {
+                        continue;
                     }
-                );
+
+                    // Yes, it does not use the Entity context stuff.
+                    final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos);
+
+                    if (collisionShape.isEmpty()) {
+                        continue;
+                    }
+
+                    final AABB toCollide = box.move(-(double)fx, -(double)fy, -(double)fz);
+
+                    final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation();
+                    if (singleAABB != null) {
+                        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) {
+                            return true;
+                        }
+                        continue;
+                    }
+
+                    if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) {
+                        return true;
+                    }
+                    continue;
+                }
+            }
         }
+
+        return false;
+        // Paper end - optimise collisions
     }
 
     public InteractionResult interact(Player player, InteractionHand hand) {
         if (this.isAlive() && this instanceof Leashable leashable) {
             if (leashable.getLeashHolder() == player) {
                 if (!this.level().isClientSide()) {
-                    leashable.dropLeash(true, !player.hasInfiniteMaterials());
+                    // CraftBukkit start - fire PlayerUnleashEntityEvent
+                    // Paper start - Expand EntityUnleashEvent
+                    org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials());
+                    if (event.isCancelled()) {
+                        // Paper end - Expand EntityUnleashEvent
+                        ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
+                        return InteractionResult.PASS;
+                    }
+                    // CraftBukkit end
+                    leashable.dropLeash(true, event.isDropLeash()); // Paper - Expand EntityUnleashEvent
                     this.gameEvent(GameEvent.ENTITY_INTERACT, player);
                 }
 
                 return InteractionResult.sidedSuccess(this.level().isClientSide);
             }
 
-            ItemStack itemStack = player.getItemInHand(hand);
-            if (itemStack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
+            ItemStack itemstack = player.getItemInHand(hand);
+
+            if (itemstack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
                 if (!this.level().isClientSide()) {
+                    // CraftBukkit start - fire PlayerLeashEntityEvent
+                    if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) {
+                        // ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item // Paper - Fix inventory desync
+                        ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
+                        player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
+                        return InteractionResult.PASS;
+                    }
+                    // CraftBukkit end
                     leashable.setLeashedTo(player, true);
                 }
 
-                itemStack.shrink(1);
+                itemstack.shrink(1);
                 return InteractionResult.sidedSuccess(this.level().isClientSide);
             }
         }
@@ -1944,14 +2983,14 @@
     }
 
     protected void positionRider(Entity passenger, Entity.MoveFunction positionUpdater) {
-        Vec3 vec3 = this.getPassengerRidingPosition(passenger);
-        Vec3 vec32 = passenger.getVehicleAttachmentPoint(this);
-        positionUpdater.accept(passenger, vec3.x - vec32.x, vec3.y - vec32.y, vec3.z - vec32.z);
-    }
+        Vec3 vec3d = this.getPassengerRidingPosition(passenger);
+        Vec3 vec3d1 = passenger.getVehicleAttachmentPoint(this);
 
-    public void onPassengerTurned(Entity passenger) {
+        positionUpdater.accept(passenger, vec3d.x - vec3d1.x, vec3d.y - vec3d1.y, vec3d.z - vec3d1.z);
     }
 
+    public void onPassengerTurned(Entity passenger) {}
+
     public Vec3 getVehicleAttachmentPoint(Entity vehicle) {
         return this.getAttachments().get(EntityAttachment.VEHICLE, 0, this.yRot);
     }
@@ -1961,11 +3000,12 @@
     }
 
     protected Vec3 getPassengerAttachmentPoint(Entity passenger, EntityDimensions dimensions, float scaleFactor) {
-        return getDefaultPassengerAttachmentPoint(this, passenger, dimensions.attachments());
+        return Entity.getDefaultPassengerAttachmentPoint(this, passenger, dimensions.attachments());
     }
 
     protected static Vec3 getDefaultPassengerAttachmentPoint(Entity vehicle, Entity passenger, EntityAttachments attachments) {
         int i = vehicle.getPassengers().indexOf(passenger);
+
         return attachments.getClamped(EntityAttachment.PASSENGER, i, vehicle.yRot);
     }
 
@@ -1974,35 +3014,58 @@
     }
 
     public boolean showVehicleHealth() {
-        return this instanceof LivingEntity;
+        return this instanceof net.minecraft.world.entity.LivingEntity;
     }
 
     public boolean startRiding(Entity entity, boolean force) {
-        if (entity == this.vehicle) {
+        if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins)
             return false;
         } else if (!entity.couldAcceptPassenger()) {
             return false;
         } else {
-            for (Entity entity2 = entity; entity2.vehicle != null; entity2 = entity2.vehicle) {
-                if (entity2.vehicle == this) {
+            for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) {
+                if (entity1.vehicle == this) {
                     return false;
                 }
             }
 
-            if (force || this.canRide(entity) && entity.canAddPassenger(this)) {
+            if (!force && (!this.canRide(entity) || !entity.canAddPassenger(this))) {
+                return false;
+            } else {
+                // CraftBukkit start
+                if (entity.getBukkitEntity() instanceof Vehicle && this.getBukkitEntity() instanceof LivingEntity) {
+                    VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) entity.getBukkitEntity(), this.getBukkitEntity());
+                    // Suppress during worldgen
+                    if (this.valid) {
+                        Bukkit.getPluginManager().callEvent(event);
+                    }
+                    if (event.isCancelled()) {
+                        return false;
+                    }
+                }
+
+                EntityMountEvent event = new EntityMountEvent(this.getBukkitEntity(), entity.getBukkitEntity());
+                // Suppress during worldgen
+                if (this.valid) {
+                    Bukkit.getPluginManager().callEvent(event);
+                }
+                if (event.isCancelled()) {
+                    return false;
+                }
+                // CraftBukkit end
                 if (this.isPassenger()) {
                     this.stopRiding();
                 }
 
-                this.setPose(Pose.STANDING);
+                this.setPose(net.minecraft.world.entity.Pose.STANDING);
                 this.vehicle = entity;
                 this.vehicle.addPassenger(this);
-                entity.getIndirectPassengersStream()
-                    .filter(passenger -> passenger instanceof ServerPlayer)
-                    .forEach(player -> CriteriaTriggers.START_RIDING_TRIGGER.trigger((ServerPlayer)player));
+                entity.getIndirectPassengersStream().filter((entity2) -> {
+                    return entity2 instanceof ServerPlayer;
+                }).forEach((entity2) -> {
+                    CriteriaTriggers.START_RIDING_TRIGGER.trigger((ServerPlayer) entity2);
+                });
                 return true;
-            } else {
-                return false;
             }
         }
     }
@@ -2012,21 +3075,35 @@
     }
 
     public void ejectPassengers() {
-        for (int i = this.passengers.size() - 1; i >= 0; i--) {
-            this.passengers.get(i).stopRiding();
+        for (int i = this.passengers.size() - 1; i >= 0; --i) {
+            ((Entity) this.passengers.get(i)).stopRiding();
         }
+
     }
 
     public void removeVehicle() {
+        // Paper start - Force entity dismount during teleportation
+        this.removeVehicle(false);
+    }
+    public void removeVehicle(boolean suppressCancellation) {
+        // Paper end - Force entity dismount during teleportation
         if (this.vehicle != null) {
             Entity entity = this.vehicle;
+
             this.vehicle = null;
-            entity.removePassenger(this);
+            if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation
         }
+
     }
 
     public void stopRiding() {
-        this.removeVehicle();
+        // Paper start - Force entity dismount during teleportation
+        this.stopRiding(false);
+    }
+
+    public void stopRiding(boolean suppressCancellation) {
+        this.removeVehicle(suppressCancellation);
+        // Paper end - Force entity dismount during teleportation
     }
 
     protected void addPassenger(Entity passenger) {
@@ -2037,6 +3114,7 @@
                 this.passengers = ImmutableList.of(passenger);
             } else {
                 List<Entity> list = Lists.newArrayList(this.passengers);
+
                 if (!this.level().isClientSide && passenger instanceof Player && !(this.getFirstPassenger() instanceof Player)) {
                     list.add(0, passenger);
                 } else {
@@ -2050,19 +3128,53 @@
         }
     }
 
-    protected void removePassenger(Entity passenger) {
-        if (passenger.getVehicle() == this) {
+    // Paper start - Force entity dismount during teleportation
+    protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);}
+    protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit
+        // Paper end - Force entity dismount during teleportation
+        if (entity.getVehicle() == this) {
             throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
         } else {
-            if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
+            // CraftBukkit start
+            CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle();
+            Entity orig = craft == null ? null : craft.getHandle();
+            if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) {
+                VehicleExitEvent event = new VehicleExitEvent(
+                        (Vehicle) this.getBukkitEntity(),
+                        (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation
+                );
+                // Suppress during worldgen
+                if (this.valid) {
+                    Bukkit.getPluginManager().callEvent(event);
+                }
+                CraftEntity craftn = (CraftEntity) entity.getBukkitEntity().getVehicle();
+                Entity n = craftn == null ? null : craftn.getHandle();
+                if (event.isCancelled() || n != orig) {
+                    return false;
+                }
+            }
+
+            EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation
+            // Suppress during worldgen
+            if (this.valid) {
+                Bukkit.getPluginManager().callEvent(event);
+            }
+            if (event.isCancelled()) {
+                return false;
+            }
+            // CraftBukkit end
+            if (this.passengers.size() == 1 && this.passengers.get(0) == entity) {
                 this.passengers = ImmutableList.of();
             } else {
-                this.passengers = this.passengers.stream().filter(entity -> entity != passenger).collect(ImmutableList.toImmutableList());
+                this.passengers = (ImmutableList) this.passengers.stream().filter((entity1) -> {
+                    return entity1 != entity;
+                }).collect(ImmutableList.toImmutableList());
             }
 
-            passenger.boardingCooldown = 60;
-            this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger);
+            entity.boardingCooldown = 60;
+            this.gameEvent(GameEvent.ENTITY_DISMOUNT, entity);
         }
+        return true; // CraftBukkit
     }
 
     protected boolean canAddPassenger(Entity passenger) {
@@ -2111,12 +3223,13 @@
     }
 
     public Vec3 getHandHoldingItemAngle(Item item) {
-        if (!(this instanceof Player player)) {
+        if (!(this instanceof Player entityhuman)) {
             return Vec3.ZERO;
         } else {
-            boolean bl = player.getOffhandItem().is(item) && !player.getMainHandItem().is(item);
-            HumanoidArm humanoidArm = bl ? player.getMainArm().getOpposite() : player.getMainArm();
-            return this.calculateViewVector(0.0F, this.getYRot() + (float)(humanoidArm == HumanoidArm.RIGHT ? 80 : -80)).scale(0.5);
+            boolean flag = entityhuman.getOffhandItem().is(item) && !entityhuman.getMainHandItem().is(item);
+            HumanoidArm enummainhand = flag ? entityhuman.getMainArm().getOpposite() : entityhuman.getMainArm();
+
+            return this.calculateViewVector(0.0F, this.getYRot() + (float) (enummainhand == HumanoidArm.RIGHT ? 80 : -80)).scale(0.5D);
         }
     }
 
@@ -2138,35 +3251,41 @@
             } else {
                 this.portalProcess = new PortalProcessor(portal, pos.immutable());
             }
+
         }
     }
 
     protected void handlePortal() {
-        if (this.level() instanceof ServerLevel serverLevel) {
+        Level world = this.level();
+
+        if (world instanceof ServerLevel worldserver) {
             this.processPortalCooldown();
             if (this.portalProcess != null) {
-                if (this.portalProcess.processPortalTeleportation(serverLevel, this, this.canUsePortal(false))) {
-                    serverLevel.getProfiler().push("portal");
+                if (this.portalProcess.processPortalTeleportation(worldserver, this, this.canUsePortal(false))) {
+                    worldserver.getProfiler().push("portal");
                     this.setPortalCooldown();
-                    DimensionTransition dimensionTransition = this.portalProcess.getPortalDestination(serverLevel, this);
-                    if (dimensionTransition != null) {
-                        ServerLevel serverLevel3 = dimensionTransition.newLevel();
-                        if (serverLevel.getServer().isLevelEnabled(serverLevel3)
-                            && (serverLevel3.dimension() == serverLevel.dimension() || this.canChangeDimensions(serverLevel, serverLevel3))) {
-                            this.changeDimension(dimensionTransition);
+                    DimensionTransition dimensiontransition = this.portalProcess.getPortalDestination(worldserver, this);
+
+                    if (dimensiontransition != null) {
+                        ServerLevel worldserver1 = dimensiontransition.newLevel();
+
+                        if (this instanceof ServerPlayer || (worldserver1 != null && (worldserver1.dimension() == worldserver.dimension() || this.canChangeDimensions(worldserver, worldserver1)))) { // CraftBukkit - always call event for players
+                            this.changeDimension(dimensiontransition);
                         }
                     }
 
-                    serverLevel.getProfiler().pop();
+                    worldserver.getProfiler().pop();
                 } else if (this.portalProcess.hasExpired()) {
                     this.portalProcess = null;
                 }
+
             }
         }
     }
 
     public int getDimensionChangingDelay() {
         Entity entity = this.getFirstPassenger();
+
         return entity instanceof ServerPlayer ? entity.getDimensionChangingDelay() : 300;
     }
 
@@ -2174,22 +3293,22 @@
         this.setDeltaMovement(x, y, z);
     }
 
-    public void handleDamageEvent(DamageSource damageSource) {
-    }
+    public void handleDamageEvent(DamageSource damageSource) {}
 
     public void handleEntityEvent(byte status) {
         switch (status) {
             case 53:
                 HoneyBlock.showSlideParticles(this);
+            default:
         }
     }
 
-    public void animateHurt(float yaw) {
-    }
+    public void animateHurt(float yaw) {}
 
     public boolean isOnFire() {
-        boolean bl = this.level() != null && this.level().isClientSide;
-        return !this.fireImmune() && (this.remainingFireTicks > 0 || bl && this.getSharedFlag(0));
+        boolean flag = this.level() != null && this.level().isClientSide;
+
+        return !this.fireImmune() && (this.remainingFireTicks > 0 || flag && this.getSharedFlag(0));
     }
 
     public boolean isPassenger() {
@@ -2233,7 +3352,7 @@
     }
 
     public boolean isCrouching() {
-        return this.hasPose(Pose.CROUCHING);
+        return this.hasPose(net.minecraft.world.entity.Pose.CROUCHING);
     }
 
     public boolean isSprinting() {
@@ -2249,7 +3368,7 @@
     }
 
     public boolean isVisuallySwimming() {
-        return this.hasPose(Pose.SWIMMING);
+        return this.hasPose(net.minecraft.world.entity.Pose.SWIMMING);
     }
 
     public boolean isVisuallyCrawling() {
@@ -2257,6 +3376,13 @@
     }
 
     public void setSwimming(boolean swimming) {
+        // CraftBukkit start
+        if (this.valid && this.isSwimming() != swimming && this instanceof net.minecraft.world.entity.LivingEntity) {
+            if (CraftEventFactory.callToggleSwimEvent((net.minecraft.world.entity.LivingEntity) this, swimming).isCancelled()) {
+                return;
+            }
+        }
+        // CraftBukkit end
         this.setSharedFlag(4, swimming);
     }
 
@@ -2281,8 +3407,9 @@
         if (player.isSpectator()) {
             return false;
         } else {
-            Team team = this.getTeam();
-            return (team == null || player == null || player.getTeam() != team || !team.canSeeFriendlyInvisibles()) && this.isInvisible();
+            PlayerTeam scoreboardteam = this.getTeam();
+
+            return scoreboardteam != null && player != null && player.getTeam() == scoreboardteam && scoreboardteam.canSeeFriendlyInvisibles() ? false : this.isInvisible();
         }
     }
 
@@ -2290,62 +3417,80 @@
         return false;
     }
 
-    public void updateDynamicGameEventListener(BiConsumer<DynamicGameEventListener<?>, ServerLevel> callback) {
-    }
+    public void updateDynamicGameEventListener(BiConsumer<DynamicGameEventListener<?>, ServerLevel> callback) {}
 
     @Nullable
     public PlayerTeam getTeam() {
+        if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - Perf: Disable Scoreboards for non players by default
         return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName());
     }
 
     public boolean isAlliedTo(Entity other) {
-        return this.isAlliedTo(other.getTeam());
+        return this.isAlliedTo((Team) other.getTeam());
     }
 
     public boolean isAlliedTo(Team team) {
-        return this.getTeam() != null && this.getTeam().isAlliedTo(team);
+        return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false;
     }
 
+    // CraftBukkit - start
     public void setInvisible(boolean invisible) {
-        this.setSharedFlag(5, invisible);
+        if (!this.persistentInvisibility) { // Prevent Minecraft from removing our invisibility flag
+            this.setSharedFlag(5, invisible);
+        }
+        // CraftBukkit - end
     }
 
     public boolean getSharedFlag(int index) {
-        return (this.entityData.get(DATA_SHARED_FLAGS_ID) & 1 << index) != 0;
+        return ((Byte) this.entityData.get(Entity.DATA_SHARED_FLAGS_ID) & 1 << index) != 0;
     }
 
     public void setSharedFlag(int index, boolean value) {
-        byte b = this.entityData.get(DATA_SHARED_FLAGS_ID);
+        byte b0 = (Byte) this.entityData.get(Entity.DATA_SHARED_FLAGS_ID);
+
         if (value) {
-            this.entityData.set(DATA_SHARED_FLAGS_ID, (byte)(b | 1 << index));
+            this.entityData.set(Entity.DATA_SHARED_FLAGS_ID, (byte) (b0 | 1 << index));
         } else {
-            this.entityData.set(DATA_SHARED_FLAGS_ID, (byte)(b & ~(1 << index)));
+            this.entityData.set(Entity.DATA_SHARED_FLAGS_ID, (byte) (b0 & ~(1 << index)));
         }
+
     }
 
     public int getMaxAirSupply() {
-        return 300;
+        return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
     }
 
     public int getAirSupply() {
-        return this.entityData.get(DATA_AIR_SUPPLY_ID);
+        return (Integer) this.entityData.get(Entity.DATA_AIR_SUPPLY_ID);
     }
 
     public void setAirSupply(int air) {
-        this.entityData.set(DATA_AIR_SUPPLY_ID, air);
+        // CraftBukkit start
+        EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), air);
+        // Suppress during worldgen
+        if (this.valid) {
+            event.getEntity().getServer().getPluginManager().callEvent(event);
+        }
+        if (event.isCancelled() && this.getAirSupply() != air) {
+            this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID);
+            return;
+        }
+        this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount());
+        // CraftBukkit end
     }
 
     public int getTicksFrozen() {
-        return this.entityData.get(DATA_TICKS_FROZEN);
+        return (Integer) this.entityData.get(Entity.DATA_TICKS_FROZEN);
     }
 
     public void setTicksFrozen(int frozenTicks) {
-        this.entityData.set(DATA_TICKS_FROZEN, frozenTicks);
+        this.entityData.set(Entity.DATA_TICKS_FROZEN, frozenTicks);
     }
 
     public float getPercentFrozen() {
         int i = this.getTicksRequiredToFreeze();
-        return (float)Math.min(this.getTicksFrozen(), i) / (float)i;
+
+        return (float) Math.min(this.getTicksFrozen(), i) / (float) i;
     }
 
     public boolean isFullyFrozen() {
@@ -2358,46 +3503,82 @@
 
     public void thunderHit(ServerLevel world, LightningBolt lightning) {
         this.setRemainingFireTicks(this.remainingFireTicks + 1);
+        // CraftBukkit start
+        final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity();
+        final org.bukkit.entity.Entity stormBukkitEntity = lightning.getBukkitEntity();
+        final PluginManager pluginManager = Bukkit.getPluginManager();
+        // CraftBukkit end
+
         if (this.remainingFireTicks == 0) {
-            this.igniteForSeconds(8.0F);
+            // CraftBukkit start - Call a combust event when lightning strikes
+            EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8.0F);
+            pluginManager.callEvent(entityCombustEvent);
+            if (!entityCombustEvent.isCancelled()) {
+                this.igniteForSeconds(entityCombustEvent.getDuration(), false);
+            // Paper start - fix EntityCombustEvent cancellation
+            } else {
+                this.setRemainingFireTicks(this.remainingFireTicks - 1);
+            // Paper end - fix EntityCombustEvent cancellation
+            }
+            // CraftBukkit end
         }
 
-        this.hurt(this.damageSources().lightningBolt(), 5.0F);
+        // CraftBukkit start
+        if (thisBukkitEntity instanceof Hanging) {
+            HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity);
+            pluginManager.callEvent(hangingEvent);
+
+            if (hangingEvent.isCancelled()) {
+                return;
+            }
+        }
+
+        if (this.fireImmune()) {
+            return;
+        }
+
+        if (!this.hurt(this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API
+            return;
+        }
+        // CraftBukkit end
     }
 
     public void onAboveBubbleCol(boolean drag) {
-        Vec3 vec3 = this.getDeltaMovement();
-        double d;
+        Vec3 vec3d = this.getDeltaMovement();
+        double d0;
+
         if (drag) {
-            d = Math.max(-0.9, vec3.y - 0.03);
+            d0 = Math.max(-0.9D, vec3d.y - 0.03D);
         } else {
-            d = Math.min(1.8, vec3.y + 0.1);
+            d0 = Math.min(1.8D, vec3d.y + 0.1D);
         }
 
-        this.setDeltaMovement(vec3.x, d, vec3.z);
+        this.setDeltaMovement(vec3d.x, d0, vec3d.z);
     }
 
     public void onInsideBubbleColumn(boolean drag) {
-        Vec3 vec3 = this.getDeltaMovement();
-        double d;
+        Vec3 vec3d = this.getDeltaMovement();
+        double d0;
+
         if (drag) {
-            d = Math.max(-0.3, vec3.y - 0.03);
+            d0 = Math.max(-0.3D, vec3d.y - 0.03D);
         } else {
-            d = Math.min(0.7, vec3.y + 0.06);
+            d0 = Math.min(0.7D, vec3d.y + 0.06D);
         }
 
-        this.setDeltaMovement(vec3.x, d, vec3.z);
+        this.setDeltaMovement(vec3d.x, d0, vec3d.z);
         this.resetFallDistance();
     }
 
-    public boolean killedEntity(ServerLevel world, LivingEntity other) {
+    public boolean killedEntity(ServerLevel world, net.minecraft.world.entity.LivingEntity other) {
         return true;
     }
 
     public void checkSlowFallDistance() {
-        if (this.getDeltaMovement().y() > -0.5 && this.fallDistance > 1.0F) {
+        if (this.getDeltaMovement().y() > -0.5D && this.fallDistance > 1.0F) {
             this.fallDistance = 1.0F;
         }
+
     }
 
     public void resetFallDistance() {
@@ -2405,34 +3586,41 @@
     }
 
     protected void moveTowardsClosestSpace(double x, double y, double z) {
-        BlockPos blockPos = BlockPos.containing(x, y, z);
-        Vec3 vec3 = new Vec3(x - (double)blockPos.getX(), y - (double)blockPos.getY(), z - (double)blockPos.getZ());
-        BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
-        Direction direction = Direction.UP;
-        double d = Double.MAX_VALUE;
+        BlockPos blockposition = BlockPos.containing(x, y, z);
+        Vec3 vec3d = new Vec3(x - (double) blockposition.getX(), y - (double) blockposition.getY(), z - (double) blockposition.getZ());
+        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+        Direction enumdirection = Direction.UP;
+        double d3 = Double.MAX_VALUE;
+        Direction[] aenumdirection = new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, Direction.UP};
+        int i = aenumdirection.length;
 
-        for (Direction direction2 : new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, Direction.UP}) {
-            mutableBlockPos.setWithOffset(blockPos, direction2);
-            if (!this.level().getBlockState(mutableBlockPos).isCollisionShapeFullBlock(this.level(), mutableBlockPos)) {
-                double e = vec3.get(direction2.getAxis());
-                double f = direction2.getAxisDirection() == Direction.AxisDirection.POSITIVE ? 1.0 - e : e;
-                if (f < d) {
-                    d = f;
-                    direction = direction2;
+        for (int j = 0; j < i; ++j) {
+            Direction enumdirection1 = aenumdirection[j];
+
+            blockposition_mutableblockposition.setWithOffset(blockposition, enumdirection1);
+            if (!this.level().getBlockState(blockposition_mutableblockposition).isCollisionShapeFullBlock(this.level(), blockposition_mutableblockposition)) {
+                double d4 = vec3d.get(enumdirection1.getAxis());
+                double d5 = enumdirection1.getAxisDirection() == Direction.AxisDirection.POSITIVE ? 1.0D - d4 : d4;
+
+                if (d5 < d3) {
+                    d3 = d5;
+                    enumdirection = enumdirection1;
                 }
             }
         }
 
-        float g = this.random.nextFloat() * 0.2F + 0.1F;
-        float h = (float)direction.getAxisDirection().getStep();
-        Vec3 vec32 = this.getDeltaMovement().scale(0.75);
-        if (direction.getAxis() == Direction.Axis.X) {
-            this.setDeltaMovement((double)(h * g), vec32.y, vec32.z);
-        } else if (direction.getAxis() == Direction.Axis.Y) {
-            this.setDeltaMovement(vec32.x, (double)(h * g), vec32.z);
-        } else if (direction.getAxis() == Direction.Axis.Z) {
-            this.setDeltaMovement(vec32.x, vec32.y, (double)(h * g));
+        float f = this.random.nextFloat() * 0.2F + 0.1F;
+        float f1 = (float) enumdirection.getAxisDirection().getStep();
+        Vec3 vec3d1 = this.getDeltaMovement().scale(0.75D);
+
+        if (enumdirection.getAxis() == Direction.Axis.X) {
+            this.setDeltaMovement((double) (f1 * f), vec3d1.y, vec3d1.z);
+        } else if (enumdirection.getAxis() == Direction.Axis.Y) {
+            this.setDeltaMovement(vec3d1.x, (double) (f1 * f), vec3d1.z);
+        } else if (enumdirection.getAxis() == Direction.Axis.Z) {
+            this.setDeltaMovement(vec3d1.x, vec3d1.y, (double) (f1 * f));
         }
+
     }
 
     public void makeStuckInBlock(BlockState state, Vec3 multiplier) {
@@ -2441,19 +3629,23 @@
     }
 
     private static Component removeAction(Component textComponent) {
-        MutableComponent mutableComponent = textComponent.plainCopy().setStyle(textComponent.getStyle().withClickEvent(null));
+        MutableComponent ichatmutablecomponent = textComponent.plainCopy().setStyle(textComponent.getStyle().withClickEvent((ClickEvent) null));
+        Iterator iterator = textComponent.getSiblings().iterator();
 
-        for (Component component : textComponent.getSiblings()) {
-            mutableComponent.append(removeAction(component));
+        while (iterator.hasNext()) {
+            Component ichatbasecomponent1 = (Component) iterator.next();
+
+            ichatmutablecomponent.append(Entity.removeAction(ichatbasecomponent1));
         }
 
-        return mutableComponent;
+        return ichatmutablecomponent;
     }
 
     @Override
     public Component getName() {
-        Component component = this.getCustomName();
-        return component != null ? removeAction(component) : this.getTypeName();
+        Component ichatbasecomponent = this.getCustomName();
+
+        return ichatbasecomponent != null ? Entity.removeAction(ichatbasecomponent) : this.getTypeName();
     }
 
     protected Component getTypeName() {
@@ -2468,11 +3660,9 @@
         return 0.0F;
     }
 
-    public void setYHeadRot(float headYaw) {
-    }
+    public void setYHeadRot(float headYaw) {}
 
-    public void setYBodyRot(float bodyYaw) {
-    }
+    public void setYBodyRot(float bodyYaw) {}
 
     public boolean isAttackable() {
         return true;
@@ -2482,40 +3672,14 @@
         return false;
     }
 
-    @Override
     public String toString() {
-        String string = this.level() == null ? "~NULL~" : this.level().toString();
-        return this.removalReason != null
-            ? String.format(
-                Locale.ROOT,
-                "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]",
-                this.getClass().getSimpleName(),
-                this.getName().getString(),
-                this.id,
-                string,
-                this.getX(),
-                this.getY(),
-                this.getZ(),
-                this.removalReason
-            )
-            : String.format(
-                Locale.ROOT,
-                "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]",
-                this.getClass().getSimpleName(),
-                this.getName().getString(),
-                this.id,
-                string,
-                this.getX(),
-                this.getY(),
-                this.getZ()
-            );
+        String s = this.level() == null ? "~NULL~" : this.level().toString();
+
+        return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info
     }
 
     public boolean isInvulnerableTo(DamageSource damageSource) {
-        return this.isRemoved()
-            || this.invulnerable && !damageSource.is(DamageTypeTags.BYPASSES_INVULNERABILITY) && !damageSource.isCreativePlayer()
-            || damageSource.is(DamageTypeTags.IS_FIRE) && this.fireImmune()
-            || damageSource.is(DamageTypeTags.IS_FALL) && this.getType().is(EntityTypeTags.FALL_DAMAGE_IMMUNE);
+        return this.isRemoved() || this.invulnerable && !damageSource.is(DamageTypeTags.BYPASSES_INVULNERABILITY) && !damageSource.isCreativePlayer() || damageSource.is(DamageTypeTags.IS_FIRE) && this.fireImmune() || damageSource.is(DamageTypeTags.IS_FALL) && this.getType().is(EntityTypeTags.FALL_DAMAGE_IMMUNE);
     }
 
     public boolean isInvulnerable() {
@@ -2531,81 +3695,183 @@
     }
 
     public void restoreFrom(Entity original) {
-        CompoundTag compoundTag = original.saveWithoutId(new CompoundTag());
-        compoundTag.remove("Dimension");
-        this.load(compoundTag);
+        // Paper start - Forward CraftEntity in teleport command
+        CraftEntity bukkitEntity = original.bukkitEntity;
+        if (bukkitEntity != null) {
+            bukkitEntity.setHandle(this);
+            this.bukkitEntity = bukkitEntity;
+        }
+        // Paper end - Forward CraftEntity in teleport command
+        CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag());
+
+        nbttagcompound.remove("Dimension");
+        this.load(nbttagcompound);
         this.portalCooldown = original.portalCooldown;
         this.portalProcess = original.portalProcess;
     }
 
     @Nullable
     public Entity changeDimension(DimensionTransition teleportTarget) {
-        if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved()) {
-            ServerLevel serverLevel3 = teleportTarget.newLevel();
-            List<Entity> list = this.getPassengers();
-            this.unRide();
-            List<Entity> list2 = new ArrayList<>();
+        Level world = this.level();
 
-            for (Entity entity : list) {
-                Entity entity2 = entity.changeDimension(teleportTarget);
-                if (entity2 != null) {
-                    list2.add(entity2);
+        // Paper start - Fix item duplication and teleport issues
+        if (!this.isAlive() || !this.valid) {
+            LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTarget.newLevel() + ":" + teleportTarget.pos(), new Throwable());
+            return null;
+        }
+        // Paper end - Fix item duplication and teleport issues
+        if (world instanceof ServerLevel worldserver) {
+            if (!this.isRemoved()) {
+                // CraftBukkit start
+                Location to = new Location(teleportTarget.newLevel().getWorld(), teleportTarget.pos().x, teleportTarget.pos().y, teleportTarget.pos().z, teleportTarget.yRot(), this.getXRot()); // Paper - use getXRot (doesn't respect DimensionTransition pitch)
+                // Paper start - gateway-specific teleport event
+                final EntityTeleportEvent teleEvent;
+                if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) {
+                    teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity));
+                    teleEvent.callEvent();
+                } else {
+                    teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to);
+                }
+                // Paper end - gateway-specific teleport event
+                if (teleEvent.isCancelled() || teleEvent.getTo() == null) {
+                    return null;
+                }
+                to = teleEvent.getTo();
+                // Paper start - Call EntityPortalExitEvent
+                CraftEntity bukkitEntity = this.getBukkitEntity();
+                Vec3 velocity = teleportTarget.speed();
+                org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(
+                    bukkitEntity,
+                    bukkitEntity.getLocation(), to.clone(),
+                    bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity)
+                );
+                event.callEvent();
+                if (this.isRemoved()) {
+                    return null;
                 }
-            }
 
-            serverLevel.getProfiler().push("changeDimension");
-            Entity entity3 = serverLevel3.dimension() == serverLevel.dimension() ? this : this.getType().create(serverLevel3);
-            if (entity3 != null) {
-                if (this != entity3) {
-                    entity3.restoreFrom(this);
-                    this.removeAfterChangingDimensions();
+                if (!event.isCancelled() && event.getTo() != null) {
+                    to = event.getTo().clone();
+                    velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter());
                 }
+                teleportTarget = new DimensionTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.postDimensionTransition(), teleportTarget.cause());
+                // Paper end - Call EntityPortalExitEvent
+                // CraftBukkit end
+                ServerLevel worldserver1 = teleportTarget.newLevel();
+                List<Entity> list = this.getPassengers();
 
-                entity3.moveTo(teleportTarget.pos().x, teleportTarget.pos().y, teleportTarget.pos().z, teleportTarget.yRot(), entity3.getXRot());
-                entity3.setDeltaMovement(teleportTarget.speed());
-                if (this != entity3) {
-                    serverLevel3.addDuringTeleport(entity3);
+                this.unRide();
+                List<Entity> list1 = new ArrayList();
+                Iterator iterator = list.iterator();
+
+                Entity entity;
+
+                while (iterator.hasNext()) {
+                    Entity entity1 = (Entity) iterator.next();
+
+                    entity = entity1.changeDimension(teleportTarget);
+                    if (entity != null) {
+                        list1.add(entity);
+                    }
                 }
 
-                for (Entity entity4 : list2) {
-                    entity4.startRiding(entity3, true);
+                worldserver.getProfiler().push("changeDimension");
+                Entity entity2 = worldserver1.dimension() == worldserver.dimension() ? this : this.getType().create(worldserver1);
+
+                if (entity2 != null) {
+                    if (this != entity2) {
+                        // Paper start - Fix item duplication and teleport issues
+                        if (this instanceof Leashable leashable) {
+                            leashable.dropLeash(true, true); // Paper drop lead
+                        }
+                        // Paper end - Fix item duplication and teleport issues
+                        entity2.restoreFrom(this);
+                        this.removeAfterChangingDimensions();
+                        // CraftBukkit start - Forward the CraftEntity to the new entity
+                        // this.getBukkitEntity().setHandle(entity2); // Paper - forward CraftEntity in teleport command; moved to Entity#restoreFrom
+                        // entity2.bukkitEntity = this.getBukkitEntity();
+                        // CraftBukkit end
+                    }
+
+                    entity2.moveTo(teleportTarget.pos().x, teleportTarget.pos().y, teleportTarget.pos().z, teleportTarget.yRot(), entity2.getXRot());
+                    entity2.setDeltaMovement(teleportTarget.speed());
+                    if (this != entity2 && this.inWorld) { // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned
+                        worldserver1.addDuringTeleport(entity2);
+                    }
+
+                    Iterator iterator1 = list1.iterator();
+
+                    while (iterator1.hasNext()) {
+                        entity = (Entity) iterator1.next();
+                        entity.startRiding(entity2, true);
+                    }
+
+                    worldserver.resetEmptyTime();
+                    worldserver1.resetEmptyTime();
+                    teleportTarget.postDimensionTransition().onTransition(entity2);
                 }
 
-                serverLevel.resetEmptyTime();
-                serverLevel3.resetEmptyTime();
-                teleportTarget.postDimensionTransition().onTransition(entity3);
+                worldserver.getProfiler().pop();
+                return entity2;
             }
-
-            serverLevel.getProfiler().pop();
-            return entity3;
         }
 
         return null;
     }
 
     public void placePortalTicket(BlockPos pos) {
-        if (this.level() instanceof ServerLevel serverLevel) {
-            serverLevel.getChunkSource().addRegionTicket(TicketType.PORTAL, new ChunkPos(pos), 3, pos);
+        Level world = this.level();
+
+        if (world instanceof ServerLevel worldserver) {
+            worldserver.getChunkSource().addRegionTicket(TicketType.PORTAL, new ChunkPos(pos), 3, pos);
         }
+
     }
 
     protected void removeAfterChangingDimensions() {
-        this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION);
-        if (this instanceof Leashable leashable) {
-            leashable.dropLeash(true, false);
+        this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause
+        if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed
+            // Paper start - Expand EntityUnleashEvent
+            final EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); // CraftBukkit
+            event.callEvent();
+            leashable.dropLeash(true, event.isDropLeash());
+            // Paper end - Expand EntityUnleashEvent
         }
+
     }
 
     public Vec3 getRelativePortalPosition(Direction.Axis portalAxis, BlockUtil.FoundRectangle portalRect) {
         return PortalShape.getRelativePosition(portalRect, portalAxis, this.position(), this.getDimensions(this.getPose()));
     }
 
+    // CraftBukkit start
+    public CraftPortalEvent callPortalEvent(Entity entity, Location exit, PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) {
+        org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity();
+        Location enter = bukkitEntity.getLocation();
+
+        // Paper start
+        final org.bukkit.PortalType portalType = switch (cause) {
+            case END_PORTAL -> org.bukkit.PortalType.ENDER;
+            case NETHER_PORTAL -> org.bukkit.PortalType.NETHER;
+            case END_GATEWAY -> org.bukkit.PortalType.END_GATEWAY; // not actually used yet
+            default -> org.bukkit.PortalType.CUSTOM;
+        };
+        EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius, portalType);
+        // Paper end
+        event.getEntity().getServer().getPluginManager().callEvent(event);
+        if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) {
+            return null;
+        }
+        return new CraftPortalEvent(event);
+    }
+    // CraftBukkit end
+
     public boolean canUsePortal(boolean allowVehicles) {
         return (allowVehicles || !this.isPassenger()) && this.isAlive();
     }
 
     public boolean canChangeDimensions(Level from, Level to) {
-        return true;
+        return this.isAlive() && this.valid; // Paper - Fix item duplication and teleport issues
     }
 
     public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) {
@@ -2625,17 +3891,26 @@
     }
 
     public void fillCrashReportCategory(CrashReportCategory section) {
-        section.setDetail("Entity Type", () -> EntityType.getKey(this.getType()) + " (" + this.getClass().getCanonicalName() + ")");
-        section.setDetail("Entity ID", this.id);
-        section.setDetail("Entity Name", () -> this.getName().getString());
-        section.setDetail("Entity's Exact location", String.format(Locale.ROOT, "%.2f, %.2f, %.2f", this.getX(), this.getY(), this.getZ()));
-        section.setDetail(
-            "Entity's Block location", CrashReportCategory.formatLocation(this.level(), Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()))
-        );
-        Vec3 vec3 = this.getDeltaMovement();
-        section.setDetail("Entity's Momentum", String.format(Locale.ROOT, "%.2f, %.2f, %.2f", vec3.x, vec3.y, vec3.z));
-        section.setDetail("Entity's Passengers", () -> this.getPassengers().toString());
-        section.setDetail("Entity's Vehicle", () -> String.valueOf(this.getVehicle()));
+        section.setDetail("Entity Type", () -> {
+            String s = String.valueOf(EntityType.getKey(this.getType()));
+
+            return s + " (" + this.getClass().getCanonicalName() + ")";
+        });
+        section.setDetail("Entity ID", (Object) this.id);
+        section.setDetail("Entity Name", () -> {
+            return this.getName().getString();
+        });
+        section.setDetail("Entity's Exact location", (Object) String.format(Locale.ROOT, "%.2f, %.2f, %.2f", this.getX(), this.getY(), this.getZ()));
+        section.setDetail("Entity's Block location", (Object) CrashReportCategory.formatLocation(this.level(), Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ())));
+        Vec3 vec3d = this.getDeltaMovement();
+
+        section.setDetail("Entity's Momentum", (Object) String.format(Locale.ROOT, "%.2f, %.2f, %.2f", vec3d.x, vec3d.y, vec3d.z));
+        section.setDetail("Entity's Passengers", () -> {
+            return this.getPassengers().toString();
+        });
+        section.setDetail("Entity's Vehicle", () -> {
+            return String.valueOf(this.getVehicle());
+        });
     }
 
     public boolean displayFireAnimation() {
@@ -2666,60 +3941,73 @@
     }
 
     public static double getViewScale() {
-        return viewScale;
+        return Entity.viewScale;
     }
 
     public static void setViewScale(double value) {
-        viewScale = value;
+        Entity.viewScale = value;
     }
 
     @Override
     public Component getDisplayName() {
-        return PlayerTeam.formatNameForTeam(this.getTeam(), this.getName())
-            .withStyle(style -> style.withHoverEvent(this.createHoverEvent()).withInsertion(this.getStringUUID()));
+        return PlayerTeam.formatNameForTeam(this.getTeam(), this.getName()).withStyle((chatmodifier) -> {
+            return chatmodifier.withHoverEvent(this.createHoverEvent()).withInsertion(this.getStringUUID());
+        });
     }
 
     public void setCustomName(@Nullable Component name) {
-        this.entityData.set(DATA_CUSTOM_NAME, Optional.ofNullable(name));
+        this.entityData.set(Entity.DATA_CUSTOM_NAME, Optional.ofNullable(name));
     }
 
     @Nullable
     @Override
     public Component getCustomName() {
-        return this.entityData.get(DATA_CUSTOM_NAME).orElse(null);
+        return (Component) ((Optional) this.entityData.get(Entity.DATA_CUSTOM_NAME)).orElse((Object) null);
     }
 
     @Override
     public boolean hasCustomName() {
-        return this.entityData.get(DATA_CUSTOM_NAME).isPresent();
+        return ((Optional) this.entityData.get(Entity.DATA_CUSTOM_NAME)).isPresent();
     }
 
     public void setCustomNameVisible(boolean visible) {
-        this.entityData.set(DATA_CUSTOM_NAME_VISIBLE, visible);
+        this.entityData.set(Entity.DATA_CUSTOM_NAME_VISIBLE, visible);
     }
 
     public boolean isCustomNameVisible() {
-        return this.entityData.get(DATA_CUSTOM_NAME_VISIBLE);
+        return (Boolean) this.entityData.get(Entity.DATA_CUSTOM_NAME_VISIBLE);
     }
 
+    // CraftBukkit start
+    public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<RelativeMovement> set, float f, float f1, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
+        return this.teleportTo(worldserver, d0, d1, d2, set, f, f1);
+    }
+    // CraftBukkit end
+
     public boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set<RelativeMovement> flags, float yaw, float pitch) {
-        float f = Mth.clamp(pitch, -90.0F, 90.0F);
+        float f2 = Mth.clamp(pitch, -90.0F, 90.0F);
+
         if (world == this.level()) {
-            this.moveTo(destX, destY, destZ, yaw, f);
+            this.moveTo(destX, destY, destZ, yaw, f2);
             this.teleportPassengers();
             this.setYHeadRot(yaw);
         } else {
             this.unRide();
             Entity entity = this.getType().create(world);
+
             if (entity == null) {
                 return false;
             }
 
             entity.restoreFrom(this);
-            entity.moveTo(destX, destY, destZ, yaw, f);
+            entity.moveTo(destX, destY, destZ, yaw, f2);
             entity.setYHeadRot(yaw);
-            this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION);
-            world.addDuringTeleport(entity);
+            this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause
+            // CraftBukkit start - Don't spawn the new entity if the current entity isn't spawned
+            if (this.inWorld) {
+                world.addDuringTeleport(entity);
+            }
+            // CraftBukkit end
         }
 
         return true;
@@ -2737,10 +4025,15 @@
     }
 
     private void teleportPassengers() {
-        this.getSelfAndPassengers().forEach(entity -> {
-            for (Entity entity2 : entity.passengers) {
-                entity.positionRider(entity2, Entity::moveTo);
+        this.getSelfAndPassengers().forEach((entity) -> {
+            UnmodifiableIterator unmodifiableiterator = entity.passengers.iterator();
+
+            while (unmodifiableiterator.hasNext()) {
+                Entity entity1 = (Entity) unmodifiableiterator.next();
+
+                entity.positionRider(entity1, Entity::moveTo);
             }
+
         });
     }
 
@@ -2753,60 +4046,60 @@
     }
 
     @Override
-    public void onSyncedDataUpdated(List<SynchedEntityData.DataValue<?>> entries) {
-    }
+    public void onSyncedDataUpdated(List<SynchedEntityData.DataValue<?>> entries) {}
 
     @Override
     public void onSyncedDataUpdated(EntityDataAccessor<?> data) {
-        if (DATA_POSE.equals(data)) {
+        if (Entity.DATA_POSE.equals(data)) {
             this.refreshDimensions();
         }
+
     }
 
+    /** @deprecated */
     @Deprecated
     protected void fixupDimensions() {
-        Pose pose = this.getPose();
-        EntityDimensions entityDimensions = this.getDimensions(pose);
-        this.dimensions = entityDimensions;
-        this.eyeHeight = entityDimensions.eyeHeight();
+        net.minecraft.world.entity.Pose entitypose = this.getPose();
+        EntityDimensions entitysize = this.getDimensions(entitypose);
+
+        this.dimensions = entitysize;
+        this.eyeHeight = entitysize.eyeHeight();
     }
 
     public void refreshDimensions() {
-        EntityDimensions entityDimensions = this.dimensions;
-        Pose pose = this.getPose();
-        EntityDimensions entityDimensions2 = this.getDimensions(pose);
-        this.dimensions = entityDimensions2;
-        this.eyeHeight = entityDimensions2.eyeHeight();
+        EntityDimensions entitysize = this.dimensions;
+        net.minecraft.world.entity.Pose entitypose = this.getPose();
+        EntityDimensions entitysize1 = this.getDimensions(entitypose);
+
+        this.dimensions = entitysize1;
+        this.eyeHeight = entitysize1.eyeHeight();
         this.reapplyPosition();
-        boolean bl = (double)entityDimensions2.width() <= 4.0 && (double)entityDimensions2.height() <= 4.0;
-        if (!this.level.isClientSide
-            && !this.firstTick
-            && !this.noPhysics
-            && bl
-            && (entityDimensions2.width() > entityDimensions.width() || entityDimensions2.height() > entityDimensions.height())
-            && !(this instanceof Player)) {
-            this.fudgePositionAfterSizeChange(entityDimensions);
+        boolean flag = (double) entitysize1.width() <= 4.0D && (double) entitysize1.height() <= 4.0D;
+
+        if (!this.level.isClientSide && !this.firstTick && !this.noPhysics && flag && (entitysize1.width() > entitysize.width() || entitysize1.height() > entitysize.height()) && !(this instanceof Player)) {
+            this.fudgePositionAfterSizeChange(entitysize);
         }
+
     }
 
     public boolean fudgePositionAfterSizeChange(EntityDimensions previous) {
-        EntityDimensions entityDimensions = this.getDimensions(this.getPose());
-        Vec3 vec3 = this.position().add(0.0, (double)previous.height() / 2.0, 0.0);
-        double d = (double)Math.max(0.0F, entityDimensions.width() - previous.width()) + 1.0E-6;
-        double e = (double)Math.max(0.0F, entityDimensions.height() - previous.height()) + 1.0E-6;
-        VoxelShape voxelShape = Shapes.create(AABB.ofSize(vec3, d, e, d));
-        Optional<Vec3> optional = this.level
-            .findFreePosition(this, voxelShape, vec3, (double)entityDimensions.width(), (double)entityDimensions.height(), (double)entityDimensions.width());
+        EntityDimensions entitysize1 = this.getDimensions(this.getPose());
+        Vec3 vec3d = this.position().add(0.0D, (double) previous.height() / 2.0D, 0.0D);
+        double d0 = (double) Math.max(0.0F, entitysize1.width() - previous.width()) + 1.0E-6D;
+        double d1 = (double) Math.max(0.0F, entitysize1.height() - previous.height()) + 1.0E-6D;
+        VoxelShape voxelshape = Shapes.create(AABB.ofSize(vec3d, d0, d1, d0));
+        Optional<Vec3> optional = this.level.findFreePosition(this, voxelshape, vec3d, (double) entitysize1.width(), (double) entitysize1.height(), (double) entitysize1.width());
+
         if (optional.isPresent()) {
-            this.setPos(optional.get().add(0.0, (double)(-entityDimensions.height()) / 2.0, 0.0));
+            this.setPos(((Vec3) optional.get()).add(0.0D, (double) (-entitysize1.height()) / 2.0D, 0.0D));
             return true;
         } else {
-            if (entityDimensions.width() > previous.width() && entityDimensions.height() > previous.height()) {
-                VoxelShape voxelShape2 = Shapes.create(AABB.ofSize(vec3, d, 1.0E-6, d));
-                Optional<Vec3> optional2 = this.level
-                    .findFreePosition(this, voxelShape2, vec3, (double)entityDimensions.width(), (double)previous.height(), (double)entityDimensions.width());
-                if (optional2.isPresent()) {
-                    this.setPos(optional2.get().add(0.0, (double)(-previous.height()) / 2.0 + 1.0E-6, 0.0));
+            if (entitysize1.width() > previous.width() && entitysize1.height() > previous.height()) {
+                VoxelShape voxelshape1 = Shapes.create(AABB.ofSize(vec3d, d0, 1.0E-6D, d0));
+                Optional<Vec3> optional1 = this.level.findFreePosition(this, voxelshape1, vec3d, (double) entitysize1.width(), (double) previous.height(), (double) entitysize1.width());
+
+                if (optional1.isPresent()) {
+                    this.setPos(((Vec3) optional1.get()).add(0.0D, (double) (-previous.height()) / 2.0D + 1.0E-6D, 0.0D));
                     return true;
                 }
             }
@@ -2816,7 +4109,7 @@
     }
 
     public Direction getDirection() {
-        return Direction.fromYRot((double)this.getYRot());
+        return Direction.fromYRot((double) this.getYRot());
     }
 
     public Direction getMotionDirection() {
@@ -2841,10 +4134,29 @@
     }
 
     public final void setBoundingBox(AABB boundingBox) {
-        this.bb = boundingBox;
+        // CraftBukkit start - block invalid bounding boxes
+        double minX = boundingBox.minX,
+                minY = boundingBox.minY,
+                minZ = boundingBox.minZ,
+                maxX = boundingBox.maxX,
+                maxY = boundingBox.maxY,
+                maxZ = boundingBox.maxZ;
+        double len = boundingBox.maxX - boundingBox.minX;
+        if (len < 0) maxX = minX;
+        if (len > 64) maxX = minX + 64.0;
+
+        len = boundingBox.maxY - boundingBox.minY;
+        if (len < 0) maxY = minY;
+        if (len > 64) maxY = minY + 64.0;
+
+        len = boundingBox.maxZ - boundingBox.minZ;
+        if (len < 0) maxZ = minZ;
+        if (len > 64) maxZ = minZ + 64.0;
+        this.bb = new AABB(minX, minY, minZ, maxX, maxY, maxZ);
+        // CraftBukkit end
     }
 
-    public final float getEyeHeight(Pose pose) {
+    public final float getEyeHeight(net.minecraft.world.entity.Pose pose) {
         return this.getDimensions(pose).eyeHeight();
     }
 
@@ -2857,7 +4169,7 @@
     }
 
     protected Vec3 getLeashOffset() {
-        return new Vec3(0.0, (double)this.getEyeHeight(), (double)(this.getBbWidth() * 0.4F));
+        return new Vec3(0.0D, (double) this.getEyeHeight(), (double) (this.getBbWidth() * 0.4F));
     }
 
     public SlotAccess getSlot(int mappedIndex) {
@@ -2865,8 +4177,7 @@
     }
 
     @Override
-    public void sendSystemMessage(Component message) {
-    }
+    public void sendSystemMessage(Component message) {}
 
     public Level getCommandSenderWorld() {
         return this.level();
@@ -2885,14 +4196,20 @@
         return false;
     }
 
-    public void startSeenByPlayer(ServerPlayer player) {
-    }
+    public void startSeenByPlayer(ServerPlayer player) {}
 
+    // Paper start - entity tracking events
     public void stopSeenByPlayer(ServerPlayer player) {
+        // Since this event cannot be cancelled, we should call it here to catch all "un-tracks"
+        if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) {
+            new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.getBukkitEntity()).callEvent();
+        }
     }
+    // Paper end - entity tracking events
 
     public float rotate(Rotation rotation) {
         float f = Mth.wrapDegrees(this.getYRot());
+
         switch (rotation) {
             case CLOCKWISE_180:
                 return f + 180.0F;
@@ -2907,6 +4224,7 @@
 
     public float mirror(Mirror mirror) {
         float f = Mth.wrapDegrees(this.getYRot());
+
         switch (mirror) {
             case FRONT_BACK:
                 return -f;
@@ -2926,7 +4244,7 @@
     }
 
     @Nullable
-    public LivingEntity getControllingPassenger() {
+    public net.minecraft.world.entity.LivingEntity getControllingPassenger() {
         return null;
     }
 
@@ -2940,7 +4258,7 @@
 
     @Nullable
     public Entity getFirstPassenger() {
-        return this.passengers.isEmpty() ? null : this.passengers.get(0);
+        return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0);
     }
 
     public boolean hasPassenger(Entity passenger) {
@@ -2948,46 +4266,74 @@
     }
 
     public boolean hasPassenger(Predicate<Entity> predicate) {
-        for (Entity entity : this.passengers) {
-            if (predicate.test(entity)) {
-                return true;
+        UnmodifiableIterator unmodifiableiterator = this.passengers.iterator();
+
+        Entity entity;
+
+        do {
+            if (!unmodifiableiterator.hasNext()) {
+                return false;
             }
-        }
 
-        return false;
+            entity = (Entity) unmodifiableiterator.next();
+        } while (!predicate.test(entity));
+
+        return true;
     }
 
     private Stream<Entity> getIndirectPassengersStream() {
+        if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration
         return this.passengers.stream().flatMap(Entity::getSelfAndPassengers);
     }
 
     @Override
     public Stream<Entity> getSelfAndPassengers() {
+        if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration
         return Stream.concat(Stream.of(this), this.getIndirectPassengersStream());
     }
 
     @Override
     public Stream<Entity> getPassengersAndSelf() {
+        if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration
         return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this));
     }
 
     public Iterable<Entity> getIndirectPassengers() {
-        return () -> this.getIndirectPassengersStream().iterator();
+        // Paper start - optimise entity tracker
+        final List<Entity> ret = new ArrayList<>();
+
+        if (this.passengers.isEmpty()) {
+            return ret;
+        }
+
+        collectIndirectPassengers(ret, this.passengers);
+
+        return ret;
+        // Paper end - optimise entity tracker
+    }
+    private Iterable<Entity> getIndirectPassengers_old() {
+        // Paper end - Optimize indirect passenger iteration
+        return () -> {
+            return this.getIndirectPassengersStream().iterator();
+        };
     }
 
     public int countPlayerPassengers() {
-        return (int)this.getIndirectPassengersStream().filter(passenger -> passenger instanceof Player).count();
+        return (int) this.getIndirectPassengersStream().filter((entity) -> {
+            return entity instanceof Player;
+        }).count();
     }
 
     public boolean hasExactlyOnePlayerPassenger() {
+        if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration
         return this.countPlayerPassengers() == 1;
     }
 
     public Entity getRootVehicle() {
-        Entity entity = this;
+        Entity entity;
 
-        while (entity.isPassenger()) {
-            entity = entity.getVehicle();
+        for (entity = this; entity.isPassenger(); entity = entity.getVehicle()) {
+            ;
         }
 
         return entity;
@@ -3001,13 +4347,20 @@
         if (!passenger.isPassenger()) {
             return false;
         } else {
-            Entity entity = passenger.getVehicle();
-            return entity == this || this.hasIndirectPassenger(entity);
+            Entity entity1 = passenger.getVehicle();
+
+            return entity1 == this ? true : this.hasIndirectPassenger(entity1);
         }
     }
 
     public boolean isControlledByLocalInstance() {
-        return this.getControllingPassenger() instanceof Player player ? player.isLocalPlayer() : this.isEffectiveAi();
+        net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger();
+
+        if (entityliving instanceof Player entityhuman) {
+            return entityhuman.isLocalPlayer();
+        } else {
+            return this.isEffectiveAi();
+        }
     }
 
     public boolean isEffectiveAi() {
@@ -3015,14 +4368,15 @@
     }
 
     protected static Vec3 getCollisionHorizontalEscapeVector(double vehicleWidth, double passengerWidth, float passengerYaw) {
-        double d = (vehicleWidth + passengerWidth + 1.0E-5F) / 2.0;
-        float f = -Mth.sin(passengerYaw * (float) (Math.PI / 180.0));
-        float g = Mth.cos(passengerYaw * (float) (Math.PI / 180.0));
-        float h = Math.max(Math.abs(f), Math.abs(g));
-        return new Vec3((double)f * d / (double)h, 0.0, (double)g * d / (double)h);
+        double d2 = (vehicleWidth + passengerWidth + 9.999999747378752E-6D) / 2.0D;
+        float f1 = -Mth.sin(passengerYaw * 0.017453292F);
+        float f2 = Mth.cos(passengerYaw * 0.017453292F);
+        float f3 = Math.max(Math.abs(f1), Math.abs(f2));
+
+        return new Vec3((double) f1 * d2 / (double) f3, 0.0D, (double) f2 * d2 / (double) f3);
     }
 
-    public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
+    public Vec3 getDismountLocationForPassenger(net.minecraft.world.entity.LivingEntity passenger) {
         return new Vec3(this.getX(), this.getBoundingBox().maxY, this.getZ());
     }
 
@@ -3049,17 +4403,7 @@
     }
 
     public CommandSourceStack createCommandSourceStack() {
-        return new CommandSourceStack(
-            this,
-            this.position(),
-            this.getRotationVector(),
-            this.level() instanceof ServerLevel ? (ServerLevel)this.level() : null,
-            this.getPermissionLevel(),
-            this.getName().getString(),
-            this.getDisplayName(),
-            this.level().getServer(),
-            this
-        );
+        return new CommandSourceStack(this, this.position(), this.getRotationVector(), this.level() instanceof ServerLevel ? (ServerLevel) this.level() : null, this.getPermissionLevel(), this.getName().getString(), this.getDisplayName(), this.level().getServer(), this);
     }
 
     protected int getPermissionLevel() {
@@ -3086,13 +4430,14 @@
     }
 
     public void lookAt(EntityAnchorArgument.Anchor anchorPoint, Vec3 target) {
-        Vec3 vec3 = anchorPoint.apply(this);
-        double d = target.x - vec3.x;
-        double e = target.y - vec3.y;
-        double f = target.z - vec3.z;
-        double g = Math.sqrt(d * d + f * f);
-        this.setXRot(Mth.wrapDegrees((float)(-(Mth.atan2(e, g) * 180.0F / (float)Math.PI))));
-        this.setYRot(Mth.wrapDegrees((float)(Mth.atan2(f, d) * 180.0F / (float)Math.PI) - 90.0F));
+        Vec3 vec3d1 = anchorPoint.apply(this);
+        double d0 = target.x - vec3d1.x;
+        double d1 = target.y - vec3d1.y;
+        double d2 = target.z - vec3d1.z;
+        double d3 = Math.sqrt(d0 * d0 + d2 * d2);
+
+        this.setXRot(Mth.wrapDegrees((float) (-(Mth.atan2(d1, d3) * 57.2957763671875D))));
+        this.setYRot(Mth.wrapDegrees((float) (Mth.atan2(d2, d0) * 57.2957763671875D) - 90.0F));
         this.setYHeadRot(this.getYRot());
         this.xRotO = this.getXRot();
         this.yRotO = this.getYRot();
@@ -3106,75 +4451,86 @@
         if (this.touchingUnloadedChunk()) {
             return false;
         } else {
-            AABB aABB = this.getBoundingBox().deflate(0.001);
-            int i = Mth.floor(aABB.minX);
-            int j = Mth.ceil(aABB.maxX);
-            int k = Mth.floor(aABB.minY);
-            int l = Mth.ceil(aABB.maxY);
-            int m = Mth.floor(aABB.minZ);
-            int n = Mth.ceil(aABB.maxZ);
-            double d = 0.0;
-            boolean bl = this.isPushedByFluid();
-            boolean bl2 = false;
-            Vec3 vec3 = Vec3.ZERO;
-            int o = 0;
-            BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+            AABB axisalignedbb = this.getBoundingBox().deflate(0.001D);
+            int i = Mth.floor(axisalignedbb.minX);
+            int j = Mth.ceil(axisalignedbb.maxX);
+            int k = Mth.floor(axisalignedbb.minY);
+            int l = Mth.ceil(axisalignedbb.maxY);
+            int i1 = Mth.floor(axisalignedbb.minZ);
+            int j1 = Mth.ceil(axisalignedbb.maxZ);
+            double d1 = 0.0D;
+            boolean flag = this.isPushedByFluid();
+            boolean flag1 = false;
+            Vec3 vec3d = Vec3.ZERO;
+            int k1 = 0;
+            BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
 
-            for (int p = i; p < j; p++) {
-                for (int q = k; q < l; q++) {
-                    for (int r = m; r < n; r++) {
-                        mutableBlockPos.set(p, q, r);
-                        FluidState fluidState = this.level().getFluidState(mutableBlockPos);
-                        if (fluidState.is(tag)) {
-                            double e = (double)((float)q + fluidState.getHeight(this.level(), mutableBlockPos));
-                            if (e >= aABB.minY) {
-                                bl2 = true;
-                                d = Math.max(e - aABB.minY, d);
-                                if (bl) {
-                                    Vec3 vec32 = fluidState.getFlow(this.level(), mutableBlockPos);
-                                    if (d < 0.4) {
-                                        vec32 = vec32.scale(d);
+            for (int l1 = i; l1 < j; ++l1) {
+                for (int i2 = k; i2 < l; ++i2) {
+                    for (int j2 = i1; j2 < j1; ++j2) {
+                        blockposition_mutableblockposition.set(l1, i2, j2);
+                        FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition);
+
+                        if (fluid.is(tag)) {
+                            double d2 = (double) ((float) i2 + fluid.getHeight(this.level(), blockposition_mutableblockposition));
+
+                            if (d2 >= axisalignedbb.minY) {
+                                flag1 = true;
+                                d1 = Math.max(d2 - axisalignedbb.minY, d1);
+                                if (flag) {
+                                    Vec3 vec3d1 = fluid.getFlow(this.level(), blockposition_mutableblockposition);
+
+                                    if (d1 < 0.4D) {
+                                        vec3d1 = vec3d1.scale(d1);
                                     }
 
-                                    vec3 = vec3.add(vec32);
-                                    o++;
+                                    vec3d = vec3d.add(vec3d1);
+                                    ++k1;
+                                }
+                                // CraftBukkit start - store last lava contact location
+                                if (tag == FluidTags.LAVA) {
+                                    this.lastLavaContact = blockposition_mutableblockposition.immutable();
                                 }
+                                // CraftBukkit end
                             }
                         }
                     }
                 }
             }
 
-            if (vec3.length() > 0.0) {
-                if (o > 0) {
-                    vec3 = vec3.scale(1.0 / (double)o);
+            if (vec3d.length() > 0.0D) {
+                if (k1 > 0) {
+                    vec3d = vec3d.scale(1.0D / (double) k1);
                 }
 
                 if (!(this instanceof Player)) {
-                    vec3 = vec3.normalize();
+                    vec3d = vec3d.normalize();
                 }
 
-                Vec3 vec33 = this.getDeltaMovement();
-                vec3 = vec3.scale(speed);
-                double f = 0.003;
-                if (Math.abs(vec33.x) < 0.003 && Math.abs(vec33.z) < 0.003 && vec3.length() < 0.0045000000000000005) {
-                    vec3 = vec3.normalize().scale(0.0045000000000000005);
+                Vec3 vec3d2 = this.getDeltaMovement();
+
+                vec3d = vec3d.scale(speed);
+                double d3 = 0.003D;
+
+                if (Math.abs(vec3d2.x) < 0.003D && Math.abs(vec3d2.z) < 0.003D && vec3d.length() < 0.0045000000000000005D) {
+                    vec3d = vec3d.normalize().scale(0.0045000000000000005D);
                 }
 
-                this.setDeltaMovement(this.getDeltaMovement().add(vec3));
+                this.setDeltaMovement(this.getDeltaMovement().add(vec3d));
             }
 
-            this.fluidHeight.put(tag, d);
-            return bl2;
+            this.fluidHeight.put(tag, d1);
+            return flag1;
         }
     }
 
     public boolean touchingUnloadedChunk() {
-        AABB aABB = this.getBoundingBox().inflate(1.0);
-        int i = Mth.floor(aABB.minX);
-        int j = Mth.ceil(aABB.maxX);
-        int k = Mth.floor(aABB.minZ);
-        int l = Mth.ceil(aABB.maxZ);
+        AABB axisalignedbb = this.getBoundingBox().inflate(1.0D);
+        int i = Mth.floor(axisalignedbb.minX);
+        int j = Mth.ceil(axisalignedbb.maxX);
+        int k = Mth.floor(axisalignedbb.minZ);
+        int l = Mth.ceil(axisalignedbb.maxZ);
+
         return !this.level().hasChunksAt(i, k, j, l);
     }
 
@@ -3183,7 +4539,7 @@
     }
 
     public double getFluidJumpThreshold() {
-        return (double)this.getEyeHeight() < 0.4 ? 0.0 : 0.4;
+        return (double) this.getEyeHeight() < 0.4D ? 0.0D : 0.4D;
     }
 
     public final float getBbWidth() {
@@ -3198,7 +4554,7 @@
         return new ClientboundAddEntityPacket(this, entityTrackerEntry);
     }
 
-    public EntityDimensions getDimensions(Pose pose) {
+    public EntityDimensions getDimensions(net.minecraft.world.entity.Pose pose) {
         return this.type.getDimensions();
     }
 
@@ -3236,7 +4592,9 @@
     }
 
     public void setDeltaMovement(Vec3 velocity) {
+        synchronized (this.posLock) { // Paper
         this.deltaMovement = velocity;
+        } // Paper
     }
 
     public void addDeltaMovement(Vec3 velocity) {
@@ -3256,11 +4614,11 @@
     }
 
     public double getX(double widthScale) {
-        return this.position.x + (double)this.getBbWidth() * widthScale;
+        return this.position.x + (double) this.getBbWidth() * widthScale;
     }
 
     public double getRandomX(double widthScale) {
-        return this.getX((2.0 * this.random.nextDouble() - 1.0) * widthScale);
+        return this.getX((2.0D * this.random.nextDouble() - 1.0D) * widthScale);
     }
 
     public final int getBlockY() {
@@ -3272,7 +4630,7 @@
     }
 
     public double getY(double heightScale) {
-        return this.position.y + (double)this.getBbHeight() * heightScale;
+        return this.position.y + (double) this.getBbHeight() * heightScale;
     }
 
     public double getRandomY() {
@@ -3280,7 +4638,7 @@
     }
 
     public double getEyeY() {
-        return this.position.y + (double)this.eyeHeight;
+        return this.position.y + (double) this.eyeHeight;
     }
 
     public final int getBlockZ() {
@@ -3292,19 +4650,63 @@
     }
 
     public double getZ(double widthScale) {
-        return this.position.z + (double)this.getBbWidth() * widthScale;
+        return this.position.z + (double) this.getBbWidth() * widthScale;
     }
 
     public double getRandomZ(double widthScale) {
-        return this.getZ((2.0 * this.random.nextDouble() - 1.0) * widthScale);
+        return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale);
     }
 
+    // Paper start - Block invalid positions and bounding box
+    public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) {
+        if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) {
+            return true;
+        }
+
+        String entityInfo;
+        try {
+            entityInfo = entity.toString();
+        } catch (Exception ex) {
+            entityInfo = "[Entity info unavailable] ";
+        }
+        LOGGER.error("New entity position is invalid! Tried to set invalid position ({},{},{}) for entity {} located at {}, entity info: {}", newX, newY, newZ, entity.getClass().getName(), entity.position, entityInfo, new Throwable());
+        return false;
+    }
     public final void setPosRaw(double x, double y, double z) {
+        this.setPosRaw(x, y, z, false);
+    }
+    public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
+        // Paper start - rewrite chunk system
+        if (this.updatingSectionStatus) {
+            LOGGER.error(
+                "Refusing to update position for entity " + this + " to position " + new Vec3(x, y, z)
+                    + " since it is processing a section status update", new Throwable()
+            );
+            return;
+        }
+        // Paper end - rewrite chunk system
+        if (!checkPosition(this, x, y, z)) {
+            return;
+        }
+        // Paper end - Block invalid positions and bounding box
+        // Paper start - Fix MC-4
+        if (this instanceof ItemEntity) {
+            if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) {
+                // encode/decode from ClientboundMoveEntityPacket
+                x = Mth.lfloor(x * 4096.0) * (1 / 4096.0);
+                y = Mth.lfloor(y * 4096.0) * (1 / 4096.0);
+                z = Mth.lfloor(z * 4096.0) * (1 / 4096.0);
+            }
+        }
+        // Paper end - Fix MC-4
         if (this.position.x != x || this.position.y != y || this.position.z != z) {
+            synchronized (this.posLock) { // Paper
             this.position = new Vec3(x, y, z);
+            } // Paper
             int i = Mth.floor(x);
             int j = Mth.floor(y);
             int k = Mth.floor(z);
+
             if (i != this.blockPosition.getX() || j != this.blockPosition.getY() || k != this.blockPosition.getZ()) {
                 this.blockPosition = new BlockPos(i, j, k);
                 this.inBlockState = null;
@@ -3315,22 +4717,29 @@
 
             this.levelCallback.onMove();
         }
-    }
 
-    public void checkDespawn() {
+        // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB
+        // hanging has its own special logic
+        if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) {
+            this.setBoundingBox(this.makeBoundingBox());
+        }
+        // Paper end - Block invalid positions and bounding box
     }
 
+    public void checkDespawn() {}
+
     public Vec3 getRopeHoldPosition(float delta) {
-        return this.getPosition(delta).add(0.0, (double)this.eyeHeight * 0.7, 0.0);
+        return this.getPosition(delta).add(0.0D, (double) this.eyeHeight * 0.7D, 0.0D);
     }
 
     public void recreateFromPacket(ClientboundAddEntityPacket packet) {
         int i = packet.getId();
-        double d = packet.getX();
-        double e = packet.getY();
-        double f = packet.getZ();
-        this.syncPacketPositionCodec(d, e, f);
-        this.moveTo(d, e, f);
+        double d0 = packet.getX();
+        double d1 = packet.getY();
+        double d2 = packet.getZ();
+
+        this.syncPacketPositionCodec(d0, d1, d2);
+        this.moveTo(d0, d1, d2);
         this.setXRot(packet.getXRot());
         this.setYRot(packet.getYRot());
         this.setId(i);
@@ -3390,8 +4799,7 @@
         return 0.0F;
     }
 
-    public void onExplosionHit(@Nullable Entity entity) {
-    }
+    public void onExplosionHit(@Nullable Entity entity) {}
 
     public final boolean isRemoved() {
         return this.removalReason != null;
@@ -3404,22 +4812,53 @@
 
     @Override
     public final void setRemoved(Entity.RemovalReason reason) {
+        // CraftBukkit start - add Bukkit remove cause
+        this.setRemoved(reason, null);
+    }
+
+    @Override
+    public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
+        // Paper start - rewrite chunk system
+        if (!((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this.level).moonrise$getEntityLookup().canRemoveEntity((Entity)(Object)this)) {
+            LOGGER.warn("Entity " + this + " is currently prevented from being removed from the world since it is processing section status updates", new Throwable());
+            return;
+        }
+        // Paper end - rewrite chunk system
+        CraftEventFactory.callEntityRemoveEvent(this, cause);
+        // CraftBukkit end
+        final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
         if (this.removalReason == null) {
-            this.removalReason = reason;
+            this.removalReason = entity_removalreason;
         }
 
         if (this.removalReason.shouldDestroy()) {
             this.stopRiding();
         }
 
-        this.getPassengers().forEach(Entity::stopRiding);
-        this.levelCallback.onRemove(reason);
+        if (this.removalReason != Entity.RemovalReason.UNLOADED_TO_CHUNK) { this.getPassengers().forEach(Entity::stopRiding); } // Paper - rewrite chunk system
+        this.levelCallback.onRemove(entity_removalreason);
+        // Paper start - Folia schedulers
+        if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
+            // Players need to be special cased, because they are regularly removed from the world
+            this.retireScheduler();
+        }
+        // Paper end - Folia schedulers
     }
 
     public void unsetRemoved() {
         this.removalReason = null;
     }
 
+    // Paper start - Folia schedulers
+    /**
+     * Invoked only when the entity is truly removed from the server, never to be added to any world.
+     */
+    public final void retireScheduler() {
+        // we need to force create the bukkit entity so that the scheduler can be retired...
+        this.getBukkitEntity().taskScheduler.retire();
+    }
+    // Paper end - Folia schedulers
+
     @Override
     public void setLevelCallback(EntityInLevelCallback changeListener) {
         this.levelCallback = changeListener;
@@ -3427,9 +4866,7 @@
 
     @Override
     public boolean shouldBeSaved() {
-        return (this.removalReason == null || this.removalReason.shouldSave())
-            && !this.isPassenger()
-            && (!this.isVehicle() || !this.hasExactlyOnePlayerPassenger());
+        return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this).moonrise$hasAnyPlayerPassengers()); // Paper - rewrite chunk system
     }
 
     @Override
@@ -3458,14 +4895,15 @@
     }
 
     protected void lerpPositionAndRotationStep(int step, double x, double y, double z, double yaw, double pitch) {
-        double d = 1.0 / (double)step;
-        double e = Mth.lerp(d, this.getX(), x);
-        double f = Mth.lerp(d, this.getY(), y);
-        double g = Mth.lerp(d, this.getZ(), z);
-        float h = (float)Mth.rotLerp(d, (double)this.getYRot(), yaw);
-        float i = (float)Mth.lerp(d, (double)this.getXRot(), pitch);
-        this.setPos(e, f, g);
-        this.setRot(h, i);
+        double d5 = 1.0D / (double) step;
+        double d6 = Mth.lerp(d5, this.getX(), x);
+        double d7 = Mth.lerp(d5, this.getY(), y);
+        double d8 = Mth.lerp(d5, this.getZ(), z);
+        float f = (float) Mth.rotLerp(d5, (double) this.getYRot(), yaw);
+        float f1 = (float) Mth.lerp(d5, (double) this.getXRot(), pitch);
+
+        this.setPos(d6, d7, d8);
+        this.setRot(f, f1);
     }
 
     public RandomSource getRandom() {
@@ -3473,8 +4911,12 @@
     }
 
     public Vec3 getKnownMovement() {
-        if (this.getControllingPassenger() instanceof Player player && this.isAlive()) {
-            return player.getKnownMovement();
+        net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger();
+
+        if (entityliving instanceof Player entityhuman) {
+            if (this.isAlive()) {
+                return entityhuman.getKnownMovement();
+            }
         }
 
         return this.getDeltaMovement();
@@ -3485,23 +4927,37 @@
         return null;
     }
 
-    @FunctionalInterface
-    public interface MoveFunction {
-        void accept(Entity entity, double x, double y, double z);
+    public static enum RemovalReason {
+
+        KILLED(true, false), DISCARDED(true, false), UNLOADED_TO_CHUNK(false, true), UNLOADED_WITH_PLAYER(false, false), CHANGED_DIMENSION(false, false);
+
+        private final boolean destroy;
+        private final boolean save;
+
+        private RemovalReason(final boolean flag, final boolean flag1) {
+            this.destroy = flag;
+            this.save = flag1;
+        }
+
+        public boolean shouldDestroy() {
+            return this.destroy;
+        }
+
+        public boolean shouldSave() {
+            return this.save;
+        }
     }
 
     public static enum MovementEmission {
-        NONE(false, false),
-        SOUNDS(true, false),
-        EVENTS(false, true),
-        ALL(true, true);
+
+        NONE(false, false), SOUNDS(true, false), EVENTS(false, true), ALL(true, true);
 
         final boolean sounds;
         final boolean events;
 
-        private MovementEmission(final boolean sounds, final boolean events) {
-            this.sounds = sounds;
-            this.events = events;
+        private MovementEmission(final boolean flag, final boolean flag1) {
+            this.sounds = flag;
+            this.events = flag1;
         }
 
         public boolean emitsAnything() {
@@ -3517,27 +4973,19 @@
         }
     }
 
-    public static enum RemovalReason {
-        KILLED(true, false),
-        DISCARDED(true, false),
-        UNLOADED_TO_CHUNK(false, true),
-        UNLOADED_WITH_PLAYER(false, false),
-        CHANGED_DIMENSION(false, false);
-
-        private final boolean destroy;
-        private final boolean save;
+    @FunctionalInterface
+    public interface MoveFunction {
 
-        private RemovalReason(final boolean destroy, final boolean save) {
-            this.destroy = destroy;
-            this.save = save;
-        }
+        void accept(Entity entity, double x, double y, double z);
+    }
 
-        public boolean shouldDestroy() {
-            return this.destroy;
-        }
+    // Paper start - Expose entity id counter
+    public static int nextEntityId() {
+        return ENTITY_COUNTER.incrementAndGet();
+    }
 
-        public boolean shouldSave() {
-            return this.save;
-        }
+    public boolean isTicking() {
+        return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this);
     }
+    // Paper end - Expose entity id counter
 }
