Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / server / src / entity / mobs / mod.rs

use std::collections::VecDeque;
use std::f32::consts::PI;

use common::entity::mob::MobKind;
use common::entity::{Entity, EntityWorld};
use common::item::{Item, ItemStack};
use common::math::{self, floor_f64};
use common::physics::Aabb;
use common::world::shape::{self, BlockView};
use common::world::{Block, ChunkPos};
use glam::DVec3;

mod ghast;
mod special;
mod wolf;

use super::{Kind, ServerEntity, path};
use crate::level::{DroppedItem, Level};

#[derive(Clone, Debug)]
pub struct Mob {
    pub kind: MobKind,
    pub target: Option<i32>,
    pub color: u8,
    pub sheared: bool,
    pub saddled: bool,
    pub powered: bool,
    pub fuse: i32,
    pub igniting: bool,
    pub egg_timer: i32,
    pub dropped: bool,
    pub special: special::Special,
    path: VecDeque<DVec3>,
    looking: Option<i32>,
    look_time: i32,
}

impl Mob {
    pub fn new(kind: MobKind) -> Self {
        Self {
            kind,
            target: None,
            color: 0,
            sheared: false,
            saddled: false,
            powered: false,
            fuse: 0,
            igniting: false,
            egg_timer: 6000,
            dropped: false,
            special: special::Special::new(kind),
            path: VecDeque::new(),
            looking: None,
            look_time: 0,
        }
    }
}

pub fn visible(level: &Level, from: &Entity, to: &Entity) -> bool {
    shape::ray_trace(
        level,
        from.pos + DVec3::Y * f64::from(from.eye_height()),
        to.pos + DVec3::Y * f64::from(to.eye_height()),
        false,
        false,
    )
    .is_none()
}

fn turn(from: f32, to: f32, limit: f32) -> f32 {
    from + ((to - from + 180.0).rem_euclid(360.0) - 180.0).clamp(-limit, limit)
}

/// The yaw that looks along `delta`.
fn yaw_towards(delta: DVec3) -> f32 {
    (delta.z.atan2(delta.x) * 180.0 / f64::from(PI)) as f32 - 90.0
}

fn face(entity: &mut Entity, target: &Entity, yaw_speed: f32, pitch_speed: f32) {
    let delta = target.pos - entity.pos;
    let dy = entity.pos.y + f64::from(entity.eye_height())
        - target.pos.y
        - f64::from(target.eye_height());
    let horizontal = f64::from((delta.x * delta.x + delta.z * delta.z).sqrt() as f32);
    let pitch = (-dy.atan2(horizontal) * 180.0 / f64::from(PI)) as f32;
    entity.yaw = turn(entity.yaw, yaw_towards(delta), yaw_speed);
    entity.pitch = -turn(entity.pitch, pitch, pitch_speed);
}

/// The area in which wolves take part in their pack's fights.
fn pack_area(pos: DVec3) -> Aabb {
    Aabb::new(pos, pos + DVec3::ONE).inflate(DVec3::new(16.0, 4.0, 16.0))
}

impl Level {
    pub fn mob_entity(&mut self, kind: MobKind, position: DVec3, yaw: f32) -> ServerEntity {
        let mut stored = self.entities.create(Kind::Mob(Mob::new(kind)));
        let entity = &mut stored.entity;
        entity.set_location_and_angles(position, yaw, 0.0);
        if let Kind::Mob(mob) = &mut stored.kind {
            mob.special.initialize(entity);
            if kind == MobKind::Chicken {
                mob.egg_timer += entity.rand.next_int(6000);
            }
        }
        stored
    }

    pub fn nearest_player(&self, position: DVec3, range: f64) -> Option<&Entity> {
        self.entities
            .iter()
            .filter(|e| {
                matches!(e.kind, Kind::Player { .. })
                    && e.entity.living.as_ref().is_some_and(|l| l.health > 0)
            })
            .map(|e| &e.entity)
            .filter(|e| range < 0.0 || self.perceived_distance(e, position) < range * range)
            .min_by(|a, b| {
                self.perceived_distance(a, position)
                    .total_cmp(&self.perceived_distance(b, position))
            })
    }

