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 tracking.rs
use common::entity::Entity;
use common::entity::mob::MobKind;
use common::entity::vehicle::VehicleKind;
use common::item::ItemStack;
use common::math::floor_f64;
use common::protocol::{Metadata, MetadataValue, Packet};
use common::world::Block;
use glam::DVec3;
use super::{Kind, ServerEntity};
use crate::level::Level;
use crate::player::ServerPlayer;
#[derive(Clone, Copy, PartialEq)]
pub(crate) struct Tracked {
position: [i32; 3],
angles: [i8; 2],
item: Option<ItemStack>,
flags: [u8; 3],
equipment: [Option<ItemStack>; 5],
riding: Option<i32>,
bed: Option<[i32; 3]>,
/// `lastTrackedEntityMotion`, for entities that send velocity updates.
motion: DVec3,
life: u32,
}
/// `Packet28EntityVelocity`.
pub(crate) fn velocity_packet(entity: &Entity) -> Packet {
Packet::EntityVelocity {
entity: entity.id,
velocity: wire_velocity(entity.motion),
}
}
fn wire_velocity(motion: DVec3) -> [i16; 3] {
motion
.to_array()
.map(|v| (v.clamp(-3.9, 3.9) * 8000.0) as i16)
}
impl ServerEntity {
fn tracked(&self) -> Tracked {
Tracked {
position: self.entity.pos.to_array().map(|v| floor_f64(v * 32.0)),
angles: [self.entity.yaw, self.entity.pitch].map(|v| (v * 256.0 / 360.0) as i32 as i8),
item: match self.kind {
Kind::Item { stack, .. } => Some(stack),
_ => None,
},
flags: self.metadata_bytes(),
equipment: match &self.kind {
Kind::Player { held, .. } => {
let [head, chest, legs, feet] =
self.entity.living.as_ref().map_or([None; 4], |l| l.armor);
[*held, head, chest, legs, feet]
}
_ => [None; 5],
},
riding: self.entity.riding,
life: match self.kind {
Kind::Player { life, .. } => life,
_ => 0,
},
bed: self.entity.sleep.bed.filter(|_| self.entity.sleep.sleeping),
motion: self.entity.motion,
}
}
/// `EntityTracker.trackEntity`: the horizontal distance players see this entity from, and
/// whether they receive its velocity.
fn tracking(&self) -> (i32, bool) {
match &self.kind {
Kind::Player { .. } => (512, false),
Kind::Hook(_) | Kind::Thrown(_) | Kind::Item { .. } => (64, true),
Kind::Arrow(_) | Kind::Fireball(_) => (64, false),
Kind::Cart(_) | Kind::Boat(_) | Kind::Tnt { .. } | Kind::Falling { .. } => (160, true),
Kind::Mob(mob) => (160, mob.kind == MobKind::Squid),
Kind::Painting { .. } => (160, false),
}
}
fn spawn_packet(&self, tracked: Tracked) -> Packet {
let [x, y, z] = tracked.position;
let [yaw, pitch] = tracked.angles;
let entity = self.entity.id;
let motion = wire_velocity(self.entity.motion);
let (kind, thrower, velocity) = match &self.kind {
Kind::Item { stack, .. } => {
let [rotation, pitch, roll] =
self.entity.motion.to_array().map(|v| (v * 128.0) as i8);
return Packet::PickupSpawn {
entity,
item: *stack,
x,
y,
z,
rotation,
pitch,
roll,
};
}
Kind::Painting { painting, .. } => {
let [x, y, z] = painting.anchor;
return Packet::Painting {
entity,
title: painting.art().name.into(),
x,
y,
z,
direction: i32::from(painting.direction),
};
}
Kind::Mob(mob) => {
return Packet::MobSpawn {
entity,
kind: mob.kind as i8,
x,
y,
z,
yaw,
pitch,
metadata: self.metadata(),
};
}
Kind::Player { name, held, .. } => {
return Packet::NamedEntitySpawn {
entity,
name: name.clone(),
x,
y,
z,
yaw,
pitch,
item: held.map_or(0, |stack| stack.item.0),
};
}
Kind::Falling { block, .. } => (if *block == Block::SAND { 70 } else { 71 }, 0, [0; 3]),
Kind::Fireball(fireball) => {
let velocity = fireball
.acceleration
.to_array()
.map(|v| (v * 8000.0) as i16);
(63, fireball.owner.unwrap_or(1).max(1), velocity)
}
Kind::Cart(cart) => {
let kind = match cart.kind {
VehicleKind::StorageCart => 11,
VehicleKind::PoweredCart => 12,
_ => 10,
};
(kind, 0, [0; 3])
}
Kind::Boat(_) => (1, 0, [0; 3]),
Kind::Hook(hook) => (90, hook.owner.max(1), motion),
Kind::Arrow(arrow) => (60, arrow.owner.unwrap_or(0), motion),
Kind::Thrown(thrown) => (if thrown.egg { 62 } else { 61 }, 0, [0; 3]),
Kind::Tnt { .. } => (50, 0, [0; 3]),
};
Packet::VehicleSpawn {
entity,
kind,
x,
y,
z,
thrower,
velocity,
}
}
fn metadata_bytes(&self) -> [u8; 3] {
let mut values = [self.entity.flags, 0, 0];
if let Kind::Mob(mob) = &self.kind {
values[1] = match mob.kind {
MobKind::Sheep => mob.color | if mob.sheared { 16 } else { 0 },
MobKind::Pig => u8::from(mob.saddled),
MobKind::Creeper if mob.igniting => 1,
MobKind::Creeper => 255,
MobKind::Slime => mob.special.slime_size(),
MobKind::Ghast => u8::from(mob.special.pose().charge > 10.0),
MobKind::Wolf => mob.wolf().unwrap().flags(),
_ => 0,
};
values[2] = if mob.kind == MobKind::Wolf {
self.entity
.living
.as_ref()
.map_or(0, |l| l.health.max(0) as u8)
} else {
u8::from(mob.powered)
};
}
values
}
fn metadata(&self) -> Metadata {
if let Kind::Mob(mob) = &self.kind
&& let Some(wolf) = mob.wolf()
{
return vec![
(0, MetadataValue::Byte(self.entity.flags as i8)),
(16, MetadataValue::Byte(wolf.flags() as i8)),
(17, MetadataValue::String(wolf.owner.clone())),
(
18,
MetadataValue::Int(self.entity.living.as_ref().map_or(0, |l| l.health)),
),
];
}
[0, 16, 17]
.into_iter()
.zip(self.metadata_bytes())
.map(|(id, value)| (id, MetadataValue::Byte(value as i8)))
.collect()
}
}
impl ServerPlayer {
/// `EntityTrackerEntry.updatePlayerEntity` and `sendLocationToAllClients` for this player.
/// `max_range` is `PlayerManager.getMaxTrackingDistance`.
pub fn track_entities(&mut self, level: &Level, max_range: i32) {
let center = self.position(level);
let in_range = |entity: &ServerEntity, [x, _, z]: [i32; 3]| {
let range = f64::from(entity.tracking().0.min(max_range));
// The original divides the encoded position as an integer
let dx = center.x - f64::from(x / 32);
let dz = center.z - f64::from(z / 32);
(-range..=range).contains(&dx) && (-range..=range).contains(&dz)
};
let mut gone = Vec::new();
self.tracked.retain(|&id, tracked| {
let keep = level.entities.get(id).is_some_and(|e| {
!e.entity.dead
&& self.sent_chunks.contains(&e.chunk())
&& in_range(e, tracked.position)
});
if !keep {
gone.push(id);
}
keep
});
for entity in gone {
self.send(Packet::DestroyEntity { entity });
}
// `sendPacketToTrackedPlayersAndTrackedEntity`: the player hears of its own flags
if let Some(own) = level.entities.get(self.entity_id) {
let flags = own.metadata_bytes()[0];
if flags != self.own_flags {
self.own_flags = flags;
self.send(Packet::EntityMetadata {
entity: self.entity_id,
metadata: vec![(0, MetadataValue::Byte(flags as i8))],
});
}
}
let mut attachments = Vec::new();
for entity in level.entities.iter() {
let id = entity.entity.id;
let current = entity.tracked();
let previous = self.tracked.get(&id).copied();
if id == self.entity_id
|| entity.entity.dead
|| !self.sent_chunks.contains(&entity.chunk())
|| previous.is_none() && !in_range(entity, current.position)
{
continue;
}
let velocity = entity.tracking().1;
if previous.is_some_and(|old| old.flags != current.flags) {
self.send(Packet::EntityMetadata {
entity: id,
metadata: entity.metadata(),
});
}
// What the player last heard of its location and velocity
let mut sent = previous.unwrap_or(current);
let mut motion = sent.motion;
// `ServerConfigurationManager.recreatePlayerEntity` untracks the old body
if previous.is_none_or(|old| old.item != current.item || old.life != current.life) {
if previous.is_some() {
self.send(Packet::DestroyEntity { entity: id });
}
self.send(entity.spawn_packet(current));
if velocity {
self.send(velocity_packet(&entity.entity));
motion = current.motion;
}
sent = current;
} else if previous != Some(current) && level.time % 3 == 0 {
let [x, y, z] = current.position;
let [yaw, pitch] = current.angles;
if velocity {
let change = current.motion - motion;
let squared = change.length_squared();
if squared > 0.02 * 0.02 || squared > 0.0 && current.motion == DVec3::ZERO {
self.send(velocity_packet(&entity.entity));
motion = current.motion;
}
}
self.send(Packet::EntityTeleport {
entity: id,
x,
y,
z,
yaw,
pitch,
});
sent = current;
}
if previous.is_none() {
self.send(Packet::EntityMetadata {
entity: id,
metadata: entity.metadata(),
});
}
for (slot, stack) in current.equipment.iter().copied().enumerate() {
if previous.is_none_or(|old| old.equipment[slot] != stack) {
self.send(Packet::PlayerInventory {
entity: id,
slot: slot as i16,
item: stack.map_or(-1, |stack| stack.item.0),
damage: stack.map_or(0, |stack| stack.damage as i16),
});
}
}
// Sent once every new entity exists, so mounts spawn before their riders attach
if previous.is_none_or(|old| old.riding != current.riding) {
attachments.push(Packet::AttachEntity {
entity: id,
vehicle: current.riding.unwrap_or(-1),
});
}
if previous.is_none_or(|old| old.bed != current.bed)
&& let Some([x, y, z]) = current.bed
{
self.send(Packet::Sleep {
entity: id,
status: 0,
x,
y: y as i8,
z,
});
}
let (position, angles) = (sent.position, sent.angles);
self.tracked.insert(
id,
Tracked {
position,
angles,
motion,
..current
},
);
let living = entity.entity.living.as_ref();
self.send(Packet::EntityState {
entity: id,
pos: entity.entity.pos.to_array(),
rotation: [
entity.entity.yaw,
entity.entity.pitch,
living.map_or(entity.entity.yaw, |l| l.render_yaw_offset),
],
walk: living.map_or([0.0; 2], |l| {
[l.walk_animation_progress, l.walk_animation_speed]
}),
hurt: living.map_or(0, |l| l.hurt_time),
death: living.map_or(0, |l| l.death_time),
vehicle: match &entity.kind {
Kind::Boat(boat) => boat.pose,
Kind::Cart(cart) => cart.pose,
_ => Default::default(),
},
special: match &entity.kind {
Kind::Mob(mob) => mob.special.pose(),
_ => Default::default(),
},
fuse: match &entity.kind {
Kind::Mob(mob) => mob.fuse,
Kind::Tnt { fuse } => *fuse,
_ => 0,
},
});
}
for packet in attachments {
self.send(packet);
}
}
}