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 client src entities mod.rs
use crate::world::ClientWorld;
use common::JavaRandom;
use common::entity::mob::MobKind;
use common::entity::player::{self, PlayerState};
use common::entity::vehicle::VehicleKind;
use common::entity::{Entity, flags};
use common::inventory::PlayerInventory;
use common::item::{Item, ItemStack};
use common::math::floor_f64;
use common::physics::Aabb;
use common::protocol::{Metadata, MetadataValue, Packet};
use common::world::shape::BlockView;
use common::world::{Block, Material};
use glam::DVec3;
use rustc_hash::FxHashMap;
#[derive(Clone, Copy)]
pub enum Kind {
Player,
Painting(common::entity::painting::Painting),
Item(ItemStack),
Falling(Block),
Mob(MobKind),
Arrow,
Fireball,
Hook(Option<i32>),
Vehicle(VehicleKind),
/// A snowball or egg in flight, drawn as its item icon.
Thrown(Item),
Tnt,
}
#[derive(Clone, Copy, Default)]
pub struct Animation {
pub rotation: [f32; 3],
pub walk: [f32; 2],
pub hurt: i32,
pub death: i32,
pub fuse: i32,
pub special: common::entity::mob::SpecialPose,
pub vehicle: common::entity::vehicle::VehiclePose,
}
pub struct RemotePlayer {
pub name: String,
pub entity: Entity,
pub inventory: PlayerInventory,
state: PlayerState,
}
pub struct RemoteEntity {
pub player: Option<Box<RemotePlayer>>,
pub riding: Option<i32>,
pub kind: Kind,
pub position: DVec3,
pub previous: DVec3,
pub age: u32,
pub flags: [u8; 3],
pub animation: Animation,
pub previous_animation: Animation,
pending: Option<(DVec3, Animation)>,
target: DVec3,
target_rotation: [f32; 2],
remaining: u32,
/// `Entity.outfitWithItem`: the held item, then the armor from boots to helmet.
pub equipment: [Option<ItemStack>; 5],
/// `serverPosX` and its siblings: the last position the server sent, in 1/32 blocks.
server_position: [i32; 3],
/// Animated on the client, as a Java server sends no `EntityState`.
remote: bool,
/// `EntityArrow.arrowShake`: the wobble of an arrow that just stuck.
pub shake: i32,
moving: bool,
velocity: DVec3,
acceleration: DVec3,
squid: Option<(common::entity::mob::SquidMotion, JavaRandom)>,
grounded: bool,
/// `Entity.rand`, for effects the client makes itself.
rand: JavaRandom,
/// `EntityLiving.livingSoundTime`.
living_sound: i32,
/// Flying into the collector after a `Collect`: its id, the ticks so far and the start.
collected: Option<(i32, u32, DVec3)>,
/// `EntityWolf.isShaking`: shaking itself dry.
shaking: bool,
}
/// A sound: name, position, volume and a pitch, or a random one.
pub type Sound = (&'static str, DVec3, f32, Option<f32>);
/// A particle: name, position and velocity.
pub type Effect = (&'static str, DVec3, DVec3);
#[derive(Default)]
pub struct Entities(
pub FxHashMap<i32, RemoteEntity>,
pub Vec<Sound>,
pub Vec<Effect>,
);
fn angle(byte: i8) -> f32 {
f32::from(byte) * 360.0 / 256.0
}
pub fn velocity(units: [i16; 3]) -> DVec3 {
DVec3::from_array(units.map(|v| f64::from(v) / 8000.0))
}
/// The small random velocity of a puff of particles.
fn drift(random: &mut JavaRandom) -> DVec3 {
DVec3::new(
random.next_gaussian(),
random.next_gaussian(),
random.next_gaussian(),
) * 0.02
}
fn in_water(world: &ClientWorld, position: DVec3) -> bool {
let [x, y, z] = position.to_array().map(floor_f64);
world.block(x, y, z).material() == Material::Water
}
impl RemoteEntity {
/// A freshly spawned entity at `position`, in 1/32 blocks.
pub fn new(id: i32, kind: Kind, position: [i32; 3]) -> Self {
let position_units = position;
let position = DVec3::from_array(position.map(f64::from)) / 32.0;
Self {
player: None,
riding: None,
kind,
position,
previous: position,
age: 0,
flags: [0; 3],
animation: Animation::default(),
previous_animation: Animation::default(),
pending: None,
target: position,
target_rotation: [0.0; 2],
remaining: 0,
shake: 0,
moving: false,
velocity: DVec3::ZERO,
acceleration: DVec3::ZERO,
squid: matches!(kind, Kind::Mob(MobKind::Squid)).then(|| {
let mut random = JavaRandom::new(i64::from(id));
let mut state = common::entity::mob::SquidMotion::default();
state.initialize(&mut random);
(state, random)
}),
grounded: false,
server_position: position_units,
remote: false,
equipment: [None; 5],
rand: JavaRandom::new(i64::from(id) ^ 0x5DEE_CE66),
living_sound: 0,
collected: None,
shaking: false,
}
}
fn metadata(&mut self, metadata: &Metadata) {
for (id, value) in metadata {
if *id == 18
&& let MetadataValue::Int(health) = value
{
self.flags[2] = (*health).clamp(0, 255) as u8;
}
if let MetadataValue::Byte(value) = value {
let index = match id {
0 => 0,
16 => 1,
17 => 2,
_ => continue,
};
self.flags[index] = *value as u8;
}
}
}
pub fn bounds(&self) -> Aabb {
if let Kind::Painting(painting) = self.kind {
return painting.bounds();
}
let [width, height] = match self.kind {
Kind::Mob(MobKind::Slime) => [0.6 * f32::from(self.flags[1].max(1)); 2],
Kind::Mob(kind) => kind.size(),
Kind::Player => [0.6, 1.8],
Kind::Fireball => [1.0; 2],
Kind::Vehicle(kind) => kind.size(),
Kind::Falling(_) | Kind::Tnt => [0.98, 0.98],
_ => [0.25, 0.25],
};
let half = f64::from(width) / 2.0;
let offset = match self.kind {
Kind::Vehicle(kind) => f64::from(kind.y_offset()),
Kind::Falling(_) | Kind::Tnt => f64::from(0.98_f32 / 2.0),
_ => 0.0,
};
Aabb::new(
self.position - DVec3::new(half, offset, half),
self.position + DVec3::new(half, f64::from(height) - offset, half),
)
}
/// Faces `yaw` and `pitch`, in 1/256 turns, as a mob or player the server moves.
fn face(&mut self, yaw: i8, pitch: i8) {
let [yaw, pitch] = [angle(yaw), angle(pitch)];
self.animation.rotation = [yaw, pitch, yaw];
self.previous_animation = self.animation;
self.target_rotation = [yaw, pitch];
self.remote = true;
}
fn interpolation_steps(&self) -> u32 {
match self.kind {
Kind::Vehicle(VehicleKind::Boat) => 7,
Kind::Vehicle(_) => 5,
// `Entity.setPositionAndRotation2` puts other things right there
Kind::Item(_) | Kind::Falling(_) | Kind::Tnt | Kind::Arrow | Kind::Thrown(_) => 1,
_ => 3,
}
}
/// `EntityWolf.onUpdate`: soaked by water or rain, then shaking itself dry.
fn wolf_update(
&mut self,
world: &ClientWorld,
sounds: &mut Vec<Sound>,
effects: &mut Vec<Effect>,
) {
let [x, y, z] = self.position.to_array().map(floor_f64);
let special = &mut self.animation.special;
if in_water(world, self.position)
|| in_water(world, self.position + DVec3::Y * 0.5)
|| world.rained_on(x, y, z)
{
special.wet = true;
self.shaking = false;
special.shake = 0.0;
} else if self.shaking {
if special.shake == 0.0 {
sounds.push(("mob.wolf.shake", self.position, 0.4, None));
}
let previous = special.shake;
special.shake += 0.05;
if previous >= 2.0 {
special.wet = false;
self.shaking = false;
special.shake = 0.0;
}
if special.shake > 0.4 {
let count = ((special.shake - 0.4) * std::f32::consts::PI).sin() * 7.0;
let width = MobKind::Wolf.size()[0];
for _ in 0..count as i32 {
let mut offset =
|| f64::from((self.rand.next_float() * 2.0 - 1.0) * width * 0.5);
let (dx, dz) = (offset(), offset());
let y = self.bounds().min.y + 0.8;
let pos = DVec3::new(self.position.x + dx, y, self.position.z + dz);
effects.push(("splash", pos, self.position - self.previous));
}
}
}
}
fn death_puff(&mut self, kind: MobKind, effects: &mut Vec<Effect>) {
let [width, height] = kind.size();
for _ in 0..20 {
let random = &mut self.rand;
let velocity = drift(random);
let pos = self.position
+ DVec3::new(
f64::from(random.next_float() * width * 2.0 - width),
f64::from(random.next_float() * height),
f64::from(random.next_float() * width * 2.0 - width),
);
effects.push(("explode", pos, velocity));
}
}
/// `getLivingSound`, which for wolves depends on their mood and health.
fn living_sound_name(&mut self, kind: MobKind) -> Option<&'static str> {
if kind != MobKind::Wolf {
return kind.sound(false, false);
}
let [_, mood, health] = self.flags;
Some(if mood & 2 != 0 {
"mob.wolf.growl"
} else if self.rand.next_int(3) == 0 {
if mood & 4 != 0 && health < 10 {
"mob.wolf.whine"
} else {
"mob.wolf.panting"
}
} else {
"mob.wolf.bark"
})
}
/// The client's own `onUpdate` of items, falling blocks, TNT, arrows, snowballs and eggs
/// between the server's position updates.
fn simulate(&mut self, world: &mut ClientWorld, effects: &mut Vec<Effect>) {
let gravity_body = match self.kind {
Kind::Item(_) => Some((0.25, 0.125)),
Kind::Falling(_) | Kind::Tnt => Some((0.98, 0.49)),
_ => None,
};
if let Some((size, offset)) = gravity_body {
self.velocity.y -= f64::from(0.04_f32);
let mut probe = Entity::new(0, size, size, JavaRandom::new(0));
probe.y_offset = offset;
probe.triggers_walking = false;
probe.set_position(self.position);
probe.move_entity(world, self.velocity);
self.position = probe.pos;
let friction = match self.kind {
Kind::Item(_) if probe.on_ground => {
let [x, y, z] = [probe.pos.x, probe.bb.min.y - 1.0, probe.pos.z].map(floor_f64);
world.block(x, y, z).slipperiness() * 0.98
}
_ => 0.98,
};
let [friction, drag] = [friction, 0.98_f32].map(f64::from);
self.velocity *= DVec3::new(friction, drag, friction);
if probe.on_ground {
if !matches!(self.kind, Kind::Item(_)) {
self.velocity.x *= f64::from(0.7_f32);
self.velocity.z *= f64::from(0.7_f32);
}
self.velocity.y *= -0.5;
}
if matches!(self.kind, Kind::Tnt) {
let fuse = self.animation.fuse;
self.animation.fuse -= 1;
if fuse > 0 {
effects.push(("smoke", self.position + DVec3::Y * 0.5, DVec3::ZERO));
}
}
return;
}
if self.grounded || !matches!(self.kind, Kind::Arrow | Kind::Thrown(_)) {
return;
}
let end = self.position + self.velocity;
match common::world::shape::ray_trace(&*world, self.position, end, false, true) {
Some(hit) if matches!(self.kind, Kind::Arrow) => {
self.position = hit.point;
self.velocity = DVec3::ZERO;
self.grounded = true;
return;
}
_ => self.position = end,
}
let drag = if in_water(world, self.position) {
0.8_f32
} else {
0.99_f32
};
self.velocity *= f64::from(drag);
self.velocity.y -= f64::from(0.03_f32);
if matches!(self.kind, Kind::Arrow) {
let horizontal =
(self.velocity.x * self.velocity.x + self.velocity.z * self.velocity.z).sqrt();
self.animation.rotation[0] = self.velocity.x.atan2(self.velocity.z).to_degrees() as f32;
self.animation.rotation[1] = self.velocity.y.atan2(horizontal).to_degrees() as f32;
}
}
/// The parts of `EntityLiving.onUpdate` the server leaves to the client: limb swing, the
/// body turning towards the head, and the hurt and death timers.
fn animate(&mut self) {
let animation = &mut self.animation;
let delta = self.position - self.previous;
let distance = ((delta.x * delta.x + delta.z * delta.z).sqrt() * 4.0).min(1.0) as f32;
animation.walk[1] += (distance - animation.walk[1]) * 0.4;
animation.walk[0] += animation.walk[1];
// `EntityLiving.onUpdate`: the body turns where it walks, never far from the head
let wrap = |angle: f32| (angle + 180.0).rem_euclid(360.0) - 180.0;
let [head, _, body] = &mut animation.rotation;
let mut heading = *body;
if (delta.x * delta.x + delta.z * delta.z).sqrt() > 0.05 {
heading = delta.z.atan2(delta.x).to_degrees() as f32 - 90.0;
}
if self
.player
.as_ref()
.and_then(|p| p.entity.living.as_ref())
.is_some_and(|l| l.swing_progress > 0.0)
{
heading = *head;
}
*body += wrap(heading - *body) * 0.3;
let offset = wrap(*head - *body).clamp(-75.0, 75.0);
*body = *head - offset;
if offset * offset > 2500.0 {
*body += offset * 0.2;
}
animation.hurt = (animation.hurt - 1).max(0);
if animation.death > 0 {
animation.death += 1;
}
}
}
impl Entities {
pub fn receive(&mut self, packet: &Packet, world: &ClientWorld) -> bool {
match packet {
Packet::Painting {
entity,
title,
x,
y,
z,
direction,
} => {
let painting = common::entity::painting::Painting::from_name(
title,
[*x, *y, *z],
*direction as u8,
);
let entity = self.spawn(*entity, Kind::Painting(painting), [0; 3]);
entity.position = painting.position();
entity.previous = entity.position;
entity.target = entity.position;
let turn = f32::from(painting.direction) * 90.0;
entity.animation.rotation = [turn, 0.0, turn];
entity.previous_animation = entity.animation;
}
Packet::NamedEntitySpawn {
entity,
name,
x,
y,
z,
yaw,
pitch,
item,
} => {
let remote = self.spawn(*entity, Kind::Player, [*x, *y, *z]);
remote.face(*yaw, *pitch);
let mut model = Entity::new_player(*entity, JavaRandom::new(i64::from(*entity)));
model.y_offset = 0.0;
let [yaw, pitch, _] = remote.animation.rotation;
model.set_location_and_angles(remote.position, yaw, pitch);
model.prev_pos = model.pos;
model.prev_yaw = model.yaw;
model.prev_pitch = model.pitch;
if let Some(living) = &mut model.living {
living.render_yaw_offset = model.yaw;
living.prev_render_yaw_offset = model.yaw;
}
let mut inventory = PlayerInventory::default();
inventory.main[0] = (*item != 0).then(|| ItemStack::one(Item(*item)));
remote.equipment[0] = inventory.main[0];
remote.player = Some(Box::new(RemotePlayer {
name: name.clone(),
entity: model,
inventory,
state: Default::default(),
}));
}
Packet::Sleep {
entity,
status: 0,
x,
y,
z,
} => {
let Some(remote) = self
.0
.get_mut(entity)
.and_then(|entity| entity.player.as_mut())
else {
return false;
};
player::lie_down(&mut remote.entity, world, [*x, i32::from(*y), *z]);
}
Packet::Animation { entity, animation } => {
let Some(remote) = self.0.get_mut(entity) else {
return false;
};
match *animation {
1 => {
if let Some(player) = &mut remote.player {
player.state.swing();
}
}
2 => remote.animation.hurt = 10,
3 => {
if let Some(player) = &mut remote.player {
player::get_up(&mut player.entity, world, 0.0, false);
}
}
_ => {}
}
}
Packet::AttachEntity { entity, vehicle } => {
let mount = self.0.contains_key(vehicle).then_some(*vehicle);
let Some(entity) = self.0.get_mut(entity) else {
return false;
};
entity.riding = mount;
}
Packet::PickupSpawn {
entity,
item,
x,
y,
z,
rotation,
pitch,
roll,
} => {
let entity = self.spawn(*entity, Kind::Item(*item), [*x, *y, *z]);
entity.remote = true;
entity.velocity =
DVec3::new(f64::from(*rotation), f64::from(*pitch), f64::from(*roll)) / 128.0;
}
Packet::VehicleSpawn {
entity,
kind: kind @ (50 | 60 | 61 | 62 | 70 | 71),
x,
y,
z,
thrower,
velocity,
} => {
let kind = match kind {
50 => Kind::Tnt,
60 => Kind::Arrow,
61 => Kind::Thrown(Item::SNOWBALL),
62 => Kind::Thrown(Item::EGG),
70 => Kind::Falling(Block::SAND),
_ => Kind::Falling(Block::GRAVEL),
};
let entity = self.spawn(*entity, kind, [*x, *y, *z]);
// Moved by the client between the server's updates
entity.remote = true;
if matches!(kind, Kind::Tnt) {
entity.animation.fuse = 80;
entity.previous_animation.fuse = 80;
}
if *thrower > 0 {
entity.velocity = self::velocity(*velocity);
}
}
Packet::VehicleSpawn {
entity,
kind: kind @ (1 | 10..=12),
x,
y,
z,
..
} => {
let kind = match kind {
10 => VehicleKind::Minecart,
11 => VehicleKind::StorageCart,
12 => VehicleKind::PoweredCart,
_ => VehicleKind::Boat,
};
self.spawn(*entity, Kind::Vehicle(kind), [*x, *y, *z])
.remote = true;
}
Packet::VehicleSpawn {
entity,
kind: 90,
x,
y,
z,
thrower,
..
} => {
self.spawn(
*entity,
Kind::Hook((*thrower > 0).then_some(*thrower)),
[*x, *y, *z],
);
}
Packet::VehicleSpawn {
entity,
kind: 63,
x,
y,
z,
velocity,
..
} => {
let entity = self.spawn(*entity, Kind::Fireball, [*x, *y, *z]);
entity.remote = true;
entity.flags[0] |= flags::ON_FIRE;
entity.acceleration = self::velocity(*velocity);
}
Packet::EntityVelocity { entity, velocity } => {
if let Some(entity) = self.0.get_mut(entity) {
entity.velocity = self::velocity(*velocity);
}
}
Packet::MobSpawn {
entity,
kind,
x,
y,
z,
yaw,
pitch,
metadata,
} => {
let Some(kind) = MobKind::from_id(*kind) else {
return false;
};
let entity = self.spawn(*entity, Kind::Mob(kind), [*x, *y, *z]);
entity.face(*yaw, *pitch);
entity.metadata(metadata);
}
Packet::EntityMetadata { entity, metadata } => {
let Some(entity) = self.0.get_mut(entity) else {
return false;
};
entity.metadata(metadata);
}
Packet::EntityState {
entity,
pos,
rotation,
walk,
hurt,
death,
fuse,
special,
vehicle,
} => {
if let Some(entity) = self.0.get_mut(entity) {
entity.remote = false;
entity.pending = Some((
DVec3::from_array(*pos),
Animation {
rotation: *rotation,
walk: *walk,
hurt: *hurt,
death: *death,
fuse: *fuse,
special: *special,
vehicle: *vehicle,
},
));
entity.remaining = 0;
}
}
Packet::EntityTeleport {
entity,
x,
y,
z,
yaw,
pitch,
} => {
if let Some(entity) = self.0.get_mut(entity) {
entity.server_position = [*x, *y, *z];
// `handleEntityTeleport` lifts absolute positions by 1/64
entity.target = DVec3::new(f64::from(*x), f64::from(*y), f64::from(*z)) / 32.0
+ DVec3::Y / 64.0;
entity.target_rotation = [angle(*yaw), angle(*pitch)];
entity.remote = true;
entity.remaining = entity.interpolation_steps();
}
}
Packet::PlayerInventory {
entity,
slot,
item,
damage,
} => {
if let Some(entity) = self.0.get_mut(entity)
&& let Some(target) = entity.equipment.get_mut(*slot as usize)
{
*target =
(*item >= 0).then(|| ItemStack::new(Item(*item), 1, i32::from(*damage)));
if let Some(player) = &mut entity.player {
if *slot == 0 {
player.inventory.main[0] = *target;
} else if (1..=4).contains(slot) {
player.inventory.armor[(*slot - 1) as usize] = *target;
}
}
}
}
Packet::RelEntityMove { entity, delta }
| Packet::RelEntityMoveLook { entity, delta, .. } => {
if let Some(entity) = self.0.get_mut(entity) {
entity.remote = true;
for (axis, d) in entity.server_position.iter_mut().zip(delta) {
*axis += i32::from(*d);
}
entity.target = DVec3::from_array(entity.server_position.map(f64::from)) / 32.0;
entity.remaining = entity.interpolation_steps();
}
if let Packet::RelEntityMoveLook { yaw, pitch, .. } = packet {
self.look(*entity, *yaw, *pitch);
}
}
Packet::EntityLook { entity, yaw, pitch } => self.look(*entity, *yaw, *pitch),
Packet::EntityStatus {
entity,
status: status @ 6..=8,
} if self
.0
.get(entity)
.is_some_and(|e| matches!(e.kind, Kind::Mob(MobKind::Wolf))) =>
{
let entity = self.0.get_mut(entity).unwrap();
if *status == 8 {
entity.shaking = true;
entity.animation.special.shake = 0.0;
} else {
let mut random = JavaRandom::new(i64::from(entity.age));
for _ in 0..7 {
let velocity = drift(&mut random);
let pos = entity.position
+ DVec3::new(
f64::from(random.next_float() * 1.6 - 0.8),
0.5 + f64::from(random.next_float() * 0.8),
f64::from(random.next_float() * 1.6 - 0.8),
);
let name = if *status == 7 { "heart" } else { "smoke" };
self.2.push((name, pos, velocity));
}
}
}
Packet::EntityStatus { entity, status } => {
let Some(entity) = self.0.get_mut(entity).filter(|_| matches!(*status, 2 | 3))
else {
return false;
};
entity.remote = true;
let (hurt, dead) = (*status == 2, *status == 3);
if hurt {
entity.animation.walk[1] = 1.5;
entity.animation.hurt = 10;
}
if dead {
entity.animation.death = entity.animation.death.max(1);
}
match entity.kind {
Kind::Mob(kind) => {
if let Some(sound) = kind.sound(hurt, dead) {
self.1.push((sound, entity.position, kind.volume(), None));
}
}
Kind::Player => self.1.push(("random.hurt", entity.position, 1.0, None)),
_ => {}
}
}
Packet::DestroyEntity { entity } => {
// A body removed before its death pose ended still disappears in a puff
if let Some(mut removed) = self.0.remove(entity)
&& let Kind::Mob(kind) = removed.kind
&& removed.remote
&& (1..22).contains(&removed.animation.death)
{
removed.death_puff(kind, &mut self.2);
}
}
// `handleCollect`: it flies into the collector, with a pop
Packet::Collect {
collected,
collector,
} => {
let Some(entity) = self.0.get_mut(collected) else {
return false;
};
entity.collected = Some((*collector, 0, entity.position));
let pitch =
((entity.rand.next_float() - entity.rand.next_float()) * 0.7 + 1.0) * 2.0;
self.1
.push(("random.pop", entity.position, 0.2, Some(pitch)));
}
_ => return false,
}
true
}
fn look(&mut self, entity: i32, yaw: i8, pitch: i8) {
if let Some(entity) = self.0.get_mut(&entity) {
entity.remote = true;
entity.target_rotation = [angle(yaw), angle(pitch)];
entity.remaining = entity.interpolation_steps();
}
}
fn spawn(&mut self, id: i32, kind: Kind, position: [i32; 3]) -> &mut RemoteEntity {
self.0.insert(id, RemoteEntity::new(id, kind, position));
self.0.get_mut(&id).unwrap()
}
/// `local` is the id and position of the local player.
pub fn tick(&mut self, world: &mut ClientWorld, local: (i32, DVec3)) {
let mut picked = Vec::new();
for (&id, entity) in &self.0 {
if let Some((collector, _, _)) = entity.collected {
let target = if collector == local.0 {
Some(local.1)
} else {
self.0.get(&collector).map(|e| e.position)
};
picked.push((id, target.unwrap_or(local.1) - DVec3::Y * 0.5));
}
}
for (id, target) in picked {
let entity = self.0.get_mut(&id).unwrap();
let Some((_, age, start)) = &mut entity.collected else {
continue;
};
*age += 1;
entity.previous = entity.position;
let progress = (*age as f64 / 3.0).powi(2);
entity.position = *start + (target - *start) * progress;
if *age >= 3 {
self.0.remove(&id);
}
}
for entity in self.0.values_mut().filter(|e| e.collected.is_none()) {
entity.previous = entity.position;
entity.previous_animation = entity.animation;
if let Some((position, animation)) = entity.pending.take() {
entity.position = position;
entity.animation = animation;
} else if entity.remaining > 0 {
entity.position += (entity.target - entity.position) / f64::from(entity.remaining);
let yaw_delta = (entity.target_rotation[0] - entity.animation.rotation[0] + 180.0)
.rem_euclid(360.0)
- 180.0;
entity.animation.rotation[0] += yaw_delta / entity.remaining as f32;
entity.animation.rotation[1] += (entity.target_rotation[1]
- entity.animation.rotation[1])
/ entity.remaining as f32;
entity.remaining -= 1;
}
if entity.remote && matches!(entity.kind, Kind::Fireball) {
if entity.remaining == 0 {
entity.position += entity.velocity;
}
let in_water = in_water(world, entity.position);
if in_water {
let bubble = (
"bubble",
entity.position - entity.velocity * 0.25,
entity.velocity,
);
self.2.extend([bubble; 4]);
}
let drag = if in_water { 0.8_f32 } else { 0.95_f32 };
entity.velocity = (entity.velocity + entity.acceleration) * f64::from(drag);
self.2
.push(("smoke", entity.position + DVec3::Y * 0.5, DVec3::ZERO));
}
entity.age = entity.age.wrapping_add(1);
if entity.remote && entity.remaining == 0 {
entity.simulate(world, &mut self.2);
}
// `EntityCreeper.onUpdate` of a multiplayer client: the fuse follows its state
if entity.remote && matches!(entity.kind, Kind::Mob(MobKind::Creeper)) {
let state = if entity.flags[1] == 1 { 1 } else { -1 };
if state > 0 && entity.animation.fuse == 0 {
self.1
.push(("random.fuse", entity.position, 1.0, Some(0.5)));
}
entity.animation.fuse = (entity.animation.fuse + state).clamp(0, 30);
}
if entity.remote {
entity.animate();
if let Kind::Mob(kind) = entity.kind {
// `EntityLiving.onEntityUpdate`: now and then a mob makes its sound
if entity.animation.death == 0
&& entity.rand.next_int(1000) < entity.living_sound
{
entity.living_sound = -80;
if let Some(sound) = entity.living_sound_name(kind) {
let wolf = kind == MobKind::Wolf;
let volume = if wolf { 0.4 } else { kind.volume() };
self.1.push((sound, entity.position, volume, None));
}
} else {
entity.living_sound += 1;
}
// The puff of a body that has finished dying (`deathTime > 20`)
if entity.animation.death == 22 {
entity.death_puff(kind, &mut self.2);
}
}
if matches!(entity.kind, Kind::Mob(MobKind::Wolf)) {
entity.wolf_update(world, &mut self.1, &mut self.2);
}
if let Some((state, random)) = &mut entity.squid {
let in_water = in_water(world, entity.position + DVec3::Y * 0.475);
let mut motion = entity.position - entity.previous;
state.advance(
random,
&mut motion,
&mut entity.animation.rotation[2],
in_water,
true,
);
entity.animation.special = state.pose;
}
if matches!(entity.kind, Kind::Mob(MobKind::Slime | MobKind::Chicken)) {
let mut probe = Entity::new(0, 0.0, 0.0, JavaRandom::new(0));
probe.bb = entity.bounds();
let grounded = !common::entity::EntityWorld::colliding_boxes(
&*world,
&probe,
&probe.bb.offset(DVec3::new(0.0, -0.01, 0.0)),
)
.is_empty();
let special = &mut entity.animation.special;
if matches!(entity.kind, Kind::Mob(MobKind::Chicken)) {
special.flap.advance(grounded);
} else {
if grounded && !entity.grounded {
special.stretch = -0.5;
}
if !grounded && entity.grounded && entity.position.y > entity.previous.y {
special.stretch = 1.0;
}
special.stretch *= 0.6;
}
entity.grounded = grounded;
}
}
let moving = entity.position != entity.previous;
if matches!(entity.kind, Kind::Arrow) && entity.moving && !moving {
entity.shake = 7;
} else if entity.shake > 0 {
entity.shake -= 1;
}
entity.moving = moving;
if let Some(player) = &mut entity.player {
let model = &mut player.entity;
model.prev_pos = model.pos;
model.prev_yaw = model.yaw;
model.prev_pitch = model.pitch;
if !model.sleep.sleeping {
model.set_position(entity.position);
}
model.yaw = entity.animation.rotation[0];
model.pitch = entity.animation.rotation[1];
model.flags = entity.flags[0];
model.set_flag(flags::RIDING, entity.riding.is_some());
model.ticks_existed = entity.age as i32;
if let Some(living) = &mut model.living {
living.prev_walk_animation_speed = entity.previous_animation.walk[1];
living.walk_animation_speed = entity.animation.walk[1];
living.walk_animation_progress = entity.animation.walk[0];
living.prev_render_yaw_offset = entity.previous_animation.rotation[2];
living.render_yaw_offset = entity.animation.rotation[2];
living.hurt_time = entity.animation.hurt;
living.death_time = entity.animation.death;
living.prev_swing_progress = living.swing_progress;
living.swing_progress = player.state.tick_swing();
}
}
}
// `updateRidden`: riders sit on their mount once it moved, whatever their own packets
let seats: Vec<_> = self
.0
.iter()
.filter_map(|(&id, entity)| {
let mount = self.mount(entity.riding?)?;
let offset = if matches!(entity.kind, Kind::Player) {
-0.5
} else {
0.0
};
Some((id, mount.seat + DVec3::Y * offset))
})
.collect();
for (id, seat) in seats {
if let Some(entity) = self.0.get_mut(&id) {
entity.position = seat;
if let Some(player) = &mut entity.player
&& !player.entity.sleep.sleeping
{
player.entity.set_position(seat);
}
}
}
}
/// The seat and turn of a vehicle for its rider, or `None` if it is gone.
pub fn mount(&self, id: i32) -> Option<crate::player::Mount> {
let entity = self.0.get(&id)?;
let offset = match entity.kind {
Kind::Mob(kind) => kind.mounted_y_offset(),
_ => 0.0,
};
let [yaw, pitch, _] = entity.animation.rotation;
let [previous_yaw, previous_pitch, _] = entity.previous_animation.rotation;
Some(crate::player::Mount {
seat: if let Kind::Vehicle(kind) = entity.kind {
kind.seat(entity.position, yaw)
} else {
entity.position + DVec3::Y * offset
},
turn: [yaw - previous_yaw, pitch - previous_pitch],
})
}
/// `canBePushed` entities within `area`: living ones and boats, with their positions.
pub fn pushers(&self, area: &Aabb) -> Vec<(i32, DVec3)> {
self.0
.iter()
.filter(|(_, e)| {
matches!(
e.kind,
Kind::Mob(_) | Kind::Player | Kind::Vehicle(VehicleKind::Boat)
) && e.animation.death == 0
&& e.bounds().intersects(area)
})
.map(|(&id, e)| (id, e.position))
.collect()
}
/// Whether a `preventEntitySpawning` entity stands in any of `boxes`.
pub fn obstruct(&self, boxes: &[Aabb]) -> bool {
self.0.values().any(|e| {
matches!(
e.kind,
Kind::Mob(_) | Kind::Player | Kind::Vehicle(_) | Kind::Falling(_) | Kind::Tnt
) && e.animation.death == 0
&& boxes.iter().any(|b| b.intersects(&e.bounds()))
})
}
/// The top of an entity, where a dismounting rider is put.
pub fn top(&self, id: i32) -> Option<DVec3> {
let entity = self.0.get(&id)?;
Some(entity.position + DVec3::Y * (entity.bounds().max.y - entity.position.y))
}
pub fn target(&self, eye: DVec3, direction: DVec3, block_distance: f64) -> Option<i32> {
let end = eye + direction * block_distance.min(3.0);
self.0
.iter()
.filter(|(_, e)| {
matches!(
e.kind,
Kind::Mob(_)
| Kind::Player
| Kind::Painting(_)
| Kind::Fireball
| Kind::Vehicle(_)
| Kind::Falling(_)
| Kind::Tnt
) && e.animation.death == 0
})
.filter_map(|(&id, e)| {
// `getCollisionBorderSize`: fireballs are easy to hit back
let fireball = matches!(e.kind, Kind::Fireball);
let border = if fireball { 1.0 } else { f64::from(0.1_f32) };
let bounds = e.bounds().inflate(DVec3::splat(border));
if bounds.contains(eye) {
Some((id, 0.0))
} else {
bounds
.intercept(eye, end)
.map(|(point, _)| (id, point.distance_squared(eye)))
}
})
.min_by(|a, b| a.1.total_cmp(&b.1))
.map(|(id, _)| id)
}
}