    /// The player as original singleplayer mobs see `EntityPlayerSP`: positioned at eye level,
    /// with its eyes 0.12 above that.
    fn perceived_distance(&self, target: &Entity, position: DVec3) -> f64 {
        let lift = if self.rules.dedicated_spawn.is_none() && target.is_player() {
            f64::from(1.62_f32)
        } else {
            0.0
        };
        (target.pos + DVec3::Y * lift).distance_squared(position)
    }

    pub(crate) fn perceived(&self, mut target: Entity) -> Entity {
        if self.rules.dedicated_spawn.is_none() && target.is_player() {
            target.pos.y += f64::from(1.62_f32);
            target.eye = Some(0.12);
        }
        target
    }

    pub(crate) fn brightness_at(&self, x: i32, y: i32, z: i32) -> f32 {
        self.brightness[usize::from(self.daylight(x, y, z))]
    }

    fn player_name(&self, id: i32) -> Option<&str> {
        match &self.entities.get(id)?.kind {
            Kind::Player { name, .. } => Some(name),
            _ => None,
        }
    }

    /// Applies `apply` to the mobs in `area` that `accept` picks, in load order.
    fn for_mobs_in(
        &mut self,
        area: &Aabb,
        accept: impl Fn(&Mob) -> bool,
        mut apply: impl FnMut(&mut Entity, &mut Mob),
    ) {
        let ids: Vec<_> = self
            .entities
            .iter()
            .filter(|e| {
                e.entity.bb.intersects(area) && matches!(&e.kind, Kind::Mob(m) if accept(m))
            })
            .map(|e| e.entity.id)
            .collect();
        for id in ids {
            if let Some(ServerEntity {
                entity,
                kind: Kind::Mob(mob),
                ..
            }) = self.entities.get_mut(id)
            {
                apply(entity, mob);
            }
        }
    }

    pub(super) fn tick_mob(&mut self, entity: &mut Entity, mob: &mut Mob) {
        if mob.kind.hostile() && self.difficulty == 0 && mob.kind != MobKind::Slime {
            entity.dead = true;
            return;
        }
        let was_grounded = entity.on_ground;
        self.pig_zombie_update(entity, mob);
        entity.living_update_with_sound(self, |entity, level| {
            if let Some(wolf) = mob.wolf() {
                let sound = if wolf.angry {
                    "mob.wolf.growl"
                } else if entity.rand.next_int(3) == 0 {
                    if !wolf.owner.is_empty()
                        && entity.living.as_ref().is_some_and(|l| l.health < 10)
                    {
                        "mob.wolf.whine"
                    } else {
                        "mob.wolf.panting"
                    }
                } else {
                    "mob.wolf.bark"
                };
                let pitch = entity.sound_pitch();
                level.play_sound(entity, sound, 0.4, pitch);
            } else {
                level.living_sound(entity);
            }
        });
        self.react_to_damage(entity, mob);
        let alive = entity.living.as_ref().is_some_and(|l| l.health > 0);
        if alive {
            // `getEntityBrightness`: sampled two thirds up the body
            let height = entity.bb.max.y - entity.bb.min.y;
            let brightness = self.brightness_at(
                floor_f64(entity.pos.x),
                floor_f64(entity.pos.y - f64::from(entity.y_offset) + height * 0.66),
                floor_f64(entity.pos.z),
            );
            if mob.kind.hostile() && brightness > 0.5 {
                entity.living.as_mut().unwrap().entity_age += 2;
            }
            let [x, y, z] = entity.pos.to_array().map(floor_f64);
            if matches!(mob.kind, MobKind::Zombie | MobKind::Skeleton)
                && self.is_daytime()
                && brightness > 0.5
                && y >= self.height(x, z)
                && entity.rand.next_float() * 30.0 < (brightness - 0.4) * 2.0
            {
                entity.fire = 300;
            }
            match mob.kind {
                MobKind::Slime => self.slime_ai(entity, mob),
                MobKind::Squid => self.squid_ai(entity, mob),
                MobKind::Ghast => self.ghast_ai(entity, mob),
                MobKind::Wolf => self.wolf_ai(entity, mob, brightness),
                _ => {
                    self.mob_ai(entity, mob, brightness);
                }
            }
        } else if !mob.dropped {
            mob.dropped = true;
            self.mob_drops(entity, mob);
        }
        if mob.kind == MobKind::Squid {
            self.squid_move(entity, mob);
        } else if mob.kind == MobKind::Ghast {
            self.ghast_move(entity);
        } else {
            entity.living_movement(self);
            entity.update_body_rotation();
        }
        self.react_to_damage(entity, mob);
        self.slime_update(entity, mob, was_grounded);
        if mob.kind == MobKind::Wolf {
            self.wolf_update(entity, mob);
        }
        if let special::Special::Chicken(flap) = &mut mob.special {
            flap.advance(entity.on_ground);
            if !entity.on_ground && entity.motion.y < 0.0 {
                entity.motion.y *= 0.6;
            }
            if alive {
                mob.egg_timer -= 1;
                if mob.egg_timer <= 0 {
                    let pitch = entity.sound_pitch();
                    self.play_sound(entity, "mob.chickenplop", 1.0, pitch);
                    self.mob_drop(entity, ItemStack::one(Item::EGG));
                    mob.egg_timer = entity.rand.next_int(6000) + 6000;
                }
            }
        }
        if mob.kind == MobKind::Creeper && mob.target.is_none() {
            mob.igniting = false;
            mob.fuse = (mob.fuse - 1).max(0);
        }
        if alive {
            self.push_mob(entity);
        }
    }

