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 / special.rs

use common::entity::{
    Entity, EntityWorld,
    mob::{Flap, MobKind, SpecialPose, SquidMotion},
};
use common::math::{cos, sin};
use glam::DVec3;

use super::{Mob, face, visible};
use crate::entity::{Kind, ServerEntity};
use crate::level::Level;

#[derive(Clone, Debug)]
pub enum Special {
    None,
    Wolf(super::wolf::Wolf),
    Ghast {
        waypoint: DVec3,
        course_delay: i32,
        aggro_delay: i32,
        attack: i32,
    },
    PigZombie {
        anger: i32,
        sound_delay: i32,
    },
    Slime {
        size: u8,
        jump_delay: i32,
        stretch: f32,
    },
    Squid(SquidMotion),
    Chicken(Flap),
}

impl Special {
    pub fn new(kind: MobKind) -> Self {
        match kind {
            MobKind::Wolf => Self::Wolf(Default::default()),
            MobKind::Ghast => Self::Ghast {
                waypoint: DVec3::ZERO,
                course_delay: 0,
                aggro_delay: 0,
                attack: 0,
            },
            MobKind::PigZombie => Self::PigZombie {
                anger: 0,
                sound_delay: 0,
            },
            MobKind::Slime => Self::Slime {
                size: 1,
                jump_delay: 0,
                stretch: 0.0,
            },
            MobKind::Squid => Self::Squid(Default::default()),
            MobKind::Chicken => Self::Chicken(Default::default()),
            _ => Self::None,
        }
    }

    pub fn initialize(&mut self, entity: &mut Entity) {
        match self {
            Self::PigZombie { .. } | Self::Ghast { .. } => entity.immune_to_fire = true,
            Self::Slime {
                size, jump_delay, ..
            } => {
                *size = 1 << entity.rand.next_int(3);
                *jump_delay = entity.rand.next_int(20) + 10;
                Self::resize_slime(entity, *size);
            }
            Self::Squid(state) => state.initialize(&mut entity.rand),
            Self::None | Self::Wolf(_) | Self::Chicken(_) => {}
        }
    }

    pub fn resize_slime(entity: &mut Entity, size: u8) {
        let width = 0.6 * f32::from(size);
        entity.set_size(width, width);
        entity.set_position(entity.pos);
        if let Some(living) = &mut entity.living {
            living.health = i32::from(size).pow(2);
        }
    }

    pub fn read(&mut self, entity: &mut Entity, nbt: &common::nbt::Compound) {
        self.initialize(entity);
        match self {
            Self::Slime { size, .. } => {
                *size = nbt.int("Size").saturating_add(1).clamp(1, 127) as u8;
                Self::resize_slime(entity, *size);
            }
            Self::PigZombie { anger, .. } => *anger = i32::from(nbt.short("Anger")),
            Self::Wolf(wolf) => {
                wolf.owner = nbt.string("Owner").into();
                wolf.angry = nbt.bool("Angry");
                wolf.sitting = nbt.bool("Sitting");
            }
            _ => {}
        }
    }

    pub fn write(&self, nbt: &mut common::nbt::Compound) {
        match self {
            Self::Slime { size, .. } => nbt.set_int("Size", i32::from(*size) - 1),
            Self::PigZombie { anger, .. } => nbt.set_short("Anger", *anger as i16),
            Self::Wolf(wolf) => {
                nbt.set_string("Owner", &wolf.owner);
                nbt.set_bool("Angry", wolf.angry);
                nbt.set_bool("Sitting", wolf.sitting);
            }
            _ => {}
        }
    }

    pub fn slime_size(&self) -> u8 {
        match self {
            Self::Slime { size, .. } => *size,
            _ => 1,
        }
    }

    pub fn anger(&self) -> i32 {
        match self {
            Self::PigZombie { anger, .. } => *anger,
            _ => 0,
        }
    }

    fn anger_at(&mut self, entity: &mut Entity) {
        if let Self::PigZombie { anger, sound_delay } = self {
            *anger = 400 + entity.rand.next_int(400);
            *sound_delay = entity.rand.next_int(40);
        }
    }

    pub fn pose(&self) -> SpecialPose {
        match self {
            Self::Slime { stretch, .. } => SpecialPose {
                stretch: *stretch,
                ..Default::default()
            },
            Self::Squid(state) => state.pose,
            Self::Wolf(wolf) => SpecialPose {
                interest: wolf.interest,
                shake: wolf.shake,
                wet: wolf.wet,
                ..Default::default()
            },
            Self::Ghast { attack, .. } => SpecialPose {
                charge: *attack as f32,
                ..Default::default()
            },
            Self::Chicken(flap) => SpecialPose {
                flap: *flap,
                ..Default::default()
            },
            _ => Default::default(),
        }
    }
}

impl Level {
    pub(super) fn anger_pig_zombies(&mut self, stored: &mut ServerEntity, source: Option<&Entity>) {
        let Kind::Mob(mob) = &mut stored.kind else {
            return;
        };
        let Some(target) = source
            .filter(|e| mob.kind == MobKind::PigZombie && e.mob.is_none())
            .filter(|e| self.player_name(e.id).is_some())
            .map(|e| e.id)
        else {
            return;
        };
        let area = stored.entity.bb.inflate(DVec3::splat(32.0));
        self.for_mobs_in(
            &area,
            |m| m.kind == MobKind::PigZombie,
            |entity, m| {
                m.special.anger_at(entity);
                m.target = Some(target);
            },
        );
        mob.special.anger_at(&mut stored.entity);
        mob.target = Some(target);
    }

