From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Sun, 5 Jul 2020 22:19:49 -0500
Subject: [PATCH] Ridables


diff --git a/src/main/java/net/minecraft/server/AttributeDefaults.java b/src/main/java/net/minecraft/server/AttributeDefaults.java
index 8f95a4e49714e352b9cdf82d7f4acdd3459978ad..ab05b5e403cf54756aa35891dc0d950187a9d4ec 100644
--- a/src/main/java/net/minecraft/server/AttributeDefaults.java
+++ b/src/main/java/net/minecraft/server/AttributeDefaults.java
@@ -9,7 +9,80 @@ import org.apache.logging.log4j.Logger;
 public class AttributeDefaults {
 
     private static final Logger LOGGER = LogManager.getLogger();
-    private static final Map<EntityTypes<? extends EntityLiving>, AttributeProvider> b = ImmutableMap.builder().put(EntityTypes.ARMOR_STAND, EntityLiving.cL().a()).put(EntityTypes.BAT, EntityBat.m().a()).put(EntityTypes.BEE, EntityBee.eZ().a()).put(EntityTypes.BLAZE, EntityBlaze.m().a()).put(EntityTypes.CAT, EntityCat.fa().a()).put(EntityTypes.CAVE_SPIDER, EntityCaveSpider.m().a()).put(EntityTypes.CHICKEN, EntityChicken.eK().a()).put(EntityTypes.COD, EntityFish.m().a()).put(EntityTypes.COW, EntityCow.eK().a()).put(EntityTypes.CREEPER, EntityCreeper.m().a()).put(EntityTypes.DOLPHIN, EntityDolphin.eM().a()).put(EntityTypes.DONKEY, EntityHorseChestedAbstract.eL().a()).put(EntityTypes.DROWNED, EntityZombie.eS().a()).put(EntityTypes.ELDER_GUARDIAN, EntityGuardianElder.m().a()).put(EntityTypes.ENDERMAN, EntityEnderman.m().a()).put(EntityTypes.ENDERMITE, EntityEndermite.m().a()).put(EntityTypes.ENDER_DRAGON, EntityEnderDragon.m().a()).put(EntityTypes.EVOKER, EntityEvoker.eK().a()).put(EntityTypes.FOX, EntityFox.eK().a()).put(EntityTypes.GHAST, EntityGhast.eJ().a()).put(EntityTypes.GIANT, EntityGiantZombie.m().a()).put(EntityTypes.GUARDIAN, EntityGuardian.eM().a()).put(EntityTypes.HOGLIN, EntityHoglin.eK().a()).put(EntityTypes.HORSE, EntityHorseAbstract.fi().a()).put(EntityTypes.HUSK, EntityZombie.eS().a()).put(EntityTypes.ILLUSIONER, EntityIllagerIllusioner.eK().a()).put(EntityTypes.IRON_GOLEM, EntityIronGolem.m().a()).put(EntityTypes.LLAMA, EntityLlama.fw().a()).put(EntityTypes.MAGMA_CUBE, EntityMagmaCube.m().a()).put(EntityTypes.MOOSHROOM, EntityCow.eK().a()).put(EntityTypes.MULE, EntityHorseChestedAbstract.eL().a()).put(EntityTypes.OCELOT, EntityOcelot.eK().a()).put(EntityTypes.PANDA, EntityPanda.eY().a()).put(EntityTypes.PARROT, EntityParrot.eU().a()).put(EntityTypes.PHANTOM, EntityMonster.eR().a()).put(EntityTypes.PIG, EntityPig.eK().a()).put(EntityTypes.PIGLIN, EntityPiglin.eT().a()).put(EntityTypes.PIGLIN_BRUTE, EntityPiglinBrute.eS().a()).put(EntityTypes.PILLAGER, EntityPillager.eK().a()).put(EntityTypes.PLAYER, EntityHuman.ep().a()).put(EntityTypes.POLAR_BEAR, EntityPolarBear.eK().a()).put(EntityTypes.PUFFERFISH, EntityFish.m().a()).put(EntityTypes.RABBIT, EntityRabbit.eL().a()).put(EntityTypes.RAVAGER, EntityRavager.m().a()).put(EntityTypes.SALMON, EntityFish.m().a()).put(EntityTypes.SHEEP, EntitySheep.eK().a()).put(EntityTypes.SHULKER, EntityShulker.m().a()).put(EntityTypes.SILVERFISH, EntitySilverfish.m().a()).put(EntityTypes.SKELETON, EntitySkeletonAbstract.m().a()).put(EntityTypes.SKELETON_HORSE, EntityHorseSkeleton.eL().a()).put(EntityTypes.SLIME, EntityMonster.eR().a()).put(EntityTypes.SNOW_GOLEM, EntitySnowman.m().a()).put(EntityTypes.SPIDER, EntitySpider.eK().a()).put(EntityTypes.SQUID, EntitySquid.m().a()).put(EntityTypes.STRAY, EntitySkeletonAbstract.m().a()).put(EntityTypes.STRIDER, EntityStrider.eM().a()).put(EntityTypes.TRADER_LLAMA, EntityLlama.fw().a()).put(EntityTypes.TROPICAL_FISH, EntityFish.m().a()).put(EntityTypes.TURTLE, EntityTurtle.eM().a()).put(EntityTypes.VEX, EntityVex.m().a()).put(EntityTypes.VILLAGER, EntityVillager.eY().a()).put(EntityTypes.VINDICATOR, EntityVindicator.eK().a()).put(EntityTypes.WANDERING_TRADER, EntityInsentient.p().a()).put(EntityTypes.WITCH, EntityWitch.eK().a()).put(EntityTypes.WITHER, EntityWither.eK().a()).put(EntityTypes.WITHER_SKELETON, EntitySkeletonAbstract.m().a()).put(EntityTypes.WOLF, EntityWolf.eU().a()).put(EntityTypes.ZOGLIN, EntityZoglin.m().a()).put(EntityTypes.ZOMBIE, EntityZombie.eS().a()).put(EntityTypes.ZOMBIE_HORSE, EntityHorseZombie.eL().a()).put(EntityTypes.ZOMBIE_VILLAGER, EntityZombie.eS().a()).put(EntityTypes.ZOMBIFIED_PIGLIN, EntityPigZombie.eW().a()).build();
+    private static final Map<EntityTypes<? extends EntityLiving>, AttributeProvider> b = ImmutableMap.<EntityTypes<? extends EntityLiving>, AttributeProvider>builder() // Purpur decompile error
+            .put(EntityTypes.ARMOR_STAND, EntityLiving.cL().a())
+            .put(EntityTypes.BAT, EntityBat.m().a())
+            .put(EntityTypes.BEE, EntityBee.eZ().a())
+            .put(EntityTypes.BLAZE, EntityBlaze.m().a())
+            .put(EntityTypes.CAT, EntityCat.fa().a())
+            .put(EntityTypes.CAVE_SPIDER, EntityCaveSpider.m().a())
+            .put(EntityTypes.CHICKEN, EntityChicken.eK().a())
+            .put(EntityTypes.COD, EntityFish.m().a())
+            .put(EntityTypes.COW, EntityCow.eK().a())
+            .put(EntityTypes.CREEPER, EntityCreeper.m().a())
+            .put(EntityTypes.DOLPHIN, EntityDolphin.eM().a())
+            .put(EntityTypes.DONKEY, EntityHorseChestedAbstract.eL().a())
+            .put(EntityTypes.DROWNED, EntityZombie.eS().a())
+            .put(EntityTypes.ELDER_GUARDIAN, EntityGuardianElder.m().a())
+            .put(EntityTypes.ENDERMAN, EntityEnderman.m().a())
+            .put(EntityTypes.ENDERMITE, EntityEndermite.m().a())
+            .put(EntityTypes.ENDER_DRAGON, EntityEnderDragon.m().a())
+            .put(EntityTypes.EVOKER, EntityEvoker.eK().a())
+            .put(EntityTypes.FOX, EntityFox.eK().a())
+            .put(EntityTypes.GHAST, EntityGhast.eJ().a())
+            .put(EntityTypes.GIANT, EntityGiantZombie.m().a())
+            .put(EntityTypes.GUARDIAN, EntityGuardian.eM().a())
+            .put(EntityTypes.HOGLIN, EntityHoglin.eK().a())
+            .put(EntityTypes.HORSE, EntityHorseAbstract.fi().a())
+            .put(EntityTypes.HUSK, EntityZombie.eS().a())
+            .put(EntityTypes.ILLUSIONER, EntityIllagerIllusioner.eK().a())
+            .put(EntityTypes.IRON_GOLEM, EntityIronGolem.m().a())
+            .put(EntityTypes.LLAMA, EntityLlama.fw().a())
+            .put(EntityTypes.MAGMA_CUBE, EntityMagmaCube.m().a())
+            .put(EntityTypes.MOOSHROOM, EntityCow.eK().a())
+            .put(EntityTypes.MULE, EntityHorseChestedAbstract.eL().a())
+            .put(EntityTypes.OCELOT, EntityOcelot.eK().a())
+            .put(EntityTypes.PANDA, EntityPanda.eY().a())
+            .put(EntityTypes.PARROT, EntityParrot.eU().a())
+            .put(EntityTypes.PHANTOM, EntityPhantom.defaultAttributes().build()) // Purpur
+            .put(EntityTypes.PIG, EntityPig.eK().a())
+            .put(EntityTypes.PIGLIN, EntityPiglin.eT().a())
+            .put(EntityTypes.PIGLIN_BRUTE, EntityPiglinBrute.eS().a())
+            .put(EntityTypes.PILLAGER, EntityPillager.eK().a())
+            .put(EntityTypes.PLAYER, EntityHuman.ep().a())
+            .put(EntityTypes.POLAR_BEAR, EntityPolarBear.eK().a())
+            .put(EntityTypes.PUFFERFISH, EntityFish.m().a())
+            .put(EntityTypes.RABBIT, EntityRabbit.eL().a())
+            .put(EntityTypes.RAVAGER, EntityRavager.m().a())
+            .put(EntityTypes.SALMON, EntityFish.m().a())
+            .put(EntityTypes.SHEEP, EntitySheep.eK().a())
+            .put(EntityTypes.SHULKER, EntityShulker.m().a())
+            .put(EntityTypes.SILVERFISH, EntitySilverfish.m().a())
+            .put(EntityTypes.SKELETON, EntitySkeletonAbstract.m().a())
+            .put(EntityTypes.SKELETON_HORSE, EntityHorseSkeleton.eL().a())
+            .put(EntityTypes.SLIME, EntityMonster.eR().a())
+            .put(EntityTypes.SNOW_GOLEM, EntitySnowman.m().a())
+            .put(EntityTypes.SPIDER, EntitySpider.eK().a())
+            .put(EntityTypes.SQUID, EntitySquid.m().a())
+            .put(EntityTypes.STRAY, EntitySkeletonAbstract.m().a())
+            .put(EntityTypes.STRIDER, EntityStrider.eM().a())
+            .put(EntityTypes.TRADER_LLAMA, EntityLlama.fw().a())
+            .put(EntityTypes.TROPICAL_FISH, EntityFish.m().a())
+            .put(EntityTypes.TURTLE, EntityTurtle.eM().a())
+            .put(EntityTypes.VEX, EntityVex.m().a())
+            .put(EntityTypes.VILLAGER, EntityVillager.eY().a())
+            .put(EntityTypes.VINDICATOR, EntityVindicator.eK().a())
+            .put(EntityTypes.WANDERING_TRADER, EntityInsentient.p().a())
+            .put(EntityTypes.WITCH, EntityWitch.eK().a())
+            .put(EntityTypes.WITHER, EntityWither.eK().a())
+            .put(EntityTypes.WITHER_SKELETON, EntitySkeletonAbstract.m().a())
+            .put(EntityTypes.WOLF, EntityWolf.eU().a())
+            .put(EntityTypes.ZOGLIN, EntityZoglin.m().a())
+            .put(EntityTypes.ZOMBIE, EntityZombie.eS().a())
+            .put(EntityTypes.ZOMBIE_HORSE, EntityHorseZombie.eL().a())
+            .put(EntityTypes.ZOMBIE_VILLAGER, EntityZombie.eS().a())
+            .put(EntityTypes.ZOMBIFIED_PIGLIN, EntityPigZombie.eW().a())
+            .build();
 
     public static AttributeProvider a(EntityTypes<? extends EntityLiving> entitytypes) {
         return (AttributeProvider) AttributeDefaults.b.get(entitytypes);
diff --git a/src/main/java/net/minecraft/server/AttributeProvider.java b/src/main/java/net/minecraft/server/AttributeProvider.java
index dd235a6fc4bc731a344d7211879f9b40bb622c3e..ac902c614dd3054f1a09298a42a88248b69552cd 100644
--- a/src/main/java/net/minecraft/server/AttributeProvider.java
+++ b/src/main/java/net/minecraft/server/AttributeProvider.java
@@ -101,6 +101,7 @@ public class AttributeProvider {
             return this;
         }
 
+        public AttributeProvider build() { return a(); } // Purpur - OBFHELPER
         public AttributeProvider a() {
             this.b = true;
             return new AttributeProvider(this.a);
diff --git a/src/main/java/net/minecraft/server/ControllerLookDolphin.java b/src/main/java/net/minecraft/server/ControllerLookDolphin.java
index e5bdddfc14b36b3a7b72ca92a9a14245fddb8833..8200ba60b7642a49742809a9c2e2ab7587259a71 100644
--- a/src/main/java/net/minecraft/server/ControllerLookDolphin.java
+++ b/src/main/java/net/minecraft/server/ControllerLookDolphin.java
@@ -1,6 +1,6 @@
 package net.minecraft.server;
 
-public class ControllerLookDolphin extends ControllerLook {
+public class ControllerLookDolphin extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur
 
     private final int h;
 
@@ -10,7 +10,7 @@ public class ControllerLookDolphin extends ControllerLook {
     }
 
     @Override
-    public void a() {
+    public void tick() { // Purpur
         if (this.d) {
             this.d = false;
             this.a.aC = this.a(this.a.aC, this.h() + 20.0F, this.b);
diff --git a/src/main/java/net/minecraft/server/ControllerMove.java b/src/main/java/net/minecraft/server/ControllerMove.java
index 8f9fb058c11ba5c2e887df048025284cd834ffe9..eaf44694683685e06dd1f012699e5c9d1f91c850 100644
--- a/src/main/java/net/minecraft/server/ControllerMove.java
+++ b/src/main/java/net/minecraft/server/ControllerMove.java
@@ -6,9 +6,9 @@ public class ControllerMove {
     protected double b;
     protected double c;
     protected double d;
-    protected double e;
-    protected float f;
-    protected float g;
+    protected double e; public double getSpeed() { return e; } public void setSpeed(double speed) { this.e = speed; } // Purpur - OBFHELPER
+    protected float f; public float getForward() { return f; } public void setForward(float forward) { this.f = forward; } // Purpur - OBFHELPER
+    protected float g; public float getStrafe() { return g; } public void setStrafe(float strafe) { this.g = strafe; } // Purpur - OBFHELPER
     protected ControllerMove.Operation h;
 
     public ControllerMove(EntityInsentient entityinsentient) {
diff --git a/src/main/java/net/minecraft/server/DamageSource.java b/src/main/java/net/minecraft/server/DamageSource.java
index bd0267ee4b3782f6d1ec39cba7966ba4f62f1adf..8b36ac2b0950a827763aa2357700f37eec5d00d3 100644
--- a/src/main/java/net/minecraft/server/DamageSource.java
+++ b/src/main/java/net/minecraft/server/DamageSource.java
@@ -56,6 +56,7 @@ public class DamageSource {
         return new EntityDamageSource("mob", entityliving);
     }
 
+    public static DamageSource indirectMobAttack(Entity entity, EntityLiving entityliving) { return a(entity, entityliving); } // Purpur - OBFHELPER
     public static DamageSource a(Entity entity, EntityLiving entityliving) {
         return new EntityDamageSourceIndirect("mob", entity, entityliving);
     }
@@ -117,6 +118,7 @@ public class DamageSource {
         return this.B;
     }
 
+    public DamageSource setProjectile() { return c(); } // Purpur - OBFHELPER
     public DamageSource c() {
         this.B = true;
         return this;
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index 30408e9e8b4acec56a95852558ead6a581b8565c..8be2b1ed66c3a72d2b752881b16b8a402cce2768 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -105,7 +105,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
     private int id;
     public boolean i; public final boolean blocksEntitySpawning() { return this.i; } // Paper - OBFHELPER
     public final List<Entity> passengers;
-    protected int j;
+    protected int j; public int getRideCooldown() { return j; } // Purpur - OBFHELPER
     @Nullable
     private Entity vehicle;
     public boolean attachedToPlayer;
@@ -121,7 +121,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
     public float lastYaw;
     public float lastPitch;
     private AxisAlignedBB boundingBox;
-    protected boolean onGround;
+    public boolean onGround; // Purpur - protected -> public
     public boolean positionChanged;
     public boolean v;
     public boolean velocityChanged;
@@ -177,7 +177,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
     private boolean az;
     private final double[] aA;
     private long aB;
-    private EntitySize size;
+    protected EntitySize size; // Purpur - private -> protected
     private float headHeight;
     // CraftBukkit start
     public boolean persist = true;
@@ -1495,6 +1495,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
         return !this.justCreated && this.M.getDouble(TagsFluid.LAVA) > 0.0D;
     }
 
+    public void moveRelative(float speed, Vec3D motion) { this.a(speed, motion); } // Purpur - OBFHELPER
     public void a(float f, Vec3D vec3d) {
         Vec3D vec3d1 = a(vec3d, f, this.yaw);
 
@@ -2251,6 +2252,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
         return this.a(entity, false);
     }
 
+    public boolean startRiding(Entity entity, boolean flag) { return a(entity, flag); } // Purpur - OBFHELPER
     public boolean a(Entity entity, boolean flag) {
         for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) {
             if (entity1.vehicle == this) {
@@ -2346,6 +2348,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
                 this.passengers.add(entity);
             }
 
+            // Purpur start
+            if (isRidable() && passengers.get(0) == entity && entity instanceof EntityHuman) {
+                EntityHuman entityhuman = (EntityHuman) entity;
+                onMount(entityhuman);
+                this.rider = entityhuman;
+            }
+            // Purpur end
         }
         return true; // CraftBukkit
     }
@@ -2386,6 +2395,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
                 return false;
             }
             // Spigot end
+            // Purpur start
+            if (rider != null && passengers.get(0) == rider) {
+                onDismount(rider);
+                this.rider = null;
+            }
+            // Purpur end
             this.passengers.remove(entity);
             entity.j = 60;
         }
@@ -2551,6 +2566,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
         this.setFlag(4, flag);
     }
 
+    public boolean isGlowing() { return bE(); } // Purpur - OBFHELPER
     public boolean bE() {
         return this.glowing || this.world.isClientSide && this.getFlag(6);
     }
@@ -2773,6 +2789,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
 
     public void setHeadRotation(float f) {}
 
+    public void setBodyYaw(float yaw) { n(yaw); } // Purpur - OBFHELPER
     public void n(float f) {}
 
     public boolean bL() {
@@ -3214,6 +3231,18 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
         return false;
     }
 
+    // Purpur Start
+    public void sendMessage(String str) {
+        sendMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(str));
+    }
+
+    public void sendMessage(IChatBaseComponent ichatbasecomponent) {
+        if (ichatbasecomponent != null) {
+            sendMessage(ichatbasecomponent, SystemUtils.getNullUUID());
+        }
+    }
+    // Purpur end
+
     @Override
     public void sendMessage(IChatBaseComponent ichatbasecomponent, UUID uuid) {}
 
@@ -3666,4 +3695,47 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
         return ((ChunkProviderServer) world.getChunkProvider()).isInEntityTickingChunk(this);
     }
     // Paper end
+
+    // Purpur start
+    private EntityHuman rider;
+
+    public EntityHuman getRider() {
+        return rider;
+    }
+
+    public boolean hasRider() {
+        return rider != null;
+    }
+
+    public boolean isRidable() {
+        return false;
+    }
+
+    public boolean isRidableInWater() {
+        return false;
+    }
+
+    public void onMount(EntityHuman entityhuman) {
+        if (this instanceof EntityInsentient) {
+            ((EntityInsentient) this).setGoalTarget(null, null, false);
+            ((EntityInsentient) this).getNavigation().stopPathfinding();
+        }
+        entityhuman.setJumping(false); // fixes jump on mount
+    }
+
+    public void onDismount(EntityHuman entityhuman) {
+    }
+
+    public boolean onSpacebar() {
+        return false;
+    }
+
+    public boolean onClick(EnumHand hand) {
+        return false;
+    }
+
+    public boolean processClick(EnumHand hand) {
+        return false;
+    }
+    // Purpur end
 }
diff --git a/src/main/java/net/minecraft/server/EntityBat.java b/src/main/java/net/minecraft/server/EntityBat.java
index 451ffcfd1fc9fa3091dc2bd697e5d829dcf6443f..7677f4a2ec8bacadcbc6e68e13f56256f1963552 100644
--- a/src/main/java/net/minecraft/server/EntityBat.java
+++ b/src/main/java/net/minecraft/server/EntityBat.java
@@ -15,9 +15,48 @@ public class EntityBat extends EntityAmbient {
 
     public EntityBat(EntityTypes<? extends EntityBat> entitytypes, World world) {
         super(entitytypes, world);
+        this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.075F); // Purpur
         this.setAsleep(true);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.batRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.batRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.batMaxY;
+    }
+
+    @Override
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        if (isAsleep()) {
+            setAsleep(false);
+            world.playEffect(null, 1025, new BlockPosition(this).up(), 0);
+        }
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider() && !onGround) {
+            float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 2;
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, 0.25, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+    // Purpur end
+
     @Override
     protected void initDatawatcher() {
         super.initDatawatcher();
@@ -62,7 +101,7 @@ public class EntityBat extends EntityAmbient {
     protected void collideNearby() {}
 
     public static AttributeProvider.Builder m() {
-        return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 6.0D);
+        return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 6.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur
     }
 
     public boolean isAsleep() {
@@ -94,6 +133,13 @@ public class EntityBat extends EntityAmbient {
 
     @Override
     protected void mobTick() {
+        // Purpur start
+        if (hasRider()) {
+            Vec3D mot = getMot();
+            setMot(mot.x, mot.y + (getVertical() > 0 ? 0.07D : 0.0D), mot.z);
+            return;
+        }
+        // Purpur end
         super.mobTick();
         BlockPosition blockposition = this.getChunkCoordinates();
         BlockPosition blockposition1 = blockposition.up();
diff --git a/src/main/java/net/minecraft/server/EntityBee.java b/src/main/java/net/minecraft/server/EntityBee.java
index f73641ddb3e82bc653732105ef0a3d41a28e845f..d8354ec4d19fc3fbddc2551ee217acb137482e63 100644
--- a/src/main/java/net/minecraft/server/EntityBee.java
+++ b/src/main/java/net/minecraft/server/EntityBee.java
@@ -37,6 +37,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
 
     public EntityBee(EntityTypes<? extends EntityBee> entitytypes, World world) {
         super(entitytypes, world);
+        final net.pl3x.purpur.controller.ControllerMoveWASDFlying flyingController = new net.pl3x.purpur.controller.ControllerMoveWASDFlying(this, 0.25F, false); // Purpur
         // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279
         this.moveController = new ControllerMoveFlying(this, 20, true) {
             @Override
@@ -46,6 +47,22 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
                 }
                 super.tick();
             }
+
+            // Purpur start
+            @Override
+            public void a() { // tick
+                if (getEntity().hasRider()) {
+                    flyingController.tick(getEntity().getRider());
+                } else {
+                    tick();
+                }
+            }
+
+            @Override
+            public boolean b() { // isUpdating
+                return getEntity().hasRider() || super.b();
+            }
+            // Purpur end
         };
         // Paper end
         this.lookController = new EntityBee.j(this);
@@ -56,6 +73,35 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
         this.a(PathType.FENCE, -1.0F);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.beeRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.beeRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.beeMaxY;
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider() && !onGround) {
+            float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 2;
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, speed, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+    // Purpur end
+
     @Override
     protected void initDatawatcher() {
         super.initDatawatcher();
@@ -70,6 +116,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
 
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(0, new EntityBee.b(this, 1.399999976158142D, true));
         this.goalSelector.a(1, new EntityBee.d());
         this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
@@ -85,6 +132,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
         this.goalSelector.a(7, new EntityBee.g());
         this.goalSelector.a(8, new EntityBee.l());
         this.goalSelector.a(9, new PathfinderGoalFloat(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new EntityBee.h(this)).a(new Class[0]));
         this.targetSelector.a(2, new EntityBee.c(this));
         this.targetSelector.a(3, new PathfinderGoalUniversalAngerReset<>(this, true));
@@ -555,6 +603,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
 
         private d() {
             super(); // CraftBukkit - decompile error
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - enter hive
         }
 
         @Override
@@ -617,6 +666,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
 
         private g() {
             super(); // CraftBukkit - decompile error
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - grow crop
         }
 
         @Override
@@ -681,6 +731,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
 
         private i() {
             super(); // CraftBukkit - decompile error
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - go to hive
         }
 
         @Override
@@ -904,16 +955,16 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB
         }
     }
 
-    class j extends ControllerLook {
+    class j extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur
 
         j(EntityInsentient entityinsentient) {
             super(entityinsentient);
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (!EntityBee.this.isAngry()) {
-                super.a();
+                super.tick(); // Purpur
             }
         }
 
diff --git a/src/main/java/net/minecraft/server/EntityBlaze.java b/src/main/java/net/minecraft/server/EntityBlaze.java
index 74082136b38491a0a50d152c455edfa61a6afb9f..dcfad16e06450068d5801fc002c9650102dbf995 100644
--- a/src/main/java/net/minecraft/server/EntityBlaze.java
+++ b/src/main/java/net/minecraft/server/EntityBlaze.java
@@ -10,6 +10,7 @@ public class EntityBlaze extends EntityMonster {
 
     public EntityBlaze(EntityTypes<? extends EntityBlaze> entitytypes, World world) {
         super(entitytypes, world);
+        this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.3F); // Purpur
         this.a(PathType.WATER, -1.0F);
         this.a(PathType.LAVA, 8.0F);
         this.a(PathType.DANGER_FIRE, 0.0F);
@@ -17,19 +18,50 @@ public class EntityBlaze extends EntityMonster {
         this.f = 10;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.blazeRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.blazeRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.blazeMaxY;
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider() && !onGround) {
+            float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue();
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, 1.0, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(4, new EntityBlaze.PathfinderGoalBlazeFireball(this));
         this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D));
         this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D, 0.0F));
         this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
-        this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a());
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
+        this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // Purpur - decompile error
         this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
     }
 
     public static AttributeProvider.Builder m() {
-        return EntityMonster.eR().a(GenericAttributes.ATTACK_DAMAGE, 6.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.23000000417232513D).a(GenericAttributes.FOLLOW_RANGE, 48.0D);
+        return EntityMonster.eR().a(GenericAttributes.ATTACK_DAMAGE, 6.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.23000000417232513D).a(GenericAttributes.FOLLOW_RANGE, 48.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur
     }
 
     @Override
@@ -84,6 +116,14 @@ public class EntityBlaze extends EntityMonster {
 
     @Override
     protected void mobTick() {
+        // Purpur start
+        if (hasRider()) {
+            Vec3D mot = getMot();
+            setMot(mot.x, getVertical() > 0 ? 0.07D : -0.07D, mot.z);
+            return;
+        }
+        // Purpur end
+
         --this.c;
         if (this.c <= 0) {
             this.c = 100;
diff --git a/src/main/java/net/minecraft/server/EntityCat.java b/src/main/java/net/minecraft/server/EntityCat.java
index 675083e3952779e43bf8cc3175ad70458a79e49c..0e6552d77adb95c94cb06b6f9289c4c12e6955bb 100644
--- a/src/main/java/net/minecraft/server/EntityCat.java
+++ b/src/main/java/net/minecraft/server/EntityCat.java
@@ -41,6 +41,25 @@ public class EntityCat extends EntityTameableAnimal {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.catRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.catRidableInWater;
+    }
+
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        setSitting(false);
+        setSleepingWithOwner(false);
+        setHeadDown(false);
+    }
+    // Purpur end
+
     public MinecraftKey eU() {
         return (MinecraftKey) EntityCat.bq.getOrDefault(this.getCatType(), EntityCat.bq.get(0));
     }
@@ -48,7 +67,8 @@ public class EntityCat extends EntityTameableAnimal {
     @Override
     protected void initPathfinder() {
         this.bx = new EntityCat.PathfinderGoalTemptChance(this, 0.6D, EntityCat.br, true);
-        this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalSit(this));
         this.goalSelector.a(2, new EntityCat.b(this));
         this.goalSelector.a(3, this.bx);
@@ -60,6 +80,7 @@ public class EntityCat extends EntityTameableAnimal {
         this.goalSelector.a(10, new PathfinderGoalBreed(this, 0.8D));
         this.goalSelector.a(11, new PathfinderGoalRandomStrollLand(this, 0.8D, 1.0000001E-5F));
         this.goalSelector.a(12, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 10.0F));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalRandomTargetNonTamed<>(this, EntityRabbit.class, false, (Predicate) null));
         this.targetSelector.a(1, new PathfinderGoalRandomTargetNonTamed<>(this, EntityTurtle.class, false, EntityTurtle.bo));
     }
@@ -76,6 +97,7 @@ public class EntityCat extends EntityTameableAnimal {
         this.datawatcher.set(EntityCat.bs, i);
     }
 
+    public void setSleepingWithOwner(boolean flag) { x(flag); } // Purpur - OBFHELPER
     public void x(boolean flag) {
         this.datawatcher.set(EntityCat.bt, flag);
     }
@@ -84,6 +106,7 @@ public class EntityCat extends EntityTameableAnimal {
         return (Boolean) this.datawatcher.get(EntityCat.bt);
     }
 
+    public void setHeadDown(boolean flag) { y(flag); } // Purpur - OBFHELPER
     public void y(boolean flag) {
         this.datawatcher.set(EntityCat.bu, flag);
     }
@@ -302,6 +325,7 @@ public class EntityCat extends EntityTameableAnimal {
 
     @Override
     public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) {
+        if (hasRider()) return EnumInteractionResult.PASS; // Purpur
         ItemStack itemstack = entityhuman.b(enumhand);
         Item item = itemstack.getItem();
 
@@ -399,6 +423,7 @@ public class EntityCat extends EntityTameableAnimal {
 
         public b(EntityCat entitycat) {
             this.a = entitycat;
+            this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - lay on owner
         }
 
         @Override
diff --git a/src/main/java/net/minecraft/server/EntityCaveSpider.java b/src/main/java/net/minecraft/server/EntityCaveSpider.java
index 776f3d25a6eeb5e97667dd06c062d1045d1afa81..2e1f2dec17e7761b6534f29bbec813d135250e4f 100644
--- a/src/main/java/net/minecraft/server/EntityCaveSpider.java
+++ b/src/main/java/net/minecraft/server/EntityCaveSpider.java
@@ -8,6 +8,18 @@ public class EntityCaveSpider extends EntitySpider {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.caveSpiderRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.caveSpiderRidableInWater;
+    }
+    // Purpur end
+
     public static AttributeProvider.Builder m() {
         return EntitySpider.eK().a(GenericAttributes.MAX_HEALTH, 12.0D);
     }
diff --git a/src/main/java/net/minecraft/server/EntityChicken.java b/src/main/java/net/minecraft/server/EntityChicken.java
index 8fb5d5c75e79a81ab46af3fbb96ebc41804113c4..ee59a9f272a9caebec8f2329e1e4b22ddd27a0f9 100644
--- a/src/main/java/net/minecraft/server/EntityChicken.java
+++ b/src/main/java/net/minecraft/server/EntityChicken.java
@@ -18,6 +18,16 @@ public class EntityChicken extends EntityAnimal {
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.chickenRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.chickenRidableInWater;
+    }
+
     @Override
     protected void initAttributes() {
         if (world.purpurConfig.chickenRetaliate) {
@@ -29,6 +39,7 @@ public class EntityChicken extends EntityAnimal {
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         //this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D)); // Purpur - moved down
         this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
         this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, EntityChicken.bv));
@@ -39,6 +50,7 @@ public class EntityChicken extends EntityAnimal {
         // Purpur start
         if (world.purpurConfig.chickenRetaliate) {
             this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, false));
+            this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
             this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this));
         } else {
             this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D));
diff --git a/src/main/java/net/minecraft/server/EntityCod.java b/src/main/java/net/minecraft/server/EntityCod.java
index 9a99af6e9cec9679af07ab6017f9beb427fa88ad..039fae4c29648afa85ea1b27d82cfe51c4165315 100644
--- a/src/main/java/net/minecraft/server/EntityCod.java
+++ b/src/main/java/net/minecraft/server/EntityCod.java
@@ -6,6 +6,18 @@ public class EntityCod extends EntityFishSchool {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.codRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+    // Purpur end
+
     @Override
     protected ItemStack eK() {
         return new ItemStack(Items.COD_BUCKET);
diff --git a/src/main/java/net/minecraft/server/EntityComplexPart.java b/src/main/java/net/minecraft/server/EntityComplexPart.java
index c1b1bd123e7a18d37fd55cacc29ebca32030e5f0..26ff230ef774999bfe37b3327a7cf711799cbf2f 100644
--- a/src/main/java/net/minecraft/server/EntityComplexPart.java
+++ b/src/main/java/net/minecraft/server/EntityComplexPart.java
@@ -47,4 +47,11 @@ public class EntityComplexPart extends Entity {
     public EntitySize a(EntityPose entitypose) {
         return this.d;
     }
+
+    // Purpur start
+    @Override
+    public EnumInteractionResult a(EntityHuman entityhuman, EnumHand enumhand) {
+        return owner.isAlive() ? owner.tryRide(entityhuman, enumhand) : EnumInteractionResult.PASS;
+    }
+    // Purpur end
 }
diff --git a/src/main/java/net/minecraft/server/EntityCow.java b/src/main/java/net/minecraft/server/EntityCow.java
index cfb009c811bd2908d38da1b0007cb7aaed4e42c3..1219b0aa9c62bc9a1bda45cc9e9a27f14a28fe2e 100644
--- a/src/main/java/net/minecraft/server/EntityCow.java
+++ b/src/main/java/net/minecraft/server/EntityCow.java
@@ -11,9 +11,22 @@ public class EntityCow extends EntityAnimal {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.cowRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.cowRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalPanic(this, 2.0D));
         this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
         if (world.purpurConfig.cowFeedMushrooms > 0) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.25D, RecipeItemStack.a(Items.WHEAT, Blocks.RED_MUSHROOM.getItem(), Blocks.BROWN_MUSHROOM.getItem()), false)); else // Purpur
@@ -55,6 +68,7 @@ public class EntityCow extends EntityAnimal {
 
     @Override
     public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) {
+        if (hasRider()) return EnumInteractionResult.PASS; // Purpur
         ItemStack itemstack = entityhuman.b(enumhand);
 
         if (itemstack.getItem() == Items.BUCKET && !this.isBaby()) {
@@ -62,7 +76,7 @@ public class EntityCow extends EntityAnimal {
             org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((WorldServer) entityhuman.world, entityhuman, this.getChunkCoordinates(), this.getChunkCoordinates(), null, itemstack, Items.MILK_BUCKET, enumhand); // Paper - add enumHand
 
             if (event.isCancelled()) {
-                return EnumInteractionResult.PASS;
+                return tryRide(entityhuman, enumhand); // Purpur
             }
             // CraftBukkit end
 
@@ -73,7 +87,7 @@ public class EntityCow extends EntityAnimal {
             return EnumInteractionResult.a(this.world.isClientSide);
         // Purpur start - feed mushroom to change to mooshroom
         } else if (world.purpurConfig.cowFeedMushrooms > 0 && getEntityType() != EntityTypes.MOOSHROOM && isMushroom(itemstack)) {
-            return feedMushroom(entityhuman, itemstack);
+            return feedMushroom(entityhuman, enumhand, itemstack);
         // Purpur end
         } else {
             return super.b(entityhuman, enumhand);
@@ -96,7 +110,7 @@ public class EntityCow extends EntityAnimal {
         }
     }
 
-    private EnumInteractionResult feedMushroom(EntityHuman entityhuman, ItemStack itemstack) {
+    private EnumInteractionResult feedMushroom(EntityHuman entityhuman, EnumHand enumhand, ItemStack itemstack) {
         world.broadcastEntityEffect(this, (byte) 18); // hearts
         playSound(SoundEffects.ENTITY_COW_MILK, 1.0F, 1.0F);
         if (incrementFeedCount(itemstack) < world.purpurConfig.cowFeedMushrooms) {
@@ -107,7 +121,7 @@ public class EntityCow extends EntityAnimal {
         }
         EntityMushroomCow mooshroom = EntityTypes.MOOSHROOM.create(world);
         if (mooshroom == null) {
-            return EnumInteractionResult.PASS;
+            return tryRide(entityhuman, enumhand);
         }
         if (itemstack.getItem() == Blocks.BROWN_MUSHROOM.getItem()) {
             mooshroom.setVariant(EntityMushroomCow.Type.BROWN);
@@ -126,10 +140,10 @@ public class EntityCow extends EntityAnimal {
             mooshroom.setCustomName(this.getCustomName());
         }
         if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
-            return EnumInteractionResult.PASS;
+            return tryRide(entityhuman, enumhand);
         }
         if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), mooshroom.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) {
-            return EnumInteractionResult.PASS;
+            return tryRide(entityhuman, enumhand);
         }
         this.world.addEntity(mooshroom);
         this.die();
diff --git a/src/main/java/net/minecraft/server/EntityCreeper.java b/src/main/java/net/minecraft/server/EntityCreeper.java
index 36bf307985624a3567eb1196e034470bd7002e1e..7e5ede0f644a6e303be36ab0915d346a02760daa 100644
--- a/src/main/java/net/minecraft/server/EntityCreeper.java
+++ b/src/main/java/net/minecraft/server/EntityCreeper.java
@@ -19,12 +19,27 @@ public class EntityCreeper extends EntityMonster {
     public int maxFuseTicks = 30;
     public int explosionRadius = 3;
     private int bs;
+    // Purpur start
+    private int spacebarCharge = 0;
+    private int prevSpacebarCharge = 0;
+    private int powerToggleDelay = 0;
+    // Purpur end
 
     public EntityCreeper(EntityTypes<? extends EntityCreeper> entitytypes, World world) {
         super(entitytypes, world);
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.creeperRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.creeperRidableInWater;
+    }
+
     @Override
     public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @javax.annotation.Nullable GroupDataEntity groupdataentity, @javax.annotation.Nullable NBTTagCompound nbttagcompound) {
         double chance = worldaccess.getMinecraftWorld().purpurConfig.creeperChargedChance;
@@ -33,18 +48,69 @@ public class EntityCreeper extends EntityMonster {
         }
         return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound);
     }
+
+    @Override
+    protected void mobTick() {
+        if (powerToggleDelay > 0) {
+            powerToggleDelay--;
+        }
+        if (hasRider()) {
+            if (getRider().getForward() != 0 || getRider().getStrafe() != 0) {
+                spacebarCharge = 0;
+                setIgnited(false);
+            }
+            if (spacebarCharge == prevSpacebarCharge) {
+                spacebarCharge = 0;
+            }
+            prevSpacebarCharge = spacebarCharge;
+        }
+        super.mobTick();
+    }
+
+    @Override
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        setIgnited(false);
+    }
+
+    @Override
+    public boolean onSpacebar() {
+        if (powerToggleDelay > 0) {
+            return true; // just toggled power, do not jump or ignite
+        }
+        spacebarCharge++;
+        if (spacebarCharge > maxFuseTicks - 2) {
+            spacebarCharge = 0;
+            if (getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) {
+                powerToggleDelay = 20;
+                setPowered(!isPowered());
+                setIgnited(false);
+                return true;
+            }
+        }
+        if (!isIgnited()) {
+            if (hasRider() && getRider().getForward() == 0 && getRider().getStrafe() == 0 &&
+                    getRider().getBukkitEntity().hasPermission("allow.special.creeper")) {
+                setIgnited(true);
+                return true;
+            }
+        }
+        return getForward() == 0 && getStrafe() == 0; // do not jump if standing still
+    }
     // Purpur end
 
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(1, new PathfinderGoalFloat(this));
         this.goalSelector.a(2, new PathfinderGoalSwell(this));
+        this.goalSelector.a(3, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityOcelot.class, 6.0F, 1.0D, 1.2D));
         this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityCat.class, 6.0F, 1.0D, 1.2D));
         this.goalSelector.a(4, new PathfinderGoalMeleeAttack(this, 1.0D, false));
         this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 0.8D));
         this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(6, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
         this.targetSelector.a(2, new PathfinderGoalHurtByTarget(this, new Class[0]));
     }
