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 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, ®ion)
};
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;
}
}