    /// `attackEntityFrom` without an attacker: wolves stand up even when unhurt, and monsters
    /// (`EntityMob`) forget their target.
    fn react_to_damage(&mut self, entity: &Entity, mob: &mut Mob) {
        let mut accepted = None;
        self.unattributed_damage.retain(|&(id, hurt)| {
            if id == entity.id {
                accepted = Some(accepted.unwrap_or(false) || hurt);
            }
            id != entity.id
        });
        let Some(accepted) = accepted else {
            return;
        };
        if let Some(wolf) = mob.wolf_mut() {
            wolf.sitting = false;
        }
        if accepted && mob.kind.is_monster() {
            mob.target = None;
        }
    }

    /// `EntityCreature.updatePlayerActionState`; returns `hasAttacked`.
    fn mob_ai(&mut self, entity: &mut Entity, mob: &mut Mob, brightness: f32) -> bool {
        // `hasAttacked` starts as `isMovementCeased`: sitting or shaking wolves stay put
        let mut attacking = mob.wolf().is_some_and(wolf::Wolf::ceased);
        if mob.target.is_none() {
            if (mob.kind.hostile() || mob.wolf().is_some_and(|w| w.angry))
                && (mob.kind != MobKind::PigZombie || mob.special.anger() != 0)
                && (mob.kind != MobKind::Spider || brightness < 0.5)
                && let Some(target) = self.nearest_player(entity.pos, 16.0)
                && (matches!(mob.kind, MobKind::Spider | MobKind::Wolf)
                    || visible(self, entity, &self.perceived(target.clone())))
            {
                mob.target = Some(target.id);
                mob.path = path::find(self, entity, target.pos, 16.0);
            }
        } else if let Some(target) = mob
            .target
            .and_then(|id| self.entities.get(id))
            .map(|e| self.perceived(e.entity.clone()))
            .filter(|e| !e.dead && e.living.as_ref().is_some_and(|l| l.health > 0))
        {
            if visible(self, entity, &target) {
                attacking |= self.mob_attack(entity, mob, &target, brightness);
            } else if mob.kind == MobKind::Creeper {
                mob.igniting = false;
                mob.fuse = (mob.fuse - 1).max(0);
            }
        } else {
            mob.target = None;
        }
        if !attacking
            && mob.target.is_some()
            && (mob.path.is_empty() || entity.rand.next_int(20) == 0)
        {
            if let Some(target) = mob.target.and_then(|id| self.entities.get(id)) {
                mob.path = path::find(self, entity, target.entity.pos, 16.0);
            }
        } else if !attacking
            && ((mob.path.is_empty() && entity.rand.next_int(80) == 0)
                || entity.rand.next_int(80) == 0)
        {
            let mut best = None;
            let mut weight = -99_999.0;
            for _ in 0..10 {
                let x = floor_f64(entity.pos.x + f64::from(entity.rand.next_int(13)) - 6.0);
                let y = floor_f64(entity.pos.y + f64::from(entity.rand.next_int(7)) - 3.0);
                let z = floor_f64(entity.pos.z + f64::from(entity.rand.next_int(13)) - 6.0);
                let next = if mob.kind == MobKind::Giant {
                    self.brightness_at(x, y, z) - 0.5
                } else if mob.kind.hostile() {
                    0.5 - self.brightness_at(x, y, z)
                } else if BlockView::block(self, x, y - 1, z) == Block::GRASS {
                    10.0
                } else {
                    self.brightness_at(x, y, z) - 0.5
                };
                if next > weight {
                    weight = next;
                    best = Some(DVec3::new(
                        f64::from(x) + 0.5,
                        f64::from(y),
                        f64::from(z) + 0.5,
                    ));
                }
            }
            if let Some(best) = best {
                mob.path = path::find(self, entity, best, 10.0);
            }
        }
        entity.pitch = 0.0;
        if !mob.path.is_empty() && entity.rand.next_int(100) != 0 {
            let distance = f64::from(entity.width * 2.0).powi(2);
            while mob.path.front().is_some_and(|p| {
                (p.x - entity.pos.x).powi(2) + (p.z - entity.pos.z).powi(2) < distance
            }) {
                mob.path.pop_front();
            }
            let living = entity.living.as_mut().unwrap();
            living.jumping = false;
            if let Some(next) = mob.path.front() {
                entity.yaw = turn(entity.yaw, yaw_towards(*next - entity.pos), 30.0);
                living.move_forward = living.move_speed;
                living.jumping = next.y > f64::from(floor_f64(entity.bb.min.y + 0.5));
                if attacking && let Some(target) = mob.target.and_then(|id| self.entities.get(id)) {
                    let previous_yaw = entity.yaw;
                    entity.yaw = yaw_towards(target.entity.pos - entity.pos);
                    let angle = (previous_yaw - entity.yaw + 90.0).to_radians();
                    living.move_strafing = -math::sin(angle) * living.move_forward;
                    living.move_forward *= math::cos(angle);
                }
            }
            if let Some(target) = mob.target.and_then(|id| self.entities.get(id)) {
                face(entity, &target.entity, 30.0, 30.0);
            }
            let swimming = entity.in_water || entity.handle_lava_movement(self);
            let jump = entity.rand.next_float() < 0.8 && swimming;
            entity.living.as_mut().unwrap().jumping |=
                (entity.collided_horizontally && mob.path.is_empty()) || jump;
        } else {
            mob.path.clear();
            self.idle_mob(entity, mob);
        }
        attacking
    }

