--- a/net/minecraft/world/entity/monster/Slime.java
+++ b/net/minecraft/world/entity/monster/Slime.java
@@ -41,8 +41,22 @@
 import net.minecraft.world.level.WorldGenLevel;
 import net.minecraft.world.level.levelgen.WorldgenRandom;
 import net.minecraft.world.phys.Vec3;
+// Paper start
+import com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent;
+import com.destroystokyo.paper.event.entity.SlimeSwimEvent;
+import com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent;
+import com.destroystokyo.paper.event.entity.SlimeWanderEvent;
+// Paper end
+// CraftBukkit start
+import java.util.ArrayList;
+import java.util.List;
+import org.bukkit.craftbukkit.v1_19_R1.event.CraftEventFactory;
+import org.bukkit.event.entity.EntityTransformEvent;
+import org.bukkit.event.entity.SlimeSplitEvent;
+// CraftBukkit end
 
 public class Slime extends Mob implements Enemy {
+
     private static final EntityDataAccessor<Integer> ID_SIZE = SynchedEntityData.defineId(Slime.class, EntityDataSerializers.INT);
     public static final int MIN_SIZE = 1;
     public static final int MAX_SIZE = 127;
@@ -62,8 +76,8 @@
         this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this));
         this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this));
         this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this));
-        this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (livingEntity) -> {
-            return Math.abs(livingEntity.getY() - this.getY()) <= 4.0D;
+        this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> {
+            return Math.abs(entityliving.getY() - this.getY()) <= 4.0D;
         }));
         this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
     }
@@ -71,32 +85,34 @@
     @Override
     protected void defineSynchedData() {
         super.defineSynchedData();
-        this.entityData.define(ID_SIZE, 1);
+        this.entityData.define(Slime.ID_SIZE, 1);
     }
 
     @VisibleForTesting
     public void setSize(int size, boolean heal) {
-        int i = Mth.clamp(size, 1, 127);
-        this.entityData.set(ID_SIZE, i);
+        int j = Mth.clamp(size, (int) 1, (int) 127);
+
+        this.entityData.set(Slime.ID_SIZE, j);
         this.reapplyPosition();
         this.refreshDimensions();
-        this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)(i * i));
-        this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double)(0.2F + 0.1F * (float)i));
-        this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double)i);
+        this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j));
+        this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j));
+        this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j);
         if (heal) {
             this.setHealth(this.getMaxHealth());
         }
 
-        this.xpReward = i;
+        this.xpReward = j;
     }
 
     public int getSize() {
-        return this.entityData.get(ID_SIZE);
+        return (Integer) this.entityData.get(Slime.ID_SIZE);
     }
 
     @Override
     public void addAdditionalSaveData(CompoundTag nbt) {
         super.addAdditionalSaveData(nbt);
+        nbt.putBoolean("Paper.canWander", this.canWander); // Paper
         nbt.putInt("Size", this.getSize() - 1);
         nbt.putBoolean("wasOnGround", this.wasOnGround);
     }
@@ -105,6 +121,11 @@
     public void readAdditionalSaveData(CompoundTag nbt) {
         this.setSize(nbt.getInt("Size") + 1, false);
         super.readAdditionalSaveData(nbt);
+        // Paper start - check exists before loading or this will be loaded as false
+        if (nbt.contains("Paper.canWander")) {
+            this.canWander = nbt.getBoolean("Paper.canWander");
+        }
+        // Paper end
         this.wasOnGround = nbt.getBoolean("wasOnGround");
     }
 