@@ -175,6 +241,7 @@ public class EntityCreeper extends EntityMonster {
         return (Integer) this.datawatcher.get(EntityCreeper.b);
     }
 
+    public void setSwellDirection(int i) { a(i); } // Purpur - OBFHELPER
     public void a(int i) {
         this.datawatcher.set(EntityCreeper.b, i);
     }
@@ -275,6 +342,7 @@ public class EntityCreeper extends EntityMonster {
             com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited);
             if (event.callEvent()) {
                 this.datawatcher.set(EntityCreeper.d, event.isIgnited());
+                if (!event.isIgnited()) setSwellDirection(-1); // Purpur
             }
         }
         // Paper end
diff --git a/src/main/java/net/minecraft/server/EntityDolphin.java b/src/main/java/net/minecraft/server/EntityDolphin.java
index 664f9693368852bfb06a7a3bd0862a10cbc81747..9d5d143a997b74e0777bb79bf14d341ad5340db9 100644
--- a/src/main/java/net/minecraft/server/EntityDolphin.java
+++ b/src/main/java/net/minecraft/server/EntityDolphin.java
@@ -17,6 +17,7 @@ public class EntityDolphin extends EntityWaterAnimal {
     public static final Predicate<EntityItem> b = (entityitem) -> {
         return !entityitem.p() && entityitem.isAlive() && entityitem.isInWater();
     };
+    private int spitCooldown; // Purpur
 
     public EntityDolphin(EntityTypes<? extends EntityDolphin> entitytypes, World world) {
         super(entitytypes, world);
@@ -25,6 +26,45 @@ public class EntityDolphin extends EntityWaterAnimal {
         this.setCanPickupLoot(true);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.dolphinRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+
+    @Override
+    public boolean onSpacebar() {
+        if (spitCooldown == 0 && hasRider()) {
+            spitCooldown = world.purpurConfig.dolphinSpitCooldown;
+            if (!hasRider()) {
+                return false;
+            }
+
+            org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity();
+            if (!player.hasPermission("allow.special.dolphin")) {
+                return false;
+            }
+
+            org.bukkit.Location loc = player.getEyeLocation();
+            loc.setPitch(loc.getPitch() - 10);
+            org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector());
+
+            net.pl3x.purpur.entity.DolphinSpit spit = new net.pl3x.purpur.entity.DolphinSpit(world, this);
+            spit.shoot(target.getX() - locX(), target.getY() - locY(), target.getZ() - locZ(), world.purpurConfig.dolphinSpitSpeed, 5.0F);
+
+            world.addEntity(spit);
+            playSound(SoundEffects.ENTITY_DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F);
+            return true;
+        }
+        return false;
+    }
+    // Purpur end
+
     @Nullable
     @Override
     public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) {
@@ -99,6 +139,7 @@ public class EntityDolphin extends EntityWaterAnimal {
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalBreath(this));
         this.goalSelector.a(0, new PathfinderGoalWater(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityDolphin.b(this));
         this.goalSelector.a(2, new EntityDolphin.c(this, 4.0D));
         this.goalSelector.a(4, new PathfinderGoalRandomSwim(this, 1.0D, 10));
@@ -109,6 +150,7 @@ public class EntityDolphin extends EntityWaterAnimal {
         this.goalSelector.a(8, new EntityDolphin.d());
         this.goalSelector.a(8, new PathfinderGoalFollowBoat(this));
         this.goalSelector.a(9, new PathfinderGoalAvoidTarget<>(this, EntityGuardian.class, 8.0F, 1.0D, 1.0D));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityGuardian.class})).a(new Class[0])); // CraftBukkit - decompile error
     }
 
