Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
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
Download file

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