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

use common::entity::{Entity, EntityWorld, mob::MobKind};
use common::item::{Item, ItemKind, ItemStack};
use common::math::{floor_f64, sin};
use common::protocol::Packet;
use glam::DVec3;

use super::{Mob, pack_area, path, special::Special};
use crate::entity::{Kind, ServerEntity};
use crate::level::Level;

#[derive(Clone, Debug, Default)]
pub struct Wolf {
    pub owner: String,
    pub angry: bool,
    pub sitting: bool,
    pub wet: bool,
    pub shaking: bool,
    pub shake: f32,
    pub interest: f32,
}

impl Wolf {
    pub fn flags(&self) -> u8 {
        u8::from(self.sitting) | (u8::from(self.angry) * 2) | (u8::from(!self.owner.is_empty()) * 4)
    }
    pub fn ceased(&self) -> bool {
        self.sitting || self.shaking
    }
    pub fn meat(item: Item) -> bool {
        matches!(
            item.properties().map(|p| p.kind),
            Some(ItemKind::Food {
                wolf_food: true,
                ..
            })
        )
    }
}

impl Mob {
    pub fn wolf(&self) -> Option<&Wolf> {
        match &self.special {
            Special::Wolf(wolf) => Some(wolf),
            _ => None,
        }
    }

    pub fn wolf_mut(&mut self) -> Option<&mut Wolf> {
        match &mut self.special {
            Special::Wolf(wolf) => Some(wolf),
            _ => None,
        }
    }
}

impl Level {
    pub fn use_wolf(
        &mut self,
        entity: &mut Entity,
        mob: &mut Mob,
        name: &str,
        held: Option<ItemStack>,
    ) -> Option<bool> {
        let wolf = mob.wolf_mut()?;
        if wolf.owner.is_empty() {
            if held.is_none_or(|h| h.item != Item::BONE) || wolf.angry {
                return None;
            }
            let tamed = entity.rand.next_int(3) == 0;
            if tamed {
                wolf.owner = name.into();
                wolf.sitting = true;
                entity.living.as_mut().unwrap().health = 20;
                mob.path.clear();
            }
            for _ in 0..7 {
                let velocity = DVec3::new(
                    entity.rand.next_gaussian(),
                    entity.rand.next_gaussian(),
                    entity.rand.next_gaussian(),
                ) * 0.02;
                let position = entity.pos
                    + DVec3::new(
                        f64::from(entity.rand.next_float() * entity.width * 2.0 - entity.width),
                        0.5 + f64::from(entity.rand.next_float() * entity.height),
                        f64::from(entity.rand.next_float() * entity.width * 2.0 - entity.width),
                    );
                self.spawn_particle(if tamed { "heart" } else { "smoke" }, position, velocity);
            }
            self.events.push(Packet::EntityStatus {
                entity: entity.id,
                status: if tamed { 7 } else { 6 },
            });
            Some(true)
        } else if held.is_some_and(|h| Wolf::meat(h.item))
            && entity.living.as_ref().is_some_and(|l| l.health < 20)
        {
            entity.heal(3);
            Some(true)
        } else if wolf.owner.eq_ignore_ascii_case(name) {
            wolf.sitting = !wolf.sitting;
            entity.living.as_mut().unwrap().jumping = false;
            mob.path.clear();
            Some(false)
        } else {
            None
        }
    }

    pub(super) fn wolf_ai(&mut self, entity: &mut Entity, mob: &mut Mob, brightness: f32) {
        if entity.in_water
            && let Some(wolf) = mob.wolf_mut()
        {
            wolf.sitting = false;
        }
        let attacked = self.mob_ai(entity, mob, brightness);
        let Some(wolf) = mob.wolf() else {
            return;
        };
        if !attacked && mob.path.is_empty() && !wolf.owner.is_empty() && entity.riding.is_none() {
            let owner = self
                .entities
                .iter()
                .find(|e| matches!(&e.kind, Kind::Player { name, .. } if *name == wolf.owner))
                .map(|e| e.entity.clone());
            if let Some(owner) = owner {
                let distance = owner.pos.distance(entity.pos) as f32;
                if distance > 5.0 {
                    mob.path = path::find(self, entity, owner.pos, 16.0);
                    if mob.path.is_empty() && distance > 12.0 {
                        // Teleports to a free spot on the ring two blocks around the owner
                        let [x, y, z] = owner.feet().to_array().map(floor_f64);
                        let spot = (-2..=2)
                            .flat_map(|dx| (-2..=2).map(move |dz| [x + dx, z + dz]))
                            .filter(|&[sx, sz]| (sx - x).abs() == 2 || (sz - z).abs() == 2)
                            .find(|&[x, z]| {
                                self.is_normal_cube(x, y - 1, z)
                                    && !self.is_normal_cube(x, y, z)
                                    && !self.is_normal_cube(x, y + 1, z)
                            });
                        if let Some([x, z]) = spot {
                            let feet = DVec3::new(
                                f64::from(x as f32 + 0.5),
                                f64::from(y),
                                f64::from(z as f32 + 0.5),
                            );
                            entity.set_location_and_angles(feet, entity.yaw, entity.pitch);
                        }
                    }
                }
            } else if !entity.in_water {
                mob.wolf_mut().unwrap().sitting = true;
            }
        } else if mob.target.is_none()
            && mob.path.is_empty()
            && wolf.owner.is_empty()
            && self.rand.next_int(100) == 0
        {
            let area = pack_area(entity.pos);
            let sheep: Vec<_> = self
                .entities
                .iter()
                .filter(|e| e.entity.mob == Some(MobKind::Sheep) && e.entity.bb.intersects(&area))
                .map(|e| e.entity.id)
                .collect();
            if !sheep.is_empty() {
                mob.target = Some(sheep[self.rand.next_int(sheep.len() as i32) as usize]);
            }
        }
    }