@@ -160,7 +202,7 @@ public class EntityDolphin extends EntityWaterAnimal {
 
     @Override
     protected boolean n(Entity entity) {
-        return true;
+        return getRideCooldown() <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs
     }
 
     @Override
@@ -195,6 +237,9 @@ public class EntityDolphin extends EntityWaterAnimal {
     @Override
     public void tick() {
         super.tick();
+        if (spitCooldown > 0) {
+            spitCooldown--;
+        }
         if (this.isNoAI()) {
             this.setAirTicks(this.bH());
         } else {
@@ -468,7 +513,7 @@ public class EntityDolphin extends EntityWaterAnimal {
 
         private int b;
 
-        private d() {}
+        private d() { this.a(java.util.EnumSet.of(PathfinderGoal.Type.MOVE)); } // Purpur - play with item
 
         @Override
         public boolean a() {
@@ -536,7 +581,7 @@ public class EntityDolphin extends EntityWaterAnimal {
         }
     }
 
-    static class a extends ControllerMove {
+    static class a extends net.pl3x.purpur.controller.ControllerMoveWASDWater { // Purpur
 
         private final EntityDolphin i;
 
@@ -546,7 +591,20 @@ public class EntityDolphin extends EntityWaterAnimal {
         }
 
         @Override
-        public void a() {
+        // Purpur start
+        public void tick(EntityHuman rider) {
+            if (this.i.getAirTicks() < 150) {
+                // if drowning override player WASD controls to find air
+                tick();
+            } else {
+                super.tick(rider);
+                this.i.setMot(this.i.getMot().add(0.0D, 0.005D, 0.0D));
+            }
+        }
+
+        @Override
+        public void tick() {
+            // Purpur end
             if (this.i.isInWater()) {
                 this.i.setMot(this.i.getMot().add(0.0D, 0.005D, 0.0D));
             }
diff --git a/src/main/java/net/minecraft/server/EntityDrowned.java b/src/main/java/net/minecraft/server/EntityDrowned.java
index 1a102816921fa3b40f6d364bb826db4459f68eb2..125eab60f2b4657e52a71eddf7586c574945252e 100644
--- a/src/main/java/net/minecraft/server/EntityDrowned.java
+++ b/src/main/java/net/minecraft/server/EntityDrowned.java
@@ -22,6 +22,16 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity {
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.drownedRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.drownedRidableInWater;
+    }
+
     @Override
     public boolean jockeyOnlyBaby() {
         return world.purpurConfig.drownedJockeyOnlyBaby;
@@ -219,7 +229,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity {
         this.d = flag;
     }
 
-    static class d extends ControllerMove {
+    static class d extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur
 
         private final EntityDrowned i;
 
@@ -229,7 +239,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity {
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             EntityLiving entityliving = this.i.getGoalTarget();
 
             if (this.i.eW() && this.i.isInWater()) {
@@ -262,7 +272,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity {
                     this.i.setMot(this.i.getMot().add(0.0D, -0.008D, 0.0D));
                 }
 
-                super.a();
+                super.tick(); // Purpur
             }
 
         }
@@ -401,6 +411,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity {
             this.a = entitydrowned;
             this.b = d0;
             this.c = i;
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - swim up
         }
 
         @Override
diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java
index f777aea69dc9949aae3251e9a530b8cffb251bad..497d05996ab84f599aa0a02f853149f074e4d3cb 100644
--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java
+++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java
@@ -46,6 +46,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
     private final int[] bK = new int[24];
     private final Path bL = new Path();
     private Explosion explosionSource = new Explosion(null, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.Effect.DESTROY); // CraftBukkit - reusable source for CraftTNTPrimed.getSource()
+    private boolean hadRider; // Purpur
 
     public EntityEnderDragon(EntityTypes<? extends EntityEnderDragon> entitytypes, World world) {
         super(EntityTypes.ENDER_DRAGON, world);
@@ -60,8 +61,44 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
         }
 
         this.bG = new DragonControllerManager(this);
+        // Purpur start
+        this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlying(this) {
+            @Override
+            public void tick() {
+                // dragon doesn't use the controller. do nothing
+            }
+        };
+        this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this) {
+            @Override
+            public void tick() {
+                // dragon doesn't use the controller. do nothing
+            }
+
+            @Override
+            public void tick(EntityHuman rider) {
+                setYawPitch(rider.yaw - 180F, rider.pitch * 0.5F);
+            }
+        };
+        // Purpur end
+    }
+
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.enderDragonRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.enderDragonRidableInWater;
     }
 
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.enderDragonMaxY;
+    }
+    // Purpur end
+
     public static AttributeProvider.Builder m() {
         return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 200.0D);
     }
@@ -94,6 +131,37 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
 
     @Override
     public void movementTick() {
+        // Purpur start
+        boolean hasRider = getRider() != null;
+        if (hasRider) {
+            if (!hadRider) {
+                hadRider = true;
+                noclip = false;
+                this.size = EntitySize.b(4.0F, 2.0F);
+            }
+
+            // dragon doesn't use controllers, so must tick manually
+            moveController.a();
+            lookController.a();
+
+            moveRelative((float) getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * 0.1F, new Vec3D(-getStrafe(), getVertical(), -getForward()));
+            Vec3D mot = getMot();
+            setMot(mot);
+            move(EnumMoveType.PLAYER, mot);
+
+            mot = mot.multiply(0.9F, 0.9F, 0.9F);
+            setMot(mot);
+
+            // control wing flap speed on client
+            getDragonControllerManager().setControllerPhase(mot.getX() * mot.getX() + mot.getZ() * mot.getZ() < 0.005F ? DragonControllerPhase.HOVER : DragonControllerPhase.HOLDING_PATTERN);
+        } else if (hadRider) {
+            hadRider = false;
+            noclip = true;
+            this.size = EntitySize.b(16.0F, 8.0F);
+            getDragonControllerManager().setControllerPhase(DragonControllerPhase.HOLDING_PATTERN); // HoldingPattern
+        }
+        // Purpur end
+
         float f;
         float f1;
 
@@ -115,6 +183,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
 
         this.bp = this.bq;
         if (this.dl()) {
+            if (hasRider) ejectPassengers(); // Purpur
             f = (this.random.nextFloat() - 0.5F) * 8.0F;
             f1 = (this.random.nextFloat() - 0.5F) * 4.0F;
             float f2 = (this.random.nextFloat() - 0.5F) * 8.0F;
@@ -126,9 +195,9 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
 
             f1 = 0.2F / (MathHelper.sqrt(c(vec3d)) * 10.0F + 1.0F);
             f1 *= (float) Math.pow(2.0D, vec3d.y);
-            if (this.bG.a().a()) {
+            if (!hasRider && this.bG.a().a()) { // Purpur
                 this.bq += 0.1F;
-            } else if (this.br) {
+            } else if (!hasRider && this.br) { // Purpur
                 this.bq += f1 * 0.5F;
             } else {
                 this.bq += f1;
@@ -172,7 +241,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
                     }
 
                     this.bG.a().b();
-                } else {
+                } else if (!hasRider) { // Purpur
                     IDragonController idragoncontroller = this.bG.a();
 
                     idragoncontroller.c();
@@ -239,7 +308,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
                 this.a(this.bz, (double) (f11 * 0.5F), 0.0D, (double) (-f12 * 0.5F));
                 this.a(this.bD, (double) (f12 * 4.5F), 2.0D, (double) (f11 * 4.5F));
                 this.a(this.bE, (double) (f12 * -4.5F), 2.0D, (double) (f11 * -4.5F));
-                if (!this.world.isClientSide && this.hurtTicks == 0) {
+                if (!hasRider && !this.world.isClientSide && this.hurtTicks == 0) { // Purpur
                     this.a(this.world.getEntities(this, this.bD.getBoundingBox().grow(4.0D, 2.0D, 4.0D).d(0.0D, -2.0D, 0.0D), IEntitySelector.e));
                     this.a(this.world.getEntities(this, this.bE.getBoundingBox().grow(4.0D, 2.0D, 4.0D).d(0.0D, -2.0D, 0.0D), IEntitySelector.e));
                     this.b(this.world.getEntities(this, this.bo.getBoundingBox().g(1.0D), IEntitySelector.e));
@@ -282,7 +351,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
                 }
 
                 if (!this.world.isClientSide) {
-                    this.br = this.b(this.bo.getBoundingBox()) | this.b(this.by.getBoundingBox()) | this.b(this.bz.getBoundingBox());
+                    this.br = !hasRider && this.b(this.bo.getBoundingBox()) | this.b(this.by.getBoundingBox()) | this.b(this.bz.getBoundingBox()); // Purpur
                     if (this.bF != null) {
                         this.bF.b(this);
                     }
diff --git a/src/main/java/net/minecraft/server/EntityEnderman.java b/src/main/java/net/minecraft/server/EntityEnderman.java
index e3364032669b473c799b759f5f89468b7584d9f5..995849212c25568d3aa28ada78babf8b8e669960 100644
--- a/src/main/java/net/minecraft/server/EntityEnderman.java
+++ b/src/main/java/net/minecraft/server/EntityEnderman.java
@@ -30,9 +30,22 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable {
         this.a(PathType.WATER, -1.0F);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.endermanRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.endermanRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityEnderman.a(this));
         this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false));
         this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D, 0.0F));
@@ -40,6 +53,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable {
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
         this.goalSelector.a(10, new EntityEnderman.PathfinderGoalEndermanPlaceBlock(this));
         this.goalSelector.a(11, new EntityEnderman.PathfinderGoalEndermanPickupBlock(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new EntityEnderman.PathfinderGoalPlayerWhoLookedAtTarget(this, this::a_));
         this.targetSelector.a(2, new PathfinderGoalHurtByTarget(this, new Class[0]));
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityEndermite.class, 10, true, false, EntityEnderman.bq));
@@ -220,7 +234,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable {
 
     @Override
     protected void mobTick() {
-        if (this.world.isDay() && this.ticksLived >= this.bs + 600) {
+        if (!hasRider() && this.world.isDay() && this.ticksLived >= this.bs + 600) { // Purpur - no random teleporting
             float f = this.aR();
 
             if (f > 0.5F && this.world.e(this.getChunkCoordinates()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper
@@ -322,6 +336,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable {
         if (this.isInvulnerable(damagesource)) {
             return false;
         } else if (net.pl3x.purpur.PurpurConfig.endermanShortHeight && damagesource == DamageSource.STUCK) { return false; // Purpur - no suffocation damage if short height
+        } else if (hasRider()) { return super.damageEntity(damagesource, f); // Purpur - no teleporting on damage
         } else if (damagesource instanceof EntityDamageSourceIndirect) {
             if (this.tryEscape(EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start
             for (int i = 0; i < 64; ++i) {
@@ -366,6 +381,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable {
 
         public PathfinderGoalEndermanPickupBlock(EntityEnderman entityenderman) {
             this.enderman = entityenderman;
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur
         }
 
         @Override
@@ -408,6 +424,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable {
 
         public PathfinderGoalEndermanPlaceBlock(EntityEnderman entityenderman) {
             this.a = entityenderman;
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur
         }
 
         @Override
diff --git a/src/main/java/net/minecraft/server/EntityEndermite.java b/src/main/java/net/minecraft/server/EntityEndermite.java
index fcebf36312c95df876652d18cf27bf1cc6589226..7eef7f523a17434b38492006526920a955fc9120 100644
--- a/src/main/java/net/minecraft/server/EntityEndermite.java
+++ b/src/main/java/net/minecraft/server/EntityEndermite.java
@@ -12,14 +12,28 @@ public class EntityEndermite extends EntityMonster {
         this.f = 3;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.endermiteRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.endermiteRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false));
         this.goalSelector.a(3, new PathfinderGoalRandomStrollLand(this, 1.0D));
         this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
-        this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a());
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
+        this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // Purpur - decompile error
         this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
     }
 
diff --git a/src/main/java/net/minecraft/server/EntityEvoker.java b/src/main/java/net/minecraft/server/EntityEvoker.java
index f40fbef32f2fc1f37f2065ad598f013766cf12f2..c75dc75611991028e9de6db7c57304e913251a6b 100644
--- a/src/main/java/net/minecraft/server/EntityEvoker.java
+++ b/src/main/java/net/minecraft/server/EntityEvoker.java
@@ -12,10 +12,23 @@ public class EntityEvoker extends EntityIllagerWizard {
         this.f = 10;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.evokerRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.evokerRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityEvoker.b());
         this.goalSelector.a(2, new PathfinderGoalAvoidTarget<>(this, EntityHuman.class, 8.0F, 0.6D, 1.0D));
         this.goalSelector.a(4, new EntityEvoker.c());
@@ -24,6 +37,7 @@ public class EntityEvoker extends EntityIllagerWizard {
         this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D));
         this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F));
         this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // Paper - decompile fix
         this.targetSelector.a(2, (new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)).a(300));
         this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)).a(300));
@@ -104,6 +118,7 @@ public class EntityEvoker extends EntityIllagerWizard {
 
         public d() {
             super();
+            this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - wolololo spell
         }
 
         @Override
@@ -182,6 +197,7 @@ public class EntityEvoker extends EntityIllagerWizard {
         private c() {
             super();
             this.e = (new PathfinderTargetCondition()).a(16.0D).c().e().a().b();
+            this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - summon spell
         }
 
         @Override
@@ -238,6 +254,7 @@ public class EntityEvoker extends EntityIllagerWizard {
 
         private a() {
             super();
+            this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - attack with spell
         }
 
         @Override
diff --git a/src/main/java/net/minecraft/server/EntityFish.java b/src/main/java/net/minecraft/server/EntityFish.java
index 9b84564401e30a7a2aafae023a1eb3b2eda3f112..668f9d61b72cf0d8e7bb0d1cd985ae653a587466 100644
--- a/src/main/java/net/minecraft/server/EntityFish.java
+++ b/src/main/java/net/minecraft/server/EntityFish.java
@@ -70,13 +70,12 @@ public abstract class EntityFish extends EntityWaterAnimal {
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
-        this.goalSelector.a(0, new PathfinderGoalPanic(this, 1.25D));
-        PathfinderGoalSelector pathfindergoalselector = this.goalSelector;
-        Predicate predicate = IEntitySelector.g;
-
-        predicate.getClass();
-        pathfindergoalselector.a(2, new PathfinderGoalAvoidTarget<>(this, EntityHuman.class, 8.0F, 1.6D, 1.4D, predicate::test));
-        this.goalSelector.a(4, new EntityFish.b(this));
+        // Purpur start
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
+        this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D));
+        this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityHuman.class, 8.0F, 1.6D, 1.4D, IEntitySelector.g::test)); // Purpur - decompile error
+        this.goalSelector.a(5, new EntityFish.b(this));
+        // Purpur end
     }
 
     @Override
@@ -87,7 +86,7 @@ public abstract class EntityFish extends EntityWaterAnimal {
     @Override
     public void g(Vec3D vec3d) {
         if (this.doAITick() && this.isInWater()) {
-            this.a(0.01F, vec3d);
+            this.a(hasRider() ? getSpeed() : 0.01F, vec3d); // Purpur
             this.move(EnumMoveType.SELF, this.getMot());
             this.setMot(this.getMot().a(0.9D));
             if (this.getGoalTarget() == null) {
@@ -161,9 +160,9 @@ public abstract class EntityFish extends EntityWaterAnimal {
     @Override
     protected void b(BlockPosition blockposition, IBlockData iblockdata) {}
 
-    static class a extends ControllerMove {
+    static class a extends net.pl3x.purpur.controller.ControllerMoveWASDWater { // Purpur
 
-        private final EntityFish i;
+        private final EntityFish i; public EntityFish getFish() { return i; } // Purpur - OBFHELPER
 
         a(EntityFish entityfish) {
             super(entityfish);
@@ -171,7 +170,15 @@ public abstract class EntityFish extends EntityWaterAnimal {
         }
 
         @Override
-        public void a() {
+        // Purpur start
+        public void tick(EntityHuman rider) {
+            super.tick(rider);
+            getFish().setMot(getFish().getMot().add(0.0D, 0.005D, 0.0D));
+        }
+
+        @Override
+        public void tick() {
+            // Purpur end
             if (this.i.a((Tag) TagsFluid.WATER)) {
                 this.i.setMot(this.i.getMot().add(0.0D, 0.005D, 0.0D));
             }
diff --git a/src/main/java/net/minecraft/server/EntityFishSchool.java b/src/main/java/net/minecraft/server/EntityFishSchool.java
index 08372b72262f2ef6e6c95f34e889e04faf731329..6a8e15f01606024aba43d57d270e09db307a9c3f 100644
--- a/src/main/java/net/minecraft/server/EntityFishSchool.java
+++ b/src/main/java/net/minecraft/server/EntityFishSchool.java
@@ -16,7 +16,7 @@ public abstract class EntityFishSchool extends EntityFish {
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
-        this.goalSelector.a(5, new PathfinderGoalFishSchool(this));
+        this.goalSelector.a(6, new PathfinderGoalFishSchool(this)); // Purpur
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/server/EntityFox.java b/src/main/java/net/minecraft/server/EntityFox.java
index 8845afd83d47902d5192ec1a9146b6f20c5667d3..f5defe4713c6be7d32fb2116110516717460284e 100644
--- a/src/main/java/net/minecraft/server/EntityFox.java
+++ b/src/main/java/net/minecraft/server/EntityFox.java
@@ -55,6 +55,39 @@ public class EntityFox extends EntityAnimal {
         this.setCanPickupLoot(true);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.foxRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.foxRidableInWater;
+    }
+
+    @Override
+    public float getJumpHeight() {
+        return !hasRider() ? super.getJumpHeight() : 0.5F;
+    }
+
+    @Override
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        setCanPickupLoot(false);
+        stopActions();
+        setChasing(false);
+        spit(getEquipment(EnumItemSlot.MAINHAND));
+        setSlot(EnumItemSlot.MAINHAND, ItemStack.NULL_ITEM);
+    }
+
+    @Override
+    public void onDismount(EntityHuman entityhuman) {
+        super.onDismount(entityhuman);
+        setCanPickupLoot(true);
+    }
+    // Purpur end
+
     @Override
     protected void initDatawatcher() {
         super.initDatawatcher();
@@ -74,6 +107,7 @@ public class EntityFox extends EntityAnimal {
             return entityliving instanceof EntityFishSchool;
         });
         this.goalSelector.a(0, new EntityFox.g());
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityFox.b());
         this.goalSelector.a(2, new EntityFox.n(2.2D));
         this.goalSelector.a(3, new EntityFox.e(1.0D));
@@ -99,6 +133,7 @@ public class EntityFox extends EntityAnimal {
         this.goalSelector.a(11, new EntityFox.p());
         this.goalSelector.a(12, new EntityFox.j(this, EntityHuman.class, 24.0F));
         this.goalSelector.a(13, new EntityFox.r());
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(3, new EntityFox.a(EntityLiving.class, false, false, (entityliving) -> {
             return EntityFox.bt.test(entityliving) && !this.c(entityliving.getUniqueID());
         }));
@@ -386,6 +421,7 @@ public class EntityFox extends EntityAnimal {
         return itemstack1.isEmpty() || this.bD > 0 && item.isFood() && !itemstack1.getItem().isFood();
     }
 
+    public void spit(ItemStack itemstack) { m(itemstack); } // Purpur - OBFHELPER
     private void m(ItemStack itemstack) {
         if (!itemstack.isEmpty() && !this.world.isClientSide) {
             EntityItem entityitem = new EntityItem(this.world, this.locX() + this.getLookDirection().x, this.locY() + 1.0D, this.locZ() + this.getLookDirection().z, itemstack);
@@ -481,6 +517,7 @@ public class EntityFox extends EntityAnimal {
         return this.t(16);
     }
 
+    public void setChasing(boolean flag) { u(flag); } // Purpur - OBFHELPER
     public void u(boolean flag) {
         this.d(16, flag);
     }
@@ -523,6 +560,7 @@ public class EntityFox extends EntityAnimal {
         this.setSleeping(false);
     }
 
+    public void stopActions() { fd(); } // Purpur - OBFHELPER
     private void fd() {
         this.w(false);
         this.setCrouching(false);
@@ -688,16 +726,16 @@ public class EntityFox extends EntityAnimal {
         }
     }
 
-    public class k extends ControllerLook {
+    public class k extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur
 
         public k() {
             super(EntityFox.this);
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (!EntityFox.this.isSleeping()) {
-                super.a();
+                super.tick(); // Purpur
             }
 
         }
@@ -1367,16 +1405,16 @@ public class EntityFox extends EntityAnimal {
         }
     }
 
-    class m extends ControllerMove {
+    class m extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur
 
         public m() {
             super(EntityFox.this);
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (EntityFox.this.fe()) {
-                super.a();
+                super.tick(); // Purpur
             }
 
         }
diff --git a/src/main/java/net/minecraft/server/EntityGhast.java b/src/main/java/net/minecraft/server/EntityGhast.java
index a67611c4f9271c116a795ee598412f25396fee88..216506a7b1f97b776ecd4e24f5b2afaf5b79ec2d 100644
--- a/src/main/java/net/minecraft/server/EntityGhast.java
+++ b/src/main/java/net/minecraft/server/EntityGhast.java
@@ -14,11 +14,42 @@ public class EntityGhast extends EntityFlying implements IMonster {
         this.moveController = new EntityGhast.ControllerGhast(this);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.ghastRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.ghastRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.ghastMaxY;
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider() && !onGround) {
+            float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue();
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, 1.0, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(5, new EntityGhast.PathfinderGoalGhastIdleMove(this));
         this.goalSelector.a(7, new EntityGhast.PathfinderGoalGhastMoveTowardsTarget(this));
         this.goalSelector.a(7, new EntityGhast.PathfinderGoalGhastAttackTarget(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, (entityliving) -> {
             return Math.abs(entityliving.locY() - this.locY()) <= 4.0D;
         }));
@@ -56,7 +87,7 @@ public class EntityGhast extends EntityFlying implements IMonster {
     }
 
     public static AttributeProvider.Builder eJ() {
-        return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 10.0D).a(GenericAttributes.FOLLOW_RANGE, 100.0D);
+        return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 10.0D).a(GenericAttributes.FOLLOW_RANGE, 100.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur
     }
 
     @Override
@@ -255,7 +286,7 @@ public class EntityGhast extends EntityFlying implements IMonster {
         }
     }
 
-    static class ControllerGhast extends ControllerMove {
+    static class ControllerGhast extends net.pl3x.purpur.controller.ControllerMoveWASDFlying { // Purpur
 
         private final EntityGhast i;
         private int j;
@@ -266,7 +297,7 @@ public class EntityGhast extends EntityFlying implements IMonster {
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (this.h == ControllerMove.Operation.MOVE_TO) {
                 if (this.j-- <= 0) {
                     this.j += this.i.getRandom().nextInt(5) + 2;
diff --git a/src/main/java/net/minecraft/server/EntityGiantZombie.java b/src/main/java/net/minecraft/server/EntityGiantZombie.java
index 565c938d879940d8e12fe320ea8524d2cf679c1f..5e99767f45f7ba7db80f5b51810689e059b5cef5 100644
--- a/src/main/java/net/minecraft/server/EntityGiantZombie.java
+++ b/src/main/java/net/minecraft/server/EntityGiantZombie.java
@@ -11,16 +11,28 @@ public class EntityGiantZombie extends EntityMonster {
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.giantRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.giantRidableInWater;
+    }
+
     @Override
     protected void initPathfinder() {
         if (world.purpurConfig.giantHaveAI) {
             this.goalSelector.a(0, new PathfinderGoalFloat(this));
+            this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
             this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D));
             this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 16.0F));
             this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
             this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D));
             if (world.purpurConfig.giantHaveHostileAI) {
                 this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false));
+                this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
                 this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this).a(EntityPigZombie.class));
                 this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
                 this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillager.class, false));
diff --git a/src/main/java/net/minecraft/server/EntityGuardian.java b/src/main/java/net/minecraft/server/EntityGuardian.java
index 27ec46c0b2e7df985ed4c68a973db18f81caac89..0a7f51a2a1cb10438c9364faf3e4adc6322fc787 100644
--- a/src/main/java/net/minecraft/server/EntityGuardian.java
+++ b/src/main/java/net/minecraft/server/EntityGuardian.java
@@ -24,15 +24,36 @@ public class EntityGuardian extends EntityMonster {
         this.f = 10;
         this.a(PathType.WATER, 0.0F);
         this.moveController = new EntityGuardian.ControllerMoveGuardian(this);
+        // Purpur start
+        this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this) {
+            @Override
+            public void setYawPitch(float yaw, float pitch) {
+                super.setYawPitch(yaw, pitch * 0.35F);
+            }
+        };
+        // Purpur end
         this.bo = this.random.nextFloat();
         this.bp = this.bo;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.guardianRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         PathfinderGoalMoveTowardsRestriction pathfindergoalmovetowardsrestriction = new PathfinderGoalMoveTowardsRestriction(this, 1.0D);
 
         this.goalRandomStroll = new PathfinderGoalRandomStroll(this, 1.0D, 80);
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(4, new EntityGuardian.PathfinderGoalGuardianAttack(this));
         this.goalSelector.a(5, pathfindergoalmovetowardsrestriction);
         this.goalSelector.a(7, this.goalRandomStroll);
@@ -41,6 +62,7 @@ public class EntityGuardian extends EntityMonster {
         this.goalSelector.a(9, new PathfinderGoalRandomLookaround(this));
         this.goalRandomStroll.a(EnumSet.of(PathfinderGoal.Type.MOVE, PathfinderGoal.Type.LOOK));
         pathfindergoalmovetowardsrestriction.a(EnumSet.of(PathfinderGoal.Type.MOVE, PathfinderGoal.Type.LOOK));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityLiving.class, 10, true, false, new EntityGuardian.EntitySelectorGuardianTargetHumanSquid(this)));
     }
 
@@ -74,6 +96,7 @@ public class EntityGuardian extends EntityMonster {
         return (Boolean) this.datawatcher.get(EntityGuardian.b);
     }
 
+    private void setMovingFlag(boolean movingFlag) { t(movingFlag); } // Purpur - OBFHELPER
     private void t(boolean flag) {
         this.datawatcher.set(EntityGuardian.b, flag);
     }
@@ -288,7 +311,7 @@ public class EntityGuardian extends EntityMonster {
     @Override
     public void g(Vec3D vec3d) {
         if (this.doAITick() && this.isInWater()) {
-            this.a(0.1F, vec3d);
+            this.a(hasRider() ? getSpeed() : 0.1F, vec3d); // Purpur
             this.move(EnumMoveType.SELF, this.getMot());
             this.setMot(this.getMot().a(0.9D));
             if (!this.eN() && this.getGoalTarget() == null) {
@@ -300,17 +323,26 @@ public class EntityGuardian extends EntityMonster {
 
     }
 
-    static class ControllerMoveGuardian extends ControllerMove {
+    static class ControllerMoveGuardian extends net.pl3x.purpur.controller.ControllerMoveWASDWater { // Purpur
 
-        private final EntityGuardian i;
+        private final EntityGuardian i; private EntityGuardian getGuardian() { return i; } // Purpur - OBFHELPER
 
         public ControllerMoveGuardian(EntityGuardian entityguardian) {
             super(entityguardian);
             this.i = entityguardian;
         }
 
+        // Purpur start
+        @Override
+        public void tick(EntityHuman rider) {
+            super.tick(rider);
+            getGuardian().setMot(getGuardian().getMot().add(0.0D, 0.005D, 0.0D));
+            getGuardian().setMovingFlag(getGuardian().getForward() > 0.0F); // control tail speed
+        }
+        // Purpur end
+
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (this.h == ControllerMove.Operation.MOVE_TO && !this.i.getNavigation().m()) {
                 Vec3D vec3d = new Vec3D(this.b - this.i.locX(), this.c - this.i.locY(), this.d - this.i.locZ());
                 double d0 = vec3d.f();
diff --git a/src/main/java/net/minecraft/server/EntityGuardianElder.java b/src/main/java/net/minecraft/server/EntityGuardianElder.java
index b691e844953bcc2853a806a3bbf9cb7338e98266..f6f882746940c9e049106aa9b41591ba27a608ce 100644
--- a/src/main/java/net/minecraft/server/EntityGuardianElder.java
+++ b/src/main/java/net/minecraft/server/EntityGuardianElder.java
@@ -16,6 +16,18 @@ public class EntityGuardianElder extends EntityGuardian {
 
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.elderGuardianRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+    // Purpur end
+
     public static AttributeProvider.Builder m() {
         return EntityGuardian.eM().a(GenericAttributes.MOVEMENT_SPEED, 0.30000001192092896D).a(GenericAttributes.ATTACK_DAMAGE, 8.0D).a(GenericAttributes.MAX_HEALTH, 80.0D);
     }
diff --git a/src/main/java/net/minecraft/server/EntityHoglin.java b/src/main/java/net/minecraft/server/EntityHoglin.java
index f6797925365836b6c2d3d2c48c746a4d58e28bf3..548ff4449faca0abdf72487276fe49207bacfe17 100644
--- a/src/main/java/net/minecraft/server/EntityHoglin.java
+++ b/src/main/java/net/minecraft/server/EntityHoglin.java
@@ -13,13 +13,25 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin {
     public int conversionTicks = 0;
     public boolean cannotBeHunted = false;
     protected static final ImmutableList<? extends SensorType<? extends Sensor<? super EntityHoglin>>> bo = ImmutableList.of(SensorType.c, SensorType.d, SensorType.n, SensorType.m);
-    protected static final ImmutableList<? extends MemoryModuleType<?>> bp = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, new MemoryModuleType[]{MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULY, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED});
+    protected static final ImmutableList<? extends MemoryModuleType<?>> bp = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULY, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED); // Purpur - decompile error
 
     public EntityHoglin(EntityTypes<? extends EntityHoglin> entitytypes, World world) {
         super(entitytypes, world);
         this.f = 5;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.hoglinRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.hoglinRidableInWater;
+    }
+    // Purpur end
+
     @Override
     public boolean a(EntityHuman entityhuman) {
         return !this.isLeashed();
@@ -77,13 +89,14 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin {
 
     @Override
     public BehaviorController<EntityHoglin> getBehaviorController() {
-        return super.getBehaviorController();
+        return (BehaviorController<EntityHoglin>) super.getBehaviorController(); // Purpur - decompile error
     }
 
     @Override
     protected void mobTick() {
         this.world.getMethodProfiler().enter("hoglinBrain");
-        this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this);
+        if (getRider() == null) // Purpur - only use brain if no rider
+        this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error
         this.world.getMethodProfiler().exit();
         HoglinAI.a(this);
         if (this.isConverting()) {
@@ -259,7 +272,7 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin {
 
     @Override
     protected SoundEffect getSoundAmbient() {
-        return this.world.isClientSide ? null : (SoundEffect) HoglinAI.b(this).orElse((Object) null);
+        return this.world.isClientSide ? null : (SoundEffect) HoglinAI.b(this).orElse(null); // Purpur - decompile error
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/server/EntityHorse.java b/src/main/java/net/minecraft/server/EntityHorse.java
index f958751b16abcc36910bb0b655ff2360459e2e4c..4ffc61acdff8c51dc9b111e3024c828fb5386118 100644
--- a/src/main/java/net/minecraft/server/EntityHorse.java
+++ b/src/main/java/net/minecraft/server/EntityHorse.java
@@ -12,6 +12,13 @@ public class EntityHorse extends EntityHorseAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.horseRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void eK() {
         this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue((double) this.fp());
diff --git a/src/main/java/net/minecraft/server/EntityHorseAbstract.java b/src/main/java/net/minecraft/server/EntityHorseAbstract.java
index 784819d252d94e8040a87f53431fcf2c7a19d1a7..8fde406273bbea0b9d2dea2362cf6372b7a5c9ae 100644
--- a/src/main/java/net/minecraft/server/EntityHorseAbstract.java
+++ b/src/main/java/net/minecraft/server/EntityHorseAbstract.java
@@ -40,12 +40,27 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven
 
     protected EntityHorseAbstract(EntityTypes<? extends EntityHorseAbstract> entitytypes, World world) {
         super(entitytypes, world);
+        this.moveController = new ControllerMove(this); // Purpur - use vanilla controller
+        this.lookController = new ControllerLook(this); // Purpur - use vanilla controller
         this.G = 1.0F;
         this.loadChest();
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return false; // vanilla handles
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return false;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.2D));
         this.goalSelector.a(1, new PathfinderGoalTame(this, 1.2D));
         this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D, EntityHorseAbstract.class));
@@ -53,6 +68,7 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven
         this.goalSelector.a(6, new PathfinderGoalRandomStrollLand(this, 0.7D));
         this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur
         this.eV();
     }
 
diff --git a/src/main/java/net/minecraft/server/EntityHorseDonkey.java b/src/main/java/net/minecraft/server/EntityHorseDonkey.java
index 767d18d5a0417813c514d0341b318d6321f6f7a5..cb8aee5691ff4ecaa6ae60f1637b1852d3b6c162 100644
--- a/src/main/java/net/minecraft/server/EntityHorseDonkey.java
+++ b/src/main/java/net/minecraft/server/EntityHorseDonkey.java
@@ -8,6 +8,13 @@ public class EntityHorseDonkey extends EntityHorseChestedAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.donkeyRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected SoundEffect getSoundAmbient() {
         super.getSoundAmbient();
diff --git a/src/main/java/net/minecraft/server/EntityHorseMule.java b/src/main/java/net/minecraft/server/EntityHorseMule.java
index 3dd4d4f0f35e2b12686e1a257250934e5678e397..243aeb736e350418e9476819bbfec0e7ab59f92f 100644
--- a/src/main/java/net/minecraft/server/EntityHorseMule.java
+++ b/src/main/java/net/minecraft/server/EntityHorseMule.java
@@ -8,6 +8,12 @@ public class EntityHorseMule extends EntityHorseChestedAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.muleRidableInWater;
+    }
+    // Purpur end
     @Override
     protected SoundEffect getSoundAmbient() {
         super.getSoundAmbient();
diff --git a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java
index a53d335f3af9df80bec3f94f81fb5ff0e0e5ebb5..e2c6a5807a4554a7eebb148e40f1f8a1d979df5e 100644
--- a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java
+++ b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java
@@ -12,6 +12,18 @@ public class EntityHorseSkeleton extends EntityHorseAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.skeletonHorseRidableInWater;
+    }
+
+    @Override
+    public boolean isTamed() {
+        return true;
+    }
+    // Purpur end
+
     public static AttributeProvider.Builder eL() {
         return fi().a(GenericAttributes.MAX_HEALTH, 15.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.20000000298023224D);
     }
@@ -22,7 +34,7 @@ public class EntityHorseSkeleton extends EntityHorseAbstract {
     }
 
     @Override
-    protected void eV() {}
+    protected void eV() { if (world.purpurConfig.skeletonHorseCanSwim) goalSelector.a(0, new PathfinderGoalFloat(this)); } // Purpur
 
     @Override
     protected SoundEffect getSoundAmbient() {
@@ -117,7 +129,7 @@ public class EntityHorseSkeleton extends EntityHorseAbstract {
 
     @Override
     public boolean bt() {
-        return true;
+        return super.bt(); // Purpur
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/server/EntityHorseZombie.java b/src/main/java/net/minecraft/server/EntityHorseZombie.java
index 0e98173607c810e0e74552a2ba8febf292357c39..559ba50977147b8e2a0e7c1e7dc281faabd7f292 100644
--- a/src/main/java/net/minecraft/server/EntityHorseZombie.java
+++ b/src/main/java/net/minecraft/server/EntityHorseZombie.java
@@ -8,6 +8,18 @@ public class EntityHorseZombie extends EntityHorseAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.zombieHorseRidableInWater;
+    }
+
+    @Override
+    public boolean isTamed() {
+        return true;
+    }
+    // Purpur end
+
     public static AttributeProvider.Builder eL() {
         return fi().a(GenericAttributes.MAX_HEALTH, 15.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.20000000298023224D);
     }
@@ -79,5 +91,5 @@ public class EntityHorseZombie extends EntityHorseAbstract {
     }
 
     @Override
-    protected void eV() {}
+    protected void eV() { if (world.purpurConfig.zombieHorseCanSwim) goalSelector.a(0, new PathfinderGoalFloat(this)); } // Purpur
 }
diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java
index ab2ad054ce8d896e38ab4eb6ed38d8ea73d42954..3a7dc584bd2b88415a238f9c0cb7f85968fb8dfb 100644
--- a/src/main/java/net/minecraft/server/EntityHuman.java
+++ b/src/main/java/net/minecraft/server/EntityHuman.java
@@ -2155,4 +2155,15 @@ public abstract class EntityHuman extends EntityLiving {
             return this.g;
         }
     }
+
+    // Purpur start
+    @Override
+    public boolean processClick(EnumHand hand) {
+        Entity vehicle = getRootVehicle();
+        if (vehicle != null && vehicle.getRider() == this) {
+            return vehicle.onClick(hand);
+        }
+        return false;
+    }
+    // Purpur end
 }
diff --git a/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java b/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java
index c57bf5091430709778dc21d70c8a32819c9d6639..b0a5c36d1132e2558a1fefbd9f8dd26448400086 100644
--- a/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java
+++ b/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java
@@ -20,6 +20,16 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.illusionerRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.illusionerRidableInWater;
+    }
+
     @Override
     protected void initAttributes() {
         this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.world.purpurConfig.illusionerMovementSpeed);
@@ -32,6 +42,7 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan
     protected void initPathfinder() {
         super.initPathfinder();
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityIllagerWizard.b());
         this.goalSelector.a(4, new EntityIllagerIllusioner.b());
         this.goalSelector.a(5, new EntityIllagerIllusioner.a());
@@ -39,6 +50,7 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan
         this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D));
         this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F));
         this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error
         this.targetSelector.a(2, (new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)).a(300));
         this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)).a(300));
diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java
index ee005e482396f8ff7e46a7ca981c843a76bdda71..b64278a8e349467305b75b2180a4a423ed49d7e4 100644
--- a/src/main/java/net/minecraft/server/EntityInsentient.java
+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
@@ -30,7 +30,7 @@ public abstract class EntityInsentient extends EntityLiving {
     protected int f;
     protected ControllerLook lookController;
     protected ControllerMove moveController;
-    protected ControllerJump bi;
+    protected ControllerJump bi; public ControllerJump getJumpController() { return bi; } // Purpur - OBFHELPER
     private final EntityAIBodyControl c;
     protected NavigationAbstract navigation;
     public PathfinderGoalSelector goalSelector;
@@ -68,8 +68,8 @@ public abstract class EntityInsentient extends EntityLiving {
         this.bA = -1.0F;
         this.goalSelector = new PathfinderGoalSelector(world.getMethodProfilerSupplier());
         this.targetSelector = new PathfinderGoalSelector(world.getMethodProfilerSupplier());
-        this.lookController = new ControllerLook(this);
-        this.moveController = new ControllerMove(this);
+        this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this); // Purpur
+        this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASD(this); // Purpur
         this.bi = new ControllerJump(this);
         this.c = this.r();
         this.navigation = this.b(world);
@@ -258,10 +258,10 @@ public abstract class EntityInsentient extends EntityLiving {
     // Purpur start
     private void incrementTicksSinceLastInteraction() {
         ++ticksSinceLastInteraction;
-        //if (hasRider()) {
-        //    ticksSinceLastInteraction = 0;
-        //    return;
-        //}
+        if (hasRider()) {
+            ticksSinceLastInteraction = 0;
+            return;
+        }
         if (world.purpurConfig.entityLifeSpan <= 0) {
             return; // feature disabled
         }
@@ -556,14 +556,17 @@ public abstract class EntityInsentient extends EntityLiving {
         return super.dp();
     }
 
+    public void setForwardSpeed(float speed) { this.t(speed); } // Purpur - OBFHELPER
     public void t(float f) {
         this.aT = f;
     }
 
+    public void setVerticalSpeed(float speed) { this.u(speed); } // Purpur - OBFHELPER
     public void u(float f) {
         this.aS = f;
     }
 
+    public void setStrafeSpeed(float speed) { this.v(speed); } // Purpur - OBFHELPER
     public void v(float f) {
         this.aR = f;
     }
@@ -1253,7 +1256,7 @@ public abstract class EntityInsentient extends EntityLiving {
     protected void a(EntityHuman entityhuman, EntityInsentient entityinsentient) {}
 
     protected EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) {
-        return EnumInteractionResult.PASS;
+        return tryRide(entityhuman, enumhand); // Purpur
     }
 
     public boolean ev() {
@@ -1634,4 +1637,54 @@ public abstract class EntityInsentient extends EntityLiving {
         this.unleash(true, event.isDropLeash());
         // Paper end
     }
+
+    // Purpur start
+    public double getMaxY() {
+        return world.getHeight();
+    }
+
+    public EnumInteractionResult tryRide(EntityHuman entityhuman, EnumHand enumhand) {
+        if (!isRidable()) {
+            return EnumInteractionResult.PASS;
+        }
+        if (enumhand != EnumHand.MAIN_HAND) {
+            return EnumInteractionResult.PASS;
+        }
+        if (entityhuman.isSneaking()) {
+            return EnumInteractionResult.PASS;
+        }
+        if (!entityhuman.getItemInHand(enumhand).isEmpty()) {
+            return EnumInteractionResult.PASS;
+        }
+        if (!passengers.isEmpty() || entityhuman.isPassenger()) {
+            return EnumInteractionResult.PASS;
+        }
+        if (this instanceof EntityTameableAnimal) {
+            EntityTameableAnimal tameable = (EntityTameableAnimal) this;
+            if (tameable.isTamed() && !tameable.isOwner(entityhuman)) {
+                return EnumInteractionResult.PASS;
+            }
+            if (!tameable.isTamed() && !world.purpurConfig.untamedTamablesAreRidable) {
+                return EnumInteractionResult.PASS;
+            }
+        }
+        if (this instanceof EntityAgeable) {
+            EntityAgeable ageable = (EntityAgeable) this;
+            if (ageable.isBaby() && !world.purpurConfig.babiesAreRidable) {
+                return EnumInteractionResult.PASS;
+            }
+        }
+        if (!entityhuman.getBukkitEntity().hasPermission("allow.ride." + getEntityType().getName())) {
+            entityhuman.sendMessage(net.pl3x.purpur.PurpurConfig.cannotRideMob);
+            return EnumInteractionResult.PASS;
+        }
+        entityhuman.yaw = this.yaw;
+        entityhuman.pitch = this.pitch;
+        if (entityhuman.startRiding(this)) {
+            return EnumInteractionResult.SUCCESS;
+        } else {
+            return EnumInteractionResult.PASS;
+        }
+    }
+    // Purpur end
 }
diff --git a/src/main/java/net/minecraft/server/EntityIronGolem.java b/src/main/java/net/minecraft/server/EntityIronGolem.java
index bdff2368836dca230a6622a205d5772834afc6ee..9ee03b233b71d1b4b85a9a5e1f0ea9feb55dfe43 100644
--- a/src/main/java/net/minecraft/server/EntityIronGolem.java
+++ b/src/main/java/net/minecraft/server/EntityIronGolem.java
@@ -22,9 +22,22 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable {
         this.G = 1.0F;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.ironGolemRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.ironGolemRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         if (world.purpurConfig.ironGolemCanSwim) this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, true));
         this.goalSelector.a(2, new PathfinderGoalMoveTowardsTarget(this, 0.9D, 32.0F));
         this.goalSelector.a(2, new PathfinderGoalStrollVillage(this, 0.6D, false));
@@ -32,6 +45,7 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable {
         this.goalSelector.a(5, new PathfinderGoalOfferFlower(this));
         this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalDefendVillage(this));
         this.targetSelector.a(2, new PathfinderGoalHurtByTarget(this, new Class[0]));
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::a_));
@@ -201,13 +215,13 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable {
         Item item = itemstack.getItem();
 
         if (item != Items.IRON_INGOT) {
-            return EnumInteractionResult.PASS;
+            return tryRide(entityhuman, enumhand); // Purpur
         } else {
             float f = this.getHealth();
 
             this.heal(25.0F);
             if (this.getHealth() == f) {
-                return EnumInteractionResult.PASS;
+                return tryRide(entityhuman, enumhand); // Purpur
             } else {
                 float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
 
diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
index 4b7ff69eac4dd5fd5c9b0b26d133daeb128c445d..af2b6bb559d78dd912eee02281f5446f31e55eae 100644
--- a/src/main/java/net/minecraft/server/EntityLiving.java
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
@@ -463,7 +463,7 @@ public abstract class EntityLiving extends Entity {
 
     @Override
     public boolean bt() {
-        return false;
+        return isRidableInWater(); // Purpur
     }
 
     protected void cU() {
@@ -2202,7 +2202,7 @@ public abstract class EntityLiving extends Entity {
         return 0.42F * this.getBlockJumpFactor();
     }
 
-    protected void jump() {
+    public void jump() { // Purpur - protected -> public
         float f = this.dJ();
 
         if (this.hasEffect(MobEffects.JUMP)) {
@@ -2451,10 +2451,12 @@ public abstract class EntityLiving extends Entity {
         return this.onGround ? this.dN() * (0.21600002F / (f * f * f)) : this.aE;
     }
 
+    public float getSpeed() { return dN(); } // Purpur - OBFHELPER
     public float dN() {
         return this.bu;
     }
 
+    public void setSpeed(float speed) { q(speed); } // Purpur - OBFHELPER
     public void q(float f) {
         this.bu = f;
     }
@@ -2853,6 +2855,20 @@ public abstract class EntityLiving extends Entity {
             }
         }
         // Paper end
+        // Purpur start
+        if (hasRider() && ((WorldServer) world).hasRidableMoveEvent && this instanceof EntityInsentient) {
+            if (lastX != locX() || lastY != locY() || lastZ != locZ() || lastYaw != yaw || lastPitch != pitch) {
+                Location from = new Location(world.getWorld(), lastX, lastY, lastZ, lastYaw, lastPitch);
+                Location to = new Location (world.getWorld(), locX(), locY(), locZ(), yaw, pitch);
+                net.pl3x.purpur.event.entity.RidableMoveEvent event = new net.pl3x.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (Player) getRider().getBukkitEntity(), from, to.clone());
+                if (!event.callEvent()) {
+                    setLocation(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch());
+                } else if (!to.equals(event.getTo())) {
+                    setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
+                }
+            }
+        }
+        // Purpur end
         if (!this.world.isClientSide && this.dO() && this.aG()) {
             this.damageEntity(DamageSource.DROWN, 1.0F);
         }
diff --git a/src/main/java/net/minecraft/server/EntityLlama.java b/src/main/java/net/minecraft/server/EntityLlama.java
index e61f53816cbf09e775762403d97e9c591fb405a6..1099277868f92fdaf4b0ec3a982f26f20ead7369 100644
--- a/src/main/java/net/minecraft/server/EntityLlama.java
+++ b/src/main/java/net/minecraft/server/EntityLlama.java
@@ -18,7 +18,46 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
 
     public EntityLlama(EntityTypes<? extends EntityLlama> entitytypes, World world) {
         super(entitytypes, world);
+        // Purpur start
+        this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASD(this) {
+            @Override
+            public void a() { // tick
+                if (entity.hasRider() && hasSaddle()) {
+                    tick(entity.getRider());
+                } else {
+                    tick();
+                }
+            }
+        };
+        this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this) {
+            @Override
+            public void a() { // tick
+                if (entity.hasRider() && hasSaddle()) {
+                    tick(entity.getRider());
+                } else {
+                    tick();
+                }
+            }
+        };
+        // Purpur end
+    }
+
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.llamaRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.llamaRidableInWater;
+    }
+
+    @Override
+    public boolean hasSaddle() {
+        return super.hasSaddle() || (isTamed() && getColor() != null);
     }
+    // Purpur end
 
     public void setStrength(int i) {
         this.datawatcher.set(EntityLlama.bx, Math.max(1, Math.min(5, i)));
@@ -66,6 +105,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalTame(this, 1.2D));
         this.goalSelector.a(2, new PathfinderGoalLlamaFollow(this, 2.0999999046325684D));
         this.goalSelector.a(3, new PathfinderGoalArrowAttack(this, 1.25D, 40, 20.0F));
@@ -75,6 +115,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
         this.goalSelector.a(6, new PathfinderGoalRandomStrollLand(this, 0.7D));
         this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur
         this.targetSelector.a(1, new EntityLlama.c(this));
         this.targetSelector.a(2, new EntityLlama.a(this));
     }
@@ -312,7 +353,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
     }
 
     @Nullable
-    public EnumColor fy() {
+    public EnumColor fy() { return getColor(); } public EnumColor getColor() { // Purpur - OBFHELPER
         int i = (Integer) this.datawatcher.get(EntityLlama.by);
 
         return i == -1 ? null : EnumColor.fromColorIndex(i);
diff --git a/src/main/java/net/minecraft/server/EntityLlamaTrader.java b/src/main/java/net/minecraft/server/EntityLlamaTrader.java
index 7d30e5c2378a2b8c540d1a1a13ec0ae97367bb9a..b6aae5cdee1f8bb842ab8e06c47fb497576b464f 100644
--- a/src/main/java/net/minecraft/server/EntityLlamaTrader.java
+++ b/src/main/java/net/minecraft/server/EntityLlamaTrader.java
@@ -11,6 +11,23 @@ public class EntityLlamaTrader extends EntityLlama {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.llamaTraderRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.llamaTraderRidableInWater;
+    }
+
+    @Override
+    public boolean hasSaddle() {
+        return super.hasSaddle() || isTamed();
+    }
+    // Purpur end
+
     @Override
     protected EntityLlama fz() {
         return (EntityLlama) EntityTypes.TRADER_LLAMA.a(this.world);
diff --git a/src/main/java/net/minecraft/server/EntityMagmaCube.java b/src/main/java/net/minecraft/server/EntityMagmaCube.java
index 874dd39825b41f4e2b366446359920989961f084..cd28463e2bf944d94c121c8f8d6e37221754c168 100644
--- a/src/main/java/net/minecraft/server/EntityMagmaCube.java
+++ b/src/main/java/net/minecraft/server/EntityMagmaCube.java
@@ -8,6 +8,23 @@ public class EntityMagmaCube extends EntitySlime {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.magmaCubeRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.magmaCubeRidableInWater;
+    }
+
+    @Override
+    public float getJumpHeight() {
+        return 0.42F * this.getBlockJumpFactor(); // from EntityLiving
+    }
+    // Purpur end
+
     public static AttributeProvider.Builder m() {
         return EntityMonster.eR().a(GenericAttributes.MOVEMENT_SPEED, 0.20000000298023224D);
     }
@@ -58,11 +75,12 @@ public class EntityMagmaCube extends EntitySlime {
     }
 
     @Override
-    protected void jump() {
+    public void jump() { // Purpur - protected -> public
         Vec3D vec3d = this.getMot();
 
         this.setMot(vec3d.x, (double) (this.dJ() + (float) this.getSize() * 0.1F), vec3d.z);
         this.impulse = true;
+        this.actualJump = false; // Purpur
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/server/EntityMushroomCow.java b/src/main/java/net/minecraft/server/EntityMushroomCow.java
index 91cfe60f764a573e3724b48a9ae02e806c1e4fb8..e38d165fefb4d552ded2198536a6663a5688070d 100644
--- a/src/main/java/net/minecraft/server/EntityMushroomCow.java
+++ b/src/main/java/net/minecraft/server/EntityMushroomCow.java
@@ -21,6 +21,18 @@ public class EntityMushroomCow extends EntityCow implements IShearable {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.mooshroomRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.mooshroomRidableInWater;
+    }
+    // Purpur end
+
     @Override
     public float a(BlockPosition blockposition, IWorldReader iworldreader) {
         return iworldreader.getType(blockposition.down()).a(Blocks.MYCELIUM) ? 10.0F : iworldreader.y(blockposition) - 0.5F;
@@ -82,7 +94,7 @@ public class EntityMushroomCow extends EntityCow implements IShearable {
         } else if (itemstack.getItem() == Items.SHEARS && this.canShear()) {
             // CraftBukkit start
             if (!CraftEventFactory.handlePlayerShearEntityEvent(entityhuman, this, itemstack, enumhand)) {
-                return EnumInteractionResult.PASS;
+                return tryRide(entityhuman, enumhand); // Purpur
             }
             // CraftBukkit end
             this.shear(SoundCategory.PLAYERS);
@@ -102,7 +114,7 @@ public class EntityMushroomCow extends EntityCow implements IShearable {
                 Optional<Pair<MobEffectList, Integer>> optional = this.l(itemstack);
 
                 if (!optional.isPresent()) {
-                    return EnumInteractionResult.PASS;
+                    return tryRide(entityhuman, enumhand); // Purpur
                 }
 
                 Pair<MobEffectList, Integer> pair = (Pair) optional.get();
diff --git a/src/main/java/net/minecraft/server/EntityOcelot.java b/src/main/java/net/minecraft/server/EntityOcelot.java
index 7bd3fea6e0e478337c7f6400a941675eebba517b..2f8275cd6b3cde0d3f949219f67ba7f0e0031dc3 100644
--- a/src/main/java/net/minecraft/server/EntityOcelot.java
+++ b/src/main/java/net/minecraft/server/EntityOcelot.java
@@ -16,6 +16,18 @@ public class EntityOcelot extends EntityAnimal {
         this.eL();
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.ocelotRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.ocelotRidableInWater;
+    }
+    // Purpur end
+
     private boolean isTrusting() {
         return (Boolean) this.datawatcher.get(EntityOcelot.bp);
     }
@@ -47,12 +59,14 @@ public class EntityOcelot extends EntityAnimal {
     protected void initPathfinder() {
         this.br = new EntityOcelot.b(this, 0.6D, EntityOcelot.bo, true);
         this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(3, this.br);
         this.goalSelector.a(7, new PathfinderGoalLeapAtTarget(this, 0.3F));
         this.goalSelector.a(8, new PathfinderGoalOcelotAttack(this));
         this.goalSelector.a(9, new PathfinderGoalBreed(this, 0.8D));
         this.goalSelector.a(10, new PathfinderGoalRandomStrollLand(this, 0.8D, 1.0000001E-5F));
         this.goalSelector.a(11, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 10.0F));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityChicken.class, false));
         this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, 10, false, false, EntityTurtle.bo));
     }
diff --git a/src/main/java/net/minecraft/server/EntityPanda.java b/src/main/java/net/minecraft/server/EntityPanda.java
index b36cc95168c6e6fbf668ebe15c1fa10b54e570d3..eafae5516b9b5d51aa943796557926cf61476d2b 100644
--- a/src/main/java/net/minecraft/server/EntityPanda.java
+++ b/src/main/java/net/minecraft/server/EntityPanda.java
@@ -46,6 +46,27 @@ public class EntityPanda extends EntityAnimal {
 
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.pandaRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.pandaRidableInWater;
+    }
+
+    @Override
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        this.setForwardSpeed(0.0F);
+        this.setScared(false);
+        this.setEating(false);
+        this.setLayingOnBack(false);
+    }
+    // Purpur end
+
     @Override
     public boolean e(ItemStack itemstack) {
         EnumItemSlot enumitemslot = EntityInsentient.j(itemstack);
@@ -69,6 +90,7 @@ public class EntityPanda extends EntityAnimal {
         return this.w(8);
     }
 
+    public void setScared(boolean scared) { this.t(scared); } // Purpur - OBFHELPER
     public void t(boolean flag) {
         this.d(8, flag);
     }
@@ -77,6 +99,7 @@ public class EntityPanda extends EntityAnimal {
         return this.w(16);
     }
 
+    public void setLayingOnBack(boolean layingOnBack) { this.u(layingOnBack); } // Purpur - OBFHELPER
     public void u(boolean flag) {
         this.d(16, flag);
     }
@@ -93,6 +116,7 @@ public class EntityPanda extends EntityAnimal {
         return (Integer) this.datawatcher.get(EntityPanda.br);
     }
 
+    public void setEating(boolean eating) { this.v(eating); } // Purpur - OBFHELPER
     private void v(int i) {
         this.datawatcher.set(EntityPanda.br, i);
     }
@@ -201,6 +225,7 @@ public class EntityPanda extends EntityAnimal {
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(2, new EntityPanda.i(this, 2.0D));
         this.goalSelector.a(2, new EntityPanda.d(this, 1.0D));
         this.goalSelector.a(3, new EntityPanda.b(this, 1.2000000476837158D, true));
@@ -216,6 +241,7 @@ public class EntityPanda extends EntityAnimal {
         this.goalSelector.a(12, new EntityPanda.j(this));
         this.goalSelector.a(13, new PathfinderGoalFollowParent(this, 1.25D));
         this.goalSelector.a(14, new PathfinderGoalRandomStrollLand(this, 1.0D));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new EntityPanda.e(this, new Class[0])).a(new Class[0]));
     }
 
@@ -536,7 +562,7 @@ public class EntityPanda extends EntityAnimal {
         ItemStack itemstack = entityhuman.b(enumhand);
 
         if (this.ff()) {
-            return EnumInteractionResult.PASS;
+            return tryRide(entityhuman, enumhand); // Purpur
         } else if (this.eN()) {
             this.u(false);
             return EnumInteractionResult.a(this.world.isClientSide);
@@ -553,7 +579,7 @@ public class EntityPanda extends EntityAnimal {
                 this.g(entityhuman);
             } else {
                 if (this.world.isClientSide || this.eM() || this.isInWater()) {
-                    return EnumInteractionResult.PASS;
+                    return tryRide(entityhuman, enumhand); // Purpur
                 }
 
                 this.ft();
@@ -570,7 +596,7 @@ public class EntityPanda extends EntityAnimal {
 
             return EnumInteractionResult.SUCCESS;
         } else {
-            return EnumInteractionResult.PASS;
+            return tryRide(entityhuman, enumhand); // Purpur
         }
     }
 
@@ -683,6 +709,7 @@ public class EntityPanda extends EntityAnimal {
 
         public f(EntityPanda entitypanda) {
             this.a = entitypanda;
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - lay on back
         }
 
         @Override
@@ -846,6 +873,7 @@ public class EntityPanda extends EntityAnimal {
 
         public l(EntityPanda entitypanda) {
             this.a = entitypanda;
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - sneeze
         }
 
         @Override
@@ -975,7 +1003,7 @@ public class EntityPanda extends EntityAnimal {
         }
     }
 
-    static class h extends ControllerMove {
+    static class h extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur
 
         private final EntityPanda i;
 
@@ -985,9 +1013,9 @@ public class EntityPanda extends EntityAnimal {
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (this.i.fh()) {
-                super.a();
+                super.tick(); // Purpur
             }
         }
     }
diff --git a/src/main/java/net/minecraft/server/EntityParrot.java b/src/main/java/net/minecraft/server/EntityParrot.java
index 0af6c9395b5d98e6bfa162f651d0e8cb89035afd..e402d4a77b57b8b12b7575a9793c30d7acfa7fb0 100644
--- a/src/main/java/net/minecraft/server/EntityParrot.java
+++ b/src/main/java/net/minecraft/server/EntityParrot.java
@@ -65,12 +65,58 @@ public class EntityParrot extends EntityPerchable implements EntityBird {
 
     public EntityParrot(EntityTypes<? extends EntityParrot> entitytypes, World world) {
         super(entitytypes, world);
-        this.moveController = new ControllerMoveFlying(this, 10, false);
+        // Purpur start
+        final net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar flyingController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.3F);
+        this.moveController = new ControllerMoveFlying(this, 10, false) {
+            @Override
+            public void a() { // tick
+                if (getEntity().hasRider()) {
+                    flyingController.tick(getEntity().getRider());
+                } else {
+                    tick();
+                }
+            }
+
+            @Override
+            public boolean b() { // isUpdating
+                return getEntity().hasRider() ? getForward() != 0 || getStrafe() != 0 : super.b();
+            }
+        };
+        // Purpur end
         this.a(PathType.DANGER_FIRE, -1.0F);
         this.a(PathType.DAMAGE_FIRE, -1.0F);
         this.a(PathType.COCOA, -1.0F);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.parrotRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.parrotRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.parrotMaxY;
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider() && !onGround) {
+            float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 2;
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, 0.25, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+    // Purpur end
+
     @Nullable
     @Override
     public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) {
@@ -89,8 +135,10 @@ public class EntityParrot extends EntityPerchable implements EntityBird {
 
     @Override
     protected void initPathfinder() {
-        this.goalSelector.a(0, new PathfinderGoalPanic(this, 1.25D));
+        // this.goalSelector.a(0, new PathfinderGoalPanic(this, 1.25D)); // Purpur - move down
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
+        this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(2, new PathfinderGoalSit(this));
         this.goalSelector.a(2, new PathfinderGoalFollowOwner(this, 1.0D, 5.0F, 1.0F, true));
diff --git a/src/main/java/net/minecraft/server/EntityPhantom.java b/src/main/java/net/minecraft/server/EntityPhantom.java
index bdfe073dcd255a7359127f9ae3a962642be5526d..c70281fddcbe5a97a1eb176def99cdf31de37579 100644
--- a/src/main/java/net/minecraft/server/EntityPhantom.java
+++ b/src/main/java/net/minecraft/server/EntityPhantom.java
@@ -25,6 +25,59 @@ public class EntityPhantom extends EntityFlying implements IMonster {
         this.lookController = new EntityPhantom.f(this);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.phantomRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.phantomRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.phantomMaxY;
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider() && !onGround) {
+            float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue();
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, speed, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+
+    public static AttributeProvider.Builder defaultAttributes() {
+        return EntityMonster.eR().a(GenericAttributes.FLYING_SPEED, 3.0D);
+    }
+
+    @Override
+    public boolean onSpacebar() {
+        if (hasRider() && getRider().getBukkitEntity().hasPermission("allow.special.phantom")) {
+            shoot();
+        }
+        return false;
+    }
+
+    public boolean shoot() {
+        org.bukkit.Location loc = ((org.bukkit.entity.LivingEntity) getBukkitEntity()).getEyeLocation();
+        loc.setPitch(-loc.getPitch());
+        org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector());
+
+        net.pl3x.purpur.entity.PhantomFlames flames = new net.pl3x.purpur.entity.PhantomFlames(world, this);
+        flames.canGrief = world.purpurConfig.phantomAllowGriefing;
+        flames.shoot(target.getX() - locX(), target.getY() - locY(), target.getZ() - locZ(), 1.0F, 5.0F);
+        world.addEntity(flames);
+        return true;
+    }
+    // Purpur end
+
     @Override
     protected EntityAIBodyControl r() {
         return new EntityPhantom.d(this);
@@ -33,6 +86,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
     @Override
     protected void initPathfinder() {
         // Purpur start
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this));
         if (world.purpurConfig.phantomOrbitCrystalRadius > 0) {
             this.goalSelector.a(1, new FindCrystalGoal(this));
             this.goalSelector.a(2, new OrbitCrystalGoal(this));
@@ -40,6 +94,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
         this.goalSelector.a(3, new EntityPhantom.c()); // PickAttackGoal
         this.goalSelector.a(4, new EntityPhantom.i()); // SweepAttackGoal
         this.goalSelector.a(5, new EntityPhantom.e()); // OrbitPointGoal
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this));
         this.targetSelector.a(1, new EntityPhantom.b()); // AttackPlayer Goal
         // Purpur end
     }
@@ -126,7 +181,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
     @Override
     public void movementTick() {
-        if (this.isAlive() && ((world.purpurConfig.phantomBurnInDaylight && this.isInDaylight()) || (world.purpurConfig.phantomBurnInLight > 0 && world.getLightLevel(new BlockPosition(this)) >= world.purpurConfig.phantomBurnInLight))) { // Purpur
+        if (this.isAlive() && !hasRider() && ((world.purpurConfig.phantomBurnInDaylight && this.isInDaylight()) || (world.purpurConfig.phantomBurnInLight > 0 && world.getLightLevel(new BlockPosition(this)) >= world.purpurConfig.phantomBurnInLight))) { // Purpur
             this.setOnFire(8);
         }
 
@@ -368,7 +423,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
         @Override
         public boolean a() {
-            if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
+            if (getRider() != null || isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
             if (this.c > 0) {
                 --this.c;
                 return false;
@@ -397,7 +452,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
         @Override
         public boolean b() {
-            if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
+            if (getRider() != null || isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
             EntityLiving entityliving = EntityPhantom.this.getGoalTarget();
 
             return entityliving != null ? EntityPhantom.this.a(entityliving, PathfinderTargetCondition.a) : false;
@@ -412,7 +467,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
         @Override
         public boolean a() {
-            if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
+            if (getRider() != null || isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
             EntityLiving entityliving = EntityPhantom.this.getGoalTarget();
 
             return entityliving != null ? EntityPhantom.this.a(EntityPhantom.this.getGoalTarget(), PathfinderTargetCondition.a) : false;
@@ -610,14 +665,23 @@ public class EntityPhantom extends EntityFlying implements IMonster {
         }
     }
 
-    class f extends ControllerLook {
+    class f extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur
 
         public f(EntityInsentient entityinsentient) {
             super(entityinsentient);
         }
 
         @Override
-        public void a() {}
+        // Purpur start
+        public void tick(EntityHuman rider) {
+            setYawPitch(rider.yaw, -rider.pitch * 0.75F);
+        }
+
+        @Override
+        public void tick() {
+            // do nothing
+        }
+        // Purpur end
     }
 
     class d extends EntityAIBodyControl {
@@ -633,7 +697,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
         }
     }
 
-    class g extends ControllerMove {
+    class g extends net.pl3x.purpur.controller.ControllerMoveWASDFlying { // Purpur
 
         private float j = 0.1F;
 
@@ -642,7 +706,19 @@ public class EntityPhantom extends EntityFlying implements IMonster {
         }
 
         @Override
-        public void a() {
+        // Purpur start
+        public void tick(EntityHuman rider) {
+            if (!EntityPhantom.this.onGround) {
+                // phantom is always in motion when flying
+                // TODO - FIX THIS
+                // rider.setForward(1.0F);
+            }
+            super.tick(rider);
+        }
+
+        @Override
+        public void tick() {
+            // Purpur end
             if (EntityPhantom.this.positionChanged) {
                 EntityPhantom.this.yaw += 180.0F;
                 this.j = 0.1F;
diff --git a/src/main/java/net/minecraft/server/EntityPig.java b/src/main/java/net/minecraft/server/EntityPig.java
index 6ea3e4ffc3f42f4282adf3554944f24d92ca2079..bf7ecd7a7d805cc8f1314a348d7b13f848da46b5 100644
--- a/src/main/java/net/minecraft/server/EntityPig.java
+++ b/src/main/java/net/minecraft/server/EntityPig.java
@@ -19,9 +19,22 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable {
         this.saddleStorage = new SaddleStorage(this.datawatcher, EntityPig.bp, EntityPig.bo);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.pigRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.pigRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D));
         this.goalSelector.a(3, new PathfinderGoalBreed(this, 1.0D));
         this.goalSelector.a(4, new PathfinderGoalTempt(this, 1.2D, RecipeItemStack.a(Items.CARROT_ON_A_STICK), false));
diff --git a/src/main/java/net/minecraft/server/EntityPigZombie.java b/src/main/java/net/minecraft/server/EntityPigZombie.java
index 32b75f710b12efbcecec2c8d72d4d8cb725870fe..3327dbbf87d8f43cbc7cd728df2f4c6a33dae40d 100644
--- a/src/main/java/net/minecraft/server/EntityPigZombie.java
+++ b/src/main/java/net/minecraft/server/EntityPigZombie.java
@@ -22,6 +22,16 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable {
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.zombifiedPiglinRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.zombifiedPiglinRidableInWater;
+    }
+
     @Override
     public boolean jockeyOnlyBaby() {
         return world.purpurConfig.zombifiedPiglinJockeyOnlyBaby;
diff --git a/src/main/java/net/minecraft/server/EntityPiglin.java b/src/main/java/net/minecraft/server/EntityPiglin.java
index ca7f9dc54ed2e58f521613b5d8027494bd20edd2..334e0f73e67ef2db7e680874faf0646995d9de8a 100644
--- a/src/main/java/net/minecraft/server/EntityPiglin.java
+++ b/src/main/java/net/minecraft/server/EntityPiglin.java
@@ -25,6 +25,18 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow {
         this.f = 5;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.piglinRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.piglinRidableInWater;
+    }
+    // Purpur end
+
     @Override
     public void saveData(NBTTagCompound nbttagcompound) {
         super.saveData(nbttagcompound);
@@ -143,7 +155,7 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow {
 
     @Override
     public BehaviorController<EntityPiglin> getBehaviorController() {
-        return super.getBehaviorController();
+        return (BehaviorController<EntityPiglin>) super.getBehaviorController(); // Purpur - decompile error
     }
 
     @Override
@@ -202,7 +214,8 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow {
     @Override
     protected void mobTick() {
         this.world.getMethodProfiler().enter("piglinBrain");
-        this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this);
+        if (getRider() == null) // Purpur - only use brain if no rider
+        this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error
         this.world.getMethodProfiler().exit();
         PiglinAI.b(this);
         super.mobTick();
@@ -341,7 +354,7 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow {
 
     @Override
     protected SoundEffect getSoundAmbient() {
-        return this.world.isClientSide ? null : (SoundEffect) PiglinAI.d(this).orElse((Object) null);
+        return this.world.isClientSide ? null : (SoundEffect) PiglinAI.d(this).orElse(null); // Purpur - decompile error
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/server/EntityPiglinBrute.java b/src/main/java/net/minecraft/server/EntityPiglinBrute.java
index 3e9a4af09d0a4af89584ce26428c38f59ff1ff11..f6b170811159544dc10b91226e4e54b349472c46 100644
--- a/src/main/java/net/minecraft/server/EntityPiglinBrute.java
+++ b/src/main/java/net/minecraft/server/EntityPiglinBrute.java
@@ -15,6 +15,18 @@ public class EntityPiglinBrute extends EntityPiglinAbstract {
         this.f = 20;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.piglinBruteRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.piglinBruteRidableInWater;
+    }
+    // Purpur end
+
     public static AttributeProvider.Builder eS() {
         return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 50.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.3499999940395355D).a(GenericAttributes.ATTACK_DAMAGE, 7.0D);
     }
@@ -44,7 +56,7 @@ public class EntityPiglinBrute extends EntityPiglinAbstract {
 
     @Override
     public BehaviorController<EntityPiglinBrute> getBehaviorController() {
-        return super.getBehaviorController();
+        return (BehaviorController<EntityPiglinBrute>) super.getBehaviorController(); // Purpur - decompile error
     }
 
     @Override
@@ -60,7 +72,8 @@ public class EntityPiglinBrute extends EntityPiglinAbstract {
     @Override
     protected void mobTick() {
         this.world.getMethodProfiler().enter("piglinBruteBrain");
-        this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this);
+        if (getRider() == null) // Purpur - only use brain if no rider
+        this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error
         this.world.getMethodProfiler().exit();
         PiglinBruteAI.b(this);
         PiglinBruteAI.c(this);
diff --git a/src/main/java/net/minecraft/server/EntityPillager.java b/src/main/java/net/minecraft/server/EntityPillager.java
index a3a428da99574c485fcf2b8c7944e0d8354146ee..cf7de0127166f6175a6246062c8664e64959edeb 100644
--- a/src/main/java/net/minecraft/server/EntityPillager.java
+++ b/src/main/java/net/minecraft/server/EntityPillager.java
@@ -13,15 +13,29 @@ public class EntityPillager extends EntityIllagerAbstract implements ICrossbow {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.pillagerRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.pillagerRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(2, new EntityRaider.a(this, 10.0F));
         this.goalSelector.a(3, new PathfinderGoalCrossbowAttack<>(this, 1.0D, 8.0F));
         this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D));
         this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 15.0F, 1.0F));
         this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 15.0F));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error
         this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false));
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index 6052643bbe1ee12bcf98126e36fc09bd2593dfa6..dbc7ca05503dd3de6d4ffe50433d9f2284016191 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -512,6 +512,15 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
         }
 
         this.advancementDataPlayer.b(this);
+
+        // Purpur start
+        if (this.world.purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && world.getTime() % 100 == 0) { // 5 seconds
+            MobEffect nightVision = this.getEffect(MobEffects.NIGHT_VISION);
+            if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds
+                this.addEffect(new MobEffect(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds
+            }
+        }
+        // Purpur end
     }
 
     public void playerTick() {
diff --git a/src/main/java/net/minecraft/server/EntityPolarBear.java b/src/main/java/net/minecraft/server/EntityPolarBear.java
index 99f0bd8f82520778d469ec51745034e6ebd3238a..3d649843f565d2c8820b525c199bd2b9f9120cc7 100644
--- a/src/main/java/net/minecraft/server/EntityPolarBear.java
+++ b/src/main/java/net/minecraft/server/EntityPolarBear.java
@@ -18,12 +18,34 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
     private static final IntRange bs = TimeRange.a(20, 39);
     private int bt;
     private UUID bu;
+    private int standTimer = 0; // Purpur
 
     public EntityPolarBear(EntityTypes<? extends EntityPolarBear> entitytypes, World world) {
         super(entitytypes, world);
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.polarBearRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.polarBearRidableInWater;
+    }
+
+    @Override
+    public boolean onSpacebar() {
+        if (!isStanding()) {
+            if (hasRider() && getRider().getForward() == 0 && getRider().getStrafe() == 0) {
+                setStanding(true);
+                playSound(SoundEffects.ENTITY_POLAR_BEAR_WARNING, 1.0F, 1.0F);
+            }
+        }
+        return false;
+    }
+
     @Override
     public boolean mate(EntityAnimal entityanimal) {
         if (entityanimal == this) {
@@ -61,6 +83,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
     protected void initPathfinder() {
         super.initPathfinder();
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityPolarBear.c());
         this.goalSelector.a(1, new EntityPolarBear.d());
         // Purpur start
@@ -73,6 +96,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
         this.goalSelector.a(5, new PathfinderGoalRandomStroll(this, 1.0D));
         this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
         this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new EntityPolarBear.b());
         this.targetSelector.a(2, new EntityPolarBear.a());
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::a_));
@@ -185,6 +209,11 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
             this.a((WorldServer) this.world, true);
         }
 
+        // Purpur start
+        if (isStanding() && --standTimer <= 0) {
+            setStanding(false);
+        }
+        // Purpur end
     }
 
     @Override
@@ -218,6 +247,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
     public void setStanding(boolean standing) { t(standing); } // Purpur - OBFHELPER
     public void t(boolean flag) {
         this.datawatcher.set(EntityPolarBear.bo, flag);
+        standTimer = flag ? 20 : -1; // Purpur
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/server/EntityPufferFish.java b/src/main/java/net/minecraft/server/EntityPufferFish.java
index 330ec38d09636232250d97baad0cef7a9305d614..16a4df27c557e2d4a0fd4f48317307b884c2688c 100644
--- a/src/main/java/net/minecraft/server/EntityPufferFish.java
+++ b/src/main/java/net/minecraft/server/EntityPufferFish.java
@@ -17,6 +17,18 @@ public class EntityPufferFish extends EntityFish {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.pufferfishRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+    // Purpur end
+
     @Override
     protected void initDatawatcher() {
         super.initDatawatcher();
@@ -60,7 +72,7 @@ public class EntityPufferFish extends EntityFish {
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
-        this.goalSelector.a(1, new EntityPufferFish.a(this));
+        this.goalSelector.a(2, new EntityPufferFish.a(this)); // Purpur
     }
 
     @Override
diff --git a/src/main/java/net/minecraft/server/EntityRabbit.java b/src/main/java/net/minecraft/server/EntityRabbit.java
index f17608730fca96af4f9779863a8c25723a3bd5cf..95f4592944a53aab0ff9843ae8e7c9b9cd0201c7 100644
--- a/src/main/java/net/minecraft/server/EntityRabbit.java
+++ b/src/main/java/net/minecraft/server/EntityRabbit.java
@@ -20,6 +20,18 @@ public class EntityRabbit extends EntityAnimal {
         this.initializePathFinderGoals(); // CraftBukkit - moved code
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.rabbitRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.rabbitRidableInWater;
+    }
+    // Purpur end
+
     // CraftBukkit start - code from constructor
     public void initializePathFinderGoals(){
         this.i(0.0D);
@@ -28,7 +40,8 @@ public class EntityRabbit extends EntityAnimal {
 
     @Override
     public void initPathfinder() {
-        this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityRabbit.PathfinderGoalRabbitPanic(this, 2.2D));
         this.goalSelector.a(2, new PathfinderGoalBreed(this, 0.8D));
         this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, RecipeItemStack.a(Items.CARROT, Items.GOLDEN_CARROT, Blocks.DANDELION), false));
@@ -41,7 +54,15 @@ public class EntityRabbit extends EntityAnimal {
     }
 
     @Override
-    protected float dJ() {
+    // Purpur start
+    public float getJumpHeight() {
+        if (hasRider()) {
+            if (getForward() < 0) {
+                setSpeed(getForward() * 2F);
+            }
+            return actualJump ? 0.5F : 0.3F;
+        }
+        // Purpur end
         if (!this.positionChanged && (!this.moveController.b() || this.moveController.e() <= this.locY() + 0.5D)) {
             PathEntity pathentity = this.navigation.k();
 
@@ -60,7 +81,7 @@ public class EntityRabbit extends EntityAnimal {
     }
 
     @Override
-    protected void jump() {
+    public void jump() { // Purpur - protected -> public
         super.jump();
         double d0 = this.moveController.c();
 
@@ -92,6 +113,7 @@ public class EntityRabbit extends EntityAnimal {
 
     }
 
+    public void startJumping() { eK(); } // Purpur - OBFHELPER
     public void eK() {
         this.setJumping(true);
         this.br = 10;
@@ -106,6 +128,13 @@ public class EntityRabbit extends EntityAnimal {
 
     @Override
     public void mobTick() {
+        // Purpur start
+        if (hasRider()) {
+            handleJumping();
+            return;
+        }
+        // Purpur end
+
         if (this.bt > 0) {
             --this.bt;
         }
@@ -156,6 +185,39 @@ public class EntityRabbit extends EntityAnimal {
         this.bs = this.onGround;
     }
 
+    // Purpur start
+    private boolean wasOnGround;
+    private boolean actualJump;
+
+    private void handleJumping() {
+        if (onGround) {
+            ControllerJumpRabbit jumpController = (ControllerJumpRabbit) getJumpController();
+            if (!wasOnGround) {
+                setJumping(false);
+                jumpController.setCanJump(false);
+            }
+            if (!jumpController.isJumping()) {
+                if (moveController.b()) { // isUpdating
+                    startJumping();
+                }
+            } else if (!jumpController.canJump()) {
+                jumpController.setCanJump(true);
+            }
+        }
+        wasOnGround = onGround;
+    }
+
+    @Override
+    public boolean onSpacebar() {
+        if (onGround) {
+            actualJump = true;
+            jump();
+            actualJump = false;
+        }
+        return true;
+    }
+    // Purpur end
+
     @Override
     public boolean aO() {
         return false;
@@ -485,7 +547,7 @@ public class EntityRabbit extends EntityAnimal {
         }
     }
 
-    static class ControllerMoveRabbit extends ControllerMove {
+    static class ControllerMoveRabbit extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur
 
         private final EntityRabbit i;
         private double j;
@@ -496,14 +558,14 @@ public class EntityRabbit extends EntityAnimal {
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (this.i.onGround && !this.i.jumping && !((EntityRabbit.ControllerJumpRabbit) this.i.bi).c()) {
                 this.i.i(0.0D);
             } else if (this.b()) {
                 this.i.i(this.j);
             }
 
-            super.a();
+            super.tick(); // Purpur
         }
 
         @Override
@@ -530,14 +592,17 @@ public class EntityRabbit extends EntityAnimal {
             this.c = entityrabbit;
         }
 
+        public boolean isJumping() { return c(); } // Purpur - OBFHELPER
         public boolean c() {
             return this.a;
         }
 
+        public boolean canJump() { return d(); } // Purpur - OBFHELPER
         public boolean d() {
             return this.d;
         }
 
+        public void setCanJump(boolean canJump) { a(canJump); } // Purpur - OBFHELPER
         public void a(boolean flag) {
             this.d = flag;
         }
diff --git a/src/main/java/net/minecraft/server/EntityRavager.java b/src/main/java/net/minecraft/server/EntityRavager.java
index e78f158d9c682c60d42c17e0f171d492552e4079..9d0a462bfc56994ee35478cc9e565123620a9f08 100644
--- a/src/main/java/net/minecraft/server/EntityRavager.java
+++ b/src/main/java/net/minecraft/server/EntityRavager.java
@@ -20,14 +20,37 @@ public class EntityRavager extends EntityRaider {
         this.f = 20;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.ravagerRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.ravagerRidableInWater;
+    }
+
+    @Override
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        getNavigation().stopPathfinding();
+
+        double speed = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getBaseValue();
+        getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(speed);
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(4, new EntityRavager.a());
         this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 0.4D));
         this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
         this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(2, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
         this.targetSelector.a(4, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, true));
@@ -99,7 +122,7 @@ public class EntityRavager extends EntityRaider {
     @Override
     public void movementTick() {
         super.movementTick();
-        if (this.isAlive()) {
+        if (this.isAlive() && !hasRider()) {
             if (this.isFrozen()) {
                 this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.0D);
             } else {
diff --git a/src/main/java/net/minecraft/server/EntitySalmon.java b/src/main/java/net/minecraft/server/EntitySalmon.java
index 51ec63413306cf9dcd685870ccbd4e5440ab2e7a..6dfcee2c9b658c2c9ee1179e412389934c066d48 100644
--- a/src/main/java/net/minecraft/server/EntitySalmon.java
+++ b/src/main/java/net/minecraft/server/EntitySalmon.java
@@ -6,6 +6,18 @@ public class EntitySalmon extends EntityFishSchool {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.salmonRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+    // Purpur end
+
     @Override
     public int eN() {
         return 5;
diff --git a/src/main/java/net/minecraft/server/EntitySheep.java b/src/main/java/net/minecraft/server/EntitySheep.java
index 2908e9cc47947daad19391d38da3c2a300f67fe5..a151d4295c02930687a23212647de60cce5405ca 100644
--- a/src/main/java/net/minecraft/server/EntitySheep.java
+++ b/src/main/java/net/minecraft/server/EntitySheep.java
@@ -56,10 +56,23 @@ public class EntitySheep extends EntityAnimal implements IShearable {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.sheepRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.sheepRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.bs = new PathfinderGoalEatTile(this);
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D));
         this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
         this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.1D, RecipeItemStack.a(Items.WHEAT), false));
diff --git a/src/main/java/net/minecraft/server/EntityShulker.java b/src/main/java/net/minecraft/server/EntityShulker.java
index 49f6ee064fa7b79c7a9fd7a3344ed2334ce1f9f6..11a6b939ebb51cdda907531d30b7beb3a8562673 100644
--- a/src/main/java/net/minecraft/server/EntityShulker.java
+++ b/src/main/java/net/minecraft/server/EntityShulker.java
@@ -30,12 +30,26 @@ public class EntityShulker extends EntityGolem implements IMonster {
         this.f = 5;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.shulkerRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.shulkerRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(4, new EntityShulker.a());
         this.goalSelector.a(7, new EntityShulker.e());
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // CraftBukkit - decompile error
         this.targetSelector.a(2, new EntityShulker.d(this));
         this.targetSelector.a(3, new EntityShulker.c(this));
@@ -519,7 +533,7 @@ public class EntityShulker extends EntityGolem implements IMonster {
 
         private int b;
 
-        private e() {}
+        private e() { this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); } // Purpur - peek
 
         @Override
         public boolean a() {
diff --git a/src/main/java/net/minecraft/server/EntitySilverfish.java b/src/main/java/net/minecraft/server/EntitySilverfish.java
index 28b490cc14f5881eb83acfbd6f30f9163ffe1926..ad428e090089a461283445022b33313520585ac5 100644
--- a/src/main/java/net/minecraft/server/EntitySilverfish.java
+++ b/src/main/java/net/minecraft/server/EntitySilverfish.java
@@ -11,13 +11,27 @@ public class EntitySilverfish extends EntityMonster {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.silverfishRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.silverfishRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.b = new EntitySilverfish.PathfinderGoalSilverfishWakeOthers(this);
         this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(3, this.b);
         this.goalSelector.a(4, new PathfinderGoalMeleeAttack(this, 1.0D, false));
         this.goalSelector.a(5, new EntitySilverfish.PathfinderGoalSilverfishHideInBlock(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // CraftBukkit - decompile error
         this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
     }
@@ -177,6 +191,7 @@ public class EntitySilverfish extends EntityMonster {
 
         public PathfinderGoalSilverfishWakeOthers(EntitySilverfish entitysilverfish) {
             this.silverfish = entitysilverfish;
+            this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur
         }
 
         public void g() {
diff --git a/src/main/java/net/minecraft/server/EntitySkeleton.java b/src/main/java/net/minecraft/server/EntitySkeleton.java
index 7c39bec8314a3db63a90ccc9f040b82d754705e2..3f130e03bf8b235360385fd169d4886ffcfa626a 100644
--- a/src/main/java/net/minecraft/server/EntitySkeleton.java
+++ b/src/main/java/net/minecraft/server/EntitySkeleton.java
@@ -6,6 +6,18 @@ public class EntitySkeleton extends EntitySkeletonAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.skeletonRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.skeletonRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected SoundEffect getSoundAmbient() {
         return SoundEffects.ENTITY_SKELETON_AMBIENT;
diff --git a/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java b/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java
index f73304240a626f3f7d9355e6e5f2963a06c4bb7d..ee4c26de15a5c304889f38f49f4584e8d4ccc5fe 100644
--- a/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java
+++ b/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java
@@ -28,12 +28,14 @@ public abstract class EntitySkeletonAbstract extends EntityMonster implements IR
 
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(2, new PathfinderGoalRestrictSun(this));
         this.goalSelector.a(3, new PathfinderGoalFleeSun(this, 1.0D));
         this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityWolf.class, 6.0F, 1.0D, 1.2D));
         this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D));
         this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(6, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[0]));
         this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true));
diff --git a/src/main/java/net/minecraft/server/EntitySkeletonStray.java b/src/main/java/net/minecraft/server/EntitySkeletonStray.java
index f985caada082eff6183d7bc9868b1782f9529eaf..d123fb82b635d5271bea9b238554a3011858eeae 100644
--- a/src/main/java/net/minecraft/server/EntitySkeletonStray.java
+++ b/src/main/java/net/minecraft/server/EntitySkeletonStray.java
@@ -8,6 +8,18 @@ public class EntitySkeletonStray extends EntitySkeletonAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.strayRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.strayRidableInWater;
+    }
+    // Purpur end
+
     public static boolean a(EntityTypes<EntitySkeletonStray> entitytypes, WorldAccess worldaccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) {
         return b(entitytypes, worldaccess, enummobspawn, blockposition, random) && (enummobspawn == EnumMobSpawn.SPAWNER || worldaccess.e(blockposition));
     }
diff --git a/src/main/java/net/minecraft/server/EntitySkeletonWither.java b/src/main/java/net/minecraft/server/EntitySkeletonWither.java
index c872be77a6cd767520d5412b38ec4ed4fa87ac2f..96cb080d940db22330598a8806726088b79a53c1 100644
--- a/src/main/java/net/minecraft/server/EntitySkeletonWither.java
+++ b/src/main/java/net/minecraft/server/EntitySkeletonWither.java
@@ -9,6 +9,18 @@ public class EntitySkeletonWither extends EntitySkeletonAbstract {
         this.a(PathType.LAVA, 8.0F);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.witherSkeletonRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.witherSkeletonRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityPiglinAbstract.class, true));
diff --git a/src/main/java/net/minecraft/server/EntitySlime.java b/src/main/java/net/minecraft/server/EntitySlime.java
index 80fab9290479f876fd78997c1bd55fe6b00aac45..7f3d57a9399f60465fae27f03fc57df625f5016d 100644
--- a/src/main/java/net/minecraft/server/EntitySlime.java
+++ b/src/main/java/net/minecraft/server/EntitySlime.java
@@ -35,12 +35,45 @@ public class EntitySlime extends EntityInsentient implements IMonster {
         this.moveController = new EntitySlime.ControllerMoveSlime(this);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.slimeRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.slimeRidableInWater;
+    }
+
+    protected boolean actualJump;
+
+    @Override
+    public float getJumpHeight() {
+        float height = super.getJumpHeight();
+        return hasRider() && actualJump ? height * 1.5F : height;
+    }
+
+    @Override
+    public boolean onSpacebar() {
+        if (onGround && hasRider()) {
+            actualJump = true;
+            if (getRider().getForward() == 0 || getRider().getStrafe() == 0) {
+                jump(); // jump() here if not moving
+            }
+        }
+        return true; // do not jump() in wasd controller, let vanilla controller handle
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntitySlime.PathfinderGoalSlimeRandomJump(this));
         this.goalSelector.a(2, new EntitySlime.PathfinderGoalSlimeNearestPlayer(this));
         this.goalSelector.a(3, new EntitySlime.PathfinderGoalSlimeRandomDirection(this));
         this.goalSelector.a(5, new EntitySlime.PathfinderGoalSlimeIdle(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, (entityliving) -> {
             return Math.abs(entityliving.locY() - this.locY()) <= 4.0D;
         }));
@@ -326,11 +359,12 @@ public class EntitySlime extends EntityInsentient implements IMonster {
     }
 
     @Override
-    protected void jump() {
+    public void jump() { // Purpur - protected -> public
         Vec3D vec3d = this.getMot();
 
         this.setMot(vec3d.x, (double) this.dJ(), vec3d.z);
         this.impulse = true;
+        this.actualJump = false; // Purpur
     }
 
     @Nullable
@@ -499,10 +533,10 @@ public class EntitySlime extends EntityInsentient implements IMonster {
         // Paper end
     }
 
-    static class ControllerMoveSlime extends ControllerMove {
+    static class ControllerMoveSlime extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur
 
         private float i;
-        private int j;
+        private int j; private int getJumpDelay() { return j; } private void setJumpDelay(int delay) { j = delay; } // Purpur - OBFHELPER
         private final EntitySlime k;
         private boolean l;
 
@@ -524,15 +558,27 @@ public class EntitySlime extends EntityInsentient implements IMonster {
 
         @Override
         public void a() {
+            // Purpur start
+            if (entity.hasRider()) {
+                tick(entity.getRider());
+                if (entity.getForward() != 0 || entity.getStrafe() != 0) {
+                    if (getJumpDelay() > 10) {
+                        setJumpDelay(6);
+                    }
+                } else {
+                    setJumpDelay(20);
+                }
+            } else {
+            // Purpur end
             this.a.yaw = this.a(this.a.yaw, this.i, 90.0F);
             this.a.aC = this.a.yaw;
             this.a.aA = this.a.yaw;
-            if (this.h != ControllerMove.Operation.MOVE_TO) {
+            } if (!entity.hasRider() && this.h != ControllerMove.Operation.MOVE_TO) { // Purpur
                 this.a.t(0.0F);
             } else {
                 this.h = ControllerMove.Operation.WAIT;
                 if (this.a.isOnGround()) {
-                    this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED)));
+                    this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED) * (entity.hasRider() && (entity.getRider().getForward() != 0 || entity.getRider().getStrafe() != 0) ? 2.0D : 1.0D))); // Purpur
                     if (this.j-- <= 0) {
                         this.j = this.k.eJ();
                         if (this.l) {
@@ -549,7 +595,7 @@ public class EntitySlime extends EntityInsentient implements IMonster {
                         this.a.q(0.0F);
                     }
                 } else {
-                    this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED)));
+                    this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED) * (entity.hasRider() && (entity.getRider().getForward() != 0 || entity.getRider().getStrafe() != 0) ? 2.0D : 1.0D))); // Purpur
                 }
 
             }
diff --git a/src/main/java/net/minecraft/server/EntitySnowman.java b/src/main/java/net/minecraft/server/EntitySnowman.java
index c39f3c06eb199a24f32a5c581e877073b580502c..e370de0ade49ac81d746588bf0991101eba0ce2d 100644
--- a/src/main/java/net/minecraft/server/EntitySnowman.java
+++ b/src/main/java/net/minecraft/server/EntitySnowman.java
@@ -14,12 +14,26 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.snowGolemRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.snowGolemRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new PathfinderGoalArrowAttack(this, 1.25D, 20, 10.0F));
         this.goalSelector.a(2, new PathfinderGoalRandomStrollLand(this, 1.0D, 1.0000001E-5F));
         this.goalSelector.a(3, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
         this.goalSelector.a(4, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityInsentient.class, 10, true, false, (entityliving) -> {
             return entityliving instanceof IMonster;
         }));
@@ -71,6 +85,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt
                 return;
             }
 
+            if (hasRider() && !world.purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden
             IBlockData iblockdata = Blocks.SNOW.getBlockData();
 
             for (int l = 0; l < 4; ++l) {
@@ -113,7 +128,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt
         if (itemstack.getItem() == Items.SHEARS && this.canShear()) {
             // CraftBukkit start
             if (!CraftEventFactory.handlePlayerShearEntityEvent(entityhuman, this, itemstack, enumhand)) {
-                return EnumInteractionResult.PASS;
+                return tryRide(entityhuman, enumhand); // Purpur
             }
             // CraftBukkit end
             this.shear(SoundCategory.PLAYERS);
@@ -133,7 +148,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt
             return EnumInteractionResult.SUCCESS;
         // Purpur end
         } else {
-            return EnumInteractionResult.PASS;
+            return tryRide(entityhuman, enumhand); // Purpur
         }
     }
 
diff --git a/src/main/java/net/minecraft/server/EntitySpider.java b/src/main/java/net/minecraft/server/EntitySpider.java
index bf68efd52f607ae353d6f84d4896926e16740523..92d74137877d096970bf9d1b4fc91beabb862b9d 100644
--- a/src/main/java/net/minecraft/server/EntitySpider.java
+++ b/src/main/java/net/minecraft/server/EntitySpider.java
@@ -11,14 +11,28 @@ public class EntitySpider extends EntityMonster {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.spiderRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.spiderRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(3, new PathfinderGoalLeapAtTarget(this, 0.4F));
         this.goalSelector.a(4, new EntitySpider.PathfinderGoalSpiderMeleeAttack(this));
         this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 0.8D));
         this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(6, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[0]));
         this.targetSelector.a(2, new EntitySpider.PathfinderGoalSpiderNearestAttackableTarget<>(this, EntityHuman.class));
         this.targetSelector.a(3, new EntitySpider.PathfinderGoalSpiderNearestAttackableTarget<>(this, EntityIronGolem.class));
diff --git a/src/main/java/net/minecraft/server/EntitySquid.java b/src/main/java/net/minecraft/server/EntitySquid.java
index 148e4b158734f136832e5c17bdc69634c0f294aa..70b952f10a2af547f58069977ee135469d02f84d 100644
--- a/src/main/java/net/minecraft/server/EntitySquid.java
+++ b/src/main/java/net/minecraft/server/EntitySquid.java
@@ -26,17 +26,38 @@ public class EntitySquid extends EntityWaterAnimal {
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.squidRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+
     @Override
     public AxisAlignedBB getAxisForFluidCheck() {
         // Stops squids from floating just over the water
         return this.getBoundingBox().shrink(0.001D).offsetY(world.purpurConfig.squidOffsetWaterCheck);
     }
+
+    private void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) {
+        double rad = Math.toRadians(degrees);
+        double cos = Math.cos(rad);
+        double sine = Math.sin(rad);
+        double x = vector.getX();
+        double z = vector.getZ();
+        vector.setX(cos * x - sine * z);
+        vector.setZ(sine * x + cos * z);
+    }
     // Purpur end
 
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new EntitySquid.PathfinderGoalSquid(this));
-        this.goalSelector.a(1, new EntitySquid.a());
+        this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur
+        this.goalSelector.a(2, new EntitySquid.a()); // Purpur
     }
 
     public static AttributeProvider.Builder m() {
@@ -181,6 +202,7 @@ public class EntitySquid extends EntityWaterAnimal {
         return blockposition.getY() > generatoraccess.getMinecraftWorld().spigotConfig.squidSpawnRangeMin && blockposition.getY() < maxHeight; // Spigot // Paper
     }
 
+    public void setMovementVector(float x, float y, float z) { a(x, y, z); } // Purpur - OBFHELPER
     public void a(float f, float f1, float f2) {
         this.bw = f;
         this.bx = f1;
@@ -252,7 +274,7 @@ public class EntitySquid extends EntityWaterAnimal {
 
     class PathfinderGoalSquid extends PathfinderGoal {
 
-        private final EntitySquid b;
+        private final EntitySquid b; public EntitySquid getSquid() { return b; } // Purpur - OBFHELPER
 
         public PathfinderGoalSquid(EntitySquid entitysquid) {
             this.b = entitysquid;
@@ -265,6 +287,38 @@ public class EntitySquid extends EntityWaterAnimal {
 
         @Override
         public void e() {
+            // Purpur start
+            EntitySquid squid = getSquid();
+            EntityHuman rider = squid.getRider();
+            if (rider != null) {
+                if (rider.jumping) {
+                    squid.onSpacebar();
+                }
+                float forward = rider.getForward();
+                float strafe = rider.getStrafe();
+                float speed = (float) squid.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * 10F;
+                if (forward < 0.0F) {
+                    speed *= -0.5;
+                }
+                org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F);
+                if (strafe != 0.0F) {
+                    if (forward == 0.0F) {
+                        dir.setY(0);
+                        rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90);
+                    } else if (forward < 0.0F) {
+                        rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45);
+                    } else {
+                        rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45);
+                    }
+                }
+                if (forward != 0.0F || strafe != 0.0F) {
+                    squid.setMovementVector((float) dir.getX(), (float) dir.getY(), (float) dir.getZ());
+                } else {
+                    squid.setMovementVector(0.0F, 0.0F, 0.0F);
+                }
+                return;
+            }
+            // Purpur end
             int i = this.b.dd();
 
             if (i > 100) {
diff --git a/src/main/java/net/minecraft/server/EntityStrider.java b/src/main/java/net/minecraft/server/EntityStrider.java
index f35203a5074f520f617f7756bbbc9eecdda08937..56b72132595db5bc9addf31aecde5c13c5fd44c2 100644
--- a/src/main/java/net/minecraft/server/EntityStrider.java
+++ b/src/main/java/net/minecraft/server/EntityStrider.java
@@ -28,6 +28,18 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab
         this.a(PathType.DAMAGE_FIRE, 0.0F);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.striderRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.striderRidableInWater;
+    }
+    // Purpur end
+
     public static boolean c(EntityTypes<EntityStrider> entitytypes, GeneratorAccess generatoraccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) {
         BlockPosition.MutableBlockPosition blockposition_mutableblockposition = blockposition.i();
 
@@ -89,6 +101,7 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab
     @Override
     protected void initPathfinder() {
         this.bv = new PathfinderGoalPanic(this, 1.65D);
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, this.bv);
         this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
         this.bu = new PathfinderGoalTempt(this, 1.4D, false, EntityStrider.bp);
@@ -368,7 +381,7 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab
             if (!enuminteractionresult.a()) {
                 ItemStack itemstack = entityhuman.b(enumhand);
 
-                return itemstack.getItem() == Items.SADDLE ? itemstack.a(entityhuman, (EntityLiving) this, enumhand) : EnumInteractionResult.PASS;
+                return itemstack.getItem() == Items.SADDLE ? itemstack.a(entityhuman, (EntityLiving) this, enumhand) : tryRide(entityhuman, enumhand); // Purpur
             } else {
                 if (flag && !this.isSilent()) {
                     this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_STRIDER_EAT, this.getSoundCategory(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F);
diff --git a/src/main/java/net/minecraft/server/EntityTameableAnimal.java b/src/main/java/net/minecraft/server/EntityTameableAnimal.java
index 9e008d56bb88550f399008095734436a5ab768c7..8f227678dc4ab1b7369a0c76173b3f695a570620 100644
--- a/src/main/java/net/minecraft/server/EntityTameableAnimal.java
+++ b/src/main/java/net/minecraft/server/EntityTameableAnimal.java
@@ -130,6 +130,7 @@ public abstract class EntityTameableAnimal extends EntityAnimal {
         return this.i(entityliving) ? false : super.c(entityliving);
     }
 
+    public boolean isOwner(EntityLiving entityLiving) { return i(entityLiving); } // Purpur - OBFHELPER
     public boolean i(EntityLiving entityliving) {
         return entityliving == this.getOwner();
     }
diff --git a/src/main/java/net/minecraft/server/EntityTropicalFish.java b/src/main/java/net/minecraft/server/EntityTropicalFish.java
index 495c28ccb86a5645459c9265c90dfffb6972d604..2c9df356e685ea6f71653023fadcf7e287dcd46e 100644
--- a/src/main/java/net/minecraft/server/EntityTropicalFish.java
+++ b/src/main/java/net/minecraft/server/EntityTropicalFish.java
@@ -19,6 +19,18 @@ public class EntityTropicalFish extends EntityFishSchool {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.tropicalFishRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return true;
+    }
+    // Purpur end
+
     @Override
     protected void initDatawatcher() {
         super.initDatawatcher();
diff --git a/src/main/java/net/minecraft/server/EntityTurtle.java b/src/main/java/net/minecraft/server/EntityTurtle.java
index 4ad393bc99881d813e2b349fb929fc8e69631723..2b34e6cf3b86319bd2875d92b63902889fec32a8 100644
--- a/src/main/java/net/minecraft/server/EntityTurtle.java
+++ b/src/main/java/net/minecraft/server/EntityTurtle.java
@@ -27,6 +27,18 @@ public class EntityTurtle extends EntityAnimal {
         this.G = 1.0F;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.turtleRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.turtleRidableInWater;
+    }
+    // Purpur end
+
     public void setHomePos(BlockPosition blockposition) {
         this.datawatcher.set(EntityTurtle.bp, blockposition.immutableCopy()); // Paper - called with mutablepos...
     }
@@ -135,12 +147,13 @@ public class EntityTurtle extends EntityAnimal {
 
     @Override
     protected void initPathfinder() {
-        this.goalSelector.a(0, new EntityTurtle.f(this, 1.2D));
-        this.goalSelector.a(1, new EntityTurtle.a(this, 1.0D));
-        this.goalSelector.a(1, new EntityTurtle.d(this, 1.0D));
-        this.goalSelector.a(2, new EntityTurtle.i(this, 1.1D, Blocks.SEAGRASS.getItem()));
-        this.goalSelector.a(3, new EntityTurtle.c(this, 1.0D));
-        this.goalSelector.a(4, new EntityTurtle.b(this, 1.0D));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
+        this.goalSelector.a(1, new EntityTurtle.f(this, 1.2D)); // Purpur
+        this.goalSelector.a(2, new EntityTurtle.a(this, 1.0D)); // Purpur
+        this.goalSelector.a(2, new EntityTurtle.d(this, 1.0D)); // Purpur
+        this.goalSelector.a(3, new EntityTurtle.i(this, 1.1D, Blocks.SEAGRASS.getItem())); // Purpur
+        this.goalSelector.a(4, new EntityTurtle.c(this, 1.0D)); // Purpur
+        this.goalSelector.a(5, new EntityTurtle.b(this, 1.0D)); // Purpur
         this.goalSelector.a(7, new EntityTurtle.j(this, 1.0D));
         this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(9, new EntityTurtle.h(this, 1.0D, 100));
@@ -323,13 +336,15 @@ public class EntityTurtle extends EntityAnimal {
         }
     }
 
-    static class e extends ControllerMove {
+    static class e extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur
 
-        private final EntityTurtle i;
+        private final EntityTurtle i; public EntityTurtle getTurtle() { return i; } // Purpur - OBFHELPER
+        private final net.pl3x.purpur.controller.ControllerMoveWASDWater waterController; // Purpur
 
         e(EntityTurtle entityturtle) {
-            super(entityturtle);
+            super(entityturtle, 0.1D); // Purpur
             this.i = entityturtle;
+            waterController = new net.pl3x.purpur.controller.ControllerMoveWASDWater(entityturtle, 0.25D); // Purpur
         }
 
         private void g() {
@@ -349,7 +364,18 @@ public class EntityTurtle extends EntityAnimal {
         }
 
         @Override
-        public void a() {
+        // Purpur start
+        public void tick(EntityHuman rider) {
+            if (getTurtle().isInWater()) {
+                waterController.tick(rider);
+            } else {
+                super.tick(rider);
+            }
+        }
+
+        @Override
+        public void tick() {
+            // Purpur end
             this.g();
             if (this.h == ControllerMove.Operation.MOVE_TO && !this.i.getNavigation().m()) {
                 double d0 = this.b - this.i.locX();
diff --git a/src/main/java/net/minecraft/server/EntityVex.java b/src/main/java/net/minecraft/server/EntityVex.java
index 9a33866e7529e1636c228bab01205737bc678b47..9336fbbd46c7c6ac11adf8e4cc2735f81c9ed639 100644
--- a/src/main/java/net/minecraft/server/EntityVex.java
+++ b/src/main/java/net/minecraft/server/EntityVex.java
@@ -18,6 +18,45 @@ public class EntityVex extends EntityMonster {
         this.f = 3;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.vexRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.vexRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.vexMaxY;
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider()) {
+            float speed;
+            if (onGround) {
+                speed = (float) getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * 0.1F;
+            } else {
+                speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue();
+            }
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, 1.0, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+
+    @Override
+    public boolean b(float f, float f1) {
+        return false; //  no fall damage please
+    }
+    // Purpur end
+
     @Override
     public void move(EnumMoveType enummovetype, Vec3D vec3d) {
         super.move(enummovetype, vec3d);
@@ -26,7 +65,7 @@ public class EntityVex extends EntityMonster {
 
     @Override
     public void tick() {
-        this.noclip = true;
+        this.noclip = !hasRider(); // Purpur
         super.tick();
         this.noclip = false;
         this.setNoGravity(true);
@@ -41,17 +80,19 @@ public class EntityVex extends EntityMonster {
     protected void initPathfinder() {
         super.initPathfinder();
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(4, new EntityVex.a());
         this.goalSelector.a(8, new EntityVex.d());
         this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F));
         this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error
         this.targetSelector.a(2, new EntityVex.b(this));
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
     }
 
     public static AttributeProvider.Builder m() {
-        return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 14.0D).a(GenericAttributes.ATTACK_DAMAGE, 4.0D);
+        return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 14.0D).a(GenericAttributes.ATTACK_DAMAGE, 4.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur
     }
 
     @Override
@@ -283,14 +324,14 @@ public class EntityVex extends EntityMonster {
         }
     }
 
-    class c extends ControllerMove {
+    class c extends net.pl3x.purpur.controller.ControllerMoveWASDFlying { // Purpur
 
         public c(EntityVex entityvex) {
             super(entityvex);
         }
 
         @Override
-        public void a() {
+        public void tick() { // Purpur
             if (this.h == ControllerMove.Operation.MOVE_TO) {
                 Vec3D vec3d = new Vec3D(this.b - EntityVex.this.locX(), this.c - EntityVex.this.locY(), this.d - EntityVex.this.locZ());
                 double d0 = vec3d.f();
diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java
index 762f1acf435c6e7b6d695fc1b2120de0116f46da..15e052cb844df310c62d8a5695e8da6c633d94b6 100644
--- a/src/main/java/net/minecraft/server/EntityVillager.java
+++ b/src/main/java/net/minecraft/server/EntityVillager.java
@@ -73,8 +73,19 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.villagerRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.villagerRidableInWater;
+    }
+
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         if (world.purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, TEMPT_ITEMS));
     }
 
@@ -239,7 +250,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation
         if (itemstack.getItem() != Items.VILLAGER_SPAWN_EGG && this.isAlive() && !this.eN() && !this.isSleeping()) {
             if (this.isBaby()) {
                 this.fk();
-                return EnumInteractionResult.a(this.world.isClientSide);
+                return super.b(entityhuman, enumhand); // Purpur
             } else {
                 boolean flag = this.getOffers().isEmpty();
 
@@ -252,8 +263,9 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation
                 }
 
                 if (flag) {
-                    return EnumInteractionResult.a(this.world.isClientSide);
+                    return tryRide(entityhuman, enumhand); // Purpur
                 } else {
+                    if (world.purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(entityhuman, enumhand); // Purpur
                     if (!this.world.isClientSide && !this.trades.isEmpty()) {
                         this.h(entityhuman);
                     }
diff --git a/src/main/java/net/minecraft/server/EntityVillagerTrader.java b/src/main/java/net/minecraft/server/EntityVillagerTrader.java
index 432956d9d5f2706014d67232b88e6b86011484f3..b9418fac0a05d4bb5f8f215a5704bba332e3f7cb 100644
--- a/src/main/java/net/minecraft/server/EntityVillagerTrader.java
+++ b/src/main/java/net/minecraft/server/EntityVillagerTrader.java
@@ -25,6 +25,7 @@ public class EntityVillagerTrader extends EntityVillagerAbstract {
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(0, new PathfinderGoalUseItem<>(this, PotionUtil.a(new ItemStack(Items.POTION), Potions.INVISIBILITY), SoundEffects.ENTITY_WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> {
             return this.world.isNight() && !entityvillagertrader.isInvisible();
         }));
@@ -50,6 +51,16 @@ public class EntityVillagerTrader extends EntityVillagerAbstract {
     }
 
     // Purpur - start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.villagerTraderRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.villagerTraderRidableInWater;
+    }
+
     @Override
     public boolean a(EntityHuman entityhuman) {
         return world.purpurConfig.villagerTraderCanBeLeashed && !this.isLeashed();
@@ -77,8 +88,9 @@ public class EntityVillagerTrader extends EntityVillagerAbstract {
             }
 
             if (this.getOffers().isEmpty()) {
-                return EnumInteractionResult.a(this.world.isClientSide);
+                return tryRide(entityhuman, enumhand); // Purpur
             } else {
+                if (world.purpurConfig.villagerTraderRidable && itemstack.isEmpty()) return tryRide(entityhuman, enumhand); // Purpur
                 if (!this.world.isClientSide) {
                     this.setTradingPlayer(entityhuman);
                     this.openTrade(entityhuman, this.getScoreboardDisplayName(), 1);
diff --git a/src/main/java/net/minecraft/server/EntityVindicator.java b/src/main/java/net/minecraft/server/EntityVindicator.java
index 23b350f793539672b6990327ed52e9bb3bdbf54e..f1bc6a4199d788215c2e7d5a835211d16603a6d9 100644
--- a/src/main/java/net/minecraft/server/EntityVindicator.java
+++ b/src/main/java/net/minecraft/server/EntityVindicator.java
@@ -18,14 +18,28 @@ public class EntityVindicator extends EntityIllagerAbstract {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.vindicatorRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.vindicatorRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(1, new EntityVindicator.a(this));
         this.goalSelector.a(2, new EntityIllagerAbstract.b(this));
         this.goalSelector.a(3, new EntityRaider.a(this, 10.0F));
         this.goalSelector.a(4, new EntityVindicator.c(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // Paper - decompile fix
         this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
         this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, true));
diff --git a/src/main/java/net/minecraft/server/EntityWitch.java b/src/main/java/net/minecraft/server/EntityWitch.java
index ca3c5150bcfe2a92b49ad5a27c23dd37a7054fbb..323d79a99402b0f6952b4fb873170069f3428953 100644
--- a/src/main/java/net/minecraft/server/EntityWitch.java
+++ b/src/main/java/net/minecraft/server/EntityWitch.java
@@ -24,6 +24,18 @@ public class EntityWitch extends EntityRaider implements IRangedEntity {
         super(entitytypes, world);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.witchRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.witchRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         super.initPathfinder();
@@ -32,10 +44,12 @@ public class EntityWitch extends EntityRaider implements IRangedEntity {
         });
         this.bs = new PathfinderGoalNearestAttackableTargetWitch<>(this, EntityHuman.class, 10, true, false, (Predicate) null);
         this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(2, new PathfinderGoalArrowAttack(this, 1.0D, 60, 10.0F));
         this.goalSelector.a(2, new PathfinderGoalRandomStrollLand(this, 1.0D));
         this.goalSelector.a(3, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(3, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class}));
         this.targetSelector.a(2, this.br);
         this.targetSelector.a(3, this.bs);
diff --git a/src/main/java/net/minecraft/server/EntityWither.java b/src/main/java/net/minecraft/server/EntityWither.java
index b1159f0258eca2bee52ec0939ba86792d24a1f99..6fc097d2ae11ec5d1c12ce070c8d69d2249cf3c7 100644
--- a/src/main/java/net/minecraft/server/EntityWither.java
+++ b/src/main/java/net/minecraft/server/EntityWither.java
@@ -32,6 +32,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
         return entityliving.getMonsterType() != EnumMonsterType.UNDEAD && entityliving.ei();
     };
     private static final PathfinderTargetCondition bz = (new PathfinderTargetCondition()).a(20.0D).a(EntityWither.by);
+    private int shootCooldown = 0; // Purpur
     // Paper start
     private boolean canPortal = false;
 
@@ -44,15 +45,122 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
         this.setHealth(this.getMaxHealth());
         this.getNavigation().d(true);
         this.f = 50;
+        this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.1F); // Purpur
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.witherRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.witherRidableInWater;
+    }
+
+    @Override
+    public double getMaxY() {
+        return world.purpurConfig.witherMaxY;
+    }
+
+    @Override
+    public void g(Vec3D vec3d) {
+        super.g(vec3d);
+        if (hasRider() && !onGround) {
+            float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 5F;
+            setSpeed(speed);
+            Vec3D mot = getMot();
+            move(EnumMoveType.SELF, mot.multiply(speed, 0.5, speed));
+            setMot(mot.a(0.9D));
+        }
+    }
+
+    @Override
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        this.datawatcher.set(bo.get(0), 0);
+        this.datawatcher.set(bo.get(1), 0);
+        this.datawatcher.set(bo.get(2), 0);
+        getNavigation().stopPathfinding();
+        shootCooldown = 20;
+    }
+
+    @Override
+    public boolean onClick(EnumHand hand) {
+        return shoot(getRider(), hand == EnumHand.MAIN_HAND ? new int[]{1} : new int[]{2});
+    }
+
+    public boolean shoot(EntityHuman rider, int[] heads) {
+        if (shootCooldown > 0) {
+            return false;
+        }
+
+        shootCooldown = 20;
+        if (rider == null) {
+            return false;
+        }
+
+        org.bukkit.craftbukkit.entity.CraftHumanEntity player = rider.getBukkitEntity();
+        if (!player.hasPermission("allow.special.wither")) {
+            return false;
+        }
+
+        MovingObjectPosition rayTrace = getRayTrace(120, RayTrace.FluidCollisionOption.NONE);
+        if (rayTrace == null) {
+            return false;
+        }
+
+        Vec3D loc;
+        if (rayTrace.getType() == MovingObjectPosition.EnumMovingObjectType.BLOCK) {
+            BlockPosition pos = ((MovingObjectPositionBlock) rayTrace).getBlockPosition();
+            loc = new Vec3D(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
+        } else if (rayTrace.getType() == MovingObjectPosition.EnumMovingObjectType.ENTITY) {
+            Entity target = ((MovingObjectPositionEntity) rayTrace).getEntity();
+            loc = new Vec3D(target.locX(), target.locY() + (target.getHeadHeight() / 2), target.locZ());
+        } else {
+            org.bukkit.block.Block block = player.getTargetBlock(null, 120);
+            loc = new Vec3D(block.getX() + 0.5D, block.getY() + 0.5D, block.getZ() + 0.5D);
+        }
+
+        for (int head : heads) {
+            shoot(head, loc.getX(), loc.getY(), loc.getZ(), rider);
+        }
+
+        return true; // handled
+    }
+
+    public void shoot(int head, double x, double y, double z, EntityHuman rider) {
+        world.playEvent(null, 1024, getChunkCoordinates(), 0);
+        double headX = getHeadX(head);
+        double headY = getHeadY(head);
+        double headZ = getHeadZ(head);
+        EntityWitherSkull skull = new EntityWitherSkull(world, this, x - headX, y - headY, z - headZ) {
+            @Override
+            public boolean canSaveToDisk() {
+                return false;
+            }
+
+            @Override
+            public boolean hitPredicate(Entity target) {
+                // do not hit rider
+                return target != rider && super.hitPredicate(target);
+            }
+        };
+        skull.setPositionRaw(headX, headY, headZ);
+        world.addEntity(skull);
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
-        this.goalSelector.a(0, new EntityWither.a());
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
+        this.goalSelector.a(1, new EntityWither.a()); // Purpur
         this.goalSelector.a(2, new PathfinderGoalArrowAttack(this, 1.0D, 40, 20.0F));
         this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D));
         this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[0]));
         if(this.world.paperConfig.fixWitherTargetingBug) this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 0, false, false, null)); // Paper - Fix MC-29274
         this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityInsentient.class, 0, false, false, EntityWither.by));
@@ -195,6 +303,16 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
 
     @Override
     protected void mobTick() {
+        // Purpur start
+        if (hasRider()) {
+            Vec3D mot = getMot();
+            setMot(mot.x, mot.y + (getVertical() > 0 ? 0.07D : 0.0D), mot.z);
+        }
+        if (shootCooldown > 0) {
+            shootCooldown--;
+        }
+        // Purpur end
+
         int i;
 
         if (this.getInvul() > 0) {
@@ -378,7 +496,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
         this.bossBattle.removePlayer(entityplayer);
     }
 
-    private double u(int i) {
+    private double u(int i) { return getHeadX(i); } private double getHeadX(int i) { // Purpur - OBFHELPER
         if (i <= 0) {
             return this.locX();
         } else {
@@ -389,11 +507,11 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
         }
     }
 
-    private double v(int i) {
+    private double v(int i) { return getHeadY(i); } private double getHeadY(int i) { // Purpur - OBFHELPER
         return i <= 0 ? this.locY() + 3.0D : this.locY() + 2.2D;
     }
 
-    private double w(int i) {
+    private double w(int i) { return getHeadZ(i); } private double getHeadZ(int i) { // Purpur - OBFHELPER
         if (i <= 0) {
             return this.locZ();
         } else {
@@ -517,7 +635,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
     }
 
     public static AttributeProvider.Builder eK() {
-        return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 300.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.6000000238418579D).a(GenericAttributes.FOLLOW_RANGE, 40.0D).a(GenericAttributes.ARMOR, 4.0D);
+        return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 300.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.6000000238418579D).a(GenericAttributes.FOLLOW_RANGE, 40.0D).a(GenericAttributes.ARMOR, 4.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur
     }
 
     public int getInvul() {
@@ -529,11 +647,11 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
     }
 
     public int getHeadTarget(int i) {
-        return (Integer) this.datawatcher.get((DataWatcherObject) EntityWither.bo.get(i));
+        return hasRider() ? 0 : this.datawatcher.get(EntityWither.bo.get(i)); // Purpur
     }
 
     public void setHeadTarget(int i, int j) {
-        this.datawatcher.set((DataWatcherObject) EntityWither.bo.get(i), j);
+        if (!hasRider()) this.datawatcher.set(EntityWither.bo.get(i), j); // Purpur
     }
 
     public final boolean isPowered() { return this.S_(); } // Paper - OBFHELPER
diff --git a/src/main/java/net/minecraft/server/EntityWolf.java b/src/main/java/net/minecraft/server/EntityWolf.java
index dcbb34313fedb21e180a0b76610a787e6419d404..9ae7168595dd66860e09ef87f946b18b010e54b1 100644
--- a/src/main/java/net/minecraft/server/EntityWolf.java
+++ b/src/main/java/net/minecraft/server/EntityWolf.java
@@ -33,9 +33,27 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable
         this.setTamed(false);
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.wolfRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.wolfRidableInWater;
+    }
+
+    public void onMount(EntityHuman entityhuman) {
+        super.onMount(entityhuman);
+        setSitting(false);
+    }
+    // Purpur end
+
     @Override
     protected void initPathfinder() {
         this.goalSelector.a(1, new PathfinderGoalFloat(this));
+        this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur
         this.goalSelector.a(2, new PathfinderGoalSit(this));
         this.goalSelector.a(3, new EntityWolf.a<>(this, EntityLlama.class, 24.0F, 1.5D, 1.5D));
         this.goalSelector.a(4, new PathfinderGoalLeapAtTarget(this, 0.4F));
@@ -46,6 +64,7 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable
         this.goalSelector.a(9, new PathfinderGoalBeg(this, 8.0F));
         this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(10, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.targetSelector.a(1, new PathfinderGoalOwnerHurtByTarget(this));
         this.targetSelector.a(2, new PathfinderGoalOwnerHurtTarget(this));
         this.targetSelector.a(3, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // CraftBukkit - decompile error
diff --git a/src/main/java/net/minecraft/server/EntityZoglin.java b/src/main/java/net/minecraft/server/EntityZoglin.java
index e76e6ebde73b93dc06e76b71cdf6371c3654160a..d92fe8013fb3b43cb7eabdf1c624291b7e881889 100644
--- a/src/main/java/net/minecraft/server/EntityZoglin.java
+++ b/src/main/java/net/minecraft/server/EntityZoglin.java
@@ -21,6 +21,18 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin {
         this.f = 5;
     }
 
+    // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.zoglinRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.zoglinRidableInWater;
+    }
+    // Purpur end
+
     @Override
     protected BehaviorController.b<EntityZoglin> cK() {
         return BehaviorController.a((Collection) EntityZoglin.c, (Collection) EntityZoglin.b);
@@ -52,10 +64,10 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin {
     }
 
     private Optional<? extends EntityLiving> eO() {
-        return ((List) this.getBehaviorController().getMemory(MemoryModuleType.VISIBLE_MOBS).orElse(ImmutableList.of())).stream().filter(EntityZoglin::i).findFirst();
+        return (this.getBehaviorController().getMemory(MemoryModuleType.VISIBLE_MOBS).orElse(ImmutableList.of())).stream().filter(EntityZoglin::predicate).findFirst(); // Purpur - decompile error
     }
 
-    private static boolean i(EntityLiving entityliving) {
+    private static boolean predicate(EntityLiving entityliving) { // Purpur - decompile error
         EntityTypes<?> entitytypes = entityliving.getEntityType();
 
         return entitytypes != EntityTypes.ZOGLIN && entitytypes != EntityTypes.CREEPER && IEntitySelector.f.test(entityliving);
@@ -140,14 +152,14 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin {
 
     @Override
     public BehaviorController<EntityZoglin> getBehaviorController() {
-        return super.getBehaviorController();
+        return (BehaviorController<EntityZoglin>) super.getBehaviorController(); // Purpur - decompile error
     }
 
     protected void eL() {
-        Activity activity = (Activity) this.bg.f().orElse((Object) null);
+        Activity activity = (Activity) this.bg.f().orElse(null); // Purpur - decompile error
 
         this.bg.a((List) ImmutableList.of(Activity.FLIGHT, Activity.IDLE));
-        Activity activity1 = (Activity) this.bg.f().orElse((Object) null);
+        Activity activity1 = (Activity) this.bg.f().orElse(null); // Purpur - decompile error
 
         if (activity1 == Activity.FLIGHT && activity != Activity.FLIGHT) {
             this.eN();
@@ -159,7 +171,8 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin {
     @Override
     protected void mobTick() {
         this.world.getMethodProfiler().enter("zoglinBrain");
-        this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this);
+        if (getRider() == null) // Purpur - only use brain if no rider
+        this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error
         this.world.getMethodProfiler().exit();
         this.eL();
     }
diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java
index 291748b3436c18653fa959cfd8c92240aa17508c..2076fe3e467c16bb400a75f9ea82f5b0773126be 100644
--- a/src/main/java/net/minecraft/server/EntityZombie.java
+++ b/src/main/java/net/minecraft/server/EntityZombie.java
@@ -46,6 +46,16 @@ public class EntityZombie extends EntityMonster {
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.zombieRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.zombieRidableInWater;
+    }
+
     public boolean jockeyOnlyBaby() {
         return world.purpurConfig.zombieJockeyOnlyBaby;
     }
@@ -61,9 +71,11 @@ public class EntityZombie extends EntityMonster {
 
     @Override
     protected void initPathfinder() {
+        this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         if (world.paperConfig.zombiesTargetTurtleEggs) this.goalSelector.a(4, new EntityZombie.a(this, 1.0D, 3)); // Paper
         this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
+        this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur
         this.m();
     }
 
diff --git a/src/main/java/net/minecraft/server/EntityZombieHusk.java b/src/main/java/net/minecraft/server/EntityZombieHusk.java
index ce6d79780197eb9300130036a8ed84648a17f9cf..02b0ae550a0ed33b5b43beedf3b1405985c58c13 100644
--- a/src/main/java/net/minecraft/server/EntityZombieHusk.java
+++ b/src/main/java/net/minecraft/server/EntityZombieHusk.java
@@ -9,6 +9,16 @@ public class EntityZombieHusk extends EntityZombie {
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.huskRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.huskRidableInWater;
+    }
+
     @Override
     public boolean jockeyOnlyBaby() {
         return world.purpurConfig.huskJockeyOnlyBaby;
diff --git a/src/main/java/net/minecraft/server/EntityZombieVillager.java b/src/main/java/net/minecraft/server/EntityZombieVillager.java
index 5a71381c9f1832710001b6bfa63d6815c3cd1ab0..5b6ed77fb2b92b5bf701de436c8d7235bfe82685 100644
--- a/src/main/java/net/minecraft/server/EntityZombieVillager.java
+++ b/src/main/java/net/minecraft/server/EntityZombieVillager.java
@@ -29,6 +29,16 @@ public class EntityZombieVillager extends EntityZombie implements VillagerDataHo
     }
 
     // Purpur start
+    @Override
+    public boolean isRidable() {
+        return world.purpurConfig.zombieVillagerRidable;
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return world.purpurConfig.zombieVillagerRidableInWater;
+    }
+
     @Override
     public boolean jockeyOnlyBaby() {
         return world.purpurConfig.zombieVillagerJockeyOnlyBaby;
diff --git a/src/main/java/net/minecraft/server/GeneratorAccess.java b/src/main/java/net/minecraft/server/GeneratorAccess.java
index cbc0b8bc854ab1b0ba95fa0a2041385f440718d9..89d64ea0d1e61dfce622df026209af129efd5773 100644
--- a/src/main/java/net/minecraft/server/GeneratorAccess.java
+++ b/src/main/java/net/minecraft/server/GeneratorAccess.java
@@ -37,6 +37,7 @@ public interface GeneratorAccess extends ICombinedAccess, IWorldTime {
 
     void addParticle(ParticleParam particleparam, double d0, double d1, double d2, double d3, double d4, double d5);
 
+    default void playEvent(@Nullable EntityHuman entityhuman, int i, BlockPosition blockposition, int j) { a(entityhuman, i, blockposition, j); } // Purpur - OBFHELPER
     void a(@Nullable EntityHuman entityhuman, int i, BlockPosition blockposition, int j);
 
     default int getHeight() {
diff --git a/src/main/java/net/minecraft/server/IProjectile.java b/src/main/java/net/minecraft/server/IProjectile.java
index dc9f2a1a132b3a7925bd62aa1da0512afd90b8b1..b7e540dfeeabb13227596ecfc6eddabf3cfde537 100644
--- a/src/main/java/net/minecraft/server/IProjectile.java
+++ b/src/main/java/net/minecraft/server/IProjectile.java
@@ -12,7 +12,7 @@ public abstract class IProjectile extends Entity {
 
     private UUID shooter;
     private int c;
-    private boolean d;
+    private boolean d; public boolean leftOwner() { return d; } public void setLeftOwner(boolean leftOwner) { this.d = leftOwner; } // Purpur - OBFHELPER
 
     IProjectile(EntityTypes<? extends IProjectile> entitytypes, World world) {
         super(entitytypes, world);
@@ -74,6 +74,7 @@ public abstract class IProjectile extends Entity {
         super.tick();
     }
 
+    public boolean checkIfLeftOwner() { return this.h(); } // Purpur - OBFHELPER
     private boolean h() {
         Entity entity = this.getShooter();
 
@@ -137,7 +138,7 @@ public abstract class IProjectile extends Entity {
         iblockdata.a(this.world, iblockdata, movingobjectpositionblock, this);
     }
 
-    protected boolean a(Entity entity) {
+    protected boolean a(Entity entity) { return hitPredicate(entity); } public boolean hitPredicate(Entity entity) { // Purpur - OBFHELPER
         if (!entity.isSpectator() && entity.isAlive() && entity.isInteractable()) {
             Entity entity1 = this.getShooter();
             // Paper start - Cancel hit for vanished players
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index a3ddf2839738e089a02d841f5b6fd759c3047305..85c14df768ec844b710bf2b32eb7f85c9e6b47ea 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1418,6 +1418,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
             WorldServer worldserver = (WorldServer) iterator.next();
             worldserver.hasPhysicsEvent =  org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
             worldserver.hasEntityMoveEvent =  EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+            worldserver.hasRidableMoveEvent = net.pl3x.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur
             TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
 
             this.methodProfiler.a(() -> {
diff --git a/src/main/java/net/minecraft/server/PathfinderGoalHasRider.java b/src/main/java/net/minecraft/server/PathfinderGoalHasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..da18e1235eed1988052fbd761c11f77efd7afc5e
--- /dev/null
+++ b/src/main/java/net/minecraft/server/PathfinderGoalHasRider.java
@@ -0,0 +1,18 @@
+package net.minecraft.server;
+
+import java.util.EnumSet;
+
+public class PathfinderGoalHasRider extends PathfinderGoal {
+    public final EntityInsentient entity;
+
+    public PathfinderGoalHasRider(EntityInsentient entity) {
+        this.entity = entity;
+        setTypes(EnumSet.of(Type.JUMP, Type.MOVE, Type.LOOK, Type.TARGET, Type.UNKNOWN_BEHAVIOR));
+    }
+
+    // shouldExecute
+    @Override
+    public boolean a() {
+        return entity.hasRider();
+    }
+}
diff --git a/src/main/java/net/minecraft/server/PathfinderGoalHorseHasRider.java b/src/main/java/net/minecraft/server/PathfinderGoalHorseHasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..44929182dfd7ad847d9657c324f440cbf29abadf
--- /dev/null
+++ b/src/main/java/net/minecraft/server/PathfinderGoalHorseHasRider.java
@@ -0,0 +1,16 @@
+package net.minecraft.server;
+
+public class PathfinderGoalHorseHasRider extends PathfinderGoalHasRider {
+    private final EntityHorseAbstract horse;
+
+    public PathfinderGoalHorseHasRider(EntityHorseAbstract entity) {
+        super(entity);
+        this.horse = entity;
+    }
+
+    // shouldExecute
+    @Override
+    public boolean a() {
+        return super.a() && horse.hasSaddle();
+    }
+}
diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
index 48d032ed74effcfd66e1ab01548913964bca4d19..7be03bb416a5723ce65411ba5d0b967040e8033c 100644
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
@@ -2307,6 +2307,8 @@ public class PlayerConnection implements PacketListenerPlayIn {
                     }
                     this.server.getPluginManager().callEvent(event);
 
+                    player.processClick(enumhand); // Purpur
+
                     // Fish bucket - SPIGOT-4048
                     if ((entity instanceof EntityFish && origItem != null && origItem.getItem() == Items.WATER_BUCKET) && (event.isCancelled() || this.player.inventory.getItemInHand() == null || this.player.inventory.getItemInHand().getItem() != origItem)) {
                         this.sendPacket(new PacketPlayOutSpawnEntityLiving((EntityFish) entity));
diff --git a/src/main/java/net/minecraft/server/ProjectileHelper.java b/src/main/java/net/minecraft/server/ProjectileHelper.java
index b2c64b31440389db5abe2322f7e31b328f590f6c..515ba50aec81497d27297e4b6c642e86b7de53ca 100644
--- a/src/main/java/net/minecraft/server/ProjectileHelper.java
+++ b/src/main/java/net/minecraft/server/ProjectileHelper.java
@@ -7,6 +7,7 @@ import javax.annotation.Nullable;
 
 public final class ProjectileHelper {
 
+    public static MovingObjectPosition getHitResult(Entity entity, Predicate<Entity> predicate) { return a(entity, predicate); } // Purpur - OBFHELPER
     public static MovingObjectPosition a(Entity entity, Predicate<Entity> predicate) {
         Vec3D vec3d = entity.getMot();
         World world = entity.world;
diff --git a/src/main/java/net/minecraft/server/Vec3D.java b/src/main/java/net/minecraft/server/Vec3D.java
index 5af554870bcf36e47aef43b966b141b9eda6c4d5..c59305ef7dd7847e204d4c4ed79758bf9d66e91e 100644
--- a/src/main/java/net/minecraft/server/Vec3D.java
+++ b/src/main/java/net/minecraft/server/Vec3D.java
@@ -39,6 +39,7 @@ public class Vec3D implements IPosition {
         return new Vec3D(vec3d.x - this.x, vec3d.y - this.y, vec3d.z - this.z);
     }
 
+    public Vec3D normalize() { return d(); } // Purpur - OBFHELPER
     public Vec3D d() {
         double d0 = (double) MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
 
@@ -98,6 +99,7 @@ public class Vec3D implements IPosition {
         return d3 * d3 + d4 * d4 + d5 * d5;
     }
 
+    public Vec3D scale(double scale) { return a(scale); } // Purpur - OBFHELPER
     public Vec3D a(double d0) {
         return this.d(d0, d0, d0);
     }
@@ -106,6 +108,7 @@ public class Vec3D implements IPosition {
         return this.d(vec3d.x, vec3d.y, vec3d.z);
     }
 
+    public Vec3D multiply (double x, double y, double z) { return d(x, y, z); } // Purpur - OBFHELPER
     public Vec3D d(double d0, double d1, double d2) {
         return new Vec3D(this.x * d0, this.y * d1, this.z * d2);
     }
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index d3b3b771640a46ce9a898f645cf50007e25ae7c2..34e5f8b3bf1b0045856ec0d06ec6d62d0b976862 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1681,5 +1681,10 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
     public boolean isTheEnd() {
         return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END;
     }
+
+    // Purpur start
+    public void playEffect(@Nullable EntityHuman entityhuman, int i, BlockPosition blockposition, int j) {
+        this.a(entityhuman, i, blockposition, j);
+    }
     // Purpur end
 }
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 59301aad8c257758cb0fe9709901847925d60be4..33b41dc393e302c114f2ea69e789505b7ec77935 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -102,6 +102,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
     public final UUID uuid;
     boolean hasPhysicsEvent = true; // Paper
     boolean hasEntityMoveEvent = false; // Paper
+    boolean hasRidableMoveEvent = false; // Purpur
     private static Throwable getAddToWorldStackTrace(Entity entity) {
         return new Throwable(entity + " Added to world at " + new java.util.Date());
     }
diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
index c314a8c9a921a95cea43b748e2037521d948e1e7..59f3122aab9940cb3c3c1efb2664ab0835656d48 100644
--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
@@ -136,11 +136,13 @@ public class PurpurConfig {
     public static String afkBroadcastBack = "§e§o%s is no longer AFK";
     public static String afkTabListPrefix = "[AFK] ";
     public static String pingCommandOutput = "§a%s's ping is %sms";
+    public static String cannotRideMob = "§cYou cannot mount that mob";
     private static void messages() {
         afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway);
         afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack);
         afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix);
         pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput);
+        cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob);
     }
 
     public static int dungeonSeed = -1;
diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
index 637cac2758a44e72794953b9fbca2accb7d86b6a..d8b5e091047210a8eff45e493f07d30ffa6d9f81 100644
--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
@@ -42,11 +42,6 @@ public class PurpurWorldConfig {
         }
     }
 
-    public double vindicatorJohnnySpawnChance = 0D;
-    private void vindicatorSettings() {
-        vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance);
-    }
-
     private ConfigurationSection getConfigurationSection(String path) {
         ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path);
         return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path);
@@ -327,61 +322,186 @@ public class PurpurWorldConfig {
         turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
     }
 
+    public boolean babiesAreRidable = true;
+    public boolean untamedTamablesAreRidable = true;
+    public boolean useNightVisionWhenRiding = false;
+    private void ridableSettings() {
+        babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable);
+        untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable);
+        useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding);
+    }
+
+    public boolean batRidable = false;
+    public boolean batRidableInWater = false;
+    public double batMaxY = 256D;
+    private void batSettings() {
+        batRidable = getBoolean("mobs.bat.ridable", batRidable);
+        batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater);
+        batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY);
+    }
+
+    public boolean beeRidable = false;
+    public boolean beeRidableInWater = false;
+    public double beeMaxY = 256D;
+    private void beeSettings() {
+        beeRidable = getBoolean("mobs.bee.ridable", beeRidable);
+        beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater);
+        beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY);
+    }
+
+    public boolean blazeRidable = false;
+    public boolean blazeRidableInWater = false;
+    public double blazeMaxY = 256D;
+    private void blazeSettings() {
+        blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable);
+        blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater);
+        blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY);
+    }
+
+    public boolean catRidable = false;
+    public boolean catRidableInWater = false;
     public int catSpawnDelay = 1200;
     public int catSpawnSwampHutScanRange = 16;
     public int catSpawnVillageScanRange = 48;
     private void catSettings() {
+        catRidable = getBoolean("mobs.cat.ridable", catRidable);
+        catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater);
         catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay);
         catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange);
         catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange);
     }
 
+    public boolean caveSpiderRidable = false;
+    public boolean caveSpiderRidableInWater = false;
+    private void caveSpiderSettings() {
+        caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable);
+        caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater);
+    }
+
+    public boolean chickenRidable = false;
+    public boolean chickenRidableInWater = false;
     public boolean chickenRetaliate = false;
     private void chickenSettings() {
+        chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable);
+        chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater);
         chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate);
     }
 
+    public boolean codRidable = false;
+    private void codSettings() {
+        codRidable = getBoolean("mobs.cod.ridable", codRidable);
+    }
+
+    public boolean cowRidable = false;
+    public boolean cowRidableInWater = false;
     public int cowFeedMushrooms = 0;
     private void cowSettings() {
+        cowRidable = getBoolean("mobs.cow.ridable", cowRidable);
+        cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater);
         cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms);
     }
 
+    public boolean creeperRidable = false;
+    public boolean creeperRidableInWater = false;
     public boolean creeperAllowGriefing = true;
     public double creeperChargedChance = 0.0D;
     private void creeperSettings() {
+        creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable);
+        creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater);
         creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing);
         creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
     }
 
+    public boolean dolphinRidable = false;
+    public int dolphinSpitCooldown = 20;
+    public float dolphinSpitSpeed = 1.0F;
+    public float dolphinSpitDamage = 2.0F;
     public boolean dolphinDisableTreasureSearching = false;
     private void dolphinSettings() {
+        dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable);
+        dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown);
+        dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed);
+        dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage);
         dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching);
     }
 
+    public boolean donkeyRidableInWater = false;
+    private void donkeySettings() {
+        donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater);
+    }
+
+    public boolean drownedRidable = false;
+    public boolean drownedRidableInWater = false;
     public boolean drownedJockeyOnlyBaby = true;
     public double drownedJockeyChance = 0.05D;
     public boolean drownedJockeyTryExistingChickens = true;
     private void drownedSettings() {
+        drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable);
+        drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater);
         drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby);
         drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance);
         drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens);
     }
 
+    public boolean elderGuardianRidable = false;
+    private void elderGuardianSettings() {
+        elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable);
+    }
+
+    public boolean enderDragonRidable = false;
+    public boolean enderDragonRidableInWater = false;
+    public double enderDragonMaxY = 256D;
     public boolean enderDragonAlwaysDropsFullExp = false;
     private void enderDragonSettings() {
+        enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable);
+        enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater);
+        enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY);
         enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp);
     }
 
+    public boolean endermanRidable = false;
+    public boolean endermanRidableInWater = false;
     public boolean endermanAllowGriefing = true;
     private void endermanSettings() {
+        endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable);
+        endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater);
         endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing);
     }
 
+    public boolean endermiteRidable = false;
+    public boolean endermiteRidableInWater = false;
+    private void endermiteSettings() {
+        endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable);
+        endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater);
+    }
+
+    public boolean evokerRidable = false;
+    public boolean evokerRidableInWater = false;
+    private void evokerSettings() {
+        evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable);
+        evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater);
+    }
+
+    public boolean foxRidable = false;
+    public boolean foxRidableInWater = false;
     public boolean foxTypeChangesWithTulips = false;
     private void foxSettings() {
+        foxRidable = getBoolean("mobs.fox.ridable", foxRidable);
+        foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater);
         foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips);
     }
 
+    public boolean ghastRidable = false;
+    public boolean ghastRidableInWater = false;
+    public double ghastMaxY = 256D;
+    private void ghastSettings() {
+        ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable);
+        ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater);
+        ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY);
+    }
+
+    public boolean giantRidable = false;
+    public boolean giantRidableInWater = false;
     public float giantStepHeight = 2.0F;
     public float giantJumpHeight = 1.0F;
     public double giantMovementSpeed = 0.5D;
@@ -390,6 +510,8 @@ public class PurpurWorldConfig {
     public boolean giantHaveHostileAI = false;
     public double giantMaxHealth = 100.0D;
     private void giantSettings() {
+        giantRidable = getBoolean("mobs.giant.ridable", giantRidable);
+        giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater);
         giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight);
         giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight);
         giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed);
@@ -404,19 +526,44 @@ public class PurpurWorldConfig {
         giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth);
     }
 
+    public boolean guardianRidable = false;
+    private void guardianSettings() {
+        guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable);
+    }
+
+    public boolean hoglinRidable = false;
+    public boolean hoglinRidableInWater = false;
+    private void hoglinSettings() {
+        hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable);
+        hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater);
+    }
+
+    public boolean horseRidableInWater = false;
+    private void horseSettings() {
+        horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater);
+    }
+
+    public boolean huskRidable = false;
+    public boolean huskRidableInWater = false;
     public boolean huskJockeyOnlyBaby = true;
     public double huskJockeyChance = 0.05D;
     public boolean huskJockeyTryExistingChickens = true;
     private void huskSettings() {
+        huskRidable = getBoolean("mobs.husk.ridable", huskRidable);
+        huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater);
         huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby);
         huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance);
         huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens);
     }
 
