alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH 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
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;
},
);
}
}