    fn despawn_mob(&self, entity: &mut Entity) {
        let living = entity.living.as_mut().unwrap();
        living.entity_age += 1;
        if let Some(player) = self.nearest_player(entity.pos, -1.0) {
            let distance = player.pos.distance_squared(entity.pos);
            if distance > 16384.0 {
                entity.dead = true;
            }
            if living.entity_age > 600 && entity.rand.next_int(800) == 0 {
                if distance < 1024.0 {
                    living.entity_age = 0;
                } else {
                    entity.dead = true;
                }
            }
        }
    }

    fn idle_mob(&self, entity: &mut Entity, mob: &mut Mob) {
        if !mob.wolf().is_some_and(|w| !w.owner.is_empty()) {
            self.despawn_mob(entity);
        }
        let living = entity.living.as_mut().unwrap();
        living.move_strafing = 0.0;
        living.move_forward = 0.0;
        if entity.rand.next_float() < 0.02 {
            if let Some(player) = self.nearest_player(entity.pos, 8.0) {
                mob.looking = Some(player.id);
                mob.look_time = 10 + entity.rand.next_int(20);
            } else {
                living.random_yaw_velocity = (entity.rand.next_float() - 0.5) * 20.0;
            }
        }
        if let Some(target) = mob.looking.and_then(|id| self.entities.get(id)) {
            // `getVerticalFaceSpeed`
            let vertical = if mob.wolf().is_some_and(|w| w.sitting) {
                20.0
            } else {
                40.0
            };
            face(entity, &target.entity, 10.0, vertical);
            mob.look_time -= 1;
            if mob.look_time < 0
                || target.entity.dead
                || entity.pos.distance_squared(target.entity.pos) > 64.0
            {
                mob.looking = None;
            }
        } else {
            let living = entity.living.as_mut().unwrap();
            if entity.rand.next_float() < 0.05 {
                living.random_yaw_velocity = (entity.rand.next_float() - 0.5) * 20.0;
            }
            entity.yaw += living.random_yaw_velocity;
            entity.pitch = 0.0;
        }
        if entity.in_water || entity.handle_lava_movement(self) {
            entity.living.as_mut().unwrap().jumping = entity.rand.next_float() < 0.8;
        }
    }