+    public boolean illusionerRidable = false;
+    public boolean illusionerRidableInWater = false;
     public double illusionerMovementSpeed = 0.5D;
     public double illusionerFollowRange = 18.0D;
     public double illusionerMaxHealth = 32.0D;
     private void illusionerSettings() {
+        illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable);
+        illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater);
         illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed);
         illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange);
         if (PurpurConfig.version < 8) {
@@ -427,11 +574,76 @@ public class PurpurWorldConfig {
         illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth);
     }
 
+    public boolean ironGolemRidable = false;
+    public boolean ironGolemRidableInWater = false;
     public boolean ironGolemCanSwim = false;
     private void ironGolemSettings() {
+        ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable);
+        ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater);
         ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
     }
 
+    public boolean llamaRidable = false;
+    public boolean llamaRidableInWater = false;
+    private void llamaSettings() {
+        llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable);
+        llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater);
+    }
+
+    public boolean llamaTraderRidable = false;
+    public boolean llamaTraderRidableInWater = false;
+    private void llamaTraderSettings() {
+        llamaTraderRidable = getBoolean("mobs.trader_llama.ridable", llamaTraderRidable);
+        llamaTraderRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", llamaTraderRidableInWater);
+    }
+
+    public boolean magmaCubeRidable = false;
+    public boolean magmaCubeRidableInWater = false;
+    private void magmaCubeSettings() {
+        magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable);
+        magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater);
+    }
+
+    public boolean mooshroomRidable = false;
+    public boolean mooshroomRidableInWater = false;
+    private void mooshroomSettings() {
+        mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable);
+        mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater);
+    }
+
+    public boolean muleRidableInWater = false;
+    private void muleSettings() {
+        muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater);
+    }
+
+    public boolean ocelotRidable = false;
+    public boolean ocelotRidableInWater = false;
+    private void ocelotSettings() {
+        ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable);
+        ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater);
+    }
+
+    public boolean pandaRidable = false;
+    public boolean pandaRidableInWater = false;
+    private void pandaSettings() {
+        pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable);
+        pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater);
+    }
+
+    public boolean parrotRidable = false;
+    public boolean parrotRidableInWater = false;
+    public double parrotMaxY = 256D;
+    private void parrotSettings() {
+        parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable);
+        parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater);
+        parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY);
+    }
+
+    public boolean phantomRidable = false;
+    public boolean phantomRidableInWater = false;
+    public double phantomMaxY = 256D;
+    public float phantomFlameDamage = 1.0F;
+    public int phantomFlameFireTime = 8;
     public double phantomAttackedByCrystalRadius = 0.0D;
     public float phantomAttackedByCrystalDamage = 1.0F;
     public double phantomOrbitCrystalRadius = 0.0D;