@@ -129,12 +150,13 @@
         if (this.onGround && !this.wasOnGround) {
             int i = this.getSize();
 
-            for(int j = 0; j < i * 8; ++j) {
-                float f = this.random.nextFloat() * ((float)Math.PI * 2F);
-                float g = this.random.nextFloat() * 0.5F + 0.5F;
-                float h = Mth.sin(f) * (float)i * 0.5F * g;
-                float k = Mth.cos(f) * (float)i * 0.5F * g;
-                this.level.addParticle(this.getParticleType(), this.getX() + (double)h, this.getY(), this.getZ() + (double)k, 0.0D, 0.0D, 0.0D);
+            for (int j = 0; j < i * 8; ++j) {
+                float f = this.random.nextFloat() * 6.2831855F;
+                float f1 = this.random.nextFloat() * 0.5F + 0.5F;
+                float f2 = Mth.sin(f) * (float) i * 0.5F * f1;
+                float f3 = Mth.cos(f) * (float) i * 0.5F * f1;
+
+                this.level.addParticle(this.getParticleType(), this.getX() + (double) f2, this.getY(), this.getZ() + (double) f3, 0.0D, 0.0D, 0.0D);
             }
 
             this.playSound(this.getSquishSound(), this.getSoundVolume(), ((this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F) / 0.8F);
@@ -157,16 +179,17 @@
 
     @Override
     public void refreshDimensions() {
-        double d = this.getX();
-        double e = this.getY();
-        double f = this.getZ();
+        double d0 = this.getX();
+        double d1 = this.getY();
+        double d2 = this.getZ();
+
         super.refreshDimensions();
-        this.setPos(d, e, f);
+        this.setPos(d0, d1, d2);
     }
 
     @Override
     public void onSyncedDataUpdated(EntityDataAccessor<?> data) {
-        if (ID_SIZE.equals(data)) {
+        if (Slime.ID_SIZE.equals(data)) {
             this.refreshDimensions();
             this.setYRot(this.yHeadRot);
             this.yBodyRot = this.yHeadRot;
@@ -180,34 +203,60 @@
 
     @Override
     public EntityType<? extends Slime> getType() {
-        return super.getType();
+        return (EntityType<? extends Slime>) super.getType(); // CraftBukkit - decompile error
     }
 
     @Override
     public void remove(Entity.RemovalReason reason) {
         int i = this.getSize();
+
         if (!this.level.isClientSide && i > 1 && this.isDeadOrDying()) {
-            Component component = this.getCustomName();
-            boolean bl = this.isNoAi();
-            float f = (float)i / 4.0F;
+            Component ichatbasecomponent = this.getCustomName();
+            boolean flag = this.isNoAi();
+            float f = (float) i / 4.0F;
             int j = i / 2;
             int k = 2 + this.random.nextInt(3);
 
-            for(int l = 0; l < k; ++l) {
-                float g = ((float)(l % 2) - 0.5F) * f;
-                float h = ((float)(l / 2) - 0.5F) * f;
-                Slime slime = this.getType().create(this.level);
+            // CraftBukkit start
+            SlimeSplitEvent event = new SlimeSplitEvent((org.bukkit.entity.Slime) this.getBukkitEntity(), k);
+            this.level.getCraftServer().getPluginManager().callEvent(event);
+
+            if (!event.isCancelled() && event.getCount() > 0) {
+                k = event.getCount();
+            } else {
+                super.remove(reason);
+                return;
+            }
+            List<LivingEntity> slimes = new ArrayList<>(j);
+            // CraftBukkit end
+
+            for (int l = 0; l < k; ++l) {
+                float f1 = ((float) (l % 2) - 0.5F) * f;
+                float f2 = ((float) (l / 2) - 0.5F) * f;
+                Slime entityslime = (Slime) this.getType().create(this.level);
+
                 if (this.isPersistenceRequired()) {
-                    slime.setPersistenceRequired();
+                    entityslime.setPersistenceRequired();
                 }
 
-                slime.setCustomName(component);
-                slime.setNoAi(bl);
-                slime.setInvulnerable(this.isInvulnerable());
-                slime.setSize(j, true);
-                slime.moveTo(this.getX() + (double)g, this.getY() + 0.5D, this.getZ() + (double)h, this.random.nextFloat() * 360.0F, 0.0F);
-                this.level.addFreshEntity(slime);
+                entityslime.aware = this.aware; // Paper
+                entityslime.setCustomName(ichatbasecomponent);
+                entityslime.setNoAi(flag);
+                entityslime.setInvulnerable(this.isInvulnerable());
+                entityslime.setSize(j, true);
+                entityslime.moveTo(this.getX() + (double) f1, this.getY() + 0.5D, this.getZ() + (double) f2, this.random.nextFloat() * 360.0F, 0.0F);
+                slimes.add(entityslime); // CraftBukkit
             }
+
+            // CraftBukkit start
+            if (CraftEventFactory.callEntityTransformEvent(this, slimes, EntityTransformEvent.TransformReason.SPLIT).isCancelled()) {
+                super.remove(reason);
+                return;
+            }
+            for (LivingEntity living : slimes) {
+                this.level.addFreshEntity(living, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); // CraftBukkit - SpawnReason
+            }
+            // CraftBukkit end
         }
 
         super.remove(reason);
@@ -217,7 +266,7 @@
     public void push(Entity entity) {
         super.push(entity);
         if (entity instanceof IronGolem && this.isDealsDamage()) {
-            this.dealDamage((LivingEntity)entity);
+            this.dealDamage((LivingEntity) entity);
         }
 
     }
@@ -233,7 +282,8 @@
     protected void dealDamage(LivingEntity target) {
         if (this.isAlive()) {
             int i = this.getSize();
-            if (this.distanceToSqr(target) < 0.6D * (double)i * 0.6D * (double)i && this.hasLineOfSight(target) && target.hurt(DamageSource.mobAttack(this), this.getAttackDamage())) {
+
+            if (this.distanceToSqr((Entity) target) < 0.6D * (double) i * 0.6D * (double) i && this.hasLineOfSight(target) && target.hurt(DamageSource.mobAttack(this), this.getAttackDamage())) {
                 this.playSound(SoundEvents.SLIME_ATTACK, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                 this.doEnchantDamageEffects(this, target);
             }
@@ -251,7 +301,7 @@
     }
 
     protected float getAttackDamage() {
-        return (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE);
+        return (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE);
     }
 
     @Override
@@ -270,7 +320,16 @@
 
     public static boolean checkSlimeSpawnRules(EntityType<Slime> type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
         if (world.getDifficulty() != Difficulty.PEACEFUL) {
-            if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) {
+            // Paper start - fix slime spawners; Fixes MC-50647
+            if (spawnReason == MobSpawnType.SPAWNER) {
+                return random.nextInt(10) == 0;
+            }
+            // Paper end
+            // Paper start - Replace rules for Height in Swamp Biome
+            final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum;
+            final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum;
+            if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) {
+                // Paper end
                 return checkMobSpawnRules(type, world, spawnReason, pos, random);
             }
 
@@ -278,9 +337,13 @@
                 return false;
             }
 
-            ChunkPos chunkPos = new ChunkPos(pos);
-            boolean bl = WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel)world).getSeed(), 987234911L).nextInt(10) == 0;
-            if (random.nextInt(10) == 0 && bl && pos.getY() < 40) {
+            ChunkPos chunkcoordintpair = new ChunkPos(pos);
+            boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper
+
+            // Paper start - Replace rules for Height in Slime Chunks
+            final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum;
+            if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) {
+            // Paper end
                 return checkMobSpawnRules(type, world, spawnReason, pos, random);
             }
         }
@@ -290,7 +353,7 @@
 
     @Override
     public float getSoundVolume() {
-        return 0.4F * (float)this.getSize();
+        return 0.4F * (float) this.getSize();
     }
 
     @Override
@@ -304,27 +367,31 @@
 
     @Override
     protected void jumpFromGround() {
-        Vec3 vec3 = this.getDeltaMovement();
-        this.setDeltaMovement(vec3.x, (double)this.getJumpPower(), vec3.z);
+        Vec3 vec3d = this.getDeltaMovement();
+
+        this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z);
         this.hasImpulse = true;
     }
 
     @Nullable
     @Override
     public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
-        RandomSource randomSource = world.getRandom();
-        int i = randomSource.nextInt(3);
-        if (i < 2 && randomSource.nextFloat() < 0.5F * difficulty.getSpecialMultiplier()) {
+        RandomSource randomsource = world.getRandom();
+        int i = randomsource.nextInt(3);
+
+        if (i < 2 && randomsource.nextFloat() < 0.5F * difficulty.getSpecialMultiplier()) {
             ++i;
         }
 
         int j = 1 << i;
+
         this.setSize(j, true);
         return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
     }
 
     float getSoundPitch() {
         float f = this.isTiny() ? 1.4F : 0.8F;
+
         return ((this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F) * f;
     }
 
@@ -334,63 +401,68 @@
 
     @Override
     public EntityDimensions getDimensions(Pose pose) {
-        return super.getDimensions(pose).scale(0.255F * (float)this.getSize());
+        return super.getDimensions(pose).scale(0.255F * (float) this.getSize());
     }
 
-    static class SlimeAttackGoal extends Goal {
+    private static class SlimeMoveControl extends MoveControl {
+
+        private float yRot;
+        private int jumpDelay;
         private final Slime slime;
-        private int growTiredTimer;
+        private boolean isAggressive;
 
-        public SlimeAttackGoal(Slime slime) {
+        public SlimeMoveControl(Slime slime) {
+            super(slime);
             this.slime = slime;
-            this.setFlags(EnumSet.of(Goal.Flag.LOOK));
+            this.yRot = 180.0F * slime.getYRot() / 3.1415927F;
         }
 
-        @Override
-        public boolean canUse() {
-            LivingEntity livingEntity = this.slime.getTarget();
-            if (livingEntity == null) {
-                return false;
-            } else {
-                return !this.slime.canAttack(livingEntity) ? false : this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
-            }
+        public void setDirection(float targetYaw, boolean jumpOften) {
+            this.yRot = targetYaw;
+            this.isAggressive = jumpOften;
         }
 
-        @Override
-        public void start() {
-            this.growTiredTimer = reducedTickDelay(300);
-            super.start();
+        public void setWantedMovement(double speed) {
+            this.speedModifier = speed;
+            this.operation = MoveControl.Operation.MOVE_TO;
         }
 
         @Override
-        public boolean canContinueToUse() {
-            LivingEntity livingEntity = this.slime.getTarget();
-            if (livingEntity == null) {
-                return false;
-            } else if (!this.slime.canAttack(livingEntity)) {
-                return false;
+        public void tick() {
+            this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F));
+            this.mob.yHeadRot = this.mob.getYRot();
+            this.mob.yBodyRot = this.mob.getYRot();
+            if (this.operation != MoveControl.Operation.MOVE_TO) {
+                this.mob.setZza(0.0F);
             } else {
-                return --this.growTiredTimer > 0;
-            }
-        }
+                this.operation = MoveControl.Operation.WAIT;
+                if (this.mob.isOnGround()) {
+                    this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
+                    if (this.jumpDelay-- <= 0) {
+                        this.jumpDelay = this.slime.getJumpDelay();
+                        if (this.isAggressive) {
+                            this.jumpDelay /= 3;
+                        }
 
-        @Override
-        public boolean requiresUpdateEveryTick() {
-            return true;
-        }
+                        this.slime.getJumpControl().jump();
+                        if (this.slime.doPlayJumpSound()) {
+                            this.slime.playSound(this.slime.getJumpSound(), this.slime.getSoundVolume(), this.slime.getSoundPitch());
+                        }
+                    } else {
+                        this.slime.xxa = 0.0F;
+                        this.slime.zza = 0.0F;
+                        this.mob.setSpeed(0.0F);
+                    }
+                } else {
+                    this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
+                }
 
-        @Override
-        public void tick() {
-            LivingEntity livingEntity = this.slime.getTarget();
-            if (livingEntity != null) {
-                this.slime.lookAt(livingEntity, 10.0F, 10.0F);
             }
-
-            ((Slime.SlimeMoveControl)this.slime.getMoveControl()).setDirection(this.slime.getYRot(), this.slime.isDealsDamage());
         }
     }
 
-    static class SlimeFloatGoal extends Goal {
+    private static class SlimeFloatGoal extends Goal {
+
         private final Slime slime;
 
         public SlimeFloatGoal(Slime slime) {
@@ -401,7 +473,7 @@
 
         @Override
         public boolean canUse() {
-            return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
+            return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new SlimeSwimEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper
         }
 
         @Override
@@ -415,86 +487,82 @@
                 this.slime.getJumpControl().jump();
             }
 
-            ((Slime.SlimeMoveControl)this.slime.getMoveControl()).setWantedMovement(1.2D);
+            ((Slime.SlimeMoveControl) this.slime.getMoveControl()).setWantedMovement(1.2D);
         }
     }
 
-    static class SlimeKeepOnJumpingGoal extends Goal {
+    private static class SlimeAttackGoal extends Goal {
+
         private final Slime slime;
+        private int growTiredTimer;
 
-        public SlimeKeepOnJumpingGoal(Slime slime) {
+        public SlimeAttackGoal(Slime slime) {
             this.slime = slime;
-            this.setFlags(EnumSet.of(Goal.Flag.JUMP, Goal.Flag.MOVE));
+            this.setFlags(EnumSet.of(Goal.Flag.LOOK));
         }
 
         @Override
         public boolean canUse() {
-            return !this.slime.isPassenger();
+            LivingEntity entityliving = this.slime.getTarget();
+
+            // Paper start
+            if (entityliving == null || !entityliving.isAlive()) {
+                return false;
+            }
+            if (!this.slime.canAttack(entityliving)) {
+                return false;
+            }
+            return this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent();
+            // Paper end
         }
 
         @Override
-        public void tick() {
-            ((Slime.SlimeMoveControl)this.slime.getMoveControl()).setWantedMovement(1.0D);
+        public void start() {
+            this.growTiredTimer = reducedTickDelay(300);
+            super.start();
         }
-    }
-
-    static class SlimeMoveControl extends MoveControl {
-        private float yRot;
-        private int jumpDelay;
-        private final Slime slime;
-        private boolean isAggressive;
 
-        public SlimeMoveControl(Slime slime) {
-            super(slime);
-            this.slime = slime;
-            this.yRot = 180.0F * slime.getYRot() / (float)Math.PI;
-        }
+        @Override
+        public boolean canContinueToUse() {
+            LivingEntity entityliving = this.slime.getTarget();
 
-        public void setDirection(float targetYaw, boolean jumpOften) {
-            this.yRot = targetYaw;
-            this.isAggressive = jumpOften;
+            // Paper start
+            if (entityliving == null || !entityliving.isAlive()) {
+                return false;
+            }
+            if (!this.slime.canAttack(entityliving)) {
+                return false;
+            }
+            return --this.growTiredTimer > 0 && this.slime.canWander && new SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent();
+            // Paper end
         }
 
-        public void setWantedMovement(double speed) {
-            this.speedModifier = speed;
-            this.operation = MoveControl.Operation.MOVE_TO;
+        @Override
+        public boolean requiresUpdateEveryTick() {
+            return true;
         }
 
         @Override
         public void tick() {
-            this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F));
-            this.mob.yHeadRot = this.mob.getYRot();
-            this.mob.yBodyRot = this.mob.getYRot();
-            if (this.operation != MoveControl.Operation.MOVE_TO) {
-                this.mob.setZza(0.0F);
-            } else {
-                this.operation = MoveControl.Operation.WAIT;
-                if (this.mob.isOnGround()) {
-                    this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
-                    if (this.jumpDelay-- <= 0) {
-                        this.jumpDelay = this.slime.getJumpDelay();
-                        if (this.isAggressive) {
-                            this.jumpDelay /= 3;
-                        }
-
-                        this.slime.getJumpControl().jump();
-                        if (this.slime.doPlayJumpSound()) {
-                            this.slime.playSound(this.slime.getJumpSound(), this.slime.getSoundVolume(), this.slime.getSoundPitch());
-                        }
-                    } else {
-                        this.slime.xxa = 0.0F;
-                        this.slime.zza = 0.0F;
-                        this.mob.setSpeed(0.0F);
-                    }
-                } else {
-                    this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
-                }
+            LivingEntity entityliving = this.slime.getTarget();
 
+            if (entityliving != null) {
+                this.slime.lookAt(entityliving, 10.0F, 10.0F);
             }
+
+            ((Slime.SlimeMoveControl) this.slime.getMoveControl()).setDirection(this.slime.getYRot(), this.slime.isDealsDamage());
+        }
+
+        // Paper start - clear timer and target when goal resets
+        public void stop() {
+            this.growTiredTimer = 0;
+            this.slime.setTarget(null);
         }
+        // Paper end
     }
 
-    static class SlimeRandomDirectionGoal extends Goal {
+    private static class SlimeRandomDirectionGoal extends Goal {
+
         private final Slime slime;
         private float chosenDegrees;
         private int nextRandomizeTime;
@@ -506,17 +574,53 @@
 
         @Override
         public boolean canUse() {
-            return this.slime.getTarget() == null && (this.slime.onGround || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
+            return this.slime.getTarget() == null && (this.slime.onGround || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander; // Paper - add canWander
         }
 
         @Override
         public void tick() {
             if (--this.nextRandomizeTime <= 0) {
                 this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60));
-                this.chosenDegrees = (float)this.slime.getRandom().nextInt(360);
+                this.chosenDegrees = (float) this.slime.getRandom().nextInt(360);
+                // Paper start
+                SlimeChangeDirectionEvent event = new SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), this.chosenDegrees);
+                if (!this.slime.canWander || !event.callEvent()) return;
+                this.chosenDegrees = event.getNewYaw();
+                // Paper end
             }
 
-            ((Slime.SlimeMoveControl)this.slime.getMoveControl()).setDirection(this.chosenDegrees, false);
+            ((Slime.SlimeMoveControl) this.slime.getMoveControl()).setDirection(this.chosenDegrees, false);
         }
     }
+
+    private static class SlimeKeepOnJumpingGoal extends Goal {
+
+        private final Slime slime;
+
+        public SlimeKeepOnJumpingGoal(Slime slime) {
+            this.slime = slime;
+            this.setFlags(EnumSet.of(Goal.Flag.JUMP, Goal.Flag.MOVE));
+        }
+
+        @Override
+        public boolean canUse() {
+            return !this.slime.isPassenger() && this.slime.canWander && new SlimeWanderEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper
+        }
+
+        @Override
+        public void tick() {
+            ((Slime.SlimeMoveControl) this.slime.getMoveControl()).setWantedMovement(1.0D);
+        }
+    }
+
+    // Paper start
+    private boolean canWander = true;
+    public boolean canWander() {
+        return canWander;
+    }
+
+    public void setWander(boolean canWander) {
+        this.canWander = canWander;
+    }
+    // Paper end
 }