    fn mob_attack(
        &mut self,
        entity: &mut Entity,
        mob: &mut Mob,
        target: &Entity,
        brightness: f32,
    ) -> bool {
        let distance = entity.pos.distance(target.pos) as f32;
        if mob.kind == MobKind::Creeper {
            mob.igniting = distance < if mob.igniting { 7.0 } else { 3.0 };
            if mob.igniting {
                if mob.fuse == 0 {
                    self.play_sound(entity, "random.fuse", 1.0, 0.5);
                }
                mob.fuse += 1;
                if mob.fuse >= 30 {
                    let creeper = entity.clone();
                    self.explode(
                        entity.pos,
                        if mob.powered { 6.0 } else { 3.0 },
                        Some(&creeper),
                        false,
                    );
                    entity.dead = true;
                }
            } else {
                mob.fuse = (mob.fuse - 1).max(0);
            }
            return mob.igniting;
        }
        if mob.kind == MobKind::Skeleton && distance < 10.0 {
            if entity.living.as_ref().unwrap().attack_time == 0 {
                self.shoot_arrow(entity, Some(target));
                entity.living.as_mut().unwrap().attack_time = 30;
            }
            entity.yaw = yaw_towards(target.pos - entity.pos);
            return true;
        }
        if mob.kind == MobKind::Spider && brightness > 0.5 && entity.rand.next_int(100) == 0 {
            mob.target = None;
            return false;
        }
        // The pounce of spiders and wolves
        if matches!(mob.kind, MobKind::Spider | MobKind::Wolf)
            && distance > 2.0
            && distance < 6.0
            && entity.rand.next_int(10) == 0
        {
            let delta = (target.pos - entity.pos).with_y(0.0);
            let length = f64::from(delta.length() as f32);
            if entity.on_ground && (mob.kind == MobKind::Spider || length > 0.0) {
                let leap = delta / length * 0.5 * f64::from(0.8_f32);
                entity.motion =
                    (leap + entity.motion * f64::from(0.2_f32)).with_y(f64::from(0.4_f32));
            }
            return false;
        }
        let wolf = mob.kind == MobKind::Wolf;
        if (wolf || mob.kind.hostile() && entity.living.as_ref().unwrap().attack_time <= 0)
            && distance < if wolf { 1.5 } else { 2.0 }
            && target.bb.max.y > entity.bb.min.y
            && target.bb.min.y < entity.bb.max.y
        {
            entity.living.as_mut().unwrap().attack_time = 20;
            let damage = match mob.kind {
                MobKind::Giant => 50,
                MobKind::Zombie | MobKind::PigZombie => 5,
                MobKind::Wolf if mob.wolf().is_some_and(|w| !w.owner.is_empty()) => 4,
                _ => 2,
            };
            self.attack_entity(target.id, Some(entity), damage);
        }
        false
    }

    pub fn attack_entity(&mut self, id: i32, source: Option<&Entity>, amount: i32) -> bool {
        self.attack_living(id, source, amount, false)
    }

    /// `attack_entity`, or with `projectile` an arrow's hit, whose damage source is its owner.
    pub(super) fn attack_living(
        &mut self,
        id: i32,
        source: Option<&Entity>,
        amount: i32,
        projectile: bool,
    ) -> bool {
        if self
            .entities
            .get(id)
            .is_some_and(|e| matches!(e.kind, Kind::Painting { .. }) && !e.entity.dead)
        {
            if let Some(mut stored) = self.entities.remove(id) {
                self.drop_painting(&stored.entity);
                stored.entity.dead = true;
                self.mark_entity_chunk(stored.chunk());
            }
            return true;
        }
        let Some(mut stored) = self.entities.take_for_tick(id) else {
            return false;
        };
        let accepted = self.attack_stored(&mut stored, source, amount, projectile);
        self.entities.finish_tick(stored);
        accepted
    }