    pub(super) fn wolf_update(&mut self, entity: &mut Entity, mob: &mut Mob) {
        let held = mob
            .looking
            .and_then(|id| match &self.entities.get(id)?.kind {
                Kind::Player { held, .. } => *held,
                _ => None,
            });
        let has_path = !mob.path.is_empty();
        let Special::Wolf(wolf) = &mut mob.special else {
            return;
        };
        let interested = !has_path
            && !wolf.angry
            && held.is_some_and(|held| {
                if wolf.owner.is_empty() {
                    held.item == Item::BONE
                } else {
                    Wolf::meat(held.item)
                }
            });
        wolf.interest += (f32::from(u8::from(interested)) - wolf.interest) * 0.4;
        if interested {
            mob.look_time = 10;
        }
        if entity.is_wet(self) {
            wolf.wet = true;
            wolf.shaking = false;
            wolf.shake = 0.0;
        } else {
            if wolf.wet && !wolf.shaking && !has_path && entity.on_ground {
                wolf.shaking = true;
                wolf.shake = 0.0;
                self.events.push(Packet::EntityStatus {
                    entity: entity.id,
                    status: 8,
                });
            }
            if wolf.shaking {
                if wolf.shake == 0.0 {
                    let pitch = entity.sound_pitch();
                    self.play_sound(entity, "mob.wolf.shake", 0.4, pitch);
                }
                let previous = wolf.shake;
                wolf.shake += 0.05;
                if previous >= 2.0 {
                    wolf.wet = false;
                    wolf.shaking = false;
                    wolf.shake = 0.0;
                }
                if wolf.shake > 0.4 {
                    let count = (sin((wolf.shake - 0.4) * std::f32::consts::PI) * 7.0) as i32;
                    for _ in 0..count {
                        let point = entity.pos
                            + DVec3::new(
                                f64::from(
                                    (entity.rand.next_float() * 2.0 - 1.0) * entity.width * 0.5,
                                ),
                                entity.bb.min.y - entity.pos.y + f64::from(0.8_f32),
                                f64::from(
                                    (entity.rand.next_float() * 2.0 - 1.0) * entity.width * 0.5,
                                ),
                            );
                        self.spawn_particle("splash", point, entity.motion);
                    }
                }
            }
        }
    }

    pub(super) fn wolf_retaliate(&mut self, stored: &mut ServerEntity, source: Option<&Entity>) {
        let Some(source) = source.filter(|s| s.living.is_some()) else {
            return;
        };
        let Kind::Mob(mob) = &mut stored.kind else {
            return;
        };
        let Some(wolf) = mob.wolf_mut() else {
            return;
        };
        let name = self.player_name(source.id);
        let wild = wolf.owner.is_empty();
        if !wild && name.is_some_and(|name| wolf.owner.eq_ignore_ascii_case(name)) {
            return;
        }
        if wild && !wolf.angry {
            let is_player = name.is_some();
            wolf.angry = is_player;
            self.for_mobs_in(
                &pack_area(stored.entity.pos),
                |m| m.target.is_none() && m.wolf().is_some_and(|w| w.owner.is_empty()),
                |_, m| {
                    m.target = Some(source.id);
                    m.wolf_mut().unwrap().angry = is_player;
                },
            );
        }
        mob.target = Some(source.id);
    }

    pub fn alert_wolves(
        &mut self,
        owner: &Entity,
        name: &str,
        target: &Entity,
        include_sitting: bool,
    ) {
        let own_wolf = self.entities.get(target.id).is_some_and(
            |other| matches!(&other.kind, Kind::Mob(m) if m.wolf().is_some_and(|w| w.owner == name)),
        );
        if matches!(target.mob, Some(MobKind::Creeper | MobKind::Ghast))
            || target.living.is_none()
            || target.mob.is_none() && !self.rules.pvp
            || own_wolf
        {
            return;
        }
        self.for_mobs_in(
            &pack_area(owner.pos),
            |m| {
                m.target.is_none()
                    && m.wolf()
                        .is_some_and(|w| w.owner == name && (include_sitting || !w.sitting))
            },
            |_, m| {
                m.target = Some(target.id);
                m.wolf_mut().unwrap().sitting = false;
            },
        );
    }
}