@@ -450,7 +662,13 @@ public class PurpurWorldConfig {
     public int phantomBurnInLight = 0;
     public boolean phantomIgnorePlayersWithTorch = false;
     public boolean phantomBurnInDaylight = true;
+    public boolean phantomAllowGriefing = false;
     private void phantomSettings() {
+        phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable);
+        phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater);
+        phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY);
+        phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage);
+        phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime);
         phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius);
         phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage);
         phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius);
@@ -469,42 +687,187 @@ public class PurpurWorldConfig {
         phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight);
         phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight);
         phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch);
+        phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing);
     }
 
+    public boolean pigRidable = false;
+    public boolean pigRidableInWater = false;
     public boolean pigGiveSaddleBack = false;
     private void pigSettings() {
+        pigRidable = getBoolean("mobs.pig.ridable", pigRidable);
+        pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater);
         pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack);
     }
 
+    public boolean piglinRidable = false;
+    public boolean piglinRidableInWater = false;
+    private void piglinSettings() {
+        piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable);
+        piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater);
+    }
+
+    public boolean piglinBruteRidable = false;
+    public boolean piglinBruteRidableInWater = false;
+    private void piglinBruteSettings() {
+        piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable);
+        piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater);
+    }
+
+    public boolean pillagerRidable = false;
+    public boolean pillagerRidableInWater = false;
+    private void pillagerSettings() {
+        pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable);
+        pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater);
+    }
+
+    public boolean polarBearRidable = false;
+    public boolean polarBearRidableInWater = false;
     public String polarBearBreedableItemString = "";
     public Item polarBearBreedableItem = null;
     private void polarBearSettings() {
+        polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable);
+        polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater);
         polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString);
         Item item = IRegistry.ITEM.get(new MinecraftKey(polarBearBreedableItemString));
         if (item != Items.AIR) polarBearBreedableItem = item;
     }
 