    pub(super) fn pig_zombie_update(&mut self, entity: &mut Entity, mob: &mut Mob) {
        if let Special::PigZombie { sound_delay, .. } = &mut mob.special {
            entity.living.as_mut().unwrap().move_speed =
                if mob.target.is_some() { 0.95 } else { 0.5 };
            if *sound_delay > 0 {
                *sound_delay -= 1;
                if *sound_delay == 0 {
                    let pitch = entity.sound_pitch() * 1.8;
                    self.play_sound(entity, "mob.zombiepig.zpigangry", 2.0, pitch);
                }
            }
        }
    }

    pub(super) fn slime_ai(&mut self, entity: &mut Entity, mob: &mut Mob) {
        self.despawn_mob(entity);
        let target = self.nearest_player(entity.pos, 16.0);
        if let Some(target) = target {
            face(entity, target, 10.0, 20.0);
        }
        let Special::Slime {
            size,
            jump_delay,
            stretch,
        } = &mut mob.special
        else {
            return;
        };
        let jump = entity.on_ground && {
            let old = *jump_delay;
            *jump_delay -= 1;
            old <= 0
        };
        let living = entity.living.as_mut().unwrap();
        living.jumping = jump;
        if jump {
            *jump_delay = entity.rand.next_int(20) + 10;
            if target.is_some() {
                *jump_delay /= 3;
            }
            *stretch = 1.0;
            living.move_strafing = 1.0 - entity.rand.next_float() * 2.0;
            living.move_forward = f32::from(*size);
            if *size > 1 {
                let pitch = entity.sound_pitch() * 0.8;
                self.play_sound(entity, "mob.slime", 0.6, pitch);
            }
        } else if entity.on_ground {
            living.move_strafing = 0.0;
            living.move_forward = 0.0;
        }
    }

    pub(super) fn slime_update(&mut self, entity: &mut Entity, mob: &mut Mob, was_grounded: bool) {
        let Special::Slime { size, stretch, .. } = &mut mob.special else {
            return;
        };
        if entity.on_ground && !was_grounded {
            for _ in 0..usize::from(*size) * 8 {
                let angle = entity.rand.next_float() * std::f32::consts::TAU;
                let radius = (entity.rand.next_float() * 0.5 + 0.5) * f32::from(*size) * 0.5;
                let point = DVec3::new(
                    entity.pos.x + f64::from(sin(angle) * radius),
                    entity.bb.min.y,
                    entity.pos.z + f64::from(cos(angle) * radius),
                );
                self.spawn_particle("slime", point, DVec3::ZERO);
            }
            if *size > 2 {
                let pitch = entity.sound_pitch() / 0.8;
                self.play_sound(entity, "mob.slime", 0.6, pitch);
            }
            *stretch = -0.5;
        }
        *stretch *= 0.6;
        if entity.dead && *size > 1 && entity.living.as_ref().is_some_and(|l| l.health <= 0) {
            for i in 0..4 {
                let x = (i % 2) as f32 - 0.5;
                let z = (i / 2) as f32 - 0.5;
                let at = entity.pos
                    + DVec3::new(
                        f64::from(x * f32::from(*size) / 4.0),
                        0.5,
                        f64::from(z * f32::from(*size) / 4.0),
                    );
                let yaw = entity.rand.next_float() * 360.0;
                let mut child = self.mob_entity(MobKind::Slime, at, yaw);
                if let Kind::Mob(Mob {
                    special:
                        Special::Slime {
                            size: child_size, ..
                        },
                    ..
                }) = &mut child.kind
                {
                    *child_size = *size / 2;
                    Special::resize_slime(&mut child.entity, *child_size);
                }
                self.entities.insert(child);
            }
        } else if *size > 1 && entity.living.as_ref().is_some_and(|l| l.health > 0) {
            let targets: Vec<_> = self
                .entities
                .iter()
                .filter(|other| {
                    matches!(other.kind, Kind::Player { .. })
                        && other.entity.bb.intersects(&entity.bb)
                        && entity.pos.distance(other.entity.pos) < 0.6 * f64::from(*size)
                        && visible(self, entity, &other.entity)
                })
                .map(|other| other.entity.id)
                .collect();
            for target in targets {
                if self.attack_entity(target, Some(entity), i32::from(*size)) {
                    let pitch = entity.sound_pitch();
                    self.play_sound(entity, "mob.slimeattack", 1.0, pitch);
                }
            }
        }
    }

    pub(super) fn squid_ai(&self, entity: &mut Entity, mob: &mut Mob) {
        let Special::Squid(state) = &mut mob.special else {
            return;
        };
        state.choose_motion(&mut entity.rand, entity.in_water);
        self.despawn_mob(entity);
    }

    pub(super) fn squid_move(&mut self, entity: &mut Entity, mob: &mut Mob) {
        let Special::Squid(state) = &mut mob.special else {
            return;
        };
        // `EntitySquid.isInWater`, before moving as `EntityLiving` does and again afterwards
        let swimming = |level: &Self, entity: &mut Entity| {
            let region = entity.bb.inflate(DVec3::new(0.0, f64::from(-0.6_f32), 0.0));
            entity.handle_water(level, &region)
        };
        swimming(self, entity);
        entity.move_entity(self, entity.motion);
        let in_water = swimming(self, entity);
        let body = &mut entity.living.as_mut().unwrap().render_yaw_offset;
        state.advance(&mut entity.rand, &mut entity.motion, body, in_water, false);
        entity.yaw = *body;
    }
}