    /// `attackEntityFrom` of an entity taken out of the list for the attack.
    fn attack_stored(
        &mut self,
        stored: &mut ServerEntity,
        source: Option<&Entity>,
        mut amount: i32,
        projectile: bool,
    ) -> bool {
        let id = stored.entity.id;
        let player = matches!(stored.kind, Kind::Player { .. });
        // `EntityPlayerMP.attackEntityFrom`
        if stored.entity.spawn_immunity > 0
            || !self.rules.pvp && player && source.is_some_and(|s| self.player_name(s.id).is_some())
        {
            return false;
        }
        match &mut stored.kind {
            // `EntityItem.attackEntityFrom` takes the damage at once but reports none
            Kind::Item { health, .. } => {
                *health -= amount;
                if *health <= 0 {
                    stored.entity.dead = true;
                }
                return false;
            }
            // `Entity.attackEntityFrom` refuses damage
            Kind::Tnt { .. }
            | Kind::Falling { .. }
            | Kind::Arrow(_)
            | Kind::Hook(_)
            | Kind::Thrown(_) => return false,
            Kind::Cart(cart) => {
                let accepted = self.hurt_cart(&mut stored.entity, cart, amount);
                self.mark_entity_chunk(stored.chunk());
                return accepted;
            }
            Kind::Boat(boat) => return self.hurt_boat(&mut stored.entity, boat, amount),
            Kind::Fireball(fireball) => {
                let Some(source) = source else {
                    return false;
                };
                stored.entity.motion = source.look();
                fireball.acceleration = stored.entity.motion * 0.1;
                self.events.push(common::protocol::Packet::EntityVelocity {
                    entity: id,
                    velocity: stored.entity.motion.to_array().map(|v| (v * 8000.0) as i16),
                });
                return true;
            }
            Kind::Mob(mob) => {
                if let Some(wolf) = mob.wolf_mut() {
                    wolf.sitting = false;
                    if source.is_some_and(|s| s.mob.is_some()) && !projectile {
                        amount = (amount + 1) / 2;
                    }
                }
            }
            Kind::Player { name, .. } => {
                if let Some(source) = source {
                    self.alert_wolves(&stored.entity, name, source, true);
                }
            }
            Kind::Painting { .. } => {}
        }
        self.anger_pig_zombies(stored, source);
        let entity = &mut stored.entity;
        let resisted = entity
            .living
            .as_ref()
            .is_some_and(|l| entity.hearts_life > l.hearts_halves_life / 2);
        // `EntityPlayer.attackEntityFrom` scales the damage of monsters
        if player && source.is_some_and(|s| s.mob.is_some_and(MobKind::is_monster)) {
            amount = match self.difficulty {
                0 => 0,
                1 => amount / 3 + 1,
                3 => amount * 3 / 2,
                _ => amount,
            };
        }
        if amount <= 0 && player {
            return false;
        }
        let alive = entity.living.as_ref().is_some_and(|l| l.health > 0);
        if !self.damage_body(entity, amount) {
            return false;
        }
        let dead = entity.living.as_ref().is_some_and(|l| l.health <= 0);
        let player_source = source.filter(|s| s.is_player());
        if alive
            && dead
            && player
            && let Some(source) = player_source
        {
            self.add_stat(source.id, 2024, 1);
        }
        if let Kind::Mob(mob) = &mut stored.kind {
            if mob.kind.hostile() {
                mob.target = source.map(|e| e.id);
            }
            if dead && !mob.dropped {
                mob.dropped = true;
                if let Some(player) = player_source {
                    self.add_stat(player.id, 2023, 1);
                    if mob.kind.is_monster() {
                        self.achievement(player.id, 13);
                    }
                }
                self.mob_drops(entity, mob);
                if mob.kind == MobKind::Creeper
                    && source.is_some_and(|e| e.mob == Some(MobKind::Skeleton))
                {
                    let record = Item(2256 + entity.rand.next_int(2) as i16);
                    self.mob_drop(entity, ItemStack::one(record));
                }
            }
        }
        if !resisted
            && entity.living.is_some()
            && let Some(source) = source
        {
            let mut dx = source.pos.x - entity.pos.x;
            let mut dz = source.pos.z - entity.pos.z;
            while dx * dx + dz * dz < 0.0001 {
                dx = (entity.rand.next_double() - entity.rand.next_double()) * 0.01;
                dz = (entity.rand.next_double() - entity.rand.next_double()) * 0.01;
            }
            let length = f64::from((dx * dx + dz * dz).sqrt() as f32);
            entity.motion *= 0.5;
            entity.motion.x -= dx / length * f64::from(0.4_f32);
            entity.motion.z -= dz / length * f64::from(0.4_f32);
            entity.motion.y = (entity.motion.y + f64::from(0.4_f32)).min(f64::from(0.4_f32));
            if let Some(living) = &mut entity.living {
                living.attacked_at_yaw = (dz.atan2(dx) * 180.0 / f64::from(PI)) as f32 - entity.yaw;
            }
            self.events.push(common::protocol::Packet::EntityVelocity {
                entity: id,
                velocity: entity
                    .motion
                    .to_array()
                    .map(|v| (v.clamp(-3.9, 3.9) * 8000.0) as i16),
            });
        } else if let Some(living) = &mut entity.living {
            living.attacked_at_yaw = if resisted {
                0.0
            } else {
                (self.effect_rand.next_int(2) * 180) as f32
            };
        }
        if player && let Some(living) = &entity.living {
            self.events.push(common::protocol::Packet::HurtDirection {
                entity: id,
                yaw: living.attacked_at_yaw,
            });
        }
        self.mark_entity_chunk(stored.chunk());
        self.wolf_retaliate(stored, source);
        true
    }