+    public boolean pufferfishRidable = false;
+    private void pufferfishSettings() {
+        pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable);
+    }
+
+    public boolean rabbitRidable = false;
+    public boolean rabbitRidableInWater = false;
     public double rabbitNaturalToast = 0.0D;
     public double rabbitNaturalKiller = 0.0D;
     private void rabbitSettings() {
+        rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable);
+        rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater);
         rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast);
         rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller);
     }
 
+    public boolean ravagerRidable = false;
+    public boolean ravagerRidableInWater = false;
+    private void ravagerSettings() {
+        ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable);
+        ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater);
+    }
+
+    public boolean salmonRidable = false;
+    private void salmonSettings() {
+        salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable);
+    }
+
+    public boolean sheepRidable = false;
+    public boolean sheepRidableInWater = false;
+    private void sheepSettings() {
+        sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable);
+        sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater);
+    }
+
+    public boolean shulkerRidable = false;
+    public boolean shulkerRidableInWater = false;
+    private void shulkerSettings() {
+        shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable);
+        shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater);
+    }
+
+    public boolean silverfishRidable = false;
+    public boolean silverfishRidableInWater = false;
+    private void silverfishSettings() {
+        silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable);
+        silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater);
+    }
+
+    public boolean skeletonRidable = false;
+    public boolean skeletonRidableInWater = false;
+    private void skeletonSettings() {
+        skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable);
+        skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater);
+    }
+
+    public boolean skeletonHorseCanSwim = false;
+    public boolean skeletonHorseRidableInWater = true;
+    private void skeletonHorseSettings() {
+        skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim);
+        skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater);
+    }
+
+    public boolean slimeRidable = false;
+    public boolean slimeRidableInWater = false;
+    private void slimeSettings() {
+        slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable);
+        slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater);
+    }
+
+    public boolean snowGolemRidable = false;
+    public boolean snowGolemRidableInWater = false;
+    public boolean snowGolemLeaveTrailWhenRidden = false;
     public boolean snowGolemDropsPumpkin = true;
     public boolean snowGolemPutPumpkinBack = false;
     private void snowGolemSettings() {
+        snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable);
+        snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater);
+        snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden);
         snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin);
         snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack);
     }
 
