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 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;
}
}
}
}