    pub fn mob_drop(&mut self, entity: &Entity, stack: ItemStack) {
        self.dropped_items.push(DroppedItem {
            x: entity.pos.x,
            y: entity.pos.y,
            z: entity.pos.z,
            stack,
            motion: None,
            impulse: glam::DVec3::ZERO,
            pickup_delay: 10,
        });
    }

    fn mob_drops(&mut self, entity: &mut Entity, mob: &Mob) {
        if mob.kind == MobKind::Sheep {
            if !mob.sheared {
                self.mob_drop(
                    entity,
                    ItemStack::new(Item::from_block(Block::WOOL), 1, i32::from(mob.color)),
                );
            }
            return;
        }
        let item = match mob.kind {
            MobKind::Pig if entity.fire > 0 => Item::COOKED_PORKCHOP,
            MobKind::Pig => Item::RAW_PORKCHOP,
            MobKind::Cow => Item::LEATHER,
            MobKind::Chicken | MobKind::Zombie => Item::FEATHER,
            MobKind::Skeleton => Item::ARROW,
            MobKind::Spider => Item::STRING,
            MobKind::Creeper | MobKind::Ghast => Item::GUNPOWDER,
            MobKind::PigZombie => Item::COOKED_PORKCHOP,
            MobKind::Slime if mob.special.slime_size() == 1 => Item::SLIME_BALL,
            MobKind::Slime => return,
            MobKind::Squid => Item::DYE,
            MobKind::Wolf | MobKind::Monster | MobKind::Giant => return,
            MobKind::Sheep => unreachable!(),
        };
        for _ in 0..entity.rand.next_int(3) + i32::from(mob.kind == MobKind::Squid) {
            self.mob_drop(entity, ItemStack::one(item));
        }
        if mob.kind == MobKind::Skeleton {
            for _ in 0..entity.rand.next_int(3) {
                self.mob_drop(entity, ItemStack::one(Item::BONE));
            }
        }
    }

    /// The pushing loop of `EntityLiving.onLivingUpdate`: living entities and boats nearby.
    fn push_mob(&mut self, entity: &mut Entity) {
        let area = entity.bb.inflate(DVec3::new(0.2, 0.0, 0.2));
        let mut ids = Vec::new();
        for x in floor_f64(area.min.x - 2.0) >> 4..=floor_f64(area.max.x + 2.0) >> 4 {
            for z in floor_f64(area.min.z - 2.0) >> 4..=floor_f64(area.max.z + 2.0) >> 4 {
                ids.extend(
                    self.entities
                        .in_chunk(ChunkPos::new(x, z))
                        .filter(|e| {
                            (e.entity.living.is_some() || matches!(e.kind, Kind::Boat(_)))
                                && !e.entity.dead
                                && e.entity.bb.intersects(&area)
                        })
                        .map(|e| e.entity.id),
                );
            }
        }
        for id in ids {
            let Some(other) = self.entities.get_mut(id) else {
                continue;
            };
            let push =
                entity.collision_push(id, other.entity.pos, other.entity.collision_reduction);
            if let Some(push) = push {
                entity.motion += push;
                other.entity.motion -= push;
            }
        }
    }
}