+    public boolean squidRidable = false;
     public boolean squidImmuneToEAR = true;
     public double squidOffsetWaterCheck = 0.0D;
     private void squidSettings() {
+        squidRidable = getBoolean("mobs.squid.ridable", squidRidable);
         squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR);
         squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck);
     }
 
+    public boolean spiderRidable = false;
+    public boolean spiderRidableInWater = false;
+    private void spiderSettings() {
+        spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable);
+        spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater);
+    }
+
+    public boolean strayRidable = false;
+    public boolean strayRidableInWater = false;
+    private void straySettings() {
+        strayRidable = getBoolean("mobs.stray.ridable", strayRidable);
+        strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater);
+    }
+
+    public boolean striderRidable = false;
+    public boolean striderRidableInWater = false;
+    private void striderSettings() {
+        striderRidable = getBoolean("mobs.strider.ridable", striderRidable);
+        striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater);
+    }
+
+    public boolean tropicalFishRidable = false;
+    private void tropicalFishSettings() {
+        tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable);
+    }
+
+    public boolean turtleRidable = false;
+    public boolean turtleRidableInWater = false;
+    private void turtleSettings() {
+        turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable);
+        turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater);
+    }
+
+    public boolean vexRidable = false;
+    public boolean vexRidableInWater = false;
+    public double vexMaxY = 256D;
+    private void vexSettings() {
+        vexRidable = getBoolean("mobs.vex.ridable", vexRidable);
+        vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater);
+        vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY);
+    }
+
+    public boolean villagerRidable = false;
+    public boolean villagerRidableInWater = false;
     public int villagerBrainTicks = 1;
     public boolean villagerUseBrainTicksOnlyWhenLagging = true;
     public boolean villagerCanBeLeashed = false;
@@ -514,6 +877,8 @@ public class PurpurWorldConfig {
     public int villagerSpawnIronGolemLimit = 0;
     public boolean villagerCanBreed = true;
     private void villagerSettings() {
+        villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable);
+        villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater);
         villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks);
         villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging);
         villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed);
@@ -524,45 +889,108 @@ public class PurpurWorldConfig {
         villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed);
     }
 
+    public boolean villagerTraderRidable = false;
+    public boolean villagerTraderRidableInWater = false;
     public boolean villagerTraderCanBeLeashed = false;
     public boolean villagerTraderFollowEmeraldBlock = false;
     private void villagerTraderSettings() {
+        villagerTraderRidable = getBoolean("mobs.wandering_trader.ridable", villagerTraderRidable);
+        villagerTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", villagerTraderRidableInWater);
         villagerTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", villagerTraderCanBeLeashed);
         villagerTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", villagerTraderFollowEmeraldBlock);
     }
 
+    public boolean vindicatorRidable = false;
+    public boolean vindicatorRidableInWater = false;
+    public double vindicatorJohnnySpawnChance = 0D;
+    private void vindicatorSettings() {
+        vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable);
+        vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater);
+        vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance);
+    }
+
+    public boolean witchRidable = false;
+    public boolean witchRidableInWater = false;
+    private void witchSettings() {
+        witchRidable = getBoolean("mobs.witch.ridable", witchRidable);
+        witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater);
+    }
+
+    public boolean witherRidable = false;
+    public boolean witherRidableInWater = false;
+    public double witherMaxY = 256D;
+    private void witherSettings() {
+        witherRidable = getBoolean("mobs.wither.ridable", witherRidable);
+        witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater);
+        witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY);
+    }
+
+    public boolean witherSkeletonRidable = false;
+    public boolean witherSkeletonRidableInWater = false;
     public boolean witherSkeletonTakesWitherDamage = false;
     private void witherSkeletonSettings() {
+        witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable);
+        witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater);
         witherSkeletonTakesWitherDamage = getBoolean("mobs.wither_skeleton.takes-wither-damage", witherSkeletonTakesWitherDamage);
     }
 
+    public boolean wolfRidable = false;
+    public boolean wolfRidableInWater = false;
+    private void wolfSettings() {
+        wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable);
+        wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater);
+    }
+
+    public boolean zoglinRidable = false;
+    public boolean zoglinRidableInWater = false;
+    private void zoglinSettings() {
+        zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable);
+        zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater);
+    }
+
+    public boolean zombieRidable = false;
+    public boolean zombieRidableInWater = false;
     public boolean zombieJockeyOnlyBaby = true;
     public double zombieJockeyChance = 0.05D;
     public boolean zombieJockeyTryExistingChickens = true;
     private void zombieSettings() {
+        zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable);
+        zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater);
         zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby);
         zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance);
         zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens);
     }
 
+    public boolean zombieHorseCanSwim = false;
+    public boolean zombieHorseRidableInWater = false;
     public double zombieHorseSpawnChance = 0.0D;
     private void zombieHorseSettings() {
+        zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim);
+        zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater);
         zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance);
     }
 
+    public boolean zombifiedPiglinRidable = false;
+    public boolean zombifiedPiglinRidableInWater = false;
     public boolean zombifiedPiglinJockeyOnlyBaby = true;
     public double zombifiedPiglinJockeyChance = 0.05D;
     public boolean zombifiedPiglinJockeyTryExistingChickens = true;
     private void zombifiedPiglinSettings() {
+        zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable);
+        zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater);
         zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby);
         zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance);
         zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens);
     }
 
+    public boolean zombieVillagerRidable = false;
+    public boolean zombieVillagerRidableInWater = false;
     public boolean zombieVillagerJockeyOnlyBaby = true;
     public double zombieVillagerJockeyChance = 0.05D;
     public boolean zombieVillagerJockeyTryExistingChickens = true;
     private void zombieVillagerSettings() {
+        zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable);
+        zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater);
         zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby);
         zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance);
         zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens);
diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..868c1ce1952193cba5ff0ce35e9d607478df7a53
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java
@@ -0,0 +1,76 @@
+package net.pl3x.purpur.controller;
+
+import net.minecraft.server.ControllerLook;
+import net.minecraft.server.EntityHuman;
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.PacketPlayOutEntity;
+
+public class ControllerLookWASD extends ControllerLook {
+    protected final EntityInsentient entity;
+    private float yawOffset = 0;
+    private float pitchOffset = 0;
+
+    public ControllerLookWASD(EntityInsentient entity) {
+        super(entity);
+        this.entity = entity;
+    }
+
+    // tick
+    @Override
+    public void a() {
+        if (entity.hasRider()) {
+            tick(entity.getRider());
+        } else {
+            tick();
+        }
+    }
+
+    protected void tick() {
+        super.a(); // tick
+    }
+
+    protected void tick(EntityHuman rider) {
+        setYawPitch(rider.yaw, rider.pitch);
+    }
+
+    public void setYawPitch(float yaw, float pitch) {
+        entity.yaw = normalizeYaw(yaw + yawOffset);
+        entity.lastYaw = entity.yaw;
+        entity.setBodyYaw(entity.yaw);
+        entity.setRenderYawOffset(entity.yaw);
+        entity.setHeadRotation(entity.yaw);
+        entity.pitch = normalizePitch(pitch + pitchOffset);
+
+        entity.tracker.broadcast(new PacketPlayOutEntity
+                .PacketPlayOutRelEntityMoveLook(entity.getId(),
+                (short) 0, (short) 0, (short) 0,
+                (byte) MathHelper.d(entity.yaw * 256.0F / 360.0F),
+                (byte) MathHelper.d(entity.pitch * 256.0F / 360.0F),
+                entity.onGround));
+    }
+
+    public void setOffsets(float yaw, float pitch) {
+        yawOffset = yaw;
+        pitchOffset = pitch;
+    }
+
+    public float normalizeYaw(float yaw) {
+        yaw %= 360.0f;
+        if (yaw >= 180.0f) {
+            yaw -= 360.0f;
+        } else if (yaw < -180.0f) {
+            yaw += 360.0f;
+        }
+        return yaw;
+    }
+
+    public float normalizePitch(float pitch) {
+        if (pitch > 90.0f) {
+            pitch = 90.0f;
+        } else if (pitch < -90.0f) {
+            pitch = -90.0f;
+        }
+        return pitch;
+    }
+}
diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASD.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..426688b3a6dc197b41ddc4d1efed0405614f0f3b
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASD.java
@@ -0,0 +1,92 @@
+package net.pl3x.purpur.controller;
+
+import net.minecraft.server.ControllerMove;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityHuman;
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.GenericAttributes;
+import net.pl3x.purpur.event.entity.RidableSpacebarEvent;
+
+public class ControllerMoveWASD extends ControllerMove {
+    protected final EntityInsentient entity;
+    private final double speedModifier;
+
+    public ControllerMoveWASD(EntityInsentient entity) {
+        this(entity, 1.0D);
+    }
+
+    public ControllerMoveWASD(EntityInsentient entity, double speedModifier) {
+        super(entity);
+        this.entity = entity;
+        this.speedModifier = speedModifier;
+    }
+
+    // isUpdating
+    @Override
+    public boolean b() {
+        return entity.hasRider() ? getForward() != 0 || getStrafe() != 0 : super.b();
+    }
+
+    // tick
+    @Override
+    public void a() {
+        if (entity.hasRider()) {
+            tick(entity.getRider());
+        } else {
+            tick();
+        }
+    }
+
+    public void tick() {
+        super.a(); // tick
+    }
+
+    public void tick(EntityHuman rider) {
+        float forward = rider.getForward() * 0.5F;
+        float strafe = rider.getStrafe() * 0.25F;
+
+        if (forward <= 0.0F) {
+            forward *= 0.5F;
+        }
+
+        float yawOffset = 0;
+        if (strafe != 0) {
+            if (forward == 0) {
+                yawOffset += strafe > 0 ? -90 : 90;
+                forward = Math.abs(strafe * 2);
+            } else {
+                yawOffset += strafe > 0 ? -30 : 30;
+                strafe /= 2;
+                if (forward < 0) {
+                    yawOffset += strafe > 0 ? -110 : 110;
+                    forward *= -1;
+                }
+            }
+        } else if (forward < 0) {
+            yawOffset -= 180;
+            forward *= -1;
+        }
+
+        ((ControllerLookWASD) entity.getControllerLook()).setOffsets(yawOffset, 0);
+
+        if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) {
+            entity.jump();
+        }
+
+        setSpeed(entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * speedModifier);
+
+        entity.setSpeed((float) getSpeed());
+        entity.setForward(forward);
+
+        setForward(entity.getForward());
+        setStrafe(entity.getStrafe());
+    }
+
+    public static boolean spacebarEvent(Entity entity) {
+        if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) {
+            return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent();
+        } else {
+            return true;
+        }
+    }
+}
diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlying.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlying.java
new file mode 100644
index 0000000000000000000000000000000000000000..33c51460ab9556e5574c99232e9e3ff843c6ccc9
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlying.java
@@ -0,0 +1,61 @@
+package net.pl3x.purpur.controller;
+
+import net.minecraft.server.EntityHuman;
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.GenericAttributes;
+
+public class ControllerMoveWASDFlying extends ControllerMoveWASD {
+    protected final float groundSpeedModifier;
+    protected int tooHighCooldown = 0;
+    protected boolean setGravityFlag = true;
+
+    public ControllerMoveWASDFlying(EntityInsentient entity) {
+        this(entity, 1.0F);
+    }
+
+    public ControllerMoveWASDFlying(EntityInsentient entity, float groundSpeedModifier) {
+        this(entity, groundSpeedModifier, true);
+    }
+
+    public ControllerMoveWASDFlying(EntityInsentient entity, float groundSpeedModifier, boolean setGravityFlag) {
+        super(entity);
+        this.groundSpeedModifier = groundSpeedModifier;
+        this.setGravityFlag = setGravityFlag;
+    }
+
+    @Override
+    public void tick(EntityHuman rider) {
+        float forward = Math.max(0.0F, rider.getForward());
+        float vertical = forward == 0.0F ? 0.0F : -(rider.pitch / 45.0F);
+        float strafe = rider.getStrafe();
+
+        if (rider.jumping && spacebarEvent(entity)) {
+            entity.onSpacebar();
+        }
+
+        if (entity.locY() >= entity.getMaxY() || --tooHighCooldown > 0) {
+            tooHighCooldown = 60;
+            entity.setMot(entity.getMot().add(0.0D, -0.05D, 0.0D));
+            vertical = 0.0F;
+        }
+
+        setSpeed(entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue());
+        float speed = (float) getSpeed();
+
+        if (entity.onGround) {
+            speed *= groundSpeedModifier; // TODO = fix this!
+        }
+
+        if (setGravityFlag) {
+            entity.setNoGravity(forward > 0);
+        }
+
+        entity.setSpeed(speed);
+        entity.setVertical(vertical);
+        entity.setStrafe(strafe);
+        entity.setForward(forward);
+
+        setForward(entity.getForward());
+        setStrafe(entity.getStrafe());
+    }
+}
diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlyingWithSpacebar.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlyingWithSpacebar.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7537593619a68eb3898f28034192fab8548655c
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlyingWithSpacebar.java
@@ -0,0 +1,61 @@
+package net.pl3x.purpur.controller;
+
+import net.minecraft.server.EntityHuman;
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.GenericAttributes;
+import net.minecraft.server.Vec3D;
+
+public class ControllerMoveWASDFlyingWithSpacebar extends ControllerMoveWASDFlying {
+    public ControllerMoveWASDFlyingWithSpacebar(EntityInsentient entity) {
+        super(entity);
+    }
+
+    public ControllerMoveWASDFlyingWithSpacebar(EntityInsentient entity, float groundSpeedModifier) {
+        super(entity, groundSpeedModifier);
+    }
+
+    @Override
+    public void tick(EntityHuman rider) {
+        float forward = rider.getForward();
+        float strafe = rider.getStrafe() * 0.5F;
+        float vertical = 0;
+
+        if (forward < 0.0F) {
+            forward *= 0.5F;
+            strafe *= 0.5F;
+        }
+
+        float speed = (float) entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue();
+
+        if (entity.onGround) {
+            speed *= groundSpeedModifier;
+        }
+
+        if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar()) {
+            entity.setNoGravity(true);
+            vertical = 1.0F;
+        } else {
+            entity.setNoGravity(false);
+        }
+
+        if (entity.locY() >= entity.getMaxY() || --tooHighCooldown > 0) {
+            tooHighCooldown = 60;
+            entity.setMot(entity.getMot().add(0.0D, -0.2D, 0.0D));
+            vertical = 0.0F;
+        }
+
+        setSpeed(speed);
+        entity.setSpeed((float) getSpeed());
+        entity.setVertical(vertical);
+        entity.setStrafe(strafe);
+        entity.setForward(forward);
+
+        setForward(entity.getForward());
+        setStrafe(entity.getStrafe());
+
+        Vec3D mot = entity.getMot();
+        if (mot.y > 0.2D) {
+            entity.setMot(mot.x, 0.2D, mot.z);
+        }
+    }
+}
diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDWater.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDWater.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b08a0905b296b989e0ef0ea3b15169759b5f2fe
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDWater.java
@@ -0,0 +1,50 @@
+package net.pl3x.purpur.controller;
+
+import net.minecraft.server.EntityHuman;
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.GenericAttributes;
+
+public class ControllerMoveWASDWater extends ControllerMoveWASD {
+    private final double speedModifier;
+
+    public ControllerMoveWASDWater(EntityInsentient entity) {
+        this(entity, 1.0D);
+    }
+
+    public ControllerMoveWASDWater(EntityInsentient entity, double speedModifier) {
+        super(entity);
+        this.speedModifier = speedModifier;
+    }
+
+    @Override
+    public void tick(EntityHuman rider) {
+        float forward = rider.getForward();
+        float strafe = rider.getStrafe() * 0.5F; // strafe slower by default
+        float vertical = -(rider.pitch / 90);
+
+        if (forward == 0.0F) {
+            // strafe slower if not moving forward
+            strafe *= 0.5F;
+            // do not move vertically if not moving forward
+            vertical = 0.0F;
+        } else if (forward < 0.0F) {
+            // water animals can't swim backwards
+            forward = 0.0F;
+            vertical = 0.0F;
+        }
+
+        if (rider.jumping && spacebarEvent(entity)) {
+            entity.onSpacebar();
+        }
+
+        setSpeed(entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * speedModifier);
+        entity.setSpeed((float) getSpeed() * 0.1F);
+
+        entity.setForward(forward * (float) speedModifier);
+        entity.setStrafe(strafe * (float) speedModifier);
+        entity.setVertical(vertical * (float) speedModifier);
+
+        setForward(entity.getForward());
+        setStrafe(entity.getStrafe());
+    }
+}
diff --git a/src/main/java/net/pl3x/purpur/entity/DolphinSpit.java b/src/main/java/net/pl3x/purpur/entity/DolphinSpit.java
new file mode 100644
index 0000000000000000000000000000000000000000..a01524c6abaec13d7249d7aba6da9e4bc39f8b99
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/entity/DolphinSpit.java
@@ -0,0 +1,120 @@
+package net.pl3x.purpur.entity;
+
+import net.minecraft.server.BlockBase;
+import net.minecraft.server.DamageSource;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityDolphin;
+import net.minecraft.server.EntityLiving;
+import net.minecraft.server.EntityLlamaSpit;
+import net.minecraft.server.EntityTypes;
+import net.minecraft.server.IBlockData;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.MovingObjectPosition;
+import net.minecraft.server.MovingObjectPositionBlock;
+import net.minecraft.server.MovingObjectPositionEntity;
+import net.minecraft.server.Particles;
+import net.minecraft.server.ProjectileHelper;
+import net.minecraft.server.Vec3D;
+import net.minecraft.server.World;
+import net.minecraft.server.WorldServer;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+
+public class DolphinSpit extends EntityLlamaSpit {
+    public EntityLiving dolphin;
+    public int ticksLived;
+
+    public DolphinSpit(EntityTypes<? extends EntityLlamaSpit> entitytypes, World world) {
+        super(entitytypes, world);
+    }
+
+    public DolphinSpit(World world, EntityDolphin dolphin) {
+        this(EntityTypes.LLAMA_SPIT, world);
+        setShooter(dolphin.hasRider() ? dolphin.getRider() : dolphin);
+        this.dolphin = dolphin;
+        this.setPosition(
+                dolphin.locX() - (double) (dolphin.getWidth() + 1.0F) * 0.5D * (double) MathHelper.sin(dolphin.getRenderYawOffset() * 0.017453292F),
+                dolphin.getHeadY() - 0.10000000149011612D,
+                dolphin.locZ() + (double) (dolphin.getWidth() + 1.0F) * 0.5D * (double) MathHelper.cos(dolphin.getRenderYawOffset() * 0.017453292F));
+    }
+
+    @Override
+    public boolean canSaveToDisk() {
+        return false;
+    }
+
+    public void tick() {
+        if (dead || !valid) {
+            return;
+        }
+
+        if (!leftOwner()) {
+            setLeftOwner(checkIfLeftOwner());
+        }
+
+        setFlag(6, isGlowing());
+        entityBaseTick();
+
+        Vec3D mot = getMot();
+
+        MovingObjectPosition hitResult = ProjectileHelper.getHitResult(this, this::hitPredicate);
+        if (hitResult != null) {
+            onHit(hitResult);
+        }
+
+        double x = this.locX() + mot.x;
+        double y = this.locY() + mot.y;
+        double z = this.locZ() + mot.z;
+        setMot(mot.scale(0.99D));
+
+        Vec3D motDouble = mot.scale(2.0);
+        for (int i = 0; i < 5; i++) {
+            ((WorldServer) world).sendParticles(null, Particles.BUBBLE,
+                    locX() + random.nextFloat() / 2 - 0.25F,
+                    locY() + random.nextFloat() / 2 - 0.25F,
+                    locZ() + random.nextFloat() / 2 - 0.25F,
+                    0, motDouble.getX(), motDouble.getY(), motDouble.getZ(), 0.1, true);
+        }
+
+        if (++ticksLived > 20) {
+            die();
+        } else {
+            setMot(mot.scale(0.99D));
+            if (!isNoGravity()) {
+                setMot(getMot().add(0.0D, -0.06D, 0.0D));
+            }
+            setPosition(x, y, z);
+        }
+    }
+
+    @Override
+    public void shoot(double x, double y, double z, float speed, float inaccuracy) {
+        setMot(new Vec3D(x, y, z).normalize().add(
+                random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+                random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+                random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
+                .scale(speed));
+    }
+
+    public void onHit(MovingObjectPosition rayTrace) {
+        CraftEventFactory.callProjectileHitEvent(this, rayTrace);
+        MovingObjectPosition.EnumMovingObjectType type = rayTrace.getType();
+        if (type == MovingObjectPosition.EnumMovingObjectType.ENTITY) {
+            onHit((MovingObjectPositionEntity) rayTrace);
+        } else if (type == MovingObjectPosition.EnumMovingObjectType.BLOCK) {
+            onHit((MovingObjectPositionBlock) rayTrace);
+        }
+    }
+
+    protected void onHit(MovingObjectPositionEntity rayTrace) {
+        Entity shooter = getShooter();
+        if (shooter instanceof EntityLiving) {
+            rayTrace.getEntity().damageEntity(DamageSource.indirectMobAttack(this, (EntityLiving) shooter).setProjectile(), world.purpurConfig.dolphinSpitDamage);
+        }
+    }
+
+    protected void onHit(MovingObjectPositionBlock rayTrace) {
+        IBlockData iblockdata = world.getType(rayTrace.getBlockPosition());
+        iblockdata.a(world, iblockdata, rayTrace, this);
+        die();
+    }
+}
diff --git a/src/main/java/net/pl3x/purpur/entity/PhantomFlames.java b/src/main/java/net/pl3x/purpur/entity/PhantomFlames.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a9e46d7ee247f9e43ae5c697f10e4665fd6e4e6
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/entity/PhantomFlames.java
@@ -0,0 +1,140 @@
+package net.pl3x.purpur.entity;
+
+import net.minecraft.server.BlockBase;
+import net.minecraft.server.DamageSource;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityArmorStand;
+import net.minecraft.server.EntityLiving;
+import net.minecraft.server.EntityLlamaSpit;
+import net.minecraft.server.EntityPhantom;
+import net.minecraft.server.EntityTypes;
+import net.minecraft.server.IBlockData;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.MovingObjectPosition;
+import net.minecraft.server.MovingObjectPositionBlock;
+import net.minecraft.server.MovingObjectPositionEntity;
+import net.minecraft.server.Particles;
+import net.minecraft.server.ProjectileHelper;
+import net.minecraft.server.Vec3D;
+import net.minecraft.server.World;
+import net.minecraft.server.WorldServer;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+
+public class PhantomFlames extends EntityLlamaSpit {
+    public EntityPhantom phantom;
+    public int ticksLived;
+    public boolean canGrief = false;
+
+    public PhantomFlames(EntityTypes<? extends EntityLlamaSpit> entitytypes, World world) {
+        super(entitytypes, world);
+    }
+
+    public PhantomFlames(World world, EntityPhantom phantom) {
+        this(EntityTypes.LLAMA_SPIT, world);
+        setShooter(phantom.hasRider() ? phantom.getRider() : phantom);
+        this.phantom = phantom;
+        this.setPosition(
+                phantom.locX() - (double) (phantom.getWidth() + 1.0F) * 0.5D * (double) MathHelper.sin(phantom.getRenderYawOffset() * 0.017453292F),
+                phantom.getHeadY() - 0.10000000149011612D,
+                phantom.locZ() + (double) (phantom.getWidth() + 1.0F) * 0.5D * (double) MathHelper.cos(phantom.getRenderYawOffset() * 0.017453292F));
+    }
+
+    @Override
+    public boolean canSaveToDisk() {
+        return false;
+    }
+
+    @Override
+    public void tick() {
+        if (dead || !valid) {
+            return;
+        }
+
+        if (!leftOwner()) {
+            setLeftOwner(checkIfLeftOwner());
+        }
+
+        setFlag(6, isGlowing());
+        entityBaseTick();
+
+        Vec3D mot = getMot();
+
+        MovingObjectPosition hitResult = ProjectileHelper.getHitResult(this, this::hitPredicate);
+        if (hitResult != null) {
+            onHit(hitResult);
+        }
+
+        if (dead) {
+            return;
+        }
+
+        double x = this.locX() + mot.x;
+        double y = this.locY() + mot.y;
+        double z = this.locZ() + mot.z;
+        setMot(mot.scale(0.99D));
+
+        Vec3D motDouble = mot.scale(2.0);
+        for (int i = 0; i < 5; i++) {
+            ((WorldServer) world).sendParticles(null, Particles.FLAME,
+                    locX() + random.nextFloat() / 2 - 0.25F,
+                    locY() + random.nextFloat() / 2 - 0.25F,
+                    locZ() + random.nextFloat() / 2 - 0.25F,
+                    0, motDouble.getX(), motDouble.getY(), motDouble.getZ(), 0.1, true);
+        }
+
+        if (world.a(getBoundingBox()).noneMatch(BlockBase.BlockData::isAir)) {
+            die();
+        } else if (isInWaterOrBubbleColumn()) {
+            die();
+        } else if (++ticksLived > 20) {
+            die();
+        } else {
+            setMot(mot.scale(0.99D));
+            if (!isNoGravity()) {
+                setMot(getMot().add(0.0D, -0.06D, 0.0D));
+            }
+            setPosition(x, y, z);
+        }
+    }
+
+    @Override
+    public void shoot(double x, double y, double z, float speed, float inaccuracy) {
+        setMot(new Vec3D(x, y, z).normalize().add(
+                random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+                random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+                random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
+                .scale(speed));
+    }
+
+    public void onHit(MovingObjectPosition rayTrace) {
+        CraftEventFactory.callProjectileHitEvent(this, rayTrace);
+        MovingObjectPosition.EnumMovingObjectType type = rayTrace.getType();
+        if (type == MovingObjectPosition.EnumMovingObjectType.ENTITY) {
+            onHit((MovingObjectPositionEntity) rayTrace);
+        } else if (type == MovingObjectPosition.EnumMovingObjectType.BLOCK) {
+            onHit((MovingObjectPositionBlock) rayTrace);
+        }
+    }
+
+    protected void onHit(MovingObjectPositionEntity rayTrace) {
+        Entity shooter = getShooter();
+        if (shooter instanceof EntityLiving) {
+            Entity target = rayTrace.getEntity();
+            if (canGrief || (target instanceof EntityLiving && !(target instanceof EntityArmorStand))) {
+                target.damageEntity(DamageSource.indirectMobAttack(this, (EntityLiving) shooter).setProjectile(), world.purpurConfig.phantomFlameDamage);
+                if (world.purpurConfig.phantomFlameFireTime > 0) {
+                    target.setOnFire(world.purpurConfig.phantomFlameFireTime);
+                }
+            }
+        }
+        die();
+    }
+
+    protected void onHit(MovingObjectPositionBlock rayTrace) {
+        if (canGrief) {
+            IBlockData iblockdata = world.getType(rayTrace.getBlockPosition());
+            iblockdata.a(world, iblockdata, rayTrace, this);
+        }
+        die();
+    }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 2af3bee540ee25ea32a7548fd0d64dea16a0a268..5471422d23d261a06c67f9374cae933430ec03a0 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1174,4 +1174,26 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
         return getHandle().isTicking();
     }
     // Paper end
+
+    // Purpur start
+    @Override
+    public org.bukkit.entity.Player getRider() {
+        return hasRider() ? (org.bukkit.entity.Player) getHandle().getRider().getBukkitEntity() : null;
+    }
+
+    @Override
+    public boolean hasRider() {
+        return getHandle().hasRider();
+    }
+
+    @Override
+    public boolean isRidable() {
+        return getHandle().isRidable();
+    }
+
+    @Override
+    public boolean isRidableInWater() {
+        return getHandle().isRidableInWater();
+    }
+    // Purpur end
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 82e36ba35c0bd4d5b3e13281bad1a0664c25c6e0..c4d0082bb0d310ad16de36df03dd99225c5cadb0 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -528,6 +528,18 @@ public class CraftEventFactory {
         }
         craftServer.getPluginManager().callEvent(event);
 
+        // Purpur start
+        switch (action) {
+            case LEFT_CLICK_BLOCK:
+            case LEFT_CLICK_AIR:
+                who.processClick(EnumHand.MAIN_HAND);
+                break;
+            case RIGHT_CLICK_BLOCK:
+            case RIGHT_CLICK_AIR:
+                who.processClick(EnumHand.OFF_HAND);
+        }
+        // Purpur end
+
         return event;
     }
 
@@ -928,6 +940,7 @@ public class CraftEventFactory {
                     damageCause = DamageCause.ENTITY_EXPLOSION;
                 }
                 event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions);
+                damager.processClick(EnumHand.MAIN_HAND); // Purpur
             }
             event.setCancelled(cancelled);
 
@@ -1012,6 +1025,7 @@ public class CraftEventFactory {
             if (!event.isCancelled()) {
                 event.getEntity().setLastDamageCause(event);
             }
+            damager.getHandle().processClick(EnumHand.MAIN_HAND); // Purpur
             return event;
         }
 
@@ -1061,6 +1075,7 @@ public class CraftEventFactory {
         EntityDamageEvent event;
         if (damager != null) {
             event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions);
+            damager.processClick(EnumHand.MAIN_HAND); // Purpur
         } else {
             event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions);
         }
