diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java
index f0056c498..d2caf6866 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java
@@ -278,6 +278,18 @@ public abstract class ScreenContainerAbstract extends Screen
 	@Override
 	public void mouseClicked(final int mx, final int my, int buttonNum)
 	{
+		if(!mc.thePlayer.getGamemode().consumeBlocks()) {
+			// Duplicate items with middle mouse button
+			if(buttonNum == 2) {
+				Slot slot = inventorySlots.getSlot(getSlotId(mx, my));
+				if (slot.getItemStack() == null) {
+					mc.playerController.handleInventoryMouseClick(inventorySlots.containerId, InventoryAction.SORT, new int[] {slot.index, 64}, mc.thePlayer);
+				} else {
+					mc.playerController.handleInventoryMouseClick(inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, new int[] {slot.index, 64}, mc.thePlayer);
+				}
+				return;
+			}
+		}
 		if (checkKeybind(getSlotId(mx, my), -1, buttonNum)) return;
 		super.mouseClicked(mx, my, buttonNum);
 		if(mc.gameSettings.enableItemDragging.value) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererDeer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererDeer.java
index c7309ae4b..93a18ff0f 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererDeer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererDeer.java
@@ -9,6 +9,10 @@ import org.useless.dragonfly.models.entity.BoneTransform;
 import org.useless.dragonfly.models.entity.StaticEntityModel;
 
 public class MobRendererDeer extends MobRenderer<MobDeer> {
+    private static final float MIN_NECK_ROT_X_SNIFF = 1.0f;
+    private static final float MAX_NECK_ROT_X_SNIFF = 1.6f;
+    private static final float MIN_HEAD_ROT_X_SNIFF = -0.4f;
+    private static final float MAX_HEAD_ROT_X_SNIFF = -0.9f;
 
     public MobRendererDeer(final float shadowSize) {
         super(shadowSize);
@@ -38,8 +42,37 @@ public class MobRendererDeer extends MobRenderer<MobDeer> {
         final float limbSwing = getLimbSwing(entity, partialTick);
         final float limbYaw = getLimbYaw(entity, partialTick);
 
-        neck.rotX = headPitch;
-        head.rotY = headYaw;
+        if (entity.brainState instanceof MobDeer.SniffingBrainState) {
+            final @NotNull MobDeer.SniffingBrainState brainState = (MobDeer.SniffingBrainState) entity.brainState;
+
+            final float neckRotX = MathHelper.lerp(MIN_NECK_ROT_X_SNIFF, MAX_NECK_ROT_X_SNIFF, brainState.leanPercentage);
+            final float headRotX = MathHelper.lerp(MIN_HEAD_ROT_X_SNIFF, MAX_HEAD_ROT_X_SNIFF, brainState.leanPercentage);
+
+            if (brainState.timer <= brainState.intoSniffTime) {
+                final float offsetTimer = brainState.timer + partialTick;
+                final float pctg = MathHelper.smootherstep(MathHelper.clamp(offsetTimer / (float) brainState.intoSniffTime, 0.0f, 1.0f), 0.0f, 1.0f);
+
+                neck.rotX = MathHelper.lerp(headPitch, neckRotX, pctg);
+                head.rotX = MathHelper.lerp(0.0f, headRotX, pctg);
+                head.rotY = MathHelper.lerp(headYaw, 0.0f, pctg);
+            } else if (brainState.timer <= brainState.intoSniffTime + brainState.sniffTime) {
+                neck.rotX = neckRotX;
+                head.rotX = headRotX;
+                head.rotY = 0.0f;
+            } else {
+                final float offsetTimer = (brainState.timer - (brainState.intoSniffTime + brainState.sniffTime)) + partialTick;
+                final float pctg = MathHelper.smootherstep(MathHelper.clamp(offsetTimer / (float) brainState.outOfSniffTime, 0.0f, 1.0f), 0.0f, 1.0f);
+
+                neck.rotX = MathHelper.lerp(neckRotX, headPitch, pctg);
+                head.rotX = MathHelper.lerp(headRotX, 0.0f, pctg);
+                head.rotY = MathHelper.lerp(0.0f, headYaw, pctg);
+            }
+        } else {
+            neck.rotX = headPitch;
+            head.rotX = 0.0f;
+            head.rotY = headYaw;
+        }
+
         leg0.rotX = MathHelper.cos(limbSwing * 0.6662F) * 1.4F * limbYaw;
         leg1.rotX = MathHelper.cos(limbSwing * 0.6662F + MathHelper.PI) * 1.4F * limbYaw;
         leg2.rotX = MathHelper.cos(limbSwing * 0.6662F + MathHelper.PI) * 1.4F * limbYaw;
diff --git a/game/client/src/main/resources/assets/minecraft/models/entity/deer/deer.geo.json b/game/client/src/main/resources/assets/minecraft/models/entity/deer/deer.geo.json
index fac15ebfb..4cd3d4743 100644
--- a/game/client/src/main/resources/assets/minecraft/models/entity/deer/deer.geo.json
+++ b/game/client/src/main/resources/assets/minecraft/models/entity/deer/deer.geo.json
@@ -22,7 +22,7 @@
 				{
 					"name": "head",
 					"parent": "neck",
-					"pivot": [0, 16.9, -6.5],
+					"pivot": [0, 18.9, -6.5],
 					"cubes": [
 						{"origin": [-2, 16.9, -11.5], "size": [4, 4, 7], "uv": [42, 0]},
 						{"origin": [0.5, 20.9, -5.5], "size": [8, 5, 0], "uv": [4, 4], "mirror": true},
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/deer/0.png b/game/client/src/main/resources/assets/minecraft/textures/entity/deer/0.png
index f2e7eb4b7..2ca62b0c7 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/entity/deer/0.png and b/game/client/src/main/resources/assets/minecraft/textures/entity/deer/0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/deer/1.png b/game/client/src/main/resources/assets/minecraft/textures/entity/deer/1.png
index 351d82664..84fa23d8c 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/entity/deer/1.png and b/game/client/src/main/resources/assets/minecraft/textures/entity/deer/1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/deer/2.png b/game/client/src/main/resources/assets/minecraft/textures/entity/deer/2.png
index 7bf4e6366..5ec16168d 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/entity/deer/2.png and b/game/client/src/main/resources/assets/minecraft/textures/entity/deer/2.png differ
diff --git a/game/core/src/main/java/net/minecraft/core/entity/MobPathfinder.java b/game/core/src/main/java/net/minecraft/core/entity/MobPathfinder.java
index bb75ed9c4..c17c11533 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/MobPathfinder.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/MobPathfinder.java
@@ -18,6 +18,7 @@ public abstract class MobPathfinder extends Mob
     protected @Nullable Entity target;
     protected @Nullable Entity closestFireflyEntity;
     protected boolean hasAttacked;
+    protected boolean doRandomWalk = true;
 
     public MobPathfinder(@NotNull final World world)
     {
@@ -58,12 +59,12 @@ public abstract class MobPathfinder extends Mob
                 attackBlockedEntity(target, distanceToEntity);
             }
         }
-        if(!hasAttacked && target != null && (pathToEntity == null || random.nextInt(20) == 0))
+        if(this.doRandomWalk && (!hasAttacked && target != null && (pathToEntity == null || random.nextInt(20) == 0)))
         {
             //pathfind to entity
             pathToEntity = world.getPathToEntity(this, target, sightRadius);
         } else
-        if(!hasAttacked && closestFireflyEntity == null && (pathToEntity == null && random.nextInt(80) == 0 || random.nextInt(80) == 0))
+        if(this.doRandomWalk && (!hasAttacked && closestFireflyEntity == null && (pathToEntity == null && random.nextInt(80) == 0 || random.nextInt(80) == 0)))
         {
             // random roaming
             roamRandomPath();
@@ -72,7 +73,7 @@ public abstract class MobPathfinder extends Mob
         boolean inWater = isInWater();
         boolean inLava = isInLava();
         xRot = 0.0F;
-        if(pathToEntity == null || random.nextInt(100) == 0)
+        if(pathToEntity == null || (this.doRandomWalk && random.nextInt(100) == 0))
         {
             super.updateAI();
             pathToEntity = null;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobDeer.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobDeer.java
index f99cb2d20..e8cb5e1f4 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobDeer.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobDeer.java
@@ -20,31 +20,23 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class MobDeer
-        extends MobAnimal
-        implements AgedMob
-{
+    extends MobAnimal
+    implements AgedMob {
     private final @NotNull MobAge age;
-    private @Nullable Entity closestThreat;
-
-    private int runawayTimer = 0;
-    private int counter = 0;
-    private boolean isRunningAway = false;
-
-    private final int MAX_RUNAWAY_TIME = 200;
-    private final int COUNTER_TIME = 30;
+    public @NotNull BrainState brainState = new RoamingBrainState(this);
 
     public List<WeightedRandomLootObject> burningMobDrops = new ArrayList<>();
 
-    public MobDeer(World world)
-    {
+    public MobDeer(final World world) {
         super(world);
-        setTextureIdentifier("minecraft", "deer");
-        setSize(0.9F, 1.3F);
-        moveSpeed = 0.8F;
-        mobDrops.add(new WeightedRandomLootObject(Items.FOOD_VENISON_RAW.getDefaultStack(), 1, 2));
-        burningMobDrops.add(new WeightedRandomLootObject(Items.FOOD_VENISON_COOKED.getDefaultStack(), 1, 2));
+        this.setTextureIdentifier("minecraft", "deer");
+        this.setSize(0.9F, 1.3F);
+        this.moveSpeed = 0.8F;
+        this.mobDrops.add(new WeightedRandomLootObject(Items.FOOD_VENISON_RAW.getDefaultStack(), 1, 2));
+        this.burningMobDrops.add(new WeightedRandomLootObject(Items.FOOD_VENISON_COOKED.getDefaultStack(), 1, 2));
         this.age = MobAge.newRandom(this, MobAge.YEAR_LENGTH_DAYS * 5, 14, MobAge.YEAR_LENGTH_DAYS * 4);
         this.jumpHeight = 0.5D;
+        this.brainState.init();
     }
 
     @Override
@@ -54,112 +46,72 @@ public class MobDeer
     }
 
     @Override
-    public boolean hurt(@Nullable Entity attacker, int damage, DamageType type)
-    {
+    public boolean hurt(@Nullable final Entity attacker, final int damage, final DamageType type) {
         if (attacker != null) {
-            beginRunaway();
+            this.brainState = new FleeingBrainState(this, attacker);
+            this.brainState.init();
         }
 
         return super.hurt(attacker, damage, type);
     }
 
-    private void beginRunaway() {
-        runawayTimer = MAX_RUNAWAY_TIME;
-        counter = COUNTER_TIME;
-        moveSpeed = 2.5F;
-        findRandomPathToRunTo();
-    }
-
-    private void stopRunaway() {
-        isRunningAway = false;
-        this.counter = 0;
-        moveSpeed = 0.4F;
-    }
-
     @Override
-    protected void updateAI()
-    {
+    protected void updateAI() {
         super.updateAI();
 
-        if(runawayTimer <= 0) {
-            findThreatNearby();
-            if (this.closestThreat != null)
-                beginRunaway();
-            else if(isRunningAway)
-                stopRunaway();
-        } else {
-            //this.isJumping = true;
-            isRunningAway = true;
-            if(counter >= 0) {
-                counter--;
-            } else {
-                findRandomPathToRunTo();
-                counter = COUNTER_TIME;
-            }
-            runawayTimer--;
-
+        final @Nullable BrainState newBrainState = this.brainState.tick();
+        if (newBrainState != null && newBrainState != this.brainState) {
+            this.brainState = newBrainState;
+            this.brainState.init();
         }
-
-        if(isRunningAway && onGround && !isJumping && isWalking) {
-            // hop along the ground when scared
-                yd = 0.35d;
-                float f = yRot * 0.01745329F;
-                xd -= MathHelper.sin(f) * 0.25F;
-                zd += MathHelper.cos(f) * 0.25F;
-
-        }
-
     }
 
     @Override
-    protected List<WeightedRandomLootObject> getMobDrops()
-    {
-        if(remainingFireTicks > 0)
-        {
-            return burningMobDrops;
-        } else
-        {
-            return mobDrops;
+    protected List<WeightedRandomLootObject> getMobDrops() {
+        if (this.remainingFireTicks > 0) {
+            return this.burningMobDrops;
+        } else {
+            return this.mobDrops;
         }
     }
 
-    protected void findThreatNearby() {
-        int dist = 10;
-        Player player = world.getClosestPlayer(x, y, z, dist);
-        if(player != null && !player.isSneaking()){
-            this.closestThreat = player;
-            return;
+    protected @Nullable Entity findThreatNearby() {
+        final int dist = 10;
+        final Player player = this.world.getClosestPlayer(this.x, this.y, this.z, dist);
+        if (player != null && player.getGamemode().areMobsHostile()) {
+            return player;
         }
 
-        if(player == null){
-            List<MobWolf> nearbyWolves = world.getEntitiesWithinAABB(MobWolf.class, AABB.fromPool(x, y, z, x + 1.0D, y + 1.0D, z + 1.0D).grow(8D, 4D, 8D));
+        if (player == null) {
+            final List<MobWolf> nearbyWolves = this.world.getEntitiesWithinAABB(MobWolf.class, AABB.fromPool(this.x, this.y, this.z, this.x + 1.0D, this.y + 1.0D, this.z + 1.0D).grow(8D, 4D, 8D));
             if (!nearbyWolves.isEmpty()) {
-                this.closestThreat = nearbyWolves.get(random.nextInt(nearbyWolves.size()));
-                return;
+                @NotNull MobWolf closestWolf = nearbyWolves.get(0);
+                double closestWolfDist = this.distanceToSqr(closestWolf);
+                for (int i = 1; i < nearbyWolves.size(); i++) {
+                    final @NotNull MobWolf iWolf = nearbyWolves.get(i);
+                    final double iWolfDist = this.distanceToSqr(iWolf);
+                    if (iWolfDist < closestWolfDist) {
+                        closestWolf = iWolf;
+                        closestWolfDist = iWolfDist;
+                    }
+                }
+                return closestWolf;
             }
         }
-        this.closestThreat = null;
-    }
 
-    private void findRandomPathToRunTo() {
-        if (this.closestThreat != null)
-            getPathAwayFromPoint(this.x + random.nextInt(8)-4, this.z + random.nextInt(8)-4, closestThreat.x, closestThreat.z);
-        else
-            getPathAwayFromPoint(this.x, this.z, this.x + random.nextInt(8)-4, this.z + random.nextInt(8)-4);
+        return null;
     }
 
-    protected void getPathAwayFromPoint(double x, double z, double pX, double pZ) {
-        int scale = 10;
-        double dX = x - pX;
-        double dZ = z - pZ;
-
-        double sqrt = Math.sqrt(dX * dX + dZ * dZ);
+    private void findRandomPathToRunTo(final @NotNull Entity threat) {
+        this.getPathAwayFromPoint(this.x + this.random.nextInt(8) - 4, this.z + this.random.nextInt(8) - 4, threat.x, threat.z);
+    }
 
-        double nDX = dX / sqrt;
-        double nDZ = dZ / sqrt;
+    protected void getPathAwayFromPoint(final double x, final double z, final double pX, final double pZ) {
+        final int scale = 10;
+        final @NotNull Vec3 vec = Vec3.fromPool(x - pX, 0.0, z - pZ).normalize().scale(scale);
 
-        Vec3 safePoint = Vec3.fromPool(x + nDX * scale, y, z + nDZ * scale);
-        pathToEntity = world.getEntityPathToXYZ(this, MathHelper.floor(safePoint.x), MathHelper.floor(y), MathHelper.floor(safePoint.z), 16F);
+        final Vec3 safePoint = Vec3.fromPool(this.x + vec.x, this.y + vec.y, this.z + vec.z);
+        this.pathToEntity = this.world.getEntityPathToXYZ(this, MathHelper.floor(safePoint.x), MathHelper.floor(safePoint.y), MathHelper.floor(safePoint.z), 16F);
     }
 
     @Override
@@ -168,49 +120,323 @@ public class MobDeer
     }
 
     @Override
-    public void addAdditionalSaveData(@NotNull CompoundTag tag)
-    {
+    public void addAdditionalSaveData(@NotNull final CompoundTag tag) {
         super.addAdditionalSaveData(tag);
-        tag.putShort("RunawayTimer", (short) runawayTimer);
         this.age.writeTag(tag);
     }
 
     @Override
-    public int getMaxSpawnedInChunk()
-    {
-        if(world.getSeasonManager().getCurrentSeason() == Seasons.OVERWORLD_WINTER)
+    public int getMaxSpawnedInChunk() {
+        if (this.world.getSeasonManager().getCurrentSeason() == Seasons.OVERWORLD_WINTER) {
             return 4;
-        return 0;
+        } else {
+            return 0;
+        }
     }
 
     @Override
-    public void readAdditionalSaveData(@NotNull CompoundTag tag)
-    {
+    public void readAdditionalSaveData(@NotNull final CompoundTag tag) {
         super.readAdditionalSaveData(tag);
-        this.runawayTimer = tag.getShort("RunawayTimer");
         this.age.readTag(tag);
     }
 
     //Todo: change with new sounds
     @Override
-    public String getLivingSound()
-    {
+    public String getLivingSound() {
         return "mob.cow";
     }
+
     @Override
-    protected String getHurtSound()
-    {
+    protected String getHurtSound() {
         return "mob.cowhurt";
     }
+
     @Override
-    protected String getDeathSound()
-    {
+    protected String getDeathSound() {
         return "mob.cowhurt";
     }
 
     @Override
-    protected float getSoundVolume()
-    {
+    protected float getSoundVolume() {
         return 0.4F;
     }
+
+    private static final float SPOOK_DISTANCE = 10.0f;
+    private static final float FLEE_DISTANCE = 7.0f;
+    private static final float SNEAK_FLEE_DISTANCE = 3.0f;
+
+    public static abstract class BrainState {
+        protected final @NotNull MobDeer deer;
+
+        public BrainState(final @NotNull MobDeer deer) {
+            this.deer = deer;
+        }
+
+        public abstract void init();
+
+        public abstract @Nullable BrainState tick();
+    }
+
+    public static class TransitionalBrainState extends BrainState {
+        private final @NotNull BrainState oldBrainState;
+        private final @NotNull BrainState newBrainState;
+
+        private long ticksToSwitch;
+
+        public TransitionalBrainState(final @NotNull MobDeer deer, final @NotNull BrainState newBrainState, final long ticksToSwitch) {
+            super(deer);
+
+            this.oldBrainState = deer.brainState;
+            this.newBrainState = newBrainState;
+            this.ticksToSwitch = ticksToSwitch;
+        }
+
+        @Override
+        public void init() {
+            // Do nothing
+        }
+
+        @Override
+        public @Nullable BrainState tick() {
+            if (this.ticksToSwitch > 0) {
+                this.ticksToSwitch--;
+                return this.oldBrainState.tick();
+            } else {
+                return this.newBrainState;
+            }
+        }
+    }
+
+    public static class RoamingBrainState extends BrainState {
+        public RoamingBrainState(final @NotNull MobDeer deer) {
+            super(deer);
+        }
+
+        @Override
+        public void init() {
+            this.deer.doRandomWalk = true;
+            this.deer.moveSpeed = 0.4f;
+        }
+
+        @Override
+        public @Nullable BrainState tick() {
+            // Find closest threat and spook or flee depending on proximity
+            final @Nullable Entity closestThreat = this.deer.findThreatNearby();
+            if (closestThreat != null) {
+                final double distSqr = this.deer.distanceToSqr(closestThreat);
+                if ((!closestThreat.isSneaking() && distSqr < FLEE_DISTANCE * FLEE_DISTANCE) || (distSqr < SNEAK_FLEE_DISTANCE * SNEAK_FLEE_DISTANCE)) {
+                    return new FleeingBrainState(this.deer, closestThreat);
+                } else if (distSqr < SPOOK_DISTANCE * SPOOK_DISTANCE) {
+                    return new SpookedBrainState(this.deer, closestThreat);
+                }
+            }
+
+            // Random sniff
+            if (this.deer.random.nextInt(200) == 0) {
+                return new SniffingBrainState(this.deer);
+            }
+
+            return null;
+        }
+    }
+
+    public static class SniffingBrainState extends BrainState {
+        public static final int MIN_INTO_SNIFF_TIME = 5;
+        public static final int MAX_INTO_SNIFF_TIME = 15;
+        public static final int MIN_SNIFF_TIME = 40;
+        public static final int MAX_SNIFF_TIME = 80;
+        public static final int MIN_OUT_OF_SNIFF_TIME = 5;
+        public static final int MAX_OUT_OF_SNIFF_TIME = 10;
+
+        public final int intoSniffTime;
+        public final int sniffTime;
+        public final int outOfSniffTime;
+        public final float leanPercentage;
+
+        public int timer = 0;
+
+        public @Nullable BrainState nextBrainState = null;
+
+        public SniffingBrainState(final @NotNull MobDeer deer) {
+            super(deer);
+
+            this.intoSniffTime = MIN_INTO_SNIFF_TIME + this.deer.random.nextInt(MAX_INTO_SNIFF_TIME - MIN_INTO_SNIFF_TIME);
+            this.sniffTime = MIN_SNIFF_TIME + this.deer.random.nextInt(MAX_SNIFF_TIME - MIN_SNIFF_TIME);
+            this.outOfSniffTime = MIN_OUT_OF_SNIFF_TIME + this.deer.random.nextInt(MAX_OUT_OF_SNIFF_TIME - MIN_OUT_OF_SNIFF_TIME);
+            this.leanPercentage = this.deer.random.nextFloat();
+        }
+
+        @Override
+        public void init() {
+            this.deer.doRandomWalk = true;
+            this.deer.moveSpeed = 0.25f;
+        }
+
+        @Override
+        public @Nullable BrainState tick() {
+            // Find closest threat and prepare spook or flee depending on proximity
+            final @Nullable Entity closestThreat = this.deer.findThreatNearby();
+            if (closestThreat != null) {
+                final double distSqr = this.deer.distanceToSqr(closestThreat);
+                final @Nullable BrainState nextBrainState;
+                if ((!closestThreat.isSneaking() && distSqr < FLEE_DISTANCE * FLEE_DISTANCE) || (distSqr < SNEAK_FLEE_DISTANCE * SNEAK_FLEE_DISTANCE)) {
+                    nextBrainState = new FleeingBrainState(this.deer, closestThreat);
+                } else if (distSqr < SPOOK_DISTANCE * SPOOK_DISTANCE) {
+                    nextBrainState = new SpookedBrainState(this.deer, closestThreat);
+                } else {
+                    nextBrainState = null;
+                }
+
+                if (nextBrainState != null) {
+                    if (this.timer < this.intoSniffTime + this.sniffTime) {
+                        this.timer = this.intoSniffTime + this.sniffTime;
+                    }
+
+                    this.nextBrainState = nextBrainState;
+                }
+            }
+
+            // Check for next brain state
+            if (this.timer >= this.intoSniffTime + this.sniffTime + this.outOfSniffTime) {
+                if (this.nextBrainState != null) {
+                    return this.nextBrainState;
+                } else {
+                    return new RoamingBrainState(this.deer);
+                }
+            } else {
+                this.timer++;
+            }
+
+            return null;
+        }
+    }
+
+    public static class SpookedBrainState extends BrainState {
+        public @NotNull Entity threat;
+        public double threatInitialDistSqr;
+
+        public SpookedBrainState(final @NotNull MobDeer deer, final @NotNull Entity threat) {
+            super(deer);
+
+            this.threat = threat;
+            this.threatInitialDistSqr = this.deer.distanceToSqr(this.threat);
+        }
+
+        @Override
+        public void init() {
+            this.deer.pathToEntity = null;
+            this.deer.doRandomWalk = false;
+            this.syncToOtherDeer();
+        }
+
+        private void syncToOtherDeer() {
+            final @NotNull List<@NotNull MobDeer> nearbyDeer = this.deer.world.getEntitiesWithinAABB(MobDeer.class, AABB.fromPool(this.deer.x, this.deer.y, this.deer.z, this.deer.x + 1.0D, this.deer.y + 1.0D, this.deer.z + 1.0D).grow(8D, 4D, 8D));
+            for (final @NotNull MobDeer deer : nearbyDeer) {
+                if (!(deer.brainState instanceof SpookedBrainState)) {
+                    deer.brainState = new TransitionalBrainState(deer, new SpookedBrainState(deer, this.threat), this.deer.random.nextInt(10));
+                }
+            }
+        }
+
+        @Override
+        public @Nullable BrainState tick() {
+            // Find closest threat and re-spook or flee if new, depending on proximity
+            final @Nullable Entity closestThreat = this.deer.findThreatNearby();
+            if (closestThreat != null && closestThreat != this.threat) {
+                final double distSqr = this.deer.distanceToSqr(closestThreat);
+                if ((!closestThreat.isSneaking() && distSqr < FLEE_DISTANCE * FLEE_DISTANCE) || (distSqr < SNEAK_FLEE_DISTANCE * SNEAK_FLEE_DISTANCE)) {
+                    return new FleeingBrainState(this.deer, closestThreat);
+                } else if (distSqr < SPOOK_DISTANCE * SPOOK_DISTANCE) {
+                    return new SpookedBrainState(this.deer, closestThreat);
+                }
+            }
+
+            // If the threat has gotten closer, flee; if the threat has gone out of range, continue roaming
+            final double threatCurrentDistSqr = this.deer.distanceToSqr(this.threat);
+            if ((!this.threat.isSneaking() && threatCurrentDistSqr < this.threatInitialDistSqr - 2.0) || (!this.threat.isSneaking() && threatCurrentDistSqr < FLEE_DISTANCE * FLEE_DISTANCE) || (threatCurrentDistSqr < SNEAK_FLEE_DISTANCE * SNEAK_FLEE_DISTANCE)) {
+                return new FleeingBrainState(this.deer, this.threat);
+            } else if (threatCurrentDistSqr > this.threatInitialDistSqr + 4.0) {
+                return new RoamingBrainState(this.deer);
+            }
+
+            // Stare at the threat
+            this.deer.lookAt(this.threat, 30.0f, 30.0f);
+            return null;
+        }
+    }
+
+    public static class FleeingBrainState extends BrainState {
+        private static final int MAX_FLEE_TIME = 200;
+        private static final int NEW_PATH_TIME = 30;
+
+        public int fleeTimer = MAX_FLEE_TIME;
+        public int newPathTimer = NEW_PATH_TIME;
+        public @NotNull Entity threat;
+
+        public FleeingBrainState(final @NotNull MobDeer deer, final @NotNull Entity threat) {
+            super(deer);
+
+            this.threat = threat;
+        }
+
+        @Override
+        public void init() {
+            this.deer.doRandomWalk = false;
+            this.deer.moveSpeed = 2.5f;
+            this.syncToOtherDeer();
+        }
+
+        private void syncToOtherDeer() {
+            final @NotNull List<@NotNull MobDeer> nearbyDeer = this.deer.world.getEntitiesWithinAABB(MobDeer.class, AABB.fromPool(this.deer.x, this.deer.y, this.deer.z, this.deer.x + 1.0D, this.deer.y + 1.0D, this.deer.z + 1.0D).grow(8D, 4D, 8D));
+            for (final @NotNull MobDeer deer : nearbyDeer) {
+                if (!(deer.brainState instanceof FleeingBrainState)) {
+                    deer.brainState = new TransitionalBrainState(deer, new FleeingBrainState(deer, this.threat), this.deer.random.nextInt(5));
+                }
+            }
+        }
+
+        @Override
+        public @Nullable BrainState tick() {
+            double threatDistSqr = this.deer.distanceToSqr(this.threat);
+
+            // Update the threat we're fleeing from
+            final @Nullable Entity closestThreat = this.deer.findThreatNearby();
+            if (closestThreat != null && closestThreat != this.threat) {
+                final double distSqr = this.deer.distanceToSqr(closestThreat);
+                if (distSqr < threatDistSqr) {
+                    this.threat = closestThreat;
+                    threatDistSqr = distSqr;
+                }
+            }
+
+            // If the threat is out of range, we can start to stop fleeing; otherwise, we keep fleeing
+            if (threatDistSqr > SPOOK_DISTANCE * SPOOK_DISTANCE) {
+                this.fleeTimer--;
+                if (this.fleeTimer <= 0) {
+                    return new RoamingBrainState(this.deer);
+                }
+            } else {
+                this.fleeTimer = MAX_FLEE_TIME;
+            }
+
+            // Find a new pathing target when the current one expires
+            if (this.newPathTimer <= 0) {
+                this.newPathTimer = NEW_PATH_TIME;
+                this.deer.findRandomPathToRunTo(this.threat);
+            } else {
+                this.newPathTimer--;
+            }
+
+            // Deer jumping behavior
+            if (this.deer.onGround && !this.deer.isJumping && this.deer.isWalking) {
+                // hop along the ground when scared
+                this.deer.yd = 0.35d;
+                final float f = this.deer.yRot * 0.01745329F;
+                this.deer.xd -= MathHelper.sin(f) * 0.25F;
+                this.deer.zd += MathHelper.cos(f) * 0.25F;
+            }
+
+            return null;
+        }
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java b/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java
index 5729f3a75..0d4bec236 100644
--- a/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java
+++ b/game/core/src/main/java/net/minecraft/core/util/helper/MathHelper.java
@@ -158,6 +158,18 @@ public class MathHelper
         return value - Math.floor(value/n) * n;
     }
 
+    public static float smoothstep(final float value, final float min, final float max) {
+        final float v = clamp((value - min) / (max - min), 0.0f, 1.0f);
+
+        return v * v * (3.0f - 2.0f * v);
+    }
+
+    public static float smootherstep(final float value, final float min, final float max) {
+        final float v = clamp((value - min) / (max - min), 0.0f, 1.0f);
+
+        return v * v * v * (v * (6.0f * v - 15.0f) + 10.0f);
+    }
+
     private static final float[] SIN_TABLE;
 
     public static final float PI = (float)